هل تساءلت يومًا كيف تُعالج نماذج التعلم الآلي بيانات الصوت؟ كيف تتعامل مع أطوال صوتية مختلفة، وتُحوّل ترددات الصوت إلى أنماط قابلة للتعلم، وتتأكد من متانة نموذجك؟ سيوضح لك هذا الدرس كيفية التعامل مع بيانات الصوت باستخدام TorchAudio، وهي مجموعة أدوات مبنية على PyTorch.
ستعمل مع بيانات الكلام الحقيقية لتعلم التقنيات الأساسية مثل تحويل الأشكال الموجية إلى أطياف، وتوحيد أطوال الصوت، وإضافة ضوضاء محكومة لبناء نماذج التعلم الآلي والعميق.
قم بالتعمق في الدرس لاستكشاف هذه المفاهيم ومعرفة كيفية تطبيقها لإعداد البيانات الصوتية لمهام التعلم العميق باستخدام TorchAudio.
تعلم المصطلحات التقنية الأساسية
قبل الخوض في التفاصيل الفنية لمعالجة الصوت باستخدام TorchAudio، خصص بعض الوقت لمراجعة بعض المصطلحات الأساسية. ستساعدك هذه المصطلحات على فهم أساسيات التعامل مع البيانات الصوتية.
شكل الموجة
الموجة هي التمثيل المرئي للصوت أثناء انتقاله عبر الهواء مع مرور الوقت. عندما تتحدث أو تغني أو تعزف الموسيقى، تُولّد اهتزازات تنتقل عبر الهواء على شكل موجات. يمكن التقاط هذه الموجات وعرضها كرسم بياني يوضح كيفية تغير ضغط الصوت مع مرور الوقت. إليك مثال:

هذا شكل موجة لموجة بتردد ٤٤٠ هرتز، مُرسومة على مدى فترة قصيرة قدرها ١٠ ملي ثانية. يُسمى هذا تمثيلًا زمنيًا، حيث يُظهر كيفية تغير سعة الموجة بمرور الوقت. يُظهر هذا الشكل الموجي الإشارة الخام كما تظهر في محرر الصوت. تعكس الارتفاعات والانخفاضات تغيرات في شدة الصوت.
السعة
السعة هي قوة أو شدة موجة الصوت، أي مدى ارتفاع الصوت لدى المستمع. في الصورة السابقة، يُمثَّل ارتفاع الموجة عن خط مركزها.
كلما زادت السعة، ارتفع الصوت، عند ضبط مستوى الصوت على جهازك، فإنك في الواقع تُغيّر سعة الإشارة الصوتية. في الصوت الرقمي، تُقاس السعة عادةً بالديسيبل (dB) أو كقيمة معيارية بين -1 و1.
التردد
التردد هو عدد مرات تكرار موجة صوتية في ثانية واحدة، ويُقاس بالهرتز (Hz). على سبيل المثال، نغمة الجهير المنخفضة هي موجة صوتية تتكرر ببطء، حوالي 50-100 هرتز. في المقابل، للصفير عالي النبرة موجة تتكرر بسرعة أكبر، حوالي 2000-3000 هرتز.
في الموسيقى، تُنتج الترددات المختلفة نوتات موسيقية مختلفة. على سبيل المثال، النوتة A4 التي يستخدمها الموسيقيون لضبط آلاتهم الموسيقية هي 440 هرتز بالضبط. الآن، إذا نظرت إلى مخطط التردد لشكل الموجة 440 هرتز السابق، فسترى ما يلي:

يعرض هذا الرسم البياني الإشارة في نطاق التردد، مما يُظهر مقدار كل تردد موجود في الصوت. تشير الذروة المميزة عند 440 هرتز إلى أن هذا هو التردد السائد في الإشارة، وهو ما تتوقعه تمامًا من نغمة نقية. بينما تكشف رسوم النطاق الزمني – مثل الرسم الذي شاهدته سابقًا – كيف تتغير سعة الصوت بمرور الوقت، فإن رسوم النطاق الترددي تساعدك على فهم الترددات التي تُكوّن الصوت.
شكل الموجة الذي استكشفته للتو كان من موجة بتردد ٤٤٠ هرتز. ستلاحظ قريبًا أن العديد من الأمثلة في معالجة الصوت تتعامل أيضًا مع هذا التردد الغامض. إذًا، ما الذي يجعله مميزًا لهذه الدرجة؟
ملاحظة: يُعد تردد 440 هرتز (نغمة A4) المعيار الدولي لضبط النغمات لآلات الضبط. طبيعته الواضحة أحادية التردد تجعله مثاليًا للمهام الصوتية، بما في ذلك أخذ العينات، وتحليل التردد، وتمثيل الموجات.
الآن بعد أن أصبحت تفهم التردد وكيفية ارتباطه بالموجات الصوتية، قد تتساءل كيف تقوم أجهزة الكمبيوتر في الواقع بالتقاط هذه الموجات وتخزينها.
أخذ العينات
عند تسجيل الصوت رقميًا، يتم التقاط لقطات سريعة للموجة الصوتية عدة مرات في الثانية. تقيس كل لقطة سعة الموجة في تلك اللحظة. وهذا ما يُسمى “العينة”. ويُعرف عدد اللقطات السريعة الملتقطة في الثانية بمعدل العينة، ويُقاس بالهرتز (Hz).
عند تسجيل الصوت الرقمي، يجب أخذ عينات بسرعة مضاعفة على الأقل من أعلى تردد ترغب في التقاطه. يُعرف هذا بمعدل نيكويست. تستطيع الأذن البشرية سماع ترددات تصل إلى حوالي 20,000 هرتز، ولهذا السبب تستخدم صيغ الصوت، مثل الأقراص المدمجة، معدل أخذ عينات يبلغ 44,100 هرتز لالتقاط النطاق الكامل للسمع البشري بدقة.
هكذا تبدو عملية أخذ العينات في الممارسة العملية:

يُظهر الخط البرتقالي الموجة الصوتية الأصلية المستمرة، وتُظهر النقاط العينات المنفصلة بمعدل أخذ عينات ٢٠، أي أن الإشارة تُؤخذ ٢٠ مرة في الثانية. هذه العينات هي ما يُخزّنه جهاز الكمبيوتر ويُعالجه فعليًا. يُتيح لك معدل أخذ عينات أعلى إعادة بناء الموجة الأصلية بدقة أكبر.
يُعد فهم هذه المفاهيم الأساسية، مثل أشكال الموجات، والسعة، والتردد، وأخذ العينات، أمرًا بالغ الأهمية للعمل مع البيانات الصوتية. تُعدّ هذه الأفكار أساسية لمعالجة الإشارات الصوتية. ستساعدك هذه الأفكار على فهم كيفية تعامل TorchAudio مع البيانات الصوتية وتحويلها بطريقة تفهمها نماذج التعلم الآلي.
عند الحديث عن TorchAudio، فقد حان الوقت الآن لتتعلم كيفية تثبيته واستكشاف كيفية مساعدته في التعامل مع بيانات الصوت في مشاريع التعلم الآلي الخاصة بك.
ابدأ مع TorchAudio
TorchAudio عبارة عن مجموعة أدوات تعتمد على PyTorch لكل ما يتعلق بالصوت—من تحميل البيانات ومعالجتها مسبقًا إلى العمل مع الكلام والموسيقى وملفات الصوت الأخرى.
يمكنك تحويل الصوت إلى صيغ مثل المخططات الطيفية للتحليل العميق، ثم إدخاله في نماذج التعلم الآلي. هذا يعني أنه يمكنك معالجة بيانات الصوت مسبقًا، ثم تدريب النماذج لمهام مثل التعرف على الكلام، كل ذلك ضمن إطار عمل PyTorch.
تثبيت TorchAudio
قبل إجراء أي عمليات متعلقة بالصوت، يجب تثبيت TorchAudio، الذي يُوسّع إمكانيات PyTorch لمعالجة بيانات الصوت. يستخدم TorchAudio نفس عمليات الموتر، وتسريع وحدة معالجة الرسومات، والتمايز التلقائي التي توفرها PyTorch للشبكات العصبية، ولكنه يُطبّقها تحديدًا على مهام معالجة الصوت. هذا يعني أن خط أنابيب معالجة الصوت لديك يتكامل مع نماذج PyTorch باستخدام هياكل بيانات وأساليب حسابية متسقة.
ملاحظة: لستَ بحاجة إلى تثبيت PyTorch بشكل منفصل، حيث يتم تثبيته تلقائيًا كاعتمادية عند تثبيت TorchAudio. مع ذلك، إذا كنتَ قد ثبّتَ PyTorch بالفعل وترغب في مواصلة العمل في بيئة Python الحالية لديك، فعليك التأكد من تثبيت حزمتي torch وtorchaudio الصحيحتين. للمزيد من التفاصيل، يُرجى مراجعة مصفوفة التوافق على الموقع الرسمي.
لمتابعة هذا الدرس، ستحتاج إلى تثبيت TorchAudio وبعض المكتبات الإضافية. يمكنك تثبيتها باستخدام Conda أو pip، حسب إعداداتك. مع ذلك، تذكّر أنه على الرغم من أن Conda توفر إدارة سهلة للتبعيات، إلا أن حزمها قد تكون أقل أداءً قليلاً من أحدث الإصدارات المتوفرة على PyPI.
على أي حال، يُنصح بإنشاء بيئة افتراضية منفصلة أولًا لعزل هذه التبعيات عن المشاريع الأخرى.
إذا كنت أحد مستخدمي pip، فاستخدم هذه الأوامر:
PS> py -m venv venv --prompt torchaudio-tutorial
PS> .\venv\Scripts\activate
(torchaudio-tutorial) PS> python -m pip install numpy matplotlib jupyterlab \
torchaudio sounddevice soundfile tqdm
للتحقق مما إذا كان تثبيت TorchAudio يعمل أم لا، قم بتشغيل Python REPL داخل البيئة الافتراضية النشطة لديك وقم بتشغيل مقتطف التعليمات البرمجية التالي:
>>> import torchaudio
>>> torchaudio.__version__
'2.7.0+cu126'
>>> torchaudio.list_audio_backends()
['soundfile']
إذا ظهرت سلسلة مثل “2.5.1” أو “2.7.0+cu126” في المخرجات، فهذا يعني أنك ثبّت TorchAudio بنجاح. بالإضافة إلى ذلك، تأكد من توفر واحد على الأقل من واجهات الصوت الخلفية الثلاثة المدعومة (ffmpeg، sox، أو soundfile) لتتمكن من تحميل بيانات الصوت وحفظها باستخدام TorchAudio.
الآن بعد أن قمت بتثبيت TorchAudio بشكل صحيح، فقد حان الوقت لتحميل مجموعة البيانات التي ستستخدمها طوال البرنامج التعليمي.
استكشف مجموعة بيانات أوامر الكلام
في هذا البرنامج التعليمي، ستتعامل مع مجموعة بيانات “أوامر الكلام“. أنشأها بيت واردن في جوجل، ونُشرت كجزء من تحدي التعرف على الكلام TensorFlow. تتضمن هذه المجموعة تسجيلات صوتية، وهي مخصصة لتدريب واختبار نماذج التعلم الآلي في مهام التعرف على الكلام.
تتضمن هذه المجموعة أكثر من 105,000 مقطع صوتي مدته ثانية واحدة. يحتوي كل مقطع على واحدة من 35 كلمة قصيرة أو أمرًا – مثل “نعم”، “لا”، “قطة”، “أعلى”، “أسفل”، أو رقم – نطقتها مجموعة واسعة من الأشخاص. صُممت هذه المجموعة للمساعدة في بناء وتقييم أدوات التحكم الصوتي، وأنظمة التعرف على الكلام، وخوارزميات معالجة الصوت.
تحميل مجموعة البيانات باستخدام TorchAudio
للوصول إلى مجموعة بيانات أوامر الكلام، يمكنك الاستفادة من فئة SPEECHCOMMANDS من TorchAudio. يمكنك اختياريًا تنزيل واستخراج أرشيف .tar.gz مع هذه المجموعة.
ملاحظة: حجم الأرشيف حوالي ٢٫٤ جيجابايت، ويتطلب المحتوى المستخرج ما يصل إلى ٢٥ جيجابايت إضافية من مساحة القرص. قد تختلف أوقات التنزيل والاستخراج حسب اتصالك بالإنترنت وأداء القرص.
للحصول على مجموعة البيانات في دليل العمل الحالي لديك، يمكنك استخدام وحدة pathlib الخاصة بـ Python مع فئة SPEECHCOMMANDS الخاصة بـ TorchAudio:
>>> from pathlib import Path
>>> from torchaudio.datasets import SPEECHCOMMANDS
>>> speech_commands_dataset = SPEECHCOMMANDS(root=Path.cwd(), download=True)
افتراضيًا، يحفظ هذا الكود مجموعة البيانات في المجلد الجذر لمشروعك ضمن المجلد الفرعي /SpeechCommands. تجدر الإشارة إلى أنه حتى مع استخدام download=True، لن يُنزّل TorchAudio الملفات تلقائيًا مرة أخرى إذا كانت موجودة بالفعل في الموقع المحدد. بل سيعيد استخدام البيانات التي تم تنزيلها سابقًا.
قم بإلقاء نظرة على دليل المشروع الخاص بك، والذي يجب أن يبدو كالتالي:
./
│
└── SpeechCommands/
│
└── speech_commands_v0.02/
│
├── backward/
│ ├── 0165e0e8_nohash_0.wav
│ ├── 017c4098_nohash_0.wav
│ └── ...
│
├── bed/
│ ├── 00176480_nohash_0.wav
│ ├── 004ae714_nohash_0.wav
│ └── ...
│
├── bird/
│ ├── 00970ce1_nohash_0.wav
│ ├── 00b01445_nohash_0.wav
│ └── ...
│
└── ...
يمكنك الوصول إلى هذه الملفات الصوتية وبياناتها الوصفية في بايثون باستخدام متغير speech_commands_dataset. يأتي كائن مجموعة بيانات PyTorch الناتج بطريقة عامة واحدة فقط، ويدعم صيغة الأقواس المربعة:
>>> speech_commands_dataset.get_metadata(10)
(
'speech_commands_v0.02/backward/02ade946_nohash_4.wav',
16000,
'backward',
'02ade946',
4
)
>>> speech_commands_dataset[10]
(
tensor([[ 0.0000e+00, -9.1553e-05, -3.0518e-05, ..., -2.1362e-04]]),
16000,
'backward',
'02ade946',
4
)
يقبل التابع .get_metadata() الفهرس الرقمي للعينة كحجة وترجع صف Python تحتوي على العناصر الخمسة التالية:
- المسار النسبي لملف الصوت
- معدل العينة
- العلامة
- معرف المتحدث
- رقم النطق
عند الوصول إلى نفس العينة باستخدام صيغة الأقواس المربعة – مثل speech_commands_dataset[10] – ستحصل على مجموعة بيانات تعكس ناتج .get_metadata(10)، باستثناء العنصر الأول. بدلاً من مسار الملف، ستحصل على موتر PyTorch يمثل شكل الموجة الصوتية الخام. يبقى باقي المجموعة دون تغيير.
لتسهيل التعامل مع عينات مجموعات البيانات هذه، يمكنك تغليفها. أنشئ وحدة بايثون جديدة – على سبيل المثال، وحدة باسم speech.py - بالمحتوى التالي:
from typing import NamedTuple
from torch import Tensor
class SpeechSample(NamedTuple):
waveform: Tensor
sample_rate: int
label: str
speaker_id: str
utterance_number: int
في هذه الوحدة المساعدة، عرّفتَ فئة SpeechSample مخصصة تمتد إلى typing.NamedTuple، مما يُعطي أسماءً ذات معنى وأنواعًا واضحة لعناصرها. يتيح لك هذا التصميم الوصول إلى كل جزء من عينة مجموعة البيانات بالاسم بدلاً من الفهرس، مما يُحسّن سهولة قراءة الكود ويُقلل الحاجة إلى تذكر ترتيب أو معنى عناصر المجموعة.
أنت جاهز! حان وقت التعمق في مجموعة البيانات.
تحميل ملف صوتي مثال
يمكنك الآن الوصول إلى ملفات الصوت في مجموعة البيانات باختيار أي فهرس للتحميل. في هذا البرنامج التعليمي، ستلتزم بعينة عند الفهرس ١٠، وهو ما يُقابل كلمة “backward” للتوضيح.
>>> from pathlib import Path
>>> from torchaudio.datasets import SPEECHCOMMANDS
>>> from speech import SpeechSample
>>> speech_commands_dataset = SPEECHCOMMANDS(root=Path.cwd(), download=True)
>>> speech_sample = SpeechSample(*speech_commands_dataset[10])
>>> speech_sample
SpeechSample(
waveform=tensor([[ 0.0000e+00, -9.1553e-05, ..., -2.1362e-04]]),
sample_rate=16000,
label='backward',
speaker_id='02ade946',
utterance_number=4
)
>>> speech_sample.sample_rate
16000
بعد استيراد الوحدات اللازمة – بما في ذلك وحدة speech المساعدة – وإنشاء كائن مجموعة البيانات، يمكنك تحميل عينة صوتية محددة. يتم تحويل المجموعة العادية المُعادة إلى عينة SpeechSample الخاصة بك إلى فك ضغط عناصرها باستخدام مُعامل النجمة (*) وتمريرها كوسائط إلى مُنشئ SpeechSample.
ستكون مهتمًا بشكل خاص بمعالجة السمات التالية:
| السمة | الوصف |
|---|---|
.waveform | الموتر الذي يمثل الإشارة الصوتية |
.sample_rate | عدد صحيح يشير إلى عدد العينات في الثانية (هرتز) |
.label | سلسلة تحتوي على ما يقوله الممثل الصوتي في ملف الصوت |
بعد تحميل عينة الصوت ووصولك إلى خصائصها الرئيسية بسهولة، أنت الآن جاهز للتعمق أكثر. بعد ذلك، ستفحص موتر الموجة لفهم كيفية تمثيل TorchAudio للصوت، وكيفية التعامل معه باستخدام أدوات PyTorch القوية.
فهم موترات الصوت (أشكال الموجة)
في TorchAudio، يُمثل الشكل الموجي بيانات الصوت كموتر. تُمثل الإشارة الصوتية كموتر PyTorch، مما يُسهّل التعامل معها ومعالجتها باستخدام الأدوات المتاحة في PyTorch وTorchAudio.
كما تعلمت سابقًا، يُمثل شكل الموجة تغير إشارة صوتية مع مرور الوقت. يُظهر رسمًا بيانيًا لموجة صوتية، حيث يُمثل المحور السيني الزمن، ويُظهر المحور الصادي سعة الموجة. بالنسبة لعينة الكلام التي حمّلتها للتو، يبدو الشكل كالتالي:

يمكنك الحصول على مزيد من المعلومات حول ملف الصوت الخاص بك عن طريق التحقق من شكل موتر الموجة الخاص بك:
>>> speech_sample.waveform.shape
torch.Size([1, 16000])
البُعد الأول لهذا الموتر هو ١، مما يدل على أن ملف الصوت أحادي، أي أنه يحتوي على قناة صوتية واحدة. إذا كان ملف الصوت ملف ستيريو، فسيكون هذا البُعد ٢، ويمثل قناتي الصوت اليمنى واليسرى.
البُعد الثاني هو ١٦٠٠٠، وهو يُمثل العدد الإجمالي لعينات الصوت في الموجة. تُقابل كل عينة قيمة سعة في نقطة زمنية مُحددة.
بما أن الشكل العام لموتر الموجة هو [القنوات، العينات]، يمكنك عرض هذه القيم من خلال فئة SpeechSample. إليك كيفية توسيع الفئة بإضافة خاصيتين محسوبتين تُسهّلان الوصول إلى عدد القنوات وعدد العينات:
from typing import NamedTuple
from torch import Tensor
class SpeechSample(NamedTuple):
waveform: Tensor
sample_rate: int
label: str
speaker_id: str
utterance_number: int
@property
def num_channels(self) -> int:
return self.waveform.size(0)
@property
def num_samples(self) -> int:
return self.waveform.size(1)
في كلتا الحالتين، يمكنك استدعاء دالة .size() للموتر لاسترداد طول البُعد المقابل. عند إعادة استيراد الفئة المُحدّثة، ستتمكن من استخدام خصائصها الجديدة:
>>> speech_sample.num_channels
1
>>> speech_sample.num_samples
16000
وبنفس الطريقة، يمكنك تعريف خاصية ملائمة لحساب مدة العينة بالثواني:
from typing import NamedTuple
from torch import Tensor
class SpeechSample(NamedTuple):
# ...
@property
def num_seconds(self) -> float:
return self.num_samples / self.sample_rate
يؤدي تقسيم العدد الإجمالي للعينات على معدل أخذ العينات إلى الحصول على طول الصوت بالثواني.
الآن، أنت تعرف كيفية الوصول إلى ملفات الصوت الفردية في مجموعة بياناتك وعرض موتراتها. لكنك لا تستطيع معرفة ما بداخلها حقًا! كيف يمكنك الاستماع إلى ملفاتك الصوتية؟
استمع إلى ملفاتك الصوتية
هناك طرق عديدة لتشغيل الصوت في بايثون باستخدام مكتبات خارجية. من الخيارات الشائعة sounddevice، الذي يتيح لك تشغيل عينات صوتية مباشرةً من مصفوفة NumPy. إذا كنت تتابع ما سبق، فمن المفترض أن تكون كلتا المكتبتين مثبتتين في بيئتك.
لتحويل موتر PyTorch إلى مصفوفة NumPy، استدعِ دالة .numpy() عليه. تذكر أنك قد تحتاج إلى إعادة تشكيل المصفوفة الناتجة بحيث تعكس عدد قنوات الصوت، لأن دالة ()sounddevice.play تتوقع تنسيق البيانات التالي:
بناءً على هذه المعلومات، يمكنك تنفيذ الطريقة التالية في صفك:
from typing import NamedTuple
import sounddevice as sd
from torch import Tensor
class SpeechSample(NamedTuple):
# ...
def play(self) -> None:
sd.play(
self.waveform.numpy().reshape(-1, self.num_channels),
self.sample_rate,
blocking=True
)
أولاً، تُحوّل الموتر إلى مصفوفة NumPy، وتُعيد تشكيلها لتتوافق مع التنسيق المتوقع للتشغيل متعدد القنوات. ثم يُشغّل الصوت بشكل متزامن بضبط blocking=True، بدلاً من التشغيل في الخلفية، باستخدام معدل أخذ العينات المناسب.
بدلاً من ذلك، إذا كنت تعمل على هذا البرنامج التعليمي في Jupyter Notebook، فيمكنك الاستفادة من عنصر واجهة المستخدم IPython.display.Audio للاستماع إلى ملفات الصوت الخاصة بك بشكل تفاعلي:
from typing import NamedTuple
import sounddevice as sd
from IPython.display import Audio
from torch import Tensor
class SpeechSample(NamedTuple):
# ...
def play_widget(self) -> Audio:
return Audio(
self.waveform.numpy(),
rate=self.sample_rate,
autoplay=True
)
يأخذ Audio مصفوفة NumPy مأخوذة من موتر الموجة. كما يأخذ sample_rate للصوت وعلامة autoplay اختيارية، والتي تُحدد ما إذا كان يجب تشغيل الصوت تلقائيًا.
عند استدعاء طريقتك الجديدة في خلية من دفتر ملاحظات Jupyter، ستظهر لك أداة تفاعلية، وستسمع صوتًا يقول كلمة “backward”. هكذا تبدو الأداة:

يمكنك تجربة معلمة الفهرس واستكشاف ملفات صوتية أخرى في مجموعة البيانات هذه. بعد ذلك، حان وقت إعداد مجموعة بياناتك الصوتية لنموذج التعلم العميق.
التحقيق في أساليب المعالجة المسبقة وزيادة البيانات
قبل إدخال بيانات الصوت في نموذج التعلم العميق، ستحتاج إلى تحويلها إلى صيغة يفهمها النموذج. يتضمن ذلك توحيد إشاراتك الصوتية وتعزيزها من خلال تقنيات مثل:
إعادة أخذ عينات الصوت
تُغيّر إعادة أخذ العينات معدل أخذ العينات للإشارة الصوتية، أو عدد العينات الصوتية المأخوذة في الثانية (بالهرتز). على سبيل المثال، يعني معدل أخذ العينات البالغ 44.1 كيلوهرتز (kHz) أخذ 44,100 عينة في الثانية. يمكنك زيادة معدل أخذ العينات عن طريق رفع معدل أخذ العينات، أو خفضه عن طريق خفض معدل أخذ العينات، حسب احتياجاتك.
قد تتساءل: لماذا تحتاج إلى إعادة أخذ عينات من مجموعة بياناتك الصوتية أصلًا؟ هناك سببان رئيسيان لذلك:
- متطلبات المعالجة: تتطلب بعض خوارزميات أو تطبيقات معالجة الصوت معدل أخذ عينات محددًا. يتم تدريب العديد من نماذج التعرف على الكلام، مثل Siri وGoogle Assistant وAlexa، على صوت بتردد 16 كيلوهرتز.
- حجم الملف: يؤدي خفض معدل أخذ العينات إلى تقليص حجم الملف، مما يساعد على توفير مساحة التخزين وتقليل عرض النطاق الترددي للبث.
يوفر TorchAudio دالة resample() ضمن وحدة torchaudio.functional. إليك كيفية دمجها في فئة SpeechSample لإعادة أخذ عينات من ملف الصوت الأساسي:
from copy import replace
from typing import NamedTuple, Self
import sounddevice as sd
from IPython.display import Audio
from torch import Tensor
from torchaudio import functional as AF
class SpeechSample(NamedTuple):
# ...
def resample(self, sample_rate: int) -> Self:
return replace(
self,
sample_rate=sample_rate,
waveform=AF.resample(
self.waveform,
orig_freq=self.sample_rate,
new_freq=sample_rate,
)
)
هناك العديد من العناصر التي تلعب دورًا هنا، لذا قد يكون من المفيد أن نستعرضها سطرًا بسطر:
- يستورد السطر الأول دالة ()replace من وحدة copy في بايثون، والتي ستستخدمها لإنشاء نسخ سطحية من عينات كلامك. تجدر الإشارة إلى أن هذه الدالة متاحة فقط بدءًا من بايثون 3.13. إذا كنت تستخدم إصدارًا أقدم، فراجع الحل البديل أدناه.
- يقوم السطر 2 باستيراد typing.Self لتوضيح الأساليب التي ترجع self.
- يقوم السطر 7 باستيراد الوحدة الوظيفية من TorchAudio ويسميها باسم AF، مما يسمح لك بالوصول إلى دالة ()resample لتغيير معدل أخذ العينات لموجة الصوت.
- تُعرّف الأسطر من ١٢ إلى ٢١ دالة التغليف
.resample()المقابلة ضمن صفك. تأخذ هذه الدالة معدل العينة المطلوب كمُعامل، وتُعيد كائن SpeechSample جديدًا مع إعادة أخذ عينات من شكل الموجة إلى المعدل المُحدد. - يستدعي السطر 13 replace() لإنشاء مثيل جديد من SpeechSample بمعدل العينة والموجة المحدثين، مما يضمن بقاء المثيل الأصلي دون تغيير.
إذا كنت تعمل على إصدار أقدم من بايثون لا يدعم دالة ()copy.replace، فيمكنك تحديد بديل لتكرار سلوك مماثل. أضف المقطع التالي أعلى وحدة بايثون الخاصة بك:
try:
from copy import replace
except ImportError:
def replace(obj, **kwargs):
return obj._replace(**kwargs)
# ...
يمكنك الاستفادة من دالة ._replace() المتوفرة على المجموعات المُسمّاة لمحاكاة وظيفة دالة ()copy.replace. مع أنها تعمل في حالات الاستخدام البسيطة التي تتضمن مجموعات مُسمّاة، إلا أنها تفتقر إلى مرونة وأمان الدالة الأحدث. استخدمها بحذر، وكإجراء مؤقت فقط عندما لا يكون تحديث بايثون خيارًا متاحًا.
عند إعادة تحميل Python REPL أو إعادة تشغيل Jupyter Notebook، ستتمكن من سماع الفرق بين عينة الكلام الأصلية والعينة المعاد أخذ العينة منها:
>>> speech_sample = SpeechSample(*speech_commands_dataset[10])
>>> speech_sample.play()
>>> resampled_audio = speech_sample.resample(sample_rate=4000)
>>> resampled_audio.play()
لقد انخفضت الجودة قليلاً، لكن يمكنك فهم ما يقوله الممثل بوضوح.
الآن وقد فهمتَ كيفية ضبط معدلات أخذ العينات، كيف تتعامل مع مقاطع صوتية بأطوال مختلفة في مجموعة بياناتك؟ حان الوقت لاستكشاف تقنيات الحشو والقص لتوحيد مدد الصوت.
تطبيق الحشو والقص
تُعدُّ تعبئة وتقليص ملفات الصوت من خطوات المعالجة المسبقة الشائعة. فهي تساعد على ضمان تساوي طول جميع عينات الصوت في مجموعة البيانات.
- الحشو: يُضيف بيانات إضافية، غالبًا أصفارًا، إلى بداية أو نهاية إشارة صوتية. هذا يُطيل الإشارة ويُلبي متطلبات حجم مُحددة.
- القص: يتضمن قطع أجزاء من إشارة صوتية لتقليل طولها إلى حجم محدد. تتطلب معظم الشبكات العصبية حجم إدخال ثابت، ويضمن التبطين والتشذيب استيفاء جميع عينات الصوت لهذا الشرط.
يوفر ملف torch.nn.functional من PyTorch دالة ()pad التي يمكنك استخدامها لتبطين ملفاتك الصوتية. لقص الصوت في بايثون، استخدم تقطيع التسلسل. يتيح لك هذا قص أجزاء من الإشارة الصوتية وتحديد طولها.
from copy import replace
from typing import NamedTuple
import sounddevice as sd
from IPython.display import Audio
from torch import Tensor
from torch.nn import functional as F
from torchaudio import functional as AF
class SpeechSample(NamedTuple):
# ...
def pad_trim(self, seconds: int | float) -> Self:
num_samples = int(self.sample_rate * seconds)
if self.num_samples > num_samples:
return replace(self, waveform=self.waveform[:, :num_samples])
elif self.num_samples < num_samples:
padding_amount = num_samples - self.num_samples
return replace(
self, waveform=F.pad(self.waveform, (0, padding_amount))
)
else:
return self
تتأكد دالة .pad_trim() من أن شكل الموجة مُبطّن أو مُقَصَّر بدقة بالثواني المحددة على طول البعد الثاني. باستخدامها، تحسب العدد المطلوب من العينات باستخدام معدل أخذ العينات. إذا كان شكل الموجة أطول من المدة المحددة، فيتم اقتطاعه. أما إذا كان أقصر، فيتم حشوه بالأصفار في نهايته. وإلا، فتُرجع عينة الكلام الأصلية.
معظم ملفات الصوت في مجموعة بيانات أوامر الكلام مدتها ثانية واحدة، ولكن بعضها أطول أو أقصر بقليل. لضمان تناسق جميع البيانات دون فقدان أي معلومات، يمكنك استخدام دالة .pad_trim() مع تحديد ثانيتين كطول مستهدف:
>>> fixed_length_audio = speech_sample.pad_trim(seconds=2)
>>> fixed_length_audio.num_samples
32000
>>> fixed_length_audio.sample_rate
16000
>>> fixed_length_audio.num_seconds
2.0
هنا، تتأكد من أن طول موجة الصوت لديك هو ثانيتان بالضبط عن طريق حشو أو قص الموجة الأصلية حسب الحاجة. يبلغ طول الموجة النهائية 32,000 عينة بمعدل أخذ عينات 16,000 هرتز.
تعمل دالة .pad_trim() كما هو مُخطط لها، ولكنك طبّقتها على ملف صوتي واحد فقط حتى الآن. في الخطوة التالية، ستستخدمها مع طرق معالجة مسبقة أخرى لتحضير جميع ملفات الصوت في مجموعة البيانات للتدريب.
توحيد مجموعة البيانات
قبل المتابعة، قم بتعريف طريقة مساعدة أخرى في فئة SpeechSample الخاصة بك والتي ستتيح لك حفظ عينة الصوت المعالجة مسبقًا على القرص في مسار الملف المحدد:
from copy import replace
from pathlib import Path
from typing import NamedTuple, Self
import sounddevice as sd
import torchaudio
from IPython.display import Audio
from torch import Tensor
from torch.nn import functional as F
from torchaudio import functional as AF
class SpeechSample(NamedTuple):
# ...
def save(self, path: str | Path) -> None:
torchaudio.save(path, self.waveform, self.sample_rate)
في الأساس، تُفوِّض هذه الطريقة العمل الفعلي إلى دالة ()torchaudio.save، مُزوِّدةً إياها بمسار الملف، وموتر الموجة، ومعدل العينة. بدمج هذه الاستدعاءات ضمن دالة من فئة SpeechSample، تُوفِّر واجهة مُبسَّطة لحفظ بيانات الصوت. هذا يضمن حفظ الصوت باستمرار بالمعلمات الصحيحة، مما يُقلِّل من احتمالية حدوث أخطاء.
بعد ذلك، ستقوم بتعريف دالة على مستوى الوحدة في ملف speech.py الخاص بك للتعامل مع منطق المعالجة المجمعة لمجموعة بيانات أوامر الكلام:
from copy import replace
from pathlib import Path
from typing import NamedTuple, Self
import sounddevice as sd
import torchaudio
from IPython.display import Audio
from torch import Tensor
from torch.nn import functional as F
from torchaudio import functional as AF
from torchaudio.datasets import SPEECHCOMMANDS
from tqdm import tqdm
# ...
def bulk_process(
dataset: SPEECHCOMMANDS,
output_dir: str | Path,
sample_rate: int,
seconds: int | float,
) -> None:
for index, sample in tqdm(enumerate(dataset), total=len(dataset)):
speech_sample = SpeechSample(*sample)
input_path, *_ = dataset.get_metadata(index)
output_path = Path(output_dir).resolve() / input_path
output_path.parent.mkdir(parents=True, exist_ok=True)
if speech_sample.sample_rate != sample_rate:
speech_sample = speech_sample.resample(sample_rate)
speech_sample = speech_sample.pad_trim(seconds)
speech_sample.save(output_path)
فيما يلي تفصيل لكل سطر:
- يقوم السطران 11 و12 باستيراد SPEECHCOMMANDS للتلميح إلى النوع وtqdm لعرض شريط التقدم، على التوالي.
- تعرف الأسطر من 16 إلى 30 دالة ()bulk_process، والتي تتكرر على مجموعة البيانات، وتعالج كل ملف صوتي عن طريق إعادة أخذ العينات منه وتقليمه إلى المدة المطلوبة، وتحفظ الصوت المعالج في دليل الإخراج المحدد.
- يُشغّل السطر 22 حلقة for على عينات مجموعة البيانات ومؤشراتها باستخدام دالة ()enumerate. يُغلّف عداد الحلقة باستدعاء دالة ()tqdm، التي تُضيف شريط تقدم لتتبع تنفيذ الحلقة بصريًا.
- يقوم السطر 23 بإنشاء مثيل SpeechSample عن طريق فك ضغط المجموعة الخام من TorchAudio.
- يسترجع السطر 24 البيانات الوصفية للعينة الحالية. يُفكك العنصر الأول كمدخل مسار، ويتجاهل الباقي باستخدام _*. يُنتج هذا مسار الملف الأصلي بالنسبة لمجلد جذر مجموعة البيانات.
- يحدد السطران 25 و26 وجهة ملف الإخراج ويقومان بشكل متكرر بإنشاء أي مجلدات رئيسية مفقودة، وقمع الأخطاء إذا كانت الدلائل موجودة بالفعل.
- تتيح لك الأسطر من 27 إلى 29 إعادة أخذ عينات من الصوت بشكل اختياري وتقليصه أو قصه ليتناسب مع المعلمات المحددة، مما يضمن أن جميع الملفات التي تتم معالجتها لها تنسيق موحد وطول دقيق.
- يقوم السطر 30 بحفظ العينة المعالجة في output_path باستخدام التابع
.save()التي قمت بتعريفها مسبقًا، وكتابة الشكل الموجي على القرص.
يمكنك الآن العودة إلى Python REPL، وتحميل مجموعة بيانات أوامر الكلام، ومعالجتها بشكل مجمع باستخدام وظيفتك الجديدة:
>>> from pathlib import Path
>>> from torchaudio.datasets import SPEECHCOMMANDS
>>> from speech import bulk_process
>>> bulk_process(
... SPEECHCOMMANDS(root=Path.cwd(), download=True),
... output_dir=Path("SpeechCommandsProcessed"),
... sample_rate=16000,
... seconds=2,
... )
23%|██████████ | 23864/105829 [08:11<26:04, 52.38it/s]
يُطبّق هذا الكود المعيار المطلوب على مجموعة البيانات بأكملها. يُرجى العلم أن معالجة أكثر من 105,000 كلمة قد تستغرق عدة دقائق. سيتم تحديث شريط التقدم فورًا لعرض الحالة الحالية للعملية.
عند الانتهاء، سوف يعكس دليل /SpeechCommandsProcessed هيكل ومحتوى دليل /SpeechCommands الأصلي، مع وجود كليهما في جذر مشروعك:
./
│
├── SpeechCommands/
│ │
│ └── (...)
│
└── SpeechCommandsProcessed/
│
└── speech_commands_v0.02/
│
├── backward/
│ ├── 0165e0e8_nohash_0.wav
│ ├── 017c4098_nohash_0.wav
│ └── ...
│
├── bed/
│ ├── 00176480_nohash_0.wav
│ ├── 004ae714_nohash_0.wav
│ └── ...
│
├── bird/
│ ├── 00970ce1_nohash_0.wav
│ ├── 00b01445_nohash_0.wav
│ └── ...
│
└── ...
طالما لم تقم بتغيير معدل أخذ العينات الأصلي البالغ 16 كيلوهرتز، فيمكنك تحميل مجموعة البيانات التي تمت معالجتها عن طريق تعيين معلمة folder_in_archive بشكل صريح في استدعاء SPEECHCOMMANDS:
>>> from pathlib import Path
>>> from torchaudio.datasets import SPEECHCOMMANDS
>>> from speech import SpeechSample
>>> processed_dataset = SPEECHCOMMANDS(
... root=Path.cwd(),
... folder_in_archive="SpeechCommandsProcessed"
... )
>>> speech_sample = SpeechSample(*processed_dataset[10])
>>> speech_sample.num_seconds
2.0
مع ذلك، بما أن TorchAudio يُبرمج بدقة معدل أخذ عينات يبلغ 16 كيلوهرتز في شيفرته المصدرية، فستحتاج إلى إنشاء مجموعة بيانات مخصصة إذا كنت ترغب في إعادة أخذ العينات من الصوت. ستكتشف كيفية القيام بذلك لاحقًا.
تتأكد دالة ()bulk_process من أن جميع ملفاتك الصوتية متساوية الطول، وهي خطوة أساسية للتعامل مع بيانات الصوت. ستتناول في هذا المقال كيفية التعامل مع مشكلة شائعة أخرى: اختلاف مستوى الصوت في مجموعة البيانات.
تطبيع الصوت
تأتي التسجيلات في مجموعة بيانات أوامر الكلام من مكبرات صوت وبيئات وأجهزة مختلفة، مما يؤدي إلى اختلافات في السعة بين العينات المختلفة. قد يُسبب هذا التناقض مشاكل لنموذج التعلم الآلي، إذ قد يجعل النموذج أكثر حساسية لحجم مجموعة البيانات بدلاً من التركيز على خصائص أكثر فائدة لملفات الصوت.
لتجنب هذه المشكلة، يُفضّل تطبيع مجموعة البيانات. هذا يعني ضبط قيم سعة جميع إشاراتك الصوتية على نطاق أو معيار محدد. لحسن الحظ، تُوفّر دالة ()torchaudio.load، المُستخدمة في الخلفية، مُعامل تطبيع، وهو مُعيّن افتراضيًا على True. لذا، يُوفّر TorchAudio الحل الأمثل، ولن تحتاج إلى أي خطوات إضافية!
حتى الآن، قمتَ بتوحيد ملفاتك الصوتية وتأكدتَ من ثبات مستويات الصوت. ولكن كيف يمكنكَ تحويل بياناتك الصوتية بطريقةٍ أكثر سهولةً في عرضها؟
إنشاء مخططات طيفية
لفهم الصوت في نموذج تعلّم آلي، غالبًا ما تحتاج إلى الانتقال من المجال الزمني إلى المجال الترددي. ستحتاج إلى عرض رقمي لملفك الصوتي يُظهر لك أي أجزاء من الصوت تحتوي على نوتات أو نغمات، وفي أي وقت. يساعدك المخطط الطيفي على القيام بذلك بالضبط:
المخطط الطيفي هو تمثيل مرئي لطيف ترددات إشارة ما، حيث يتغير مع الزمن. عند تطبيقه على إشارة صوتية، يُطلق على المخططات الطيفية أحيانًا اسم الموجات فوق الصوتية، أو بصمات الصوت، أو مخططات الصوت. (المصدر)
لإنشاء مخطط طيفي، يجب إجراء تحويل فورييه (FT) على أجزاء صغيرة من الإشارة الصوتية. خوارزمية تحويل فورييه السريع (FFT) هي المستخدمة لهذا الغرض، وتتطلب تحديد عدد نقاط التردد التي تريد حسابها.
إليك كيفية عمل تحويل الطيف:
- يتم تقسيم الإشارة الصوتية إلى أجزاء صغيرة متداخلة تسمى النوافذ، ويبلغ طولها عادةً بضعة مللي ثانية.
- بالنسبة لكل نافذة، يقوم FFT بتحليل الترددات الموجودة ومدى قوتها.
- تتداخل النوافذ وتنزلق على طول الإشارة الصوتية، مما يؤدي إلى إنشاء سلسلة من لقطات التردد.
- يتم دمج هذه اللقطات لتشكيل الطيف، الذي يوضح كيفية تغير الترددات بمرور الوقت.
تُسمى هذه التقنية تحويل فورييه قصير الأمد (STFT) لأنها تُحلل مقاطع صوتية قصيرة بدلاً من معالجة الإشارة بأكملها دفعةً واحدة. يتيح لك هذا النهج قصير الأمد رؤية كيفية تطور الترددات في مقطعك الصوتي. والنتيجة هي صورة مُفصلة تُظهر كيفية تغير الترددات في مقطعك الصوتي من لحظة لأخرى، وهو ما يُظهره المخطط الطيفي بالضبط.
الآن بعد أن تعرفت على كيفية إنشاء المخططات الطيفية، حان الوقت لإنشاء واحد. لإنشاء مخطط طيفي في TorchAudio، ستستخدم فئة Spectrogram من torchaudio.transforms:
>>> import torchaudio.transforms as T
>>> spectrogram_transform = T.Spectrogram(
... n_fft=1024,
... win_length=None,
... hop_length=512,
... normalized=True
... )
عند إنشاء مخطط طيفي، تحتاج إلى تحديد بعض المعلمات:
| المعلمة | المعنى | التأثير على الطيف |
|---|---|---|
n_fft | عدد العينات لكل FFT | القيم الأعلى تعطي دقة تردد أفضل (مزيد من صناديق التردد) |
win_length | طول كل نافذة في العينات | إذا لم يكن هناك أي شيء، فسيتم تعيينه افتراضيًا على n_fft |
hop_length | عدد العينات بين النوافذ المتعاقبة | القيم الأصغر تعطي دقة زمنية أفضل (تداخل أكبر) |
يمكنك تغذية ملف الصوت الخاص بك إلى spectrogram_transform الذي قمت بإنشائه مسبقًا لرؤية شكل الطيف الخاص بك حتى الآن:
>>> spectrogram = spectrogram_transform(speech_sample.waveform)
>>> spectrogram
tensor([[[4.0992e-08, 1.2626e-10, 5.0785e-08, ..., 2.1378e-08,
1.1199e-08, 1.1576e-09],
[1.3024e-07, 2.1598e-08, 1.2256e-07, ..., 1.0029e-07,
7.8936e-09, 4.8156e-08],
[1.8714e-07, 1.3087e-07, 4.9596e-08, ..., 4.7979e-07,
1.7561e-07, 1.6183e-07],
...,
[3.4479e-10, 3.5584e-10, 7.7859e-12, ..., 2.4817e-10,
6.2482e-12, 4.3299e-10],
[2.4906e-09, 3.5388e-10, 3.8308e-11, ..., 9.0459e-11,
3.4527e-10, 4.4065e-10],
[4.6073e-09, 7.3008e-10, 6.5713e-11, ..., 5.0754e-11,
4.5115e-10, 1.7723e-09]]])
>>> spectrogram.shape
torch.Size([1, 513, 32])
القيم داخل الموتر هي قيم مكونات التردد في كل إطار زمني. وكما يتضح من الناتج، فإن موتر الطيف له ثلاثة أبعاد:
| البعد | الوصف |
|---|---|
| القنوات | عدد الإشارات الصوتية المنفصلة في المدخل |
| صناديق التردد | عدد مكونات التردد التي تم تحليلها في كل جزء زمني |
| خطوات الوقت | عدد المقاطع الزمنية التي يتم تقسيم الصوت إليها للتحليل |
لذا، بالنسبة لملف الصوت أعلاه، يُخبرك أنه يحتوي على قناة واحدة، و513 صندوق تردد، وأن الصوت تم تقسيمه إلى 32 جزءًا صغيرًا متداخلًا للتحليل.
يمكنك تصوّر مخططك الطيفي لفهمٍ أكثر بديهية. للقيام بذلك، يمكنك إنشاء رسم بياني ثنائي الأبعاد، حيث:
- يمثل المحور x الوقت.
- يمثل المحور y التردد.
- تمثل كثافة اللون المقدار، حيث تشير الألوان الأكثر سطوعًا إلى طاقة أعلى.
قبل الخوض في هذا، عليك تطبيق تحويل على موتر الطيف الخاص بك. القيم الخام في الطيف تمثل القوة أو المقدار، وتوضح مدى قوة كل تردد في كل لحظة زمنية. لكن هذه القيم غالبًا ما تكون صغيرة جدًا وقد تمتد على نطاق واسع، مما يجعل تفسيرها أو تصورها مباشرةً أمرًا صعبًا. وهنا يأتي دور مقياس الديسيبل (dB).
مقياس الديسيبل:
- يضغط نطاقًا كبيرًا من القيم إلى مقياس أكثر قابلية للقراءة
- يؤكد على الاختلافات بين الأجزاء الصاخبة والهادئة
- يتطابق مع الإدراك البشري بشكل أوثق – حيث تدرك أذنيك مستوى الصوت بشكل لوغاريتمي وليس خطيًا
يُتيح لك TorchAudio طريقة سهلة للقيام بذلك. يمكنك استيراد AmplitudeToDB من torchaudio.transforms وتطبيقه على مخططك الطيفي:
>>> from torchaudio.transforms import AmplitudeToDB
>>> spectrogram_db = T.AmplitudeToDB()(spectrogram)
>>> spectrogram_db
tensor([[[ -73.8730, -98.9874, -72.9426, ..., -76.7003, -79.5084,
-89.3645],
[ -68.8526, -76.6559, -69.1164, ..., -69.9872, -81.0273,
-73.1735],
[ -67.2783, -68.8316, -73.0456, ..., -63.1895, -67.5546,
-67.9095],
...,
[ -94.6244, -94.4875, -100.0000, ..., -96.0526, -100.0000,
-93.6352],
[ -86.0369, -94.5114, -100.0000, ..., -100.0000, -94.6185,
-93.5591],
[ -83.3656, -91.3663, -100.0000, ..., -100.0000, -93.4568,
-87.5146]]])
يؤدي التحويل إلى الديسيبل إلى تحويل قيم الطيف الخام لديك إلى مقياس أكثر قابلية للتفسير، حيث تتراوح القيم عادةً من -100 ديسيبل (هادئ للغاية) إلى 0 ديسيبل (أقصى شدة).
تصور مخططات الطيف الصوتي
لرسم كل هذه المعلومات وتصور مخطط الطيف الخاص بملف الصوت الخاص بك، يمكنك استخدام Matplotlib:
>>> import matplotlib.pyplot as plt
>>> plt.figure(figsize=(15, 8))
<Figure size 1500x800 with 0 Axes>
>>> plt.subplot(2, 1, 1)
<Axes: >
>>> plt.imshow(
... spectrogram_db[0].numpy(),
... aspect="auto",
... origin="lower",
... cmap="magma",
... extent=[
... 0, speech_sample.num_seconds,
... 0, spectrogram_transform.n_fft // 2
... ]
... )
<matplotlib.image.AxesImage object at 0x7585d54d9a90>
>>> plt.colorbar(format="%+2.0f dB")
<matplotlib.colorbar.Colorbar object at 0x7585d4e86a50>
>>> plt.title(f"Spectrogram - Label: {speech_sample.label}")
Text(0.5, 1.0, 'Spectrogram - Label: backward')
>>> plt.ylabel("Frequency (Hz)")
Text(0, 0.5, 'Frequency (Hz)')
>>> plt.tight_layout()
>>> plt.show()
أنت ترسم التردد على المحور y، والوقت على المحور x، وتستخدم اللون لإظهار شدة كل تردد بمرور الوقت.
وهذا هو الشكل الذي يبدو عليه مخطط الطيف الخاص بك:

تُظهر الأشكال الرأسية وقت إصدار مكبر الصوت للصوت. هذه دفقات من الطاقة أثناء الكلام. تُظهر الأشرطة الأفقية داخل كل دفقة الترددات النشطة. تُعد هذه الأنماط مهمة بشكل خاص في الكلام، ويمكن أن تساعد نموذجك على التمييز بين الكلمات المختلفة. تشير المناطق الداكنة إلى الصمت أو الأجزاء منخفضة الطاقة من شكل الموجة. يمكنك تجربة تصوّر ملفات صوتية أخرى لمعرفة مدى اختلاف أنماطها.
لتطبيق مثل هذه التحويلات على كائنات SpeechSample، يمكنك تحديد طريقة التغليف المناسبة:
from copy import replace
from pathlib import Path
from typing import Callable, NamedTuple, Self
import sounddevice as sd
import torchaudio
from IPython.display import Audio
from torch import Tensor
from torch.nn import functional as F
from torchaudio import functional as AF
from torchaudio.datasets import SPEECHCOMMANDS
from tqdm import tqdm
class SpeechSample(NamedTuple):
# ...
def apply(self, transform: Callable[[Tensor], Tensor]) -> Self:
return replace(self, waveform=transform(self.waveform))
# ...
يقوم بإرجاع نسخة من عينة الكلام الأصلي مع موتر الموجة المحول.
بينما تُقدم لك المخططات الطيفية تمثيلًا بصريًا واضحًا لبياناتك الصوتية، ماذا يحدث عندما تحتوي تلك البيانات على ضوضاء أو تداخل غير مرغوب فيه؟ هذا يقودنا إلى تقنية معالجة مسبقة مهمة أخرى: إضافة ضوضاء مُتحكم بها لجعل نموذجك أكثر متانة.
إضافة ضوضاء غاوسية بيضاء مضافة (AWGN)
لتحسين قدرة نموذج التعلم الآلي على التعميم، غالبًا ما يكون من الضروري إضافة تشويش إلى بياناتك. قد يبدو هذا غريبًا للوهلة الأولى، ولكن هناك أسباب وجيهة للقيام بذلك.
ملاحظة: عندما تقوم بإضافة ضوضاء إلى بياناتك في التعلم الآلي، فهذا يعني أنك تقوم عمدًا بإدخال بعض الاختلافات العشوائية أو الأخطاء الصغيرة في مجموعة البيانات الخاصة بك.
يمكن أن تساعد إضافة الضوضاء في جعل نموذجك أكثر متانة وقدرةً على التعميم على البيانات الجديدة غير المرئية. فهي تمنع النموذج من الإفراط في التجهيز، والذي يحدث عندما يتقن النموذج استيعاب تفاصيل بيانات التدريب وضوضاءها، مما يقلل من فعاليته مع البيانات الجديدة.
إحدى الطرق الشائعة هي الضوضاء الغوسية البيضاء المضافة (AWGN)، والتي تحاكي العشوائية في العالم الحقيقي عن طريق إضافة نوع معين من الضوضاء إلى الإشارة.
ملاحظة: إشارة AWGN جمعيّة لأنها تُضاف إلى الإشارة الأصلية. وهي بيضاء اللون لأنها تؤثر على جميع الترددات بالتساوي، بكثافة طيفية ثابتة للقدرة. يشير مصطلح “غاوسي” إلى توزيع سعتها، والذي يتبع دالة كثافة احتمالية طبيعية (منحنية جرسية).
يُحسّن نظام AWGN من قوة النماذج أثناء التدريب. إضافة ضوضاء مُتحكّم بها تمنع النماذج من حفظ الأنماط الدقيقة بصوت نقي. بدلاً من ذلك، تتعلّم النماذج ميزات مهمة تُميّز الأصوات، حتى مع وجود تداخل. بالإضافة إلى ذلك، لا تحتاج إلى جمع عينات أو تسميات جديدة، بل يمكنك إنشاء تنويعات بإضافة مستويات ضوضاء مختلفة.
لإضافة AWGN إلى شكل موجة، يمكنك إنشاء دالة أخرى في فئة SpeechSample. ستستخدم هذه الدالة دالة الموتر المتخصصة في PyTorch لإضافة ضوضاء غاوسية إلى موتر شكل الموجة:
from copy import replace
from pathlib import Path
from typing import Callable, NamedTuple, Self
import sounddevice as sd
import torchaudio
from IPython.display import Audio
from torch import Tensor, clamp, randn_like
from torch.nn import functional as F
from torchaudio import functional as AF
from torchaudio.datasets import SPEECHCOMMANDS
from tqdm import tqdm
class SpeechSample(NamedTuple):
# ...
def with_gaussian_noise(self, level=0.01) -> Self:
noise = randn_like(self.waveform) * level
return replace(self, waveform=clamp(self.waveform + noise, -1.0, 1.0))
# ...
إليك ما يحدث في هذا الكود:
- أولاً، تُنشئ دالة ()randn_like موتر ضوضاء يُطابق شكل الموجة. يتبع هذا الموتر توزيعًا غاوسيًا مركزه 0 وانحراف معياري قدره 1.
- ثم يُقاس مستوى الضوضاء. تُنتج قيمة أصغر، مثل 0.001، ضوضاءً خفيفة، بينما تُنتج قيمة أكبر، مثل 0.1، تداخلاً أكثر وضوحًا.
- يتم إضافة هذه الضوضاء المقاسة مباشرةً إلى شكل الموجة لديك باستخدام عملية إضافة قديمة جيدة (+).
- تحافظ الدالة ()clamp على جميع القيم بين -1.0 و1.0، وهو النطاق القياسي لإشارات الصوت. هذا يمنع التشويه في الصوت النهائي.
تُنتج هذه العملية مقدارًا مُتحكمًا به من التداخل، مما يُساعد في جعل نموذجك أكثر قوةً أثناء التدريب. عند الاستماع إلى صوتك المُزعج، ستلاحظ أن الصوت أصبح أقل وضوحًا.
>>> noisy_waveform = speech_sample.with_gaussian_noise(0.005)
>>> noisy_waveform.save("noisy_audio.wav")
>>> noisy_waveform.play()
لا تتردد في تجربة مستوى الضوضاء وملاحظة مدى تأثيره على جودة الصوت.
إنشاء مجموعة بيانات PyTorch مخصصة
أنت الآن تفهم الخطوات الأساسية لمعالجة البيانات اللازمة لمجموعة بيانات صوتية وكيفية تنفيذها. مع ذلك، كان عليك تطبيق العديد من التحويلات يدويًا وبشكل متكرر على مجموعة البيانات بأكملها. يتيح لك PyTorch التعامل مع هذا بكفاءة أكبر مع مجموعات البيانات المخصصة من خلال توسيع فئة قاعدة البيانات.
الطريقتان الإلزاميتان اللتان يجب عليك تنفيذهما في مجموعة البيانات المخصصة الخاصة بك هما:
.__len__(self): تُرجع هذه الطريقة العدد الإجمالي للعناصر في مجموعة البيانات. هذا مهم لمُحمّل بيانات PyTorch لمعرفة عدد العينات المطلوب تكرارها..__getitem__(self, index): تسترجع هذه الطريقة عنصرًا واحدًا من مجموعة البيانات بناءً على الفهرس المُعطى. هنا، تُحدد منطق تحميل ومعالجة كل عينة بيانات على حدة.
بالإضافة إلى ذلك، على الرغم من عدم كونها مطلوبة بشكل صارم، فغالبًا ما ستحدد طريقة .__init__(self)، حيث يمكنك إعداد أي سمات أو معلمات أو خطوات معالجة مسبقة مطلوبة عبر جميع العناصر في مجموعة البيانات.
لديك بالفعل كل ما يلزم من منطق لمعالجة بياناتك مسبقًا. الآن، يمكنك تجميعها في مجموعة بيانات مخصصة. ابدأ بإضافة فئة جديدة، AugmentedSpeechCommands، إلى وحدة الكلام، والتي ستستقبل خمسة معلمات إدخال:
from copy import replace
from pathlib import Path
from typing import Callable, NamedTuple, Self
import sounddevice as sd
import torchaudio
from IPython.display import Audio
from torch import Tensor, clamp, randn_like
from torch.nn import functional as F
from torch.utils.data import Dataset
from torchaudio import functional as AF
from torchaudio.datasets import SPEECHCOMMANDS
from torchaudio.datasets.speechcommands import FOLDER_IN_ARCHIVE
from tqdm import tqdm
# ...
class AugmentedSpeechCommands(Dataset):
def __init__(
self,
folder: str | Path | None = None,
seconds: int | float | None = None,
noise_level: float = 0.005,
enable_noise: bool = True,
transform: Callable[[Tensor], Tensor] | None = None
) -> None:
if folder:
self.folder = Path(folder).resolve()
else:
self.folder = Path.cwd() / FOLDER_IN_ARCHIVE
self._raw_dataset = SPEECHCOMMANDS(
self.folder.parent,
folder_in_archive=self.folder.name
)
self._noise = noise_level
self._enable_noise = enable_noise
self._transform = transform
self._seconds = seconds
# ...
فئتك عبارة عن مجموعة بيانات PyTorch مخصصة تُحمّل مجموعة بيانات الصوت “أوامر الكلام” وتوفر تعزيزًا اختياريًا للبيانات. يُمكنه إضافة ضوضاء خلفية إلى الصوت وتطبيق تحويلات مخصصة للمساعدة في تدريب نماذج أكثر متانة. يمكنك تحديد مقدار الضوضاء المُراد إضافتها، وما إذا كنت تريد استخدامها أصلًا، ومدة كل مقطع صوتي. في حال عدم تحديد أي مجلد، فسيتم استخدام مسار مجموعة البيانات الافتراضي من TorchAudio.
على الرغم من أن فئتك تمتد إلى مجموعة بيانات PyTorch، إلا أنك لا تزال بحاجة إلى توفير التنفيذ الخاص بك للطريقتين الخاصتين المذكورتين سابقًا:
# ...
class AugmentedSpeechCommands(Dataset):
# ...
def __len__(self) -> int:
return len(self._raw_dataset)
def __getitem__(self, index: int) -> SpeechSample:
relative_path, _, *metadata = self._raw_dataset.get_metadata(index)
absolute_path = self.folder / relative_path
waveform, sample_rate = torchaudio.load(absolute_path)
speech_sample = SpeechSample(waveform, sample_rate, *metadata)
if self._seconds is not None:
speech_sample = speech_sample.pad_trim(self._seconds)
if self._enable_noise:
speech_sample = speech_sample.with_gaussian_noise(self._noise)
if self._transform:
speech_sample = speech_sample.apply(self._transform)
return speech_sample
# ...
الطريقة الأولى، .__len__()، تُرجع عدد العينات في مجموعة البيانات. الطريقة الثانية، .__getitem__()، تُرجع العينة المحددة عند الفهرس المُحدد. لاحظ أن هذه الطريقة تُرجع مثيل SpeechSample الخاص بك، والذي يحتوي على أسماء سمات ذات معنى، بدلاً من جملة بسيطة. بالإضافة إلى ذلك، تُطبّق مجموعة البيانات المُخصصة معالجة اختيارية على كل عينة.
لتجربة مجموعة البيانات المخصصة الخاصة بك، قد ترغب في تجربة ما يلي:
>>> from torchaudio import transforms as T
>>> from speech import AugmentedSpeechCommands
>>> custom_dataset = AugmentedSpeechCommands(
... folder="SpeechCommandsProcessed",
... seconds=2,
... noise_level=0.005,
... enable_noise=True,
... transform=T.Vol(gain=0.5, gain_type="amplitude")
... )
>>> custom_dataset[10]
SpeechSample(
waveform=tensor([[-0.0010, 0.0024, 0.0020, ..., -0.0016]]),
sample_rate=4000,
label='backward',
speaker_id='02ade946',
utterance_number=4
)
الآن، لديك فئة نظيفة مخصصة لجميع التحويلات اللازمة لتدريب نموذج تعلُّم الآلة. كائن مجموعة البيانات هذا هو كائن PyTorch متخصص، ويمكن دمجه مع مُحمِّلات البيانات، مما يتيح لك الاستفادة الكاملة من أساليب PyTorch وTorchAudio المدمجة لتدريب نماذج الصوت.
الخطوات التالية
يوفر TorchAudio نماذج تعلم صوتي عميق مدمجة. لديك كائن مجموعة بيانات PyTorch، لذا يمكنك تغذية مجموعة بياناتك بهذه النماذج مباشرةً. إليك نموذجان مدمجان للتعلم العميق في TorchAudio:
- Wav2Vec 2.0: نموذج فعّال للتعرف على الكلام، يستخدم التعلم الذاتي على أشكال الموجات الصوتية الخام. يمكنك ضبط هذا النموذج بدقة لمهام محددة للتعرف على الكلام.
- DeepSpeech: نموذج شامل للتعرف على الكلام، يعتمد على الشبكات العصبية المتكررة (RNNs). يُحوّل هذا النموذج الموجات الصوتية مباشرةً إلى نص عبر معالجة تسلسلات من الإطارات الصوتية.
تُغنيك هذه النماذج عن بناء كل شيء من الصفر، ما يُمكّنك من التركيز على إعداد بياناتك وضبط النموذج لمهمتك. يُسهّل TorchAudio بناء أنظمة قوية للتعرف على الكلام بسرعة.
عمل رائع! لقد نجحتَ في استكشاف كيفية تسخير قوة TorchAudio، ومعالجة البيانات الصوتية بفعالية لمشاريع التعلم الآلي الخاصة بك. بفضل هذه المهارات، يمكنك الآن إعداد البيانات الصوتية لمهام التعلم الآلي مثل التعرف على الكلام، واستخدامها مع نماذج TorchAudio المتقدمة مثل Wav2Vec 2.0 وDeepSpeech.
استمر بتجربة تركيبات معالجة مسبقة مختلفة، ولا تتردد في تكييف هذه التقنيات مع تحدياتك الصوتية الخاصة. استمر في إبداعك!
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.