من الاختبارات الاصطناعية إلى الصوت الحقيقي: تجربتي في التحقق من FastConformer CTC مع ONNX وNVIDIA NeMo
مقدمة
أثناء مساهمتي في مشروع Munajjam مفتوح المصدر، ومن خلال Pull Request ما يزال قيد المراجعة وقت كتابة هذا المقال، عملت على التحقق من مسار FastConformer CTC مع ONNX ومقارنته مع NVIDIA NeMo باعتباره Reference Implementation.
في البداية، كانت الاختبارات الآلية المبنية على بيانات اصطناعية (Synthetic Data) تنجح دون مشاكل. لكن ذلك أثار سؤالًا جوهريًا:
هل نجاح Unit Tests يعني فعلًا أن النموذج سيُنتج النتائج نفسها عند تمرير تسجيل صوتي حقيقي؟
للإجابة عن هذا السؤال، انتقلت من الاختبارات الاصطناعية إلى تحقق شامل من الطرف إلى الطرف (End-to-End Validation) باستخدام تسجيل صوتي عربي حقيقي ونموذج NVIDIA NeMo كمرجع عددي.
في هذا المقال أشارك المنهجية التي اتبعتها، والمشكلات التقنية الدقيقة التي كشفتها البيانات الحقيقية، والنتائج التي حصلت عليها.
1. نطاق التحقق وأهدافه
لم يكن الهدف مجرد التأكد من أن كود ONNX يعمل دون أخطاء، بل التحقق من سلامة المسار الكامل:
تسجيل عربي حقيقي (WAV)
↓
معالجة أولية بـ NumPy
↓
Mel Spectrogram
↓
FastConformer ONNX
↓
CTC Log Probabilities
↓
Greedy CTC Decoding
↓
SentencePiece
↓
النص العربي النهائي
ثم مقارنة النتائج مع NVIDIA NeMo باعتباره المرجع.
حددت ثلاثة مستويات رئيسية للتحقق:
- مقارنة المعالجة الأولية (Preprocessing) عدديًا مع NeMo.
- التأكد من نجاح ONNX Runtime Inference وإنتاج Logits سليمة دون NaN أو Inf.
- مقارنة النص النهائي الناتج عن CTC Decoding مع النص الناتج عن NeMo.
2. لماذا لم تكن Unit Tests وحدها كافية؟
الاختبارات الاصطناعية مفيدة جدًا للتحقق من المكونات المعزولة، لكنها لا تكشف دائمًا الحالات الحدّية (Edge Cases) التي تظهر مع البيانات الحقيقية.
وهذا ما حدث بالفعل.
عند استخدام تسجيل عربي حقيقي، ظهر اختلاف ملحوظ بين Preprocessing الخاص بالتنفيذ وبين NeMo.
بدل محاولة معالجة النتيجة النهائية مباشرة، بدأت بمقارنة مراحل المعالجة واحدة تلو الأخرى لتحديد أول نقطة يبدأ عندها الاختلاف.
3. بناء مرجع موثوق باستخدام NVIDIA NeMo
استخدمت النموذج التالي:
nvidia/stt_ar_fastconformer_hybrid_large_pc_v1.0
وكانت الفكرة كالتالي:
Munajjam Implementation
↕
Numerical Comparison
↕
NVIDIA NeMo
بهذه الطريقة، لم يعد معيار النجاح مجرد أن "الكود يعمل"، بل أصبح لدينا Numerical Oracle نستطيع مقارنة كل مرحلة به بشكل مستقل.
4. المشكلات التقنية المكتشفة وطريقة حلها
4.1 تفاصيل STFT وHann Window
أحد أهم الدروس التي ظهرت أثناء التحقيق هو أن تنفيذ STFT لا يعتمد فقط على المعاملات الأساسية مثل:
n_fft
hop_length
win_length
بل توجد تفاصيل أخرى تؤثر مباشرة على النتائج العددية، مثل:
• Padding
• Window Centering
• Hann Window Semantics
كان من الضروري مطابقة طريقة NeMo في تمركز النافذة داخل FFT واستخدام Padding المناسب.
كما ظهر اختلاف مهم في Hann Window بين:
periodic=True
و:
periodic=False
وبعد المقارنة العددية مع NeMo، أمكن تحديد الـSemantics الصحيحة بدل الاعتماد على التخمين.
الدرس المستفاد:
عند إعادة تنفيذ Preprocessing لنموذج تعلم آلي، لا يكفي أن تكون الخوارزمية "مشابهة" للمرجع. أحيانًا يجب مطابقة التفاصيل العددية الدقيقة للـReference Implementation.
4.2 Slaney Mel Scale
ظهرت أيضًا مشكلة مهمة أثناء تحويل التردد من Hz إلى Mel.
كان التنفيذ يحتاج إلى مطابقة Slaney Mel Scale المستخدمة في مسار NeMo/librosa.
القيمة الأساسية في الجزء الخطي هي:
f_sp = 200.0 / 3.0
وليس الثابت المرتبط بصيغة HTK في هذا الموضع.
بعد تصحيح هذه النقطة، انخفض الفرق العددي بين NumPy وNeMo بشكل كبير.
4.3 المفاجأة التي كشفها الصوت الحقيقي: ceil أم floor؟
كانت هذه من أهم النتائج التي كشفها Real-Audio Validation.
التسجيل المستخدم في الاختبار كان يحتوي على:
49,536 samples
ومع:
hop_length = 160
كان التنفيذ السابق يؤدي إلى اعتبار عدد الإطارات 310، بينما NeMo يعتبر عدد الإطارات الصالحة 309.
الحساب الصحيح أصبح:
valid_length = waveform.size // HOP_LENGTH
أي:
49536 // 160 = 309
لماذا هذه النقطة مهمة؟
لأن Valid Frame Count يمكن أن يؤثر على:
• Normalization
• Masking
• Encoder Lengths
• النتيجة النهائية للنموذج
وهنا ظهرت قيمة الاختبار باستخدام بيانات حقيقية: الأطوال الاصطناعية المستخدمة في الاختبارات السابقة لم تكن تكشف هذه الحالة الحدّية.
5. التحقق العددي من Preprocessing
بعد معالجة الاختلافات السابقة، قارنت Mel Features الناتجة عن تنفيذ NumPy مع NeMo.
كانت النتائج:
Our shape : (1, 80, 310)
NeMo shape : (1, 80, 310)
NeMo length: [309]
MAE : 2.5163737404909625e-07
MaxAE : 9.03010368347168e-06
هذه النتائج تشير إلى تقارب عددي قوي جدًا بين التنفيذين.
والأهم أن Munajjam أصبح يحسب Valid Length بشكل مستقل باستخدام:
mel_length = np.array(
[wave_np.size // HOP_LENGTH],
dtype=np.int32,
)
ثم تتم مقارنة هذه القيمة مع NeMo كمرجع.
بهذا الشكل، لا يتم أخذ Valid Length من NeMo وإعطاؤه مباشرة إلى مسار ONNX، بل يحسبه مسار Munajjam بشكل مستقل ثم يستخدم NeMo للتحقق من صحة النتيجة.
6. الانتقال إلى ONNX Runtime
بعد التأكد من سلامة Preprocessing، انتقلت إلى تشغيل نموذج FastConformer المصدّر بصيغة ONNX.
كانت النتائج:
Mel input shape : (1, 80, 310)
Mel valid length: [309]
Logprobs shape : (1, 39, 1025)
Encoded length : [39]
NaN : False
Inf : False
هذا يؤكد أن ONNX Runtime استطاع معالجة Mel Features الحقيقية وإنتاج CTC Log-Probabilities سليمة عدديًا.
7. لا تعتمد على اسم ملف ONNX لتحديد الـContract
أثناء التحقق ظهرت ملاحظة مهمة.
اسم الملف ينتهي بـ:
_ctc_rawaudio.onnx
وقد يوحي الاسم بأن النموذج يستقبل Raw Waveform مباشرة.
لكن عند فحص ONNX Input Contract الفعلي، تبين أن المدخل هو:
[B, 80, T]
أي أن النموذج يستقبل Mel Features وليس Waveform أحادي البعد.
الدرس المستفاد:
عند التعامل مع ONNX، يجب فحص Inputs وOutputs وDtypes الفعلية للنموذج، وعدم استنتاج الـContract اعتمادًا على اسم الملف فقط.
8. CTC Greedy Decoding
بعد الحصول على Logprobs، استخدمت Greedy CTC Decoding.
ومن المهم هنا احترام عدد الإطارات الصالحة التي يعيدها الـEncoder:
valid_t = int(encoded_lengths[0])
token_ids = np.argmax(
logprobs[0, :valid_t],
axis=-1
)
ثم يمر فك الترميز بالمراحل التالية:
Argmax
↓
إزالة الرموز المتكررة
↓
إزالة CTC Blank
↓
SentencePiece Decoding
استخدام encoded_lengths مهم لأن logprobs قد تحتوي في حالات أكثر عمومية على إطارات غير صالحة أو Padding، خاصة عند التعامل مع Batches أو ملفات بأطوال مختلفة.
9. النتيجة النهائية
بعد تشغيل المسارين على التسجيل العربي الحقيقي نفسه، كانت النتيجة:
NeMo : عما كانوا يعملون
ONNX : عما كانوا يعملون
Match exact: True
أي أن المسارين أعطيا النص نفسه حرفيًا على العينة الصوتية المستخدمة في هذا الاختبار.
من المهم هنا وضع النتيجة في سياقها الصحيح:
هذا Exact Match يمثل دليل E2E قويًا على العينة المختبرة، لكنه لا يعني إثبات التكافؤ المطلق على جميع التسجيلات الصوتية الممكنة.
10. ماذا تعلمت من هذه التجربة؟
أهم درس خرجت به من هذه التجربة هو:
نجاح Unit Tests لا يعني بالضرورة أن التكامل الكامل صحيح على بيانات حقيقية.
الاختبارات الاصطناعية كانت ضرورية، لكنها لم تكن نهاية عملية التحقق.
إضافة Real-Audio E2E Validation ساعدت على كشف تفاصيل دقيقة مرتبطة بـ:
• STFT
• Hann Window
• Slaney Mel Scale
• Valid Frame Count
• ONNX Input Contract
• CTC Decoding
وهذا يوضح أهمية الجمع بين ثلاث طبقات من التحقق:
Unit Tests
+
Numerical Equivalence Tests
+
Real-World E2E Validation
11. منهجية قابلة لإعادة الاستخدام
يمكن تلخيص المنهجية التي اتبعتها كالتالي:
تحديد Reference Implementation موثوق.
اختبار كل مرحلة عدديًا بشكل مستقل.
تحديد أول مرحلة يبدأ عندها الاختلاف.
إصلاح السبب الجذري بدل محاولة تعديل النتيجة النهائية.
إضافة Regression Test حتى لا يعود الخطأ مستقبلًا.
الانتقال من Synthetic Data إلى بيانات حقيقية.
تنفيذ End-to-End Validation للمسار الكامل.
توثيق الخطوات والنتائج بحيث يمكن إعادة إنتاجها.
هذه المنهجية ليست خاصة بـFastConformer.
يمكن تطبيقها أيضًا في مشاريع:
Machine Learning
ASR
ONNX
Model Conversion
Inference Pipelines
Numerical Validation
12. الخاتمة
بدأت هذه المهمة كتعديل تقني ضمن مساهمة مفتوحة المصدر، لكنها تحولت إلى تجربة عملية في Numerical Debugging وModel Validation.
أكثر ما وجدته مفيدًا هو الانتقال من السؤال:
"هل الكود يعمل؟"
إلى سؤال أكثر دقة:
"هل كل مرحلة من التنفيذ تتصرف مثل الـReference Implementation عند استخدام بيانات حقيقية؟"
هذا الفرق بين الاختبار الوظيفي البسيط والتحقق الهندسي العميق هو ما ساعد على اكتشاف المشكلات والوصول في النهاية إلى تقارب عددي قوي ونتيجة Transcription متطابقة على العينة الحقيقية.
13. المصادر والمشروع
المشروع:
Munajjam
المستودع:
https://github.com/Itqan-community/Munajjam
الفرع:
feat/ctc-segmentation
Pull Request #114:
https://github.com/Itqan-community/Munajjam/pull/114
حالة Pull Request وقت نشر المقال:
قيد المراجعة (Under Review)
النموذج المرجعي:
NVIDIA FastConformer / NeMo
دليل إعادة إنتاج اختبار E2E
دفتر Google Colab لإعادة تنفيذ التحقق عمليًا
ملاحظة حول حالة المساهمة
وقت نشر هذا المقال، ما يزال Pull Request #114 قيد المراجعة من قِبل Maintainers مشروع Munajjam.
النتائج والتجارب الواردة في المقال تصف التحقق التقني الذي أجريته على النسخة الحالية من المساهمة، ولا تعني أن التغييرات قد تم اعتمادها أو دمجها رسميًا في المشروع.
إذا أدت عملية المراجعة إلى تغييرات تقنية جوهرية، سأقوم بتحديث المقال بما يعكس النسخة النهائية.
تم إعداد هذا المقال انطلاقًا من تجربة مساهمة فعلية في مشروع مفتوح المصدر. النتائج الرقمية المذكورة تخص بيئة التحقق والعينة الصوتية المستخدمة في هذه التجربة.