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

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza su GitHub Scarica quaderno Vedi modello TF Hub

YAMNet è una rete neurale profonda pre-addestrata in grado di prevedere eventi audio da 521 classi , come risate, abbaiati o una sirena.

In questo tutorial imparerai come:

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

Importa TensorFlow e altre librerie

Inizia installando TensorFlow I/O , che ti semplificherà il caricamento di 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

A proposito di YAMNet

YAMNet è una rete neurale pre-addestrata che utilizza l'architettura di convoluzione separabile in profondità MobileNetV1 . Può utilizzare una forma d'onda audio come input e fare previsioni indipendenti per ciascuno dei 521 eventi audio dal corpus AudioSet .

Internamente, il modello estrae i "frame" dal segnale audio ed elabora i 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 array 1-D float32 Tensor o NumPy contenente una forma d'onda di lunghezza arbitraria, rappresentata come campioni a canale singolo (mono) 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 output, inclusi i punteggi della classe, gli incorporamenti (che utilizzerai per trasferire l'apprendimento) e lo spettrogramma log mel . Puoi trovare maggiori dettagli qui .

Un uso specifico di YAMNet è come estrattore di funzionalità di alto livello: l'output di incorporamento a 1.024 dimensioni. Utilizzerai le funzionalità di input del modello di base (YAMNet) e le inserirai nel tuo modello meno profondo costituito da uno strato nascosto tf.keras.layers.Dense . Quindi, addestrerai la rete su una piccola quantità di dati per la classificazione audio senza richiedere molti dati etichettati e formazione end-to-end. (Questo è simile al trasferimento dell'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

Utilizzerai uno YAMNet pre-addestrato da Tensorflow Hub per estrarre gli incorporamenti dai file audio.

Il caricamento di un modello da TensorFlow Hub è semplice: scegli il modello, copia il suo URL e utilizza la funzione di load .

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

Con il modello caricato, puoi seguire il tutorial sull'utilizzo di base di YAMNet 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
229376/215546 [===============================] - 0s 0us/step
./test_data/miaow_16k.wav

Avrai bisogno di una funzione per caricare i file audio, che verrà utilizzata anche in seguito quando si lavora con i dati di allenamento. (Ulteriori informazioni sulla lettura dei file audio e delle 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)
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

Carica la mappatura della classe

È importante caricare i nomi delle classi che YAMNet è in grado di riconoscere. Il file di mappatura è presente in yamnet_model.class_map_path() nel 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 l'inferenza

YAMNet fornisce punteggi di classe a livello di frame (ovvero 521 punteggi per ogni frame). Al fine di 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 di seguito da 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

Il set di dati ESC-50 ( Piczak, 2015 ) è una raccolta etichettata di 2.000 registrazioni audio ambientali lunghe cinque secondi. 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
645103616/Unknown - 47s 0us/step

Esplora i dati

I metadati per ogni file sono specificati nel file CSV in ./datasets/ESC-50-master/meta/esc50.csv

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

Creerai un DataFrame panda con la mappatura e la utilizzerai 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 archiviati in DataFrame , applica alcune trasformazioni:

  • Filtra le righe e usa solo le classi selezionate: dog e cat . Se vuoi usare altre classi, qui puoi sceglierle.
  • Modifica il nome del file per avere il percorso completo. Questo 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 invece 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 applicherai load_wav_16k_mono e preparerai i dati WAV per il modello.

Quando si estraggono gli incorporamenti dai dati WAV, si ottiene un array di forma (N, 1024) dove N è il numero di fotogrammi trovati da YAMNet (uno ogni 0,48 secondi di audio).

Il tuo modello utilizzerà ogni frame come un input. Pertanto, è necessario creare una nuova colonna con un frame per riga. Devi anche espandere le etichette e la colonna fold per riflettere correttamente queste nuove righe.

La colonna di fold espansa mantiene i valori originali. Non puoi mischiare i fotogrammi perché, quando esegui le divisioni, potresti finire per avere parti dello stesso audio su divisioni diverse, il che renderebbe meno efficaci le tue fasi 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

Utilizzerai la colonna fold per suddividere il set di dati in set di treni, convalida e test.

L'ESC-50 è organizzato in cinque fold di convalida incrociata di dimensioni uniformi, in modo tale che le clip della stessa fonte originale siano sempre nella stessa fold - scopri di più nell'ESC: Dataset for Environmental Sound Classification paper.

L'ultimo passaggio consiste nel rimuovere la colonna fold dal set di dati poiché non la utilizzerai 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! Quindi, definisci un modello sequenziale molto semplice con un livello nascosto e due uscite per 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()
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

Eseguiamo il metodo di evaluate sui dati del test solo per essere sicuri che non ci sia overfitting.

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

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 dai 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 semplificare l'utilizzo del risultato del modello, il livello finale sarà un'operazione reduce_mean . Quando usi questo modello per servire (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 rende difficile il test, poiché continuerà a cambiare ogni volta che si addestra il modello. Quando si utilizza un'operazione TensorFlow grezza, non è possibile assegnarle un nome. Per risolvere questo problema, creerai un livello personalizzato che applica reduce_mean e lo chiamerai '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

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

Prossimi passi

Hai creato un modello in grado di classificare i suoni di cani o gatti. Con la stessa idea e un set di dati diverso puoi provare, ad esempio, a costruire un identificatore acustico degli uccelli basato sul loro canto.

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