همهٔ مقاله‌ها

· Casimiro Ferreira· 9 دقیقه مطالعه

خانواده‌ای از کتابخانه‌های گفتاریِ خالصِ ONNX

  • ONNX
  • TTS
  • voice cloning
  • VAD
  • self-hosted
  • phoonnx

‏ONNX یک قالب فایل برای یک شبکهٔ عصبیِ آموزش‌دیده است: وزن‌ها و گراف محاسباتی، منجمدشده، بدون وابستگی به چارچوبی که آن را آموزش داده. مدلی که به ONNX صادر شده می‌تواند از میان ONNX Runtime اجرا شود، یک موتور استنتاج کوچک که کاری جز اجرای آن گراف نمی‌کند. نمی‌داند مدل چگونه آموزش دیده، از آموزش پشتیبانی نمی‌کند، و نیازی به نصب بودن PyTorch یا TensorFlow ندارد.

چند تا از کتابخانه‌های ما یک قاعده را رعایت می‌کنند: در زمان اجرا، تنها وابستگی‌ها onnxruntime و numpy هستند. نه «بیشترِ اوقات» — خودِ import کردن بسته هرگز یک چارچوب آموزشی را نمی‌کشد. audiosronnx (بسط پهنای‌باند و رفع نویز)، voiceclonnx (شبیه‌سازی صدا)، speakeronnx (جاسازی‌های گوینده)، speechonnxmetrics (ارزیابی)، stressonnx (تکیهٔ واژه)، vadonnx (تشخیص فعالیت صوتی) و phoonnx (واج‌سازی و متن به گفتار) همگی از این قاعده پیروی می‌کنند، هر یک در بستهٔ PyPI خودش. دو تای دیگر، phoonnx.js و precise-onnx-js، همان ایده را در مرورگر با onnxruntime-web به کار می‌بندند.

چرا به این زحمت می‌ارزد

راه بدیهی برای عرضهٔ یک مدل گفتاری این است که چارچوب آموزشی را برای استنتاج نیز نگه دارید. در توسعه راحت است. در تولید یک بار مسئولیت است.

  • اندازهٔ نصب. یک نصب PyTorch + CUDA پیش از بارگذاری حتی یک مدل به گیگابایت‌ها می‌رسد. onnxruntime و numpy با هم چند ده مگابایت‌اند.
  • بدون CUDA برای مدیریت. هم‌ترازسازی درایور GPU، نسخهٔ CUDA toolkit و یک نسخهٔ چارچوب منبع تکرارشوندهٔ خرابی است. ONNX Runtime تنها-CPU به‌طور کامل از این عبور می‌کند، و همچنان همان گراف را روی GPU، هرجا در دسترس باشد، اجرا می‌کند.
  • روی سخت‌افزار متوسط اجرا می‌شود. یک Raspberry Pi یا یک لپ‌تاپ ده‌ساله می‌تواند onnxruntime را به‌راحتی اجرا کند. معمولاً نمی‌تواند یک پشتهٔ کامل PyTorch را با سرعت قابل‌استفاده اجرا کند، یا اصلاً روی یک برد ۳۲ بیتی یا محدود از نظر حافظه نصبش کند.
  • یک artifact، هر پلتفرم. همان فایل .onnx بدون تغییر روی Linux، macOS، Windows — و از طریق onnxruntime-web، درون یک تب مرورگر اجرا می‌شود. هیچ گام صادراتِ جداگانه‌ای به‌ازای هر هدف لازم نیست.
  • بدون تعارض نسخهٔ آموزش/سرویس‌دهی. یک پشتهٔ آموزشی نسخه‌های خاصی از چارچوب و CUDA را پین می‌کند. یک پشتهٔ سرویس‌دهی کوچک‌ترین و پایدارترین مجموعهٔ ممکن از وابستگی‌ها را می‌خواهد. جدا نگه‌داشتنشان یعنی می‌توانید یکی را ارتقا دهید بدون آنکه دیگری بشکند.

چه هزینه‌ای دارد

این قید واقعی است، و رایگان نیست.

نمی‌توانید درون‌فرایند fine-tune کنید. یک گراف ONNX هیچ بهینه‌سازی، هیچ backward pass ندارد. هر یک از این کتابخانه‌ها با مدل‌ها به‌عنوان artifact های ثابت رفتار می‌کند: آن‌ها را بارگذاری می‌کنید و اجرا می‌کنید. آموزش یا fine-tune جداگانه، با چارچوب اصلی، رخ می‌دهد و نتیجه بعداً به ONNX صادر می‌شود. stressonnx و speechonnxmetrics هر دو یک extra اختیاری به نام export نگه می‌دارند که torch را خالصاً برای همان گام تبدیل آفلاین می‌کشد — هرگز برای استنتاج.

هر معماری‌ای تمیز صادر نمی‌شود. جریان کنترل پویا، هسته‌های سفارشی CUDA، یا عملیات‌هایی بدون معادل ONNX می‌توانند جلوی یک صادراتِ ساده را بگیرند. README خودِ audiosronnx این را صریحاً مستند می‌کند: یک فهرستِ عرضه‌نشده از مدل‌هایی که ارزیابی و رد کرده، با دلیل، نگه می‌دارد، به‌جای وانمود کردن که هر مدل پژوهشی به‌راحتی منتقل می‌شود.

پیش‌پردازش باید دستی دوباره پیاده‌سازی شود. چارچوبی مانند PyTorch یا Kaldi پیاده‌سازی‌های سریع و آزموده‌ای از STFT (تبدیل یک موج‌شکل به یک طیف‌نگار)، ویژگی‌های bank فیلتر mel، و نمونه‌برداری مجدد عرضه می‌کند. وقتی خودِ مدل دیگر به آن چارچوب وابسته نیست، پیش‌پردازشش هم نمی‌تواند باشد — speakeronnx دقیقاً به همین دلیل یک bank فیلتر log-mel ۸۰-بانده را در NumPy خالص دوباره پیاده‌سازی می‌کند، و audiosronnx همین کار را برای STFT و نمونه‌برداری مجدد انجام می‌دهد. کد بیشتری برای درست انجام دادن است، و به آزمون‌های تطبیقِ خودش در برابر نسخهٔ اصلی نیاز دارد.

یک وظیفه، چند موتور، یک API

مدل‌های گفتاریِ آموزش‌دیده از نظر زبان، شرایط ضبط و حوزهٔ هدف به‌شدت متفاوت‌اند. یک مدل تأیید گوینده که روی گفتارِ خواندنیِ تمیز آموزش دیده ممکن است روی صدای تلفن شکست بخورد. یک مدل شبیه‌سازی صدا که برای انتقال طنین انگلیسی تنظیم شده ممکن است روی زبان‌های تُنی وضوح خود را از دست بدهد. هیچ مدل واحدی همه‌جا برنده نیست، پس تعهد پیشاپیش به یکی یک حدس است.

هر کتابخانه در این خانواده یک وظیفهٔ واحد را انتخاب می‌کند و چندین مدل منتشرشدهٔ مستقل را پشت یک واسط می‌پیچد، پس تعویض موتور یک تغییرِ یک‌خطی است نه یک بازنویسی.

audiosronnx دو وظیفهٔ خود — رفع نویز و بسط پهنای‌باند (تبدیل یک ضبط narrowband، مانند صدای تلفن ۸ کیلوهرتزی، به یک سیگنال با نرخ نمونه‌برداری بالاتر و صدایی پرتر) — را پشت دو بارگذار جدا می‌کند، هر یک پشتیبانی‌شده توسط چندین موتور:

from audiosronnx import load_denoise, load_sr

clean, rate = load_denoise("dpdfnet").denoise("noisy_call.wav")   # remove noise
wide, _ = load_sr("lavasr").upscale(clean, rate)                  # extend to 48 kHz

load_denoise اکنون ده رفع‌نویزکننده ثبت می‌کند (dpdfnet، mossformer2، frcrn، mpsenet، gtcrn، cmgan، metadenoiser، mossformergan، voicefixer، deepfilternet)، از یک مدل ۰٫۵۴ مگابایتی تا یک مدل ۴۱۵ مگابایتی، تحت مجوزهای متفاوت. load_sr هفت بسط‌دهندهٔ پهنای‌باند ثبت می‌کند (lavasr، novasr، flowhigh، hifiganbwe، apbwe، sidon، callenhancer). مدل‌های مغلوب — آن‌هایی که موتور دیگری در هر معیار اندازه‌گیری‌شده از آن‌ها بهتر است — همچنان در ثبت‌نام باقی می‌مانند، پس یک نتیجهٔ محک منتشرشده هر وقت خواستید بازتولیدپذیر می‌ماند.

voiceclonnx همان رویکرد را برای شبیه‌سازی صدا در پیش می‌گیرد — تبدیل صدای درون یک ضبط موجود تا مانند یک گویندهٔ مرجعِ متفاوت به نظر برسد، بدون عبور از متن:

from voiceclonnx import VoiceCloner

cloner = VoiceCloner(engine="facodec")
out = cloner.clone_voice("source.wav", "reference.wav", "out.wav")

ده موتور ثبت شده‌اند (facodec، openvoice، chatterbox، triaan، cosyvoice، bicodec، knnvc، focalcodec، lscodec، rvc)، که شش خانوادهٔ مدل متمایز را دربرمی‌گیرند — تعویض ویژگی kNN، codec فاکتورشده، flow-matching، انتقال رنگ‌آوا، AR codec-LM و codec جداشده از گوینده. در پشت صحنه هر یک با اعداد وضوح و شباهت-گویندهٔ منتشرشده عرضه می‌شود، پس انتخاب یک موتور یک مقایسه است، نه پرتاب سکه.

vadonnx این الگو را برای تشخیص فعالیت صوتی به کار می‌بندد — تصمیم اینکه کدام بخش‌های یک جریان صوتی اصلاً گفتار دارند:

from vadonnx import load_vad

vad = load_vad("silero")
segments = vad.get_speech_segments(audio, sample_rate=16000)
# -> [SpeechSegment(start=0.32, end=2.27), SpeechSegment(start=3.27, end=4.45), ...]

شش خانوادهٔ مدل ثبت شده‌اند (silero، marblenet، pyannote، fsmn، speechbrain، ten)، و یک IOSignature اعلانی به یک موتور عمومیِ واحد اجازه می‌دهد بیشتر آن‌ها را برانَد، یا به هر فایل VAD سفارشیِ .onnx اشاره کند.

speakeronnx یک جاسازیِ گوینده استخراج می‌کند — برداری با طول ثابت که خلاصه می‌کند چه‌کسی صحبت می‌کند، مستقل از آنچه گفته شده — و دو جاسازی را با شباهت کسینوسی مقایسه می‌کند تا بررسی کند آیا دو کلیپ همان گوینده‌اند:

from speakeronnx import SpeakerEmbedder, cosine

embedder = SpeakerEmbedder(model="wespeaker-resnet34")
alice1 = embedder.embed("alice_clip1.wav")
alice2 = embedder.embed("alice_clip2.wav")
print(cosine(alice1, alice2))   # e.g. 0.82 - same speaker

این کتابخانه نه مدل را در چهار خانوادهٔ معماری ثبت می‌کند (WeSpeaker، CAM++، ERes2Net، ReDimNet)، با ابعاد جاسازی و مجوزهای منتشرشده.

stressonnx تکیهٔ واژه را برای فرانت‌اندهای متن به گفتار انتخاب می‌کند — اینکه کدام هجای یک واژه تأکید را حمل می‌کند، اطلاعاتی که بسیاری از زبان‌ها آن را ننوشته می‌گذارند (روسی за́мок، قلعه، در برابر замо́к، قفل، هر حرف را مشترک دارند). یک خط لولهٔ عصبی برای روسی، دومی برای اوکراینی و بلاروسی، و یک باک‌اندِ مبتنی بر قاعده و واژگان که ۲۶ زبان را بدون هیچ استنتاج عصبی پوشش می‌دهد ثبت می‌کند:

from stressonnx import stress

stress("старинный замок стоит на горе", "ru")
# 'стари́нный за́мок сто́ит на горе́'

phoonnx متن را واج‌سازی می‌کند (واژه‌های هجی‌شده را به واحدهای صوتی‌ای که یک مدل TTS مصرف می‌کند تبدیل می‌کند) و متن به گفتار را در سراسر ۱۷ موتور ترکیب ثبت‌شده و صداهای صادرشده از چندین بوم‌سازگان (phoonnx بومی، Piper، Mimic3، Coqui، MMS، Transformers) اجرا می‌کند:

import wave
from phoonnx.voice import TTSVoice

voice = TTSVoice.load("model.onnx", "model.json")
with wave.open("hello.wav", "wb") as wav_file:
    voice.synthesize_wav("Hello world!", wav_file)

phoonnx.js همان مسیرهای توکن‌ساز را با onnxruntime-web به مرورگر می‌برد، و precise-onnx-js تشخیص wake-word (استخراج ویژگی MFCC به‌علاوه یک دسته‌بند ONNX، سازگار با مدل‌های Mycroft Precise) را به JavaScript منتقل می‌کند، هر دو بدون سرور:

import { loadVoice, synthesizeWav } from "phoonnx";
import { getVoice } from "phoonnx/voices";

const voice = await loadVoice(getVoice("phoonnx_eu-ES_dii_unicode")!);
const blob = await synthesizeWav(voice, "Kaixo mundua!");

وزن‌های audiosronnx (۱۸ مدل منتشرشده) و voiceclonnx (۱۰ مدل منتشرشده) به‌صورت دانلودهای جداگانه در سازمان Hugging Face تیگره‌گوتیکو زندگی می‌کنند، در نخستین استفاده واکشی و به‌صورت محلی cache می‌شوند، پس انتخاب یک موتور متفاوت یک تغییر پیکربندی است، نه یک استقرار دوباره.

بستن حلقه: قضاوت موتورها به‌جای حدس‌زدن

ثبت چندین موتور پشت یک API تنها اگر بتوانید بگویید کدام یک واقعاً برای ورودی شما بهتر است سودمند است. برای همین speechonnxmetrics وجود دارد: یک کتابخانهٔ سنجه ساخته‌شده روی همان قید numpy + onnxruntime، پس امتیازدهی به یک مدل هیچ هزینهٔ نصبِ اضافه‌ای ندارد.

این کتابخانه سنجه‌ها را در سه دسته گروه‌بندی می‌کند. برآوردگرهای MOS بدون‌مرجع — UTMOS، DNSMOS، NISQA، SIGMOS — یک امتیاز میانگین نظر را پیش‌بینی می‌کنند، رتبهٔ طبیعی‌بودنِ ۱ تا ۵ که یک پانل شنوندهٔ انسانی به یک کلیپ می‌داد، بدون نیاز به یک مرجع تمیز برای مقایسه. سنجه‌های تهاجمی — STOI (وضوح عینیِ کوتاه‌مدت)، SI-SDR (نسبت سیگنال به اعوجاجِ مقیاس-ناوردا)، MCD (اعوجاج mel-cepstral) — به یک مرجع تمیزِ مطابق نیاز دارند و اندازه می‌گیرند خروجی چقدر به آن نزدیک است. سنجه‌های متنیِ مبتنی بر ASR — WER (نرخ خطای واژه) و CER (نرخ خطای نویسه) — یک تشخیص‌دهندهٔ گفتار را روی خروجی اجرا می‌کنند و رونوشت را با متن مورد انتظار مقایسه می‌کنند، و مواردی را می‌گیرند که مدلی صدایی تولید می‌کند که خوب به نظر می‌رسد اما واژه‌های اشتباه می‌گوید.

import speechonnxmetrics as s

print(s.score("degraded.wav", ["utmos"]))
# -> {'utmos': 4.41...}

print(s.score("clone_output.wav", ["stoi", "mcd", "si_sdr"], ref="source.wav"))
# -> {'stoi': 0.662..., 'mcd': 10.459..., 'si_sdr': -26.937...}

این کار انتخاب موتور را از یک آزمون شنیداری به یک جدول تبدیل می‌کند. voiceclonnx دقیقاً همین مقایسه را برای ده موتور شبیه‌سازی‌اش منتشر می‌کند — WER در برابر رونوشت مبدأ به‌علاوهٔ یک امتیاز شباهت-گوینده جداگانه برای هر یک، پس ادعاهایی مانند «facodec به WER صفر درصد می‌رسد» یا «lscodec وضوح را با انتقال طنین قوی‌تر معاوضه می‌کند» ادعاهایی اندازه‌گیری‌شده‌اند، نه برداشت‌های شخصی. این را در سراسر زبان‌ها و شرایط ضبط ضرب کنید و مقایسهٔ دستی دیگر واقع‌بینانه نیست؛ یک سنجهٔ عینی چیزی است که یک ثبت‌نام ده‌موتوره را قابل‌استفاده می‌کند، به‌جای طاقت‌فرسا.

کجا سودمند است

اگر به پردازش گفتار آفلاین نیاز دارید — تمیزکردن یک ضبط، شبیه‌سازی یک صدا، تشخیص اینکه چه‌کسی صحبت می‌کند، یا سنتز یکی — روی سخت‌افزاری که هرگز GPU نخواهد دید، این شکلی است که باید دنبالش بگردید: یک وابستگیِ زمان‌اجرای کوچک، انتخابی از مدل‌های منتشرشده به‌جای یک پیش‌فرض ثابت، و راهی برای اندازه‌گیری اینکه کدام یک واقعاً برای مورد شما کار می‌کند. هر کتابخانهٔ بالا یک pip install فاصله دارد، در سطح کد تحت مجوز MIT یا Apache است (وزن‌های مدل‌های منفرد مجوزهای بالادستیِ خودشان را حمل می‌کنند، مستندشده به‌ازای هر موتور)، و روی یک لپ‌تاپ، یک سرور یا یک Raspberry Pi یکسان اجرا می‌شود.

از طریق /contact با ما تماس بگیرید یا ببینید چه چیزهای دیگری در /services می‌سازیم.