Sehen Sie sich Keynotes, Produktsitzungen, Workshops und mehr in Google I / O an. Siehe Wiedergabeliste

Transfer Learning mit YAMNet zur Klassifizierung von Umgebungsgeräuschen

Ansicht auf TensorFlow.org In Google Colab ausführen Ansicht auf GitHub Notizbuch herunterladen Siehe TF Hub-Modell

YAMNet ist ein Audioereignisklassifizierer, der Audioereignisse aus 521 Klassen wie Lachen, Bellen oder eine Sirene vorhersagen kann.

In diesem Tutorial erfahren Sie, wie Sie:

  • Laden Sie das YAMNet-Modell und verwenden Sie es für die Inferenz.
  • Erstellen Sie ein neues Modell mit den YAMNet-Einbettungen, um Katzen- und Hundegeräusche zu klassifizieren.
  • Bewerten und exportieren Sie Ihr Modell.

Importieren Sie TensorFlow und andere Bibliotheken

Beginnen Sie mit der Installation von TensorFlow I / O , damit Sie Audiodateien leichter von der Festplatte laden können.

pip install -q 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_datasets as tfds
import tensorflow_hub as hub
import tensorflow_io as tfio

Über YAMNet

YAMNet ist ein Audioereignisklassifizierer, der die Audiowellenform als Eingabe verwendet und unabhängige Vorhersagen für jedes der 521 Audioereignisse aus der AudioSet- Ontologie macht.

Intern extrahiert das Modell "Frames" aus dem Audiosignal und verarbeitet Stapel dieser Frames. Diese Version des Modells verwendet Frames mit einer Länge von 0,96 Sekunden und extrahiert alle 0,48 Sekunden einen Frame.

Das Modell akzeptiert ein 1-D-float32-Tensor- oder NumPy-Array, das eine Wellenform beliebiger Länge enthält, die als Mono-16-kHz-Abtastwerte im Bereich [-1.0, +1.0] . Dieses Tutorial enthält Code, mit dem Sie eine .wav Datei in das richtige Format konvertieren können.

Das Modell gibt 3 Ausgaben zurück, einschließlich der Klassenergebnisse, Einbettungen (die Sie für das Transferlernen verwenden) und des Log-Mel-Spektrogramms. Weitere Details finden Sie hier . In diesem Tutorial erfahren Sie, wie Sie diese in der Praxis anwenden.

Eine spezielle Verwendung von YAMNet ist ein High-Level-Feature-Extraktor: Die 1024-D Einbettungsausgabe von YAMNet kann als Eingabe-Feature eines anderen flachen Modells verwendet werden, das dann auf eine kleine Datenmenge für eine bestimmte Aufgabe trainiert werden kann. Dies ermöglicht die schnelle Erstellung spezieller Audioklassifizierer, ohne dass viele beschriftete Daten erforderlich sind und ohne dass ein großes Modell durchgängig trainiert werden muss.

Sie verwenden die Einbettungsausgabe von YAMNet für das Transferlernen und trainieren darüber hinaus eine oder mehrere dichte Ebenen.

Zuerst probieren Sie das Modell aus und sehen die Ergebnisse der Klassifizierung von Audio. Anschließend erstellen Sie die Datenvorverarbeitungspipeline.

Laden von YAMNet vom TensorFlow Hub

Sie werden YAMNet von Tensorflow Hub verwenden , um die Einbettungen aus den Sounddateien zu extrahieren.

Das Laden eines Modells aus TensorFlow Hub ist unkompliziert: Wählen Sie das Modell aus, kopieren Sie die URL und verwenden Sie die load .

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

Wenn das Modell geladen ist und dem Tutorial zur grundlegenden Verwendung des Modells folgt, laden Sie eine Beispiel-WAV-Datei herunter und führen die Inferenz aus.

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

Sie benötigen eine Funktion zum Laden der Audiodateien. Sie werden auch später bei der Arbeit mit den Trainingsdaten verwendet.

# Util functions for loading audio files and ensure the correct sample rate

@tf.function
def load_wav_16k_mono(filename):
    """ read in a waveform file and convert to 16 kHz mono """
    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

Laden Sie die Klassenzuordnung

Es ist wichtig, die Klassennamen zu laden, die YAMNet erkennen kann. Die Zuordnungsdatei befindet sich unter yamnet_model.class_map_path() im csv Format.

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
...

Inferenz ausführen

YAMNet bietet Klassen-Scores auf Frame-Ebene (dh 521 Scores für jeden Frame). Um Vorhersagen auf Clip-Ebene zu ermitteln, können die Bewertungen pro Klasse über Frames hinweg aggregiert werden (z. B. unter Verwendung der mittleren oder maximalen Aggregation). Dies erfolgt unten durch scores_np.mean(axis=0) . Um die am besten bewertete Klasse auf Clip-Ebene zu finden, nehmen wir schließlich das Maximum der 521 aggregierten Bewertungen.

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

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

ESC-50-Datensatz

Der hier gut beschriebene ESC-50-Datensatz ist eine beschriftete Sammlung von 2000 Umgebungs-Audioaufnahmen (jeweils 5 Sekunden lang). Die Daten bestehen aus 50 Klassen mit 40 Beispielen pro Klasse.

Als nächstes werden Sie es herunterladen und extrahieren.

_ = 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 - 46s 0us/step

Erkunden Sie die Daten

Die Metadaten für jede Datei werden in der CSV-Datei unter ./datasets/ESC-50-master/meta/esc50.csv

und alle Audiodateien befinden sich in ./datasets/ESC-50-master/audio/

Sie erstellen einen Pandas-Datenrahmen mit der Zuordnung und verwenden diesen, um eine klarere Ansicht der Daten zu erhalten.

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()

Filtern Sie die Daten

Angesichts der Daten im Datenrahmen werden Sie einige Transformationen anwenden:

  • Filtern Sie Zeilen heraus und verwenden Sie nur die ausgewählten Klassen (Hund und Katze). Wenn Sie andere Klassen verwenden möchten, können Sie diese hier auswählen.
  • Ändern Sie den Dateinamen so, dass er den vollständigen Pfad enthält. Dies erleichtert später das Laden.
  • Ändern Sie die Ziele so, dass sie innerhalb eines bestimmten Bereichs liegen. In diesem Beispiel bleibt der Hund 0, aber die Katze wird 1 anstelle ihres ursprünglichen Werts von 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)

Laden Sie die Audiodateien und rufen Sie Einbettungen ab

Hier wenden Sie load_wav_16k_mono und bereiten die WAV-Daten für das Modell vor.

Beim Extrahieren von Einbettungen aus den WAV-Daten erhalten Sie ein Array von Formen (N, 1024) wobei N die Anzahl der von YAMNet gefundenen Frames ist (eines pro 0,48 Sekunden Audio).

Ihr Modell verwendet jeden Frame als eine Eingabe, sodass Sie eine neue Spalte mit einem Frame pro Zeile erstellen müssen. Sie müssen auch die Beschriftungen erweitern und die Spalte falten, um diese neuen Zeilen richtig wiederzugeben.

Die erweiterte Faltspalte behält den ursprünglichen Wert bei. Sie können keine Frames mischen, da Sie bei der Aufteilung möglicherweise Teile desselben Audios auf verschiedenen Aufteilungen beenden. Dies würde unsere Validierungs- und Testschritte weniger effektiv machen.

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))

Teilen Sie die Daten

Sie verwenden die fold , um den Datensatz in Zug, Validierung und Test aufzuteilen.

Die Falzwerte sind so, dass Dateien aus derselben ursprünglichen WAV-Datei auf derselben Aufteilung bleiben. Weitere Informationen finden Sie auf dem Dokument , das den Datensatz beschreibt.

Der letzte Schritt besteht darin, die fold aus dem Datensatz zu entfernen, da wir sie im Trainingsprozess nicht mehr verwenden werden.

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)

Erstellen Sie Ihr Modell

Du hast die meiste Arbeit gemacht! Definieren Sie als Nächstes zunächst ein sehr einfaches sequentielles Modell - eine ausgeblendete Ebene und zwei Ausgänge zum Erkennen von Katzen und Hunden.

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 [==============================] - 6s 28ms/step - loss: 1.0148 - accuracy: 0.8167 - val_loss: 0.7994 - val_accuracy: 0.8687
Epoch 2/20
15/15 [==============================] - 0s 17ms/step - loss: 0.4651 - accuracy: 0.8750 - val_loss: 0.1999 - val_accuracy: 0.9187
Epoch 3/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2734 - accuracy: 0.8938 - val_loss: 0.3368 - val_accuracy: 0.9187
Epoch 4/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3543 - accuracy: 0.9000 - val_loss: 0.5660 - val_accuracy: 0.8687
Epoch 5/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2469 - accuracy: 0.9104 - val_loss: 0.2928 - val_accuracy: 0.8750
Epoch 6/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2248 - accuracy: 0.9250 - val_loss: 0.5726 - val_accuracy: 0.8687
Epoch 7/20
15/15 [==============================] - 0s 16ms/step - loss: 0.2794 - accuracy: 0.9125 - val_loss: 0.3606 - val_accuracy: 0.8813
Epoch 8/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2326 - accuracy: 0.9187 - val_loss: 0.7255 - val_accuracy: 0.8750
Epoch 9/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3952 - accuracy: 0.9312 - val_loss: 0.4447 - val_accuracy: 0.8687

Führen Sie die Auswertungsmethode für die Testdaten aus, um sicherzustellen, dass keine Überanpassung vorliegt.

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 3ms/step - loss: 0.8820 - accuracy: 0.8188
Loss:  0.8819669485092163
Accuracy:  0.8187500238418579

Du hast es geschafft!

Testen Sie Ihr Modell

Probieren Sie als Nächstes Ihr Modell für die Einbettung aus dem vorherigen Test nur mit YAMNet aus.

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

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

Speichern Sie ein Modell, das eine WAV-Datei direkt als Eingabe verwenden kann

Ihr Modell funktioniert, wenn Sie ihm die Einbettungen als Eingabe geben.

In einer realen Situation möchten Sie ihm die Audiodaten direkt geben.

Dazu kombinieren Sie YAMNet mit Ihrem Modell zu einem einzigen Modell, das Sie für andere Anwendungen exportieren können.

Um die Verwendung des Modellergebnisses zu vereinfachen, ist die letzte Ebene eine reduce_mean Operation. Wenn Sie dieses Modell zum Servieren verwenden, benötigen Sie, wie Sie unten sehen werden, den Namen der letzten Ebene. Wenn Sie keine definieren, definiert TF automatisch eine inkrementelle, die das Testen erschwert, da sie sich jedes Mal ändert, wenn Sie das Modell trainieren. Wenn Sie eine rohe tf-Operation verwenden, können Sie ihr keinen Namen zuweisen. Um dieses Problem zu reduce_mean , erstellen Sie eine benutzerdefinierte Ebene, die nur reduce_mean und nennen sie "Klassifizierer".

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

Laden Sie Ihr gespeichertes Modell, um zu überprüfen, ob es wie erwartet funktioniert.

reloaded_model = tf.saved_model.load(saved_model_path)

Und für den letzten Test: Gibt Ihr Modell bei einigen Sounddaten das richtige Ergebnis zurück?

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

Wenn Sie Ihr neues Modell in einem Serving-Setup testen möchten, können Sie die Signatur 'Serving_Default' verwenden.

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

(Optional) Weitere Tests

Das Modell ist fertig.

Vergleichen wir es mit YAMNet im Testdatensatz.

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)
infered_class = class_names[top_class]
top_score = class_scores[top_class]
print(f'[YAMNet] The main sound is: {infered_class} ({top_score})')

reloaded_results = reloaded_model(waveform)
your_top_class = tf.argmax(reloaded_results)
your_infered_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_infered_class} ({your_top_score})')
[YAMNet] The main sound is: Animal (0.9570276141166687)
[Your model] The main sound is: dog (0.9998551607131958)

Nächste Schritte

Sie haben gerade ein Modell erstellt, mit dem Geräusche von Hunden oder Katzen klassifiziert werden können. Mit der gleichen Idee und den richtigen Daten könnten Sie beispielsweise einen Vogelerkenner basierend auf dessen Gesang erstellen.

Lassen Sie uns wissen, was Sie sich einfallen lassen! Teilen Sie Ihr Projekt mit uns in den sozialen Medien.