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

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

تمیزکردن صدای بد: رفع نویز و بسط پهنای‌باند در audiosronnx

  • ONNX
  • denoising
  • bandwidth extension
  • speech

یک ضبط می‌تواند به دو شیوهٔ متفاوت بد باشد، و رفع‌ها هم‌پوشانی ندارند.

شیوهٔ نخست: نویز پس‌زمینه روی گفتار می‌نشیند — ترافیک، یک پنکه، همهمهٔ اتاق. سیگنالی که اهمیت دارد آنجاست؛ چیز دیگری با آن مخلوط شده. حذف آن رفع نویز است.

شیوهٔ دوم: ضبط از همان ابتدا هرگز کل سیگنال را نگرفته. صدای تلفن با ۸٬۰۰۰ نمونه بر ثانیه (۸ کیلوهرتز) نمونه‌برداری می‌شود؛ یک ضبط با کیفیت کامل معمولاً ۴۸ کیلوهرتز است. نرخ نمونه‌برداری بالاترین فرکانسی را که یک سیگنال دیجیتال می‌تواند بازنمایی کند تعیین می‌کند، پس یک تماسِ ۸ کیلوهرتزی اصلاً هیچ محتوایی بالای ۴ کیلوهرتز ندارد — نه بی‌صدا، نه فیلترشده، صرفاً هرگز ضبط نشده. کامل به‌نظر رساندنِ دوبارهٔ آن صدا یعنی اختراع فرکانس‌های بالای محتمل که هرگز گرفته نشده‌اند. آن بسط پهنای‌باند است.

audiosronnx با این‌ها به‌عنوان دو مسئلهٔ متفاوت با دو نقطهٔ ورود متفاوت رفتار می‌کند، چون استفاده از یکیِ اشتباه کار اشتباه را انجام می‌دهد. یک بسط‌دهندهٔ پهنای‌باند را روی یک سیگنال پرنویز اجرا کنید و با وفاداری تمام یک نسخهٔ فرکانس‌بالای همان نویز را بازسازی می‌کند. رفع نویز باید نخست رخ دهد.

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() و load_sr() موتورهای همدیگر را رد می‌کنند — درخواست یک بسط‌دهندهٔ پهنای‌باند از load_denoise به‌جای انجام خاموشِ کار اشتباه، یک خطا برمی‌انگیزد.

چرا این پیش از تشخیص می‌آید

تشخیص گفتار و شناسایی گوینده معمولاً روی صدای نسبتاً تمیز آموزش دیده‌اند. گفتار ۸ کیلوهرتزیِ تلفنی، یا گفتاری که یک پنکه زیرش در حال کار است، به یک تشخیص‌دهنده بدهید و نرخ خطای واژه بالا می‌رود — نه چون مدل بد است، بلکه چون ورودی دیگر شبیه چیزی نیست که رویش آموزش دیده. همین برای جاسازی‌های گوینده‌ای که برای شناسایی یا تفکیک‌گویندگان استفاده می‌شوند صدق می‌کند: نویز و پهنای‌باند ازدست‌رفته جزئیات دقیق آکوستیکی‌ای را که آن جاسازی‌ها به آن تکیه دارند مخدوش می‌کنند.

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

ثبت‌نام موتورها

audiosronnx ده رفع‌نویزکننده و هفت بسط‌دهندهٔ پهنای‌باند عرضه می‌کند، همه با نام از طریق load_denoise() / load_sr() بارگذاری‌پذیرند، همه ONNX خالص بدون torch در زمان اجرا.

رفع‌نویزکننده‌ها:

موتورنرخاندازهمجوز
dpdfnet (پیش‌فرض)۸/۱۶/۴۸ کیلوهرتز۸٫۷–۱۴٫۹ مگابایتApache-2.0
mossformer2۴۸ کیلوهرتز۲۲۹ مگابایتApache-2.0
frcrn۱۶ کیلوهرتز۵۷٫۵ مگابایتApache-2.0
mpsenet۱۶ کیلوهرتز۹٫۷ مگابایتMIT
gtcrn۱۶ کیلوهرتز۰٫۵۴ مگابایتMIT
cmgan۱۶ کیلوهرتز۷٫۸ مگابایتMIT
metadenoiser۱۶ کیلوهرتز۱۹–۳۴ مگابایتCC-BY-NC-4.0
mossformergan۱۶ کیلوهرتز۱۷٫۷ مگابایتApache-2.0
voicefixer۴۴٫۱ کیلوهرتز۴۱۵ مگابایتMIT
deepfilternet۴۸ کیلوهرتز~۲ مگابایتMIT

بسط‌دهنده‌های پهنای‌باند:

موتورورودیاندازهمجوز
lavasr (پیش‌فرض)۸–۴۸ کیلوهرتز~۵۲ مگابایتApache-2.0
novasr۱۶ کیلوهرتز~۰٫۲ مگابایتApache-2.0
flowhighهر~۲۰۰ مگابایتMIT
hifiganbweهر~۴ مگابایتMIT
apbweهر (باند ۱۲ کیلوهرتزی)~۱۲۰ مگابایتMIT
sidon۱۶ کیلوهرتز~۴۱۰ مگابایتMIT
callenhancer۸–۱۶ کیلوهرتز~۳ گیگابایت / ~۱٫۳ گیگابایت int8CC-BY-NC-4.0

کوچک‌ترین مدل در این کتابخانه، gtcrn، ۰٫۵۴ مگابایت است. بزرگ‌ترین، voicefixer، ۴۱۵ مگابایت است — تقریباً ۸۰۰ برابر بزرگ‌تر، و کار متفاوتی انجام می‌دهد: یک مدل بازیابی است که نویز، پژواک، کلیپ‌شدگی و پهنای‌باند ازدست‌رفته را با هم مدیریت می‌کند، نه یک مسئله در یک زمان.

بیشتر وزن‌ها MIT یا Apache-2.0 هستند. دو تا نیستند: metadenoiser و callenhancer تحت CC-BY-NC-4.0 عرضه می‌شوند، غیرتجاری. آن مجوز وزن‌ها را دربرمی‌گیرد، نه صدای پردازش‌شده با آن‌ها، و کتابخانه در هر نقطهٔ استفاده آن را اعلام می‌کند — audiosronnx list آن را به‌ازای هر موتور گزارش می‌دهد. هیچ‌چیز فراخوان را از انتخاب metadenoiser به‌خاطر معماری زمانیِ آن بازنمی‌دارد، اما انتخاب باید آگاهانه انجام شود.

ثبت‌نام وجود دارد چون هیچ مدل واحدی روی هر ضبطی برنده نیست. dpdfnet پیش‌فرض است چون به هیچ وابستگی اضافه‌ای نیاز ندارد و ۸، ۱۶ و ۴۸ کیلوهرتز را از یک مدل پوشش می‌دهد. mossformer2 بهترین انتخابِ اندازه‌گیری‌شده روی ورودی fullband است. mossformergan بالاترین امتیاز PESQ منتشرشده (۳٫۴۷) را در میان رفع‌نویزکننده‌های عرضه‌شده ثبت می‌کند. gtcrn انتخاب زمانی است که قید محدودکننده ردپا باشد، در ۰٫۵۴ مگابایت. روی یک کلیپ آزمایشیِ با نویز گاوسیِ پهن‌باند، رفع‌نویزکننده‌ها ۳٫۵ تا ۵٫۹ دسی‌بل SNR را در یک SNR ورودیِ ۱۹ دسی‌بلی بازیابی کردند، که به ۷٫۵–۱۳٫۷ دسی‌بل در یک ورودی سخت‌ترِ ۵ دسی‌بلی می‌رسد. این یک مورد نویزِ مصنوعی و خصمانه است: موتورها را به‌طور پیوسته رتبه‌بندی می‌کند اما دربارهٔ نویز babble یا عیب‌های codec چیز چندانی نمی‌گوید، که دقیقاً همان دلیلی است که ثبت‌نام ده مدل را نگه می‌دارد به‌جای عرضهٔ تنها برنده.

cmgan روشن‌ترین مورد از یک مدل است که عمداً باوجود باختن نگه داشته می‌شود: هم روی PESQ و هم روی SNR توسط gtcrn، در چهارده برابر اندازه، مغلوب می‌شود، و باز هم می‌ماند — تا نتایج منتشرشده ساخته‌شده در برابر cmgan بازتولیدپذیر بمانند و یک معماری متمایز برای مقایسه در دسترس بماند.

در سمت بسط پهنای‌باند، sidon و callenhancer کاری متفاوت از lavasr یا novasr انجام می‌دهند: به‌جای افزودن یک باند بالای محتمل روی سیگنال موجود، گفتار را از صفر از طریق یک vocoder عصبی بازسنتز می‌کنند، که می‌تواند آسیب codec را که یک بسط‌دهندهٔ باند نمی‌تواند لمس کند تعمیر کند — با هزینهٔ محاسباتیِ بسیار بالاتر. callenhancer به‌طور خاص روی صدای تلفنی آموزش دیده، به همین دلیل وزن‌هایش مجوز غیرتجاری را حمل می‌کنند.

چه چیزی راه پیدا نکرد

audiosronnx یک موتور را تنها زمانی عرضه می‌کند که به یک گراف ONNX استاتیکِ واحد صادر شود، روی CPU از طریق onnxruntime اجرا شود، مجوزی روشن داشته باشد، و سرتاسر در برابر پیاده‌سازی اصلی اعتبارسنجی شده باشد — نه فقط در برابر خودِ مدل خام، چون گرافی که با شبکه تطابق دارد اما با نرمال‌سازیِ اطرافش نه، صدایی تولید می‌کند که خوب به‌نظر می‌رسد و به‌خاموشی اشتباه است.

docs/not-shipped.md این پروژه هر نامزدی را که ارزیابی و رد کرده، با دلیل مشخص، مستند می‌کند، که آن را به یکی از سودمندترین اسناد در مخزن تبدیل می‌کند چون مرزهای واقعیِ آنچه «ONNX خالص، تنها-CPU» امروز می‌تواند انجام دهد را نشان می‌دهد به‌جای ادعا کردنشان.

نمونه‌گیرهای تکراری هیچ گراف استاتیکی برای صادرات ندارند. مدل‌های diffusion و flow-matching یک شبکه را چندین بار به‌ازای هر گفته اجرا می‌کنند، با حلقه‌ای که طولش در زمان صادرات ثابت نیست. AudioSR (یک خط لولهٔ latent-diffusion تقریباً ۶ گیگابایتی با یک VAE، LDM و vocoder جداگانه) و SGMSE هر دو اینجا می‌افتند — دنبالهٔ streaming خودِ SGMSE در سال ۲۰۲۵ تنها روی یک GPU مصرفی به زمان واقعی می‌رسد، چه رسد به CPU.

کانولوشن‌های location-variable ظاهرِ رد‌کننده دارند و بیشترشان نیستند. resemble-enhance مدت‌ها در این سند به‌خاطر LVCNet، کانولوشنِ location-variable خودِ vocoder، رد شده بود، با این نظریه که هسته‌های پیش‌بینی‌شده به‌ازای هر موقعیت از طریق unfold و einsum نمی‌توانند در یک گراف استاتیک جای بگیرند. آزمایش‌شده به‌طور مستقیم، این اشتباه از آب درآمد — هر دو عملیات معادل‌های ONNX دارند. شکست واقعی یک خطای tracing جداگانه و شناخته‌شده است («صادرات ONNX کانولوشن برای هستهٔ شکل ناشناخته») که از پیش جای دیگری در پایگاه‌کد برای نمونه‌بردارهای BigVGAN حل شده. آنچه هنوز resemble-enhance را بیرون نگه می‌دارد مقیاس است: چهار شبکه شامل یک نمونه‌گیر ODE CFM و یک خودرمزگذار، در ۴۴٫۱ کیلوهرتز — یک تصمیم دامنه، نه یک ناممکنی.

برخی مدل‌ها چیزی آموزش‌دیده برای صادرات ندارند. RNNoise به‌صورت C دست‌نویس عرضه می‌شود، نه یک گراف در یک چارچوب قابل‌آموزش — پورت کردنش یعنی آموزشِ دوبارهٔ یک شبکهٔ معادل از صفر. Fast-ULCNet تنها کدِ معماری منتشر می‌کند، اصلاً بدون checkpoint.

یک مجوز محدودکننده یک تصمیم برچسب‌گذاری است، نه رد خودکار — دقیقاً به همین دلیل callenhancer و metadenoiser عرضه می‌شوند. آنچه واقعاً رد‌کننده است وزن‌های منتشرشده بدون هیچ مجوزی است: mdctGAN دقیقاً به همین دلیل رد شد، افزون بر یک فرانت‌اند مبتنی بر torch.fft که به‌طور قابل‌اعتمادی صادر نمی‌شود.

فراخوانی torch.stft درون مدل یک مانع ساختاریِ واقعی است. حلقهٔ نمونه‌گیر NU-Wave2 مشکل نیست — آن می‌توانست در numpy بیرون از گراف اجرا شود، همان‌طور که STFT هر موتور دیگری این کار را می‌کند. آنچه آن را مسدود می‌کند این است که متد forward آن درونی torch.stft و torch.istft را فرامی‌خواند، که این کتابخانه عمداً از هر گرافی که عرضه می‌کند بیرون نگه می‌دارد، و که همچنین عملگری است که به‌طور کلی کمترین قابلیت‌اعتماد را در صادرات دارد. تعمیرش یعنی تقسیم مدل در مرز تبدیل، بازساختاردهیِ واقعی به‌جای تعویض یک عملگر.

بازتولید معماری یک مدل همان بازتولید خروجی آن نیست. LiSenNet ۵۶ هزار پارامتر دارد، زیر ۳۰۰ کیلوبایت — کوچک‌ترین موتور در این کتابخانه می‌شد. پورت ONNXِ در دسترس عمومی آن اجرا می‌شود و صدای تضعیف‌شدهٔ محتمل‌به‌نظر تولید می‌کند، اما سرتاسر اندازه‌گیری‌شده سیگنال را نابود می‌کند: منفی ۱۰٫۸ دسی‌بل SNR در ورودی ۱۱ دسی‌بلی. بازتولید دقیق پیاده‌سازی مرجع خودِ آن پورت همان نتیجهٔ منفیِ یکسان را می‌دهد، که یعنی خودِ پیاده‌سازیِ مرجع با فرانت‌اندی که مستندات خودش توصیف می‌کند تطابق ندارد — هنوز هیچ هدف درستی برای اعتبارسنجی در برابرش وجود ندارد.

الگوی مشترک در همهٔ این‌ها این است که شکست‌های جالب به‌ندرت «مدل خیلی بزرگ است» یا «diffusion کند است» هستند. آن‌ها مشخص‌اند: یک عملگر پشتیبانی‌نشده با یک جایگزین دقیق (torch.complex هیچ عملیات ONNX ندارد، اما atan2(im, re) همان زاویهٔ فاز را محاسبه می‌کند)، یک تانسور ساخته‌شده از شکل زمان‌اجرای یک ورودی که یک tracer نمی‌تواند مشخصش کند، یا یک تبدیل قرارگرفته در سمت اشتباهِ یک مرز گراف.

تأیید اینکه خروجی واقعاً بهتر شده

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

کتابخانهٔ خواهرِ speechonnxmetrics وجود دارد تا آن قضاوت را قابل‌اندازه‌گیری کند به‌جای برداشتی. این کتابخانه صدا را روی MOS (امتیاز میانگین نظر، یک رتبه‌بندیِ ۱ تا ۵ از کیفیت ادراک‌شده) به دو شیوه امتیاز می‌دهد: پیش‌بین‌های عصبیِ بدون‌مرجع مانند DNSMOS و UTMOS، که به یک ضبط بدون اصل تمیزی برای مقایسه امتیاز می‌دهند، و سنجه‌های تهاجمی مانند STOI و SI-SDR، که به مرجع تمیز نیاز دارند و اندازه می‌گیرند خروجی واقعاً چقدر به آن نزدیک است.

import speechonnxmetrics as s

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

s.score("denoised.wav", ["stoi", "si_sdr"], ref="clean.wav")
# -> {'stoi': 0.66, 'si_sdr': -26.9}

پیش و پس از یک رفع‌نویزکننده یا بسط‌دهنده اجرا کنید و شکل یک مقایسهٔ واقعی بیرون می‌افتد: DNSMOS یا UTMOS روی صدای خام و پردازش‌شده برای دیدن اینکه آیا کیفیتِ ادراک‌شده اصلاً حرکت کرده، و — وقتی یک مرجع تمیز وجود دارد، که برای آزمون‌های نویز مصنوعی وجود دارد اما به‌ندرت برای یک تماس تلفنیِ واقعی — SI-SDR یا STOI برای دیدن اینکه آیا سیگنال پردازش‌شده واقعاً به‌سویش همگرا شده نه فقط متفاوت به‌نظر می‌رسد. این همان نظمی است که پشتِ ارقام SNR در جدول رفع‌نویزکنندهٔ بالاست: عددی وصل‌شده به یک شرایط نویزِ مشخص، نه یک صفت. خانوادهٔ گسترده‌تر کتابخانه‌های ONNX خالص که این در آن جای می‌گیرد، شامل خودِ speechonnxmetrics، در خانواده‌ای از کتابخانه‌های گفتاریِ خالصِ ONNX پوشش داده شده.

تمیزکردن صدا پیش از رسیدن به یک تشخیص‌دهنده، یک سامانهٔ شناسایی گوینده، یا یک شنوندهٔ انسانی خودش یک قطعه مهندسی است، با ثبت‌نام معاوضه‌های خودش و فهرست رویکردهای خودش که امتحان شده‌اند و در تماس با یک سیگنال واقعی زنده نمانده‌اند.

پرسش‌ها دربارهٔ به‌کاربردنِ این در یک خط لولهٔ خاص: با ما تماس بگیرید.