יש שאלה? התחבר לקהילה בפורום הביקור של TensorFlow

העברת למידה עם YAMNet לסיווג צלילי סביבתי

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה ב- GitHub הורד מחברת ראה מודל TF Hub

YAMNet היא רשת עצבית עמוקה שהוכשרה מראש שיכולה לחזות אירועי שמע מ -521 שיעורים , כגון צחוק, נביחות או צפירה.

במדריך זה תלמד כיצד:

  • טען והשתמש במודל YAMNet למסקנות.
  • בנה דגם חדש באמצעות הטביעה של YAMNet לסיווג צלילי חתול וכלב.
  • הערך וייצא את המודל שלך.

ייבא את TensorFlow וספריות אחרות

התחל בהתקנת TensorFlow קלט / פלט , אשר יקל עליך לטעון קבצי שמע מהדיסק.

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 היא רשת עצבית שהוכשרה מראש, והיא משתמשת בארכיטקטורת הפיתול המופרדת לעומק. הוא יכול להשתמש בצורת גל שמע כקלט ולחזות עצמאית לכל אחד מ -521 אירועי השמע מקורפוס AudioSet .

באופן פנימי, המודל מחלץ "מסגרות" מאותות השמע ומעבד קבוצות של מסגרות אלה. גרסה זו של הדגם משתמשת במסגרות שאורכן 0.96 שניות ומחלצת מסגרת אחת בכל 0.48 שניות.

המודל מקבל מערך Tensor 1-D float32 או NumPy המכיל צורת גל באורך שרירותי, המיוצג כדוגמאות של 16 קילו-הרץ בערוץ יחיד (מונו) בטווח [-1.0, +1.0] . מדריך זה מכיל קוד שיעזור לכם להמיר קבצי WAV לפורמט הנתמך.

המודל מחזיר 3 תפוקות, כולל ציוני הכיתה, הטבילות (בהן תשמש למידת העברה) וספקטרוגרמת log mel. תוכל למצוא פרטים נוספים כאן .

שימוש ספציפי אחד ב- YAMNet הוא כחולץ תכונות ברמה גבוהה - פלט ההטבעה הממדי 1,024. תשתמש בתכונות הקלט של מודל הבסיס (YAMNet) ותאכיל אותן במודל הרדוד שלך המורכב משכבה אחת מוסתרת tf.keras.layers.Dense . לאחר מכן, תוכלו לאמן את הרשת על כמות קטנה של נתונים לסיווג שמע מבלי לדרוש הרבה נתונים שכותרתו והדרכה מקצה לקצה. (זה דומה למידת העברה למיון תמונות עם TensorFlow Hub לקבלת מידע נוסף.)

ראשית, תבדוק את המודל ותראה את תוצאות סיווג האודיו. לאחר מכן תבנה את הצינור לעיבוד מראש של הנתונים.

טוען את YAMNet מ- TensorFlow Hub

אתה הולך להשתמש ב- YAMNet שהוכשר מראש מ- Tensorflow Hub כדי לחלץ את ההטבעות מקבצי הקול.

טעינת מודל מ- TensorFlow Hub היא פשוטה: בחר את הדגם, העתק את כתובת האתר שלו והשתמש בפונקציית 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
./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)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/parallel_for/pfor.py:2382: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/parallel_for/pfor.py:2382: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.
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 ) הוא אוסף מתויג של 2,000 הקלטות שמע סביבתיות באורך חמש שניות. מערך הנתונים מורכב מ- 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
645701632/Unknown - 41s 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 יהפוך ל 1 במקום לערכו המקורי של 5 .
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 ים, כך קליפים מאותו המקור הם תמיד באותו fold - לברר פרטים נוספים על ESC: בסיס הנתונים עבור הסביבה סאונד סיווג נייר.

השלב האחרון הוא להסיר את עמודת 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)

צור את המודל שלך

עשית את רוב העבודה! לאחר מכן, הגדירו מודל רצף פשוט ביותר עם שכבה נסתרת אחת ושתי תפוקות לזיהוי חתולים וכלבים מצלילים.

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()
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
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 [==============================] - 5s 25ms/step - loss: 0.7833 - accuracy: 0.8000 - val_loss: 0.6789 - val_accuracy: 0.8687
Epoch 2/20
15/15 [==============================] - 0s 16ms/step - loss: 0.5082 - accuracy: 0.8958 - val_loss: 0.3775 - val_accuracy: 0.8813
Epoch 3/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3210 - accuracy: 0.8750 - val_loss: 0.5043 - val_accuracy: 0.8750
Epoch 4/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2146 - accuracy: 0.9021 - val_loss: 0.3757 - val_accuracy: 0.8750
Epoch 5/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2113 - accuracy: 0.9062 - val_loss: 0.2740 - val_accuracy: 0.8750
Epoch 6/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2672 - accuracy: 0.9167 - val_loss: 0.4483 - val_accuracy: 0.8750
Epoch 7/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2386 - accuracy: 0.9333 - val_loss: 0.5775 - val_accuracy: 0.8687
Epoch 8/20
15/15 [==============================] - 0s 17ms/step - loss: 0.1639 - accuracy: 0.9229 - val_loss: 0.4539 - val_accuracy: 0.8750
Epoch 9/20
15/15 [==============================] - 0s 18ms/step - loss: 0.3539 - accuracy: 0.9250 - val_loss: 0.2091 - val_accuracy: 0.9187
Epoch 10/20
15/15 [==============================] - 0s 18ms/step - loss: 0.2705 - accuracy: 0.9271 - val_loss: 0.2505 - val_accuracy: 0.9062
Epoch 11/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2582 - accuracy: 0.9312 - val_loss: 0.2182 - val_accuracy: 0.9250

בואו להריץ את שיטת evaluate על נתוני הבדיקה רק כדי להיות בטוחים שאין התאמה יתרה.

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 4ms/step - loss: 0.6575 - accuracy: 0.8125
Loss:  0.657511293888092
Accuracy:  0.8125

עשית את זה!

בדוק את המודל שלך

לאחר מכן, נסה את המודל שלך להטבעה מהמבחן הקודם באמצעות 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.
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-212454-A-0.wav
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
Waveform values: [-8.8849301e-09  2.6603255e-08 -1.1731625e-08 ... -1.3478296e-03
 -1.0509168e-03 -9.1038318e-04]

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: Animal (0.9570276141166687)
[Your model] The main sound is: dog (0.9999711513519287)

הצעדים הבאים

יצרת מודל שיכול לסווג צלילים מכלבים או חתולים. עם אותו רעיון ומערך נתונים אחר תוכלו לנסות, למשל, לבנות מזהה אקוסטי של ציפורים על סמך שירתם.

שתף את הפרויקט שלך עם צוות TensorFlow ברשתות החברתיות!