ML Community Day è il 9 novembre! Unisciti a noi per gli aggiornamenti da tensorflow, JAX, e più Per saperne di più

Trasferire l'apprendimento con YAMNet per la classificazione del suono ambientale

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza su GitHub Scarica taccuino Vedi il modello del mozzo TF

YAMNet è una rete neurale profonda pre-addestrati in grado di prevedere gli eventi audio da 521 classi , come il riso, che abbaiano, o una sirena.

In questo tutorial imparerai come:

  • Carica e usa il modello YAMNet per l'inferenza.
  • Costruisci un nuovo modello utilizzando gli incorporamenti YAMNet per classificare i suoni di cani e gatti.
  • Valuta ed esporta il tuo modello.

Importa TensorFlow e altre librerie

Inizia con l'installazione di tensorflow I / O , che renderà più facile per voi di caricare i file audio dal disco.

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

Informazioni su YAMNet

YAMNet è una rete neurale addestrata pre-che impiega il MobileNetV1 depthwise-separabile architettura convoluzione. Si può utilizzare una forma d'onda audio come input e fare previsioni indipendenti per ciascuno dei 521 eventi audio dal AudioSet corpus.

Internamente, il modello estrae "frame" dal segnale audio ed elabora batch di questi frame. Questa versione del modello utilizza frame lunghi 0,96 secondi ed estrae un frame ogni 0,48 secondi.

Il modello accetta un 1-D float32 Tensor o NumPy matrice contenente una forma d'onda di lunghezza arbitraria, rappresentato come singolo canale (mono) campioni a 16 kHz nell'intervallo [-1.0, +1.0] . Questo tutorial contiene codice per aiutarti a convertire i file WAV nel formato supportato.

Il modello restituisce 3 uscite, tra cui i punteggi di classe, incastri (che si intende utilizzare per l'apprendimento di trasferimento), e il registro mel spettrogramma . Potete trovare maggiori dettagli qui .

Un uso specifico di YAMNet è come estrattore di funzionalità di alto livello: l'output di incorporamento a 1.024 dimensioni. Potrai utilizzare le funzioni di ingresso del modello di base (YAMNet) e dar loro da mangiare nel modello meno profondo costituito da uno nascosto tf.keras.layers.Dense strato. Poi, si addestrare la rete su una piccola quantità di dati per la classificazione audio senza richiedere un sacco di dati etichettati e formazione end-to-end. (Questo è simile a trasferire l'apprendimento per la classificazione delle immagini con tensorflow Hub per ulteriori informazioni.)

Innanzitutto, testerai il modello e vedrai i risultati della classificazione dell'audio. Quindi costruirai la pipeline di pre-elaborazione dei dati.

Caricamento di YAMNet da TensorFlow Hub

Avete intenzione di utilizzare un YAMNet pre-addestrati da tensorflow Hub per estrarre i embeddings dai file audio.

Caricamento di un modello da tensorflow Hub è semplice: scegliere il modello, copiare l'URL, e utilizzare il load delle funzioni.

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

Con il modello caricato, è possibile seguire la YAMNet uso di base esercitazione e scaricare un file WAV di esempio per eseguire l'inferenza.

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

Avrai bisogno di una funzione per caricare i file audio, che verranno utilizzati anche in seguito quando si lavora con i dati di allenamento. (Ulteriori informazioni sulla lettura di file audio e le relative etichette in riconoscimento audio semplice .

# 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

Carica la mappatura delle classi

È importante caricare i nomi delle classi che YAMNet è in grado di riconoscere. Il file di mapping è presente a yamnet_model.class_map_path() in formato 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
...

Esegui inferenza

YAMNet fornisce punteggi di classe a livello di frame (cioè 521 punteggi per ogni frame). Per determinare le previsioni a livello di clip, i punteggi possono essere aggregati per classe tra i fotogrammi (ad esempio, utilizzando l'aggregazione media o massima). Questo viene fatto in appresso con scores_np.mean(axis=0) . Infine, per trovare la classe con il punteggio più alto a livello di clip, prendi il massimo dei 521 punteggi aggregati.

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)

Set di dati ESC-50

L'ESC-50 set di dati ( Piczak 2015 ) è una raccolta etichetta di 2.000 registrazioni audio cinque secondi lungo ambientali. Il set di dati è composto da 50 classi, con 40 esempi per classe.

Scarica il set di dati ed estrailo.

_ = 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

Esplora i dati

I metadati per ciascun file è specificato nel file CSV in ./datasets/ESC-50-master/meta/esc50.csv

e tutti i file audio sono in ./datasets/ESC-50-master/audio/

Si creerà un panda DataFrame con la mappatura e l'uso che per avere una visione più chiara dei dati.

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

Filtra i dati

Ora che i dati sono memorizzati nel DataFrame , applicare alcune trasformazioni:

  • Filtrare le righe e utilizzare solo il selezionato classi - dog e cat . Se vuoi usare altre classi, qui è dove puoi sceglierle.
  • Modificare il nome del file per avere il percorso completo. Ciò faciliterà il caricamento in seguito.
  • Modifica gli obiettivi in ​​modo che rientrino in un intervallo specifico. In questo esempio, dog rimarrà a 0 , ma cat diventerà 1 al posto del suo valore originale di 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)

Carica i file audio e recupera gli incorporamenti

Qui si applica la load_wav_16k_mono e preparare i dati WAV per il modello.

Quando si estraggono embeddings dai dati WAV, si ottiene una matrice di forma (N, 1024) dove N è il numero di fotogrammi che YAMNet trovati (uno ogni 0,48 secondi di audio).

Il tuo modello utilizzerà ogni fotogramma come input. Pertanto, è necessario creare una nuova colonna con un frame per riga. È inoltre necessario ampliare le etichette e la fold colonna per una corretta riflettono queste nuove righe.

L'espanso fold colonna mantiene i valori originali. Non è possibile mescolare i fotogrammi perché, durante l'esecuzione delle divisioni, si potrebbe finire per avere parti dello stesso audio su divisioni diverse, il che renderebbe meno efficaci i passaggi di convalida e test.

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

Dividi i dati

Si utilizzerà la fold colonna per dividere il set di dati in trenini, validazione e test.

ESC-50 è organizzato in cinque uniformemente dimensioni convalida incrociata fold s, in modo tale che clip dalla stessa fonte originale sono sempre nella stessa fold - trovi tutto nella CES: Dataset per suono ambientale Classificazione carta.

L'ultimo passo è quello di rimuovere la fold della colonna dal set di dati dal momento che non avete intenzione di usarlo durante l'allenamento.

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)

Crea il tuo modello

Hai fatto la maggior parte del lavoro! Successivamente, definire un semplice sequenziale modello con uno strato nascosto e due uscite di riconoscere cani e gatti dai suoni.

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

Corriamo il evaluate metodo sui dati di test solo per essere sicuri non c'è overfitting.

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

Ce l'hai fatta!

Metti alla prova il tuo modello

Quindi, prova il tuo modello sull'incorporamento del test precedente utilizzando solo 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

Salva un modello che può prendere direttamente un file WAV come input

Il tuo modello funziona quando gli fornisci gli incorporamenti come input.

In uno scenario reale, ti consigliamo di utilizzare i dati audio come input diretto.

Per fare ciò, combinerai YAMNet con il tuo modello in un unico modello che puoi esportare per altre applicazioni.

Per rendere più semplice l'utilizzo risultato del modello, lo strato finale sarà un reduce_mean operazione. Quando usi questo modello per la pubblicazione (che imparerai più avanti nel tutorial), avrai bisogno del nome del livello finale. Se non ne definisci uno, TensorFlow ne definirà automaticamente uno incrementale che lo rende difficile da testare, poiché continuerà a cambiare ogni volta che esegui il training del modello. Quando si utilizza un'operazione TensorFlow non elaborata, non è possibile assegnarle un nome. Per risolvere questo problema, si creerà un layer personalizzato che si applica reduce_mean e chiamarla '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

Carica il tuo modello salvato per verificare che funzioni come previsto.

reloaded_model = tf.saved_model.load(saved_model_path)

E per il test finale: dati alcuni dati sonori, il tuo modello restituisce il risultato corretto?

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

Se vuoi provare il tuo nuovo modello su una configurazione di servizio, puoi utilizzare la firma "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

(Facoltativo) Altri test

Il modello è pronto.

Confrontiamolo con YAMNet sul set di dati di test.

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)

Prossimi passi

Hai creato un modello in grado di classificare i suoni di cani o gatti. Con la stessa idea e un insieme di dati di diverso si può provare, ad esempio, la costruzione di un identificatore acustico degli uccelli in base al loro canto.

Condividi il tuo progetto con il team di TensorFlow sui social media!