انتقال یادگیری با YAMNet برای طبقه بندی صدای محیطی

مشاهده در TensorFlow.org در Google Colab اجرا شود در GitHub مشاهده کنید دانلود دفترچه یادداشت مدل TF Hub را ببینید

YAMNet یک شبکه عصبی عمیق از پیش آموزش دیده است که می تواند رویدادهای صوتی را از 521 کلاس ، مانند خنده، پارس کردن، یا آژیر پیش بینی کند.

در این آموزش شما یاد خواهید گرفت که چگونه:

  • مدل YAMNet را برای استنتاج بارگذاری و استفاده کنید.
  • یک مدل جدید با استفاده از تعبیه‌های YAMNet برای طبقه‌بندی صداهای سگ و گربه بسازید.
  • مدل خود را ارزیابی و صادر کنید.

TensorFlow و کتابخانه های دیگر را وارد کنید

با نصب TensorFlow I/O شروع کنید، که بارگذاری فایل های صوتی را از روی دیسک برای شما آسان تر می کند.

pip install tensorflow_io
import os

from IPython import display
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_io as tfio

درباره YAMNet

YAMNet یک شبکه عصبی از پیش آموزش دیده است که از معماری کانولوشن قابل جداسازی در عمق MobileNetV1 استفاده می کند. می تواند از شکل موج صوتی به عنوان ورودی استفاده کند و برای هر یک از 521 رویداد صوتی از مجموعه AudioSet پیش بینی های مستقل انجام دهد.

در داخل، مدل «فریم‌ها» را از سیگنال صوتی استخراج می‌کند و دسته‌هایی از این فریم‌ها را پردازش می‌کند. این نسخه از مدل از فریم هایی با طول 0.96 ثانیه استفاده می کند و هر 0.48 ثانیه یک فریم را استخراج می کند.

مدل یک آرایه 1-D float32 Tensor یا NumPy را می پذیرد که حاوی شکل موجی با طول دلخواه است که به صورت نمونه های تک کانالی (مونو) 16 کیلوهرتز در محدوده [-1.0, +1.0] نمایش داده می شود. این آموزش حاوی کدهایی است که به شما کمک می کند فایل های WAV را به فرمت پشتیبانی شده تبدیل کنید.

این مدل 3 خروجی شامل امتیازات کلاس، جاسازی‌ها (که برای یادگیری انتقال استفاده خواهید کرد) و طیف‌نگار log mel را برمی‌گرداند. شما می توانید جزئیات بیشتر را در اینجا بیابید.

یکی از کاربردهای خاص YAMNet به عنوان یک استخراج کننده ویژگی های سطح بالا است - خروجی تعبیه 1024 بعدی. شما از ویژگی های ورودی مدل پایه (YAMNet) استفاده کرده و آنها را به مدل کم عمق تر خود متشکل از یک لایه پنهان tf.keras.layers.Dense می کنید. سپس، شبکه را بر روی مقدار کمی داده برای طبقه بندی صدا بدون نیاز به داده های برچسب دار زیاد و آموزش سرتاسر آموزش می دهید. (این شبیه به انتقال یادگیری برای طبقه بندی تصاویر با TensorFlow Hub برای اطلاعات بیشتر است.)

ابتدا مدل را تست کرده و نتایج طبقه بندی صدا را مشاهده خواهید کرد. سپس خط لوله پیش پردازش داده را می سازید.

در حال بارگیری YAMNet از TensorFlow Hub

شما می خواهید از یک YAMNet از پیش آموزش دیده از Tensorflow Hub برای استخراج جاسازی ها از فایل های صوتی استفاده کنید.

بارگیری یک مدل از TensorFlow Hub ساده است: مدل را انتخاب کنید، URL آن را کپی کنید و از تابع load استفاده کنید.

yamnet_model_handle = 'https://tfhub.dev/google/yamnet/1'
yamnet_model = hub.load(yamnet_model_handle)

با بارگذاری مدل، می‌توانید آموزش استفاده اولیه YAMNet را دنبال کنید و یک نمونه فایل WAV را برای اجرای استنتاج دانلود کنید.

testing_wav_file_name = tf.keras.utils.get_file('miaow_16k.wav',
                                                'https://storage.googleapis.com/audioset/miaow_16k.wav',
                                                cache_dir='./',
                                                cache_subdir='test_data')

print(testing_wav_file_name)
Downloading data from https://storage.googleapis.com/audioset/miaow_16k.wav
221184/215546 [==============================] - 0s 0us/step
229376/215546 [===============================] - 0s 0us/step
./test_data/miaow_16k.wav

برای بارگذاری فایل های صوتی به یک تابع نیاز دارید که بعداً هنگام کار با داده های آموزشی نیز از آن استفاده می شود. (درباره خواندن فایل‌های صوتی و برچسب‌های آنها در تشخیص ساده صوتی بیشتر بیاموزید.

# Utility functions for loading audio files and making sure the sample rate is correct.

@tf.function
def load_wav_16k_mono(filename):
    """ Load a WAV file, convert it to a float tensor, resample to 16 kHz single-channel audio. """
    file_contents = tf.io.read_file(filename)
    wav, sample_rate = tf.audio.decode_wav(
          file_contents,
          desired_channels=1)
    wav = tf.squeeze(wav, axis=-1)
    sample_rate = tf.cast(sample_rate, dtype=tf.int64)
    wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
    return wav
testing_wav_data = load_wav_16k_mono(testing_wav_file_name)

_ = plt.plot(testing_wav_data)

# Play the audio file.
display.Audio(testing_wav_data,rate=16000)
2022-01-26 08:07:19.084427: W tensorflow_io/core/kernels/audio_video_mp3_kernels.cc:271] libmp3lame.so.0 or lame functions are not available
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample

png

نقشه کلاس را بارگیری کنید

بارگیری نام کلاس هایی که YAMNet قادر به تشخیص آن است، مهم است. فایل نگاشت در yamnet_model.class_map_path() با فرمت CSV موجود است.

class_map_path = yamnet_model.class_map_path().numpy().decode('utf-8')
class_names =list(pd.read_csv(class_map_path)['display_name'])

for name in class_names[:20]:
  print(name)
print('...')
Speech
Child speech, kid speaking
Conversation
Narration, monologue
Babbling
Speech synthesizer
Shout
Bellow
Whoop
Yell
Children shouting
Screaming
Whispering
Laughter
Baby laughter
Giggle
Snicker
Belly laugh
Chuckle, chortle
Crying, sobbing
...

استنتاج را اجرا کنید

YAMNet امتیازهای کلاس در سطح فریم را ارائه می دهد (یعنی 521 امتیاز برای هر فریم). به منظور تعیین پیش‌بینی‌های سطح کلیپ، نمرات را می‌توان در هر کلاس در فریم‌ها جمع کرد (مثلاً با استفاده از میانگین یا حداکثر تجمع). این کار در زیر توسط scores_np.mean(axis=0) انجام می شود. در نهایت، برای پیدا کردن کلاس با امتیاز بالا در سطح کلیپ، حداکثر 521 امتیاز جمع‌آوری شده را می‌گیرید.

scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
class_scores = tf.reduce_mean(scores, axis=0)
top_class = tf.argmax(class_scores)
inferred_class = class_names[top_class]

print(f'The main sound is: {inferred_class}')
print(f'The embeddings shape: {embeddings.shape}')
The main sound is: Animal
The embeddings shape: (13, 1024)

مجموعه داده ESC-50

مجموعه داده ESC-50 ( Piczak، 2015 ) مجموعه ای برچسب دار از 2000 ضبط صوتی محیطی پنج ثانیه ای است. مجموعه داده شامل 50 کلاس با 40 نمونه در هر کلاس است.

مجموعه داده را دانلود و استخراج کنید.

_ = tf.keras.utils.get_file('esc-50.zip',
                        'https://github.com/karoldvl/ESC-50/archive/master.zip',
                        cache_dir='./',
                        cache_subdir='datasets',
                        extract=True)
Downloading data from https://github.com/karoldvl/ESC-50/archive/master.zip
645103616/Unknown - 47s 0us/step

داده ها را کاوش کنید

ابرداده برای هر فایل در فایل csv در ./datasets/ESC-50-master/meta/esc50.csv مشخص شده است.

و تمامی فایل های صوتی در ./datasets/ESC-50-master/audio/

شما با نقشه برداری یک DataFrame پاندا ایجاد می کنید و از آن برای داشتن دید واضح تری از داده ها استفاده می کنید.

esc50_csv = './datasets/ESC-50-master/meta/esc50.csv'
base_data_path = './datasets/ESC-50-master/audio/'

pd_data = pd.read_csv(esc50_csv)
pd_data.head()

داده ها را فیلتر کنید

اکنون که داده ها در DataFrame ذخیره می شوند، برخی از تبدیل ها را اعمال کنید:

  • ردیف ها را فیلتر کنید و فقط از کلاس های انتخاب شده استفاده کنید - dog و cat . اگر می خواهید از هر کلاس دیگری استفاده کنید، اینجاست که می توانید آنها را انتخاب کنید.
  • نام فایل را اصلاح کنید تا مسیر کامل داشته باشد. این کار بارگذاری را بعدا آسان تر می کند.
  • اهداف را تغییر دهید تا در محدوده خاصی قرار گیرند. در این مثال، dog در 0 باقی می ماند، اما cat به جای مقدار اصلی خود 5 ، 1 می شود.
my_classes = ['dog', 'cat']
map_class_to_id = {'dog':0, 'cat':1}

filtered_pd = pd_data[pd_data.category.isin(my_classes)]

class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name])
filtered_pd = filtered_pd.assign(target=class_id)

full_path = filtered_pd['filename'].apply(lambda row: os.path.join(base_data_path, row))
filtered_pd = filtered_pd.assign(filename=full_path)

filtered_pd.head(10)

فایل های صوتی را بارگیری کنید و موارد تعبیه شده را بازیابی کنید

در اینجا شما load_wav_16k_mono را اعمال می‌کنید و داده‌های WAV را برای مدل آماده می‌کنید.

هنگام استخراج جاسازی‌ها از داده‌های WAV، آرایه‌ای از شکل (N, 1024) دریافت می‌کنید که N تعداد فریم‌هایی است که YAMNet پیدا کرده است (یک فریم برای هر 0.48 ثانیه صدا).

مدل شما از هر فریم به عنوان یک ورودی استفاده می کند. بنابراین، باید یک ستون جدید ایجاد کنید که دارای یک فریم در هر سطر باشد. همچنین باید برچسب ها و ستون fold را گسترش دهید تا به درستی این ردیف های جدید را منعکس کنند.

ستون fold گسترش یافته مقادیر اصلی را حفظ می کند. نمی‌توانید فریم‌ها را با هم ترکیب کنید، زیرا هنگام انجام تقسیم‌ها، ممکن است در نهایت بخش‌هایی از یک صدا را در تقسیم‌های مختلف داشته باشید، که باعث می‌شود اعتبارسنجی و مراحل تست شما مؤثرتر نباشد.

filenames = filtered_pd['filename']
targets = filtered_pd['target']
folds = filtered_pd['fold']

main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds))
main_ds.element_spec
(TensorSpec(shape=(), dtype=tf.string, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None))
def load_wav_for_map(filename, label, fold):
  return load_wav_16k_mono(filename), label, fold

main_ds = main_ds.map(load_wav_for_map)
main_ds.element_spec
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
(TensorSpec(shape=<unknown>, dtype=tf.float32, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None))
# applies the embedding extraction model to a wav data
def extract_embedding(wav_data, label, fold):
  ''' run YAMNet to extract embedding from the wav data '''
  scores, embeddings, spectrogram = yamnet_model(wav_data)
  num_embeddings = tf.shape(embeddings)[0]
  return (embeddings,
            tf.repeat(label, num_embeddings),
            tf.repeat(fold, num_embeddings))

# extract embedding
main_ds = main_ds.map(extract_embedding).unbatch()
main_ds.element_spec
(TensorSpec(shape=(1024,), dtype=tf.float32, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None),
 TensorSpec(shape=(), dtype=tf.int64, name=None))

داده ها را تقسیم کنید

شما از ستون fold برای تقسیم مجموعه داده ها به مجموعه های قطار، اعتبار سنجی و آزمایش استفاده خواهید کرد.

ESC-50 در پنج تای اعتبار fold با اندازه یکنواخت مرتب شده است، به طوری که کلیپ های یک منبع اصلی همیشه در یک تا هستند - در مقاله ESC: fold for Environmental Sound Classification بیشتر بدانید.

آخرین مرحله حذف ستون fold از مجموعه داده است زیرا قرار نیست در طول آموزش از آن استفاده کنید.

cached_ds = main_ds.cache()
train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4)
val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4)
test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5)

# remove the folds column now that it's not needed anymore
remove_fold_column = lambda embedding, label, fold: (embedding, label)

train_ds = train_ds.map(remove_fold_column)
val_ds = val_ds.map(remove_fold_column)
test_ds = test_ds.map(remove_fold_column)

train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)

مدل خود را ایجاد کنید

شما بیشتر کار را انجام دادید! بعد، یک مدل Sequential بسیار ساده با یک لایه پنهان و دو خروجی برای تشخیص گربه ها و سگ ها از صداها تعریف کنید.

my_model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=(1024), dtype=tf.float32,
                          name='input_embedding'),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dense(len(my_classes))
], name='my_model')

my_model.summary()
Model: "my_model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 512)               524800    
                                                                 
 dense_1 (Dense)             (None, 2)                 1026      
                                                                 
=================================================================
Total params: 525,826
Trainable params: 525,826
Non-trainable params: 0
_________________________________________________________________
my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                 optimizer="adam",
                 metrics=['accuracy'])

callback = tf.keras.callbacks.EarlyStopping(monitor='loss',
                                            patience=3,
                                            restore_best_weights=True)
history = my_model.fit(train_ds,
                       epochs=20,
                       validation_data=val_ds,
                       callbacks=callback)
Epoch 1/20
15/15 [==============================] - 6s 49ms/step - loss: 0.7811 - accuracy: 0.8229 - val_loss: 0.4866 - val_accuracy: 0.9125
Epoch 2/20
15/15 [==============================] - 0s 18ms/step - loss: 0.3385 - accuracy: 0.8938 - val_loss: 0.2185 - val_accuracy: 0.8813
Epoch 3/20
15/15 [==============================] - 0s 18ms/step - loss: 0.3091 - accuracy: 0.9021 - val_loss: 0.4290 - val_accuracy: 0.8813
Epoch 4/20
15/15 [==============================] - 0s 18ms/step - loss: 0.5354 - accuracy: 0.9062 - val_loss: 0.2074 - val_accuracy: 0.9125
Epoch 5/20
15/15 [==============================] - 0s 18ms/step - loss: 0.4651 - accuracy: 0.9333 - val_loss: 0.6857 - val_accuracy: 0.8813
Epoch 6/20
15/15 [==============================] - 0s 18ms/step - loss: 0.2489 - accuracy: 0.9167 - val_loss: 0.3640 - val_accuracy: 0.8750
Epoch 7/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2020 - accuracy: 0.9292 - val_loss: 0.2158 - val_accuracy: 0.9125
Epoch 8/20
15/15 [==============================] - 0s 16ms/step - loss: 0.4550 - accuracy: 0.9208 - val_loss: 0.9893 - val_accuracy: 0.8750
Epoch 9/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3434 - accuracy: 0.9354 - val_loss: 0.2670 - val_accuracy: 0.8813
Epoch 10/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2864 - accuracy: 0.9208 - val_loss: 0.5122 - val_accuracy: 0.8813

بیایید روش evaluate را روی داده های آزمایش اجرا کنیم تا مطمئن شویم که بیش از حد برازش وجود ندارد.

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 9ms/step - loss: 0.2526 - accuracy: 0.9000
Loss:  0.25257644057273865
Accuracy:  0.8999999761581421

توانجامش دادی!

مدل خود را تست کنید

در مرحله بعد، مدل خود را روی جاسازی آزمایش قبلی فقط با استفاده از YAMNet امتحان کنید.

scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
result = my_model(embeddings).numpy()

inferred_class = my_classes[result.mean(axis=0).argmax()]
print(f'The main sound is: {inferred_class}')
The main sound is: cat

مدلی را ذخیره کنید که بتواند مستقیماً یک فایل WAV را به عنوان ورودی بگیرد

مدل شما زمانی کار می کند که به آن جاسازی ها را به عنوان ورودی بدهید.

در یک سناریوی واقعی، شما می خواهید از داده های صوتی به عنوان ورودی مستقیم استفاده کنید.

برای انجام این کار، YAMNet را با مدل خود در یک مدل ترکیب می‌کنید که می‌توانید برای برنامه‌های کاربردی دیگر صادر کنید.

برای سهولت استفاده از نتیجه مدل، لایه نهایی یک عملیات reduce_mean خواهد بود. هنگام استفاده از این مدل برای سرو (که در ادامه آموزش با آن آشنا خواهید شد) به نام لایه نهایی نیاز خواهید داشت. اگر یکی را تعریف نکنید، TensorFlow به صورت خودکار یک افزایشی را تعریف می‌کند که آزمایش آن را سخت می‌کند، زیرا هر بار که مدل را آموزش می‌دهید تغییر می‌کند. هنگام استفاده از یک عملیات خام TensorFlow، نمی توانید نامی به آن اختصاص دهید. برای رسیدگی به این مشکل، یک لایه سفارشی ایجاد می‌کنید که reduce_mean را اعمال می‌کند و آن را 'classifier' طبقه‌بند» می‌نامید.

class ReduceMeanLayer(tf.keras.layers.Layer):
  def __init__(self, axis=0, **kwargs):
    super(ReduceMeanLayer, self).__init__(**kwargs)
    self.axis = axis

  def call(self, input):
    return tf.math.reduce_mean(input, axis=self.axis)
saved_model_path = './dogs_and_cats_yamnet'

input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio')
embedding_extraction_layer = hub.KerasLayer(yamnet_model_handle,
                                            trainable=False, name='yamnet')
_, embeddings_output, _ = embedding_extraction_layer(input_segment)
serving_outputs = my_model(embeddings_output)
serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs)
serving_model = tf.keras.Model(input_segment, serving_outputs)
serving_model.save(saved_model_path, include_optimizer=False)
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
2022-01-26 08:08:33.807036: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: ./dogs_and_cats_yamnet/assets
INFO:tensorflow:Assets written to: ./dogs_and_cats_yamnet/assets
tf.keras.utils.plot_model(serving_model)

png

مدل ذخیره شده خود را بارگیری کنید تا بررسی کنید که مطابق انتظار کار می کند.

reloaded_model = tf.saved_model.load(saved_model_path)

و برای تست نهایی: با توجه به برخی داده های صوتی، آیا مدل شما نتیجه صحیح را برمی گرداند؟

reloaded_results = reloaded_model(testing_wav_data)
cat_or_dog = my_classes[tf.argmax(reloaded_results)]
print(f'The main sound is: {cat_or_dog}')
The main sound is: cat

اگر می‌خواهید مدل جدید خود را در تنظیمات سرویس امتحان کنید، می‌توانید از امضای «serving_default» استفاده کنید.

serving_results = reloaded_model.signatures['serving_default'](testing_wav_data)
cat_or_dog = my_classes[tf.argmax(serving_results['classifier'])]
print(f'The main sound is: {cat_or_dog}')
The main sound is: cat

(اختیاری) چند آزمایش دیگر

مدل آماده است.

بیایید آن را با YAMNet در مجموعه داده آزمایشی مقایسه کنیم.

test_pd = filtered_pd.loc[filtered_pd['fold'] == 5]
row = test_pd.sample(1)
filename = row['filename'].item()
print(filename)
waveform = load_wav_16k_mono(filename)
print(f'Waveform values: {waveform}')
_ = plt.plot(waveform)

display.Audio(waveform, rate=16000)
./datasets/ESC-50-master/audio/5-214759-A-5.wav
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
Waveform values: [ 3.2084468e-09 -7.7704687e-09 -1.2222010e-08 ...  2.2788899e-02
  1.0315948e-02 -3.4766860e-02]

png

# Run the model, check the output.
scores, embeddings, spectrogram = yamnet_model(waveform)
class_scores = tf.reduce_mean(scores, axis=0)
top_class = tf.argmax(class_scores)
inferred_class = class_names[top_class]
top_score = class_scores[top_class]
print(f'[YAMNet] The main sound is: {inferred_class} ({top_score})')

reloaded_results = reloaded_model(waveform)
your_top_class = tf.argmax(reloaded_results)
your_inferred_class = my_classes[your_top_class]
class_probabilities = tf.nn.softmax(reloaded_results, axis=-1)
your_top_score = class_probabilities[your_top_class]
print(f'[Your model] The main sound is: {your_inferred_class} ({your_top_score})')
[YAMNet] The main sound is: Silence (0.500638484954834)
[Your model] The main sound is: cat (0.9981643557548523)

مراحل بعدی

شما مدلی ایجاد کرده اید که می تواند صداهای سگ یا گربه را طبقه بندی کند. با همین ایده و مجموعه داده های متفاوت می توانید سعی کنید، برای مثال، یک شناسه صوتی پرندگان را بر اساس آواز آنها بسازید.

پروژه خود را با تیم TensorFlow در رسانه های اجتماعی به اشتراک بگذارید!