دسته بندی صدا با یادگیری عمیق — راهنمای کاربردی
در این مطلب، چگونگی انجام دسته بندی صدا با یادگیری عمیق بیان شده است. در این مقاله، ابتدا چشمانداز کلی پروژه ارائه و سپس، «مجموعه داده» (Data Set)، ابزارهای مورد استفاده و نتایج حاصل از انجام پروژه مورد بررسی قرار میگیرند. کلیه کدهای مورد استفاده برای این پروژه، در همین مطلب درج شدهاند.
چشمانداز کلی پروژه دسته بندی صدا
دسته بندی صدا (+) با یادگیری عمیق و به طور خودکار، حوزهای در حال رشد با کاربردهای جهان واقعی متعدد است. در حالی که تحقیقات بسیار زیادی در زمینه فایلهای صوتی مانند صحبت کردن یا موسیقی انجام شده است، کار روی صدای محیط نسبتا نادر است.
به همین ترتیب، مشاهده پیشرفتهای اخیر در زمینه دستهبندی تصویر که در آن «شبکههای عصبی پیچشی» (Convolutional Neural Networks) برای دستهبندی تصاویر با صحت بالا مورد استفاده قرار میگیرند، این پرسش را به وجود میآورد که آیا این روشها در دیگر زمینهها مانند دستهبندی صوت نیز کاربرد دارند یا خیر. کاربردهای جهان واقعی زیادی برای آنچه در این مطلب آموزش داده میشود وجود دارد که در ادامه بیان شدهاند.
- اندیسگذاری و بازیابی چند رسانهای مبتنی بر محتوا
- کمک به ناشنوایان بریا انجام فعالیتهای روزمره
- استفاده در کاربردهای خانههای هوشمند مانند امنیت ۳۶۰ درجه و قابلیتهای امنیتی
- استفادههای صنعتی مانند نگهداری پیشبین
بیان مسأله
در ادامه، چگونگی اعمال روشهای یادگیری عمیق برای دستهبندی صداهای محیط، با تمرکز بر صداهای شهری خاص آموزش داده شده است. هنگامی که یک نمونه صوتی در فرمت قابل خواندن به وسیله کامپیوتر (مانند فایل wav.) با طول چند ثانیه داده میشود، هدف تشخیص محتویات آن بر اساس صداهای شهری، با امتیاز صحت دستهبندی متناظر است.
مجموعه داده
برای این مسئله، از یک مجموعه داده که Urbansound8K نامیده میشود، استفاده شده است. این مجموعه داده، حاوی ۸۷۳۲ فایل صوتی گزینش شده (کمتر از ۴ ثانیه) از صداهای شهری از ۱۰ کلاس است که عبارتند از:
- تهویه کننده هوا
- بوق ماشین
- بازی کودکان
- پارس سگ
- حفاری
- صدای موتور خودرو در حال سکون
- شلیک تفنگ
- جکامر (مته دستی)
- آژیر
- موسیقی خیابانی
نمونهای از این مجموعه داده به صورت قابل دانلود، از این مسیر [+] در دسترس است.
بررسی فایلهای صوتی
این گلچین صداها، فایلهای صوتی در فرمت wav. هستند. موجهای صدا با نمونهبرداری از آنها در بازههای گسسته که به آن نرخ نمونهبرداری (معمولا، نرخ نمونهبرداری برای فایلهای صوتی روی CD برابر با ۴۴/۱ KHz است، بدین معنا که ۴۴۱۰۰ بار در ثانیه نمونهها گرفته شدهاند) گفته میشود، دیجیتالی شدهاند.
هر نمونه، دامنه موج در یک بازه زمانی مشخص است که عمق بیت در آن، نشان میدهد که چه میزان جزئیات در نمونه وجود دارد؛ به این مورد، طیف پویای سیگنال نیز گفته میشود (معمولا ۱۶ بیت که به معنای آن است که یک نمونه طیفی بین ۶۵,۶۳۶ مقادیر دامنه است).
تصویر بالا نشان میدهد که چگونه یک قطعه صوت از شکل موج گرفته و به یک آرایه یک بُعدی یا بردار دامنه مقادیر تبدیل میشود.
اکتشاف داده
با یک بررسی بصری، میتوان مشاهده کرد که بصریسازی برخی از ویژگیها، کاری نیازمند کمی ترفند برای نمایش تفاوت برخی از کلاسها است. اساسا، شکل موج برای صداهای تکرار شونده برای تهویه کننده هوا، حفاری، موتور روشن خودرو در حال ثابت و جکامر دارای شکل مشابهی هستند.
سپس، بررسیهای عمیقتری برای استخراج برخی از خصوصیات فایلهای صوتی، شامل تعداد کانالهای صوتی، نرخ نمونهبرداری و عمیق بیت با استفاده از کد زیر انجام میشود.
1# Load various imports
2import pandas as pd
3import os
4import librosa
5import librosa.display
6
7from helpers.wavfilehelper import WavFileHelper
8wavfilehelper = WavFileHelper()
9
10audiodata = []
11for index, row in metadata.iterrows():
12
13 file_name = os.path.join(os.path.abspath('/UrbanSound8K/audio/'),'fold'+str(row["fold"])+'/',str(row["slice_file_name"]))
14 data = wavfilehelper.read_file_properties(file_name)
15 audiodata.append(data)
16
17# Convert into a Panda dataframe
18audiodf = pd.DataFrame(audiodata, columns=['num_channels','sample_rate','bit_depth'])
1import struct
2
3class WavFileHelper():
4
5 def read_file_properties(self, filename):
6
7 wave_file = open(filename,"rb")
8
9 riff = wave_file.read(12)
10 fmt = wave_file.read(36)
11
12 num_channels_string = fmt[10:12]
13 num_channels = struct.unpack('<H', num_channels_string)[0]
14
15 sample_rate_string = fmt[12:16]
16 sample_rate = struct.unpack("<I",sample_rate_string)[0]
17
18 bit_depth_string = fmt[22:24]
19 bit_depth = struct.unpack("<H",bit_depth_string)[0]
20
21 return (num_channels, sample_rate, bit_depth)
در اینجا، میتوان مشاهده کرد که مجموعه داده دارای طیف وسیعی از خصوصیات صوتی متغیر است. همین امر موجب میشود تا پیش از استفاده از فایلها، نیاز به استانداردسازی آنها برای آموزش دادن مدل باشد.
کانالهای صوتی: اغلب نمونهها دارای دو کانال صوتی هستند (بدین معنا که استریو هستند) و برخی از فایلها تنها یک کانال دارند (مونو).
نرخ نمونهبرداری: طیف وسیعی از نرخ نمونهها وجود دارد که در سراسر نمونهها استفاده شده است (طیفی از ۹۶ کیلوهرتز تا ۸ کیلوهرتز).
عمق بیت: همچنین، طیفی از عمق بیت (عمق بیتی از ۴ بیت تا ۳۲ بیت) وجود دارد.
پیشپردازش دادهها
در بخش پیشین، ویژگیهای صوتی که در ادامه بیان شدهاند و نیاز به پیشپردازش دارند تا اطمینان حاصل شود که استحکام لازم را در کل مجموعه دارند یا خیر، مورد بررسی قرار گفتند.
- کانالهای صوتی
- نرخ نمونه
- عمق بیت
«لیبروسا» (Librosa) یک بسته پایتون برای پردازش موسیقی و فایلهای صوتی است که توسط «بریان مکآفی» منتشر شده و به کاربر این امکان را میدهد تا فایلهای صوتی را در نوتبوک به عنوان آرایه «نامپای» (NumPy) برای تحلیل و دستکاری وارد کنند.
برای بخش عمده پیشپردازشها، امکان استفاده از تابع Librosa’s load() وجود دارد که به طور پیشفرض، نرخ نمونهبرداری را به 22.05 KHz تغییر میدهد، دادهها را نرمال میکند، بنابراین طیف مقادیر عمیق بیت بین ۱- و ۱ و کانالهای صوتی را به صورت مونو (Mono) مسطح میکند.
استخراج ویژگیها
گام بعدی، استخراج ویژگیهایی است که برای آموزش دادن مدل به آنها نیاز است. برای انجام این کار، یک ارائه بصری از هر نمونه صوتی ساخته میشود که به کاربر امکان شناسایی ویژگیها برای بصریسازی را با استفاده از روشهای مشابه مورد استفاده برای دستهبندی با صحت بالا فراهم میکند.
«طیفسنجها» (Spectrograms) روشهای مفیدی برای بصریسازی طیف فرکانسهای یک صدا و چگونگی تغییر آنها در طول یک بازه زمانی هستند. از روش مشابهی که با عنوان Mel-frequency cepstrum (سرنام آن MFCC است) شناخته شده است استفاده میشود.
تفاوت اصلی این دو آن است که طیفسنج از مقیاس فرکانس فضاگذاری شده خطی استفاده میکند (بنابراین، هر دسته فرکانس با مقدار هرتز یکسانی از هم جدا شدهاند)، در حالیکه MFCC از مقایس فرکانس شبه لگاریتمی استفاده میکند که بیشتر شبیه به سیستمی است که سیستم شنوایی انسان کار میکند.
تصویر زیر، سه ارائه بصری مختلف از موج صوتی را نشان میدهد که اولین آن ارائه دامنه زمانی است و نوسان را در طول زمان با یکدیگر مقایسه میکند. سپس، طیفسنجی است که نشان میدهد انرژی در باندهای فرکانسی مختلف در طول زمان تغییر میکند. در نهایت، یک MFCC را میتوان مشاهده کرد که بسیار شبیه به طیفسنج است، اما جزئیات قابل تمایز بیشتری دارد.
برای هر فایل صوتی در مجموعه داده، یک MFCC استخراج میشود (بدین معنا که یک ارائه تصویر برای هر نمونه صوتی وجود دارد) و در «دیتافریم» (Dataframe) کتابخانه «پانداس» (Pandas) با برچسبهای دستهبندی آن ارائه شدهاند. بدین منظور، از تابع Librosa’s mfcc استفاده کرد که MFCC را از دادههای صوتی سری زمانی تولید میکند.
1def extract_features(file_name):
2
3 try:
4 audio, sample_rate = librosa.load(file_name, res_type='kaiser_fast')
5 mfccs = librosa.feature.mfcc(y=audio, sr=sample_rate, n_mfcc=40)
6 mfccsscaled = np.mean(mfccs.T,axis=0)
7
8 except Exception as e:
9 print("Error encountered while parsing file: ", file)
10 return None
11
12 return mfccsscaled
13
14
15# Load various imports
16import pandas as pd
17import os
18import librosa
19
20# Set the path to the full UrbanSound dataset
21fulldatasetpath = '/Urban Sound/UrbanSound8K/audio/'
22
23metadata = pd.read_csv(fulldatasetpath + '../metadata/UrbanSound8K.csv')
24
25features = []
26
27# Iterate through each sound file and extract the features
28for index, row in metadata.iterrows():
29
30 file_name = os.path.join(os.path.abspath(fulldatasetpath),'fold'+str(row["fold"])+'/',str(row["slice_file_name"]))
31
32 class_label = row["class_name"]
33 data = extract_features(file_name)
34
35 features.append([data, class_label])
36
37# Convert into a Panda dataframe
38featuresdf = pd.DataFrame(features, columns=['feature','class_label'])
39
40print('Finished feature extraction from ', len(featuresdf), ' files')
تبدیل دادهها و برچسبها و سپس، تقسیم مجموعه داده
از sklearn.preprocessing.LabelEncoder برای رمزنگاری دادههای متنی دستهای به دادههای متنی قابل درک برای مدل استفاده شده است. سپس، از sklearn.model_selection.train_test_split برای تقسیم مجموعه داده به مجموعههای «تست» (Test) و «آموزش» (Train) استفاده میشود.
اندازه مجموعه تست ٪۲۰ است و یک وضعیت تصادفی برای آن تعیین میشود.
1from sklearn.preprocessing import LabelEncoder
2from keras.utils import to_categorical
3
4# Convert features and corresponding classification labels into numpy arrays
5X = np.array(featuresdf.feature.tolist())
6y = np.array(featuresdf.class_label.tolist())
7
8# Encode the classification labels
9le = LabelEncoder()
10yy = to_categorical(le.fit_transform(y))
11
12# split the dataset
13from sklearn.model_selection import train_test_split
14
15x_train, x_test, y_train, y_test = train_test_split(X, yy, test_size=0.2, random_state = 42)
ساخت مدل
گام بعدی، ساختن و آموزش دادن یک «شبکه عصبی عمیق» (Deep Neural Network) با این مجموعه دادهها و انجام پیشبینی است. در اینجا، از یک «شبکه عصبی پیچشی» (Convolutional Neural Network | CNN) استفاده خواهد شد. CNN به طور معمول دستهبندهای خوبی میسازد و در وظایف دستهبندی تصاویر با توجه به بخشهای «استخراج ویژگی» (Feature Extraction) و دستهبندی، عملکرد بسیار خوبی دارد. به نظر میرسد که این کار در پیدا کردن الگوها درون MFCC، کارایی به اندازه عملکرد آن در پیدا کردن الگو در تصاویر داشته باشد.
در اینجا، از مدل پی در پی استفاده شده است که با یک معماری مدل ساده که شامل چهار لایه پیچشی Conv2D است آغاز میشود و لایه نهایی خروجی یک لایه چگال خواهد بود. لایه خروجی، دارای ۱۰ گره است (num_labels) بدین معنا که با تعداد دستهبندیهای ممکن تطبیق دارد.
1import numpy as np
2from keras.models import Sequential
3from keras.layers import Dense, Dropout, Activation, Flatten
4from keras.layers import Convolution2D, Conv2D, MaxPooling2D, GlobalAveragePooling2D
5from keras.optimizers import Adam
6from keras.utils import np_utils
7from sklearn import metrics
8
9num_rows = 40
10num_columns = 174
11num_channels = 1
12
13x_train = x_train.reshape(x_train.shape[0], num_rows, num_columns, num_channels)
14x_test = x_test.reshape(x_test.shape[0], num_rows, num_columns, num_channels)
15
16num_labels = yy.shape[1]
17filter_size = 2
18
19# Construct model
20model = Sequential()
21model.add(Conv2D(filters=16, kernel_size=2, input_shape=(num_rows, num_columns, num_channels), activation='relu'))
22model.add(MaxPooling2D(pool_size=2))
23model.add(Dropout(0.2))
24
25model.add(Conv2D(filters=32, kernel_size=2, activation='relu'))
26model.add(MaxPooling2D(pool_size=2))
27model.add(Dropout(0.2))
28
29model.add(Conv2D(filters=64, kernel_size=2, activation='relu'))
30model.add(MaxPooling2D(pool_size=2))
31model.add(Dropout(0.2))
32
33model.add(Conv2D(filters=128, kernel_size=2, activation='relu'))
34model.add(MaxPooling2D(pool_size=2))
35model.add(Dropout(0.2))
36model.add(GlobalAveragePooling2D())
37
38model.add(Dense(num_labels, activation='softmax'))
برای کامپایل کردن مدل، از سه پارامتر زیر استفاده میشود.
1# Compile the model
2model.compile(loss='categorical_crossentropy', metrics=['accuracy'], optimizer='adam')
3
4# Display model architecture summary
5model.summary()
6
7# Calculate pre-training accuracy
8score = model.evaluate(x_test, y_test, verbose=1)
9accuracy = 100*score[1]
10
11print("Pre-training accuracy: %.4f%%" % accuracy)
در اینجا، مدل آموزش داده میشود. با توجه به آنکه آموزش دادن یک مدل CCN ممکن است زمان زیادی ببرد، کار با تعداد کمی از «دورهها» (Epochs) و سایز دسته کم آغاز میشود. اگر در خروجیها مشاهده شد که مدل در حال همگرا شدن است، هر دو افزایش پیدا میکنند.
1from keras.callbacks import ModelCheckpoint
2from datetime import datetime
3
4num_epochs = 72
5num_batch_size = 256
6
7checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.basic_cnn.hdf5',
8 verbose=1, save_best_only=True)
9start = datetime.now()
10
11model.fit(x_train, y_train, batch_size=num_batch_size, epochs=num_epochs, validation_data=(x_test, y_test), callbacks=[checkpointer], verbose=1)
12
13
14duration = datetime.now() - start
15print("Training completed in time: ", duration)
قطعه کد زیر، صحت مدل را روی دادههای تست و آموزش بررسی میکند.
1# Evaluating the model on the training and testing set
2score = model.evaluate(x_train, y_train, verbose=0)
3print("Training Accuracy: ", score[1])
4
5score = model.evaluate(x_test, y_test, verbose=0)
6print("Testing Accuracy: ", score[1])
نتایج
مدل آموزش دیده، صحت ٪98.19 و صحت تست ٪91.92 را به دست آورده است. کارایی بسیار خوب است و مدل به خوبی تعمیم پیدا کرده است و به نظر میرسد هنگامی که دادههای صوتی جدید روی آن تست میشوند، پیشبینی خوبی ارائه میکند.
مشاهدات
پیشتر در بحث اکتشاف داده بیان شد که بصریسازی تفاوت بین برخی از کلاسها کار دشواری است. در واقع، زیرگروههای زیر، دارای شکل مشابهی هستند.
- صداهای تکرار شونده برای تهویه هوا، حفاری، کار کردن موتور در حال ثابت و جکامر
- قلههای تیز برای پارس سگ و شلیک گلوله
- الگوی مشابه برای بازی کودکان و موسیقی خیابانی
با استفاده از «ماتریس درهمریختگی» (Confusion Matrix) بررسی میشود که آیا مدل نهایی برای متمایز کردن این کلاسها استفاده شده است یا خیر.
ماتریس مجاورت اما چیز دیگری میگوید. در اینجا میتوان مشاهده کرد که مدل اغلب با زیرگروههای زیر در چالش بوده است.
- تهویه هوا، جکامر، موسیقی خیابانی.
- بوق ماشین، حفاری، موسیقی خیابانی
- تهویه هوا، بازی کودکان و صدای موتور خودروی در حال کار در حالت سکون
- تهویه هوا، بوق ماشین، بازی کودکان و موسیقی خیابانی
این نشان میدهد که این مسئله تفاوتهای جزئی با با ارزیابیهای اولیه انجام شده دارد و بینشی نسبت به ویژگیهایی که CNN برای انجام دستهبندی استخراج میکند، به دست میدهد. برای مثال، موسیقی خیابانی یکی از دستههایی است که اغلب به اشتباه دستهبندی شده و میتواند انواع گستردهای از نمونههای داخلی کلاس را داشته باشد.
گام بعدی
گام منطقی بعدی، مشاهده آن است که موارد بالا را چطور میتوان بر دادههای صوتی که به صورت بیدرنگ استریم میشوند و همچنین با استفاده از صداهای جهان واقعی، اعمال کرد.
صدا در جهان واقعی چالش آشفتهتری است؛ زیرا فرد نیاز به تطبیق صداهای پسزمینه گوناگون، سطح صداهای مختلف از صدای هدف و درستنمایی برای دورهها دارد.
انجام این کار در زمان واقعی، این چالش را نیز نشان میدهد که مدل باید با زمان تاخیر کم به خوبی محاسبات MFCC کار کند.
اگر مطلب بالا برای شما مفید بوده، آموزشهای زیر نیز به شما پیشنهاد میشود:
- مجموعه آموزشهای تصویر و پردازش سیگنال
- گنجینه آموزشهای برنامه نویسی پایتون (Python)
- مجموعه آموزشهای یادگیری ماشین و بازشناسی الگو
- زبان برنامهنویسی پایتون (Python) — از صفر تا صد
- یادگیری علم داده (Data Science) با پایتون — از صفر تا صد
- آموزش پایتون (Python) — مجموعه مقالات جامع وبلاگ فرادرس
^^