¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Transferir el aprendizaje con YAMNet para la clasificación del sonido ambiental

Ver en TensorFlow.org Ejecutar en Google Colab Ver en GitHub Descargar cuaderno Ver modelo TF Hub

YAMNet es una red neuronal profunda pre-entrenado que pueda predecir los eventos de audio a partir de 521 clases , como la risa, ladridos, o una sirena.

En este tutorial aprenderá a:

  • Cargue y use el modelo YAMNet para la inferencia.
  • Cree un nuevo modelo utilizando las incrustaciones de YAMNet para clasificar los sonidos de perros y gatos.
  • Evalúe y exporte su modelo.

Importar TensorFlow y otras bibliotecas

Lo primero es instalar TensorFlow de E / S , lo que hará que sea más fácil para que cargue los archivos de audio de 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

Sobre YAMNet

YAMNet es una red neural entrenada previamente que emplea la MobileNetV1 arquitectura convolución profundidad para dar lugar-separable. Se puede utilizar una forma de onda de audio como entrada y hacer predicciones independientes para cada uno de los 521 eventos de audio del AudioSet corpus.

Internamente, el modelo extrae "fotogramas" de la señal de audio y procesa lotes de estos fotogramas. Esta versión del modelo utiliza fotogramas de 0,96 segundos de duración y extrae un fotograma cada 0,48 segundos.

El modelo acepta un 1-D float32 array Tensor o NumPy que contiene una forma de onda de longitud arbitraria, representado como un solo canal (mono) muestras de 16 kHz en el intervalo [-1.0, +1.0] . Este tutorial contiene código para ayudarlo a convertir archivos WAV al formato compatible.

El modelo devuelve 3 salidas, incluyendo las puntuaciones de clase, inclusiones (que va a utilizar para la transferencia del aprendizaje), y el registro de mel espectrograma . Puede encontrar más detalles aquí .

Un uso específico de YAMNet es como extractor de características de alto nivel: la salida de incrustación de 1024 dimensiones. Que va a utilizar las características de entrada del modelo base (YAMNet) y alimentarlos en su modelo más superficial que consiste en un escondido tf.keras.layers.Dense capa. A continuación, se le entrenar a la red en una pequeña cantidad de datos para la clasificación de audio sin necesidad de una gran cantidad de datos etiquetados y formación de extremo a extremo. (Esto es similar a la transferencia de aprendizaje para la clasificación de imágenes con TensorFlow concentrador para obtener más información.)

Primero, probará el modelo y verá los resultados de la clasificación de audio. Luego, construirá la canalización de preprocesamiento de datos.

Cargando YAMNet desde TensorFlow Hub

Usted va a utilizar un YAMNet pre-formados a partir Tensorflow Hub para extraer las incrustaciones de los archivos de sonido.

Carga de un modelo de TensorFlow Hub es sencillo: elegir el modelo, copiar su URL, y el uso de la load la función.

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

Con el modelo de carga, se puede seguir el YAMNet uso básico tutorial y descargar un archivo WAV ejemplo para ejecutar la inferencia.

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

Necesitará una función para cargar archivos de audio, que también se utilizará más adelante cuando trabaje con los datos de entrenamiento. (Más información sobre la lectura de archivos de audio y sus etiquetas de reconocimiento de audio simple .

# 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

Cargar el mapeo de la clase

Es importante cargar los nombres de las clases que YAMNet puede reconocer. El archivo de asignación está presente en yamnet_model.class_map_path() en el 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
...

Ejecutar inferencia

YAMNet proporciona puntajes de clase a nivel de marco (es decir, 521 puntajes para cada marco). Para determinar las predicciones a nivel de clip, las puntuaciones se pueden agregar por clase en los fotogramas (p. Ej., Utilizando la agregación media o máxima). Esto se hace a continuación por scores_np.mean(axis=0) . Finalmente, para encontrar la clase con mejor puntuación a nivel de clip, se toma el máximo de las 521 puntuaciones agregadas.

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)

Conjunto de datos ESC-50

El CES-50 conjunto de datos ( Piczak, 2015 ) es una colección marcada de 2.000 grabaciones de audio de cinco segundos de largo ambientales. El conjunto de datos consta de 50 clases, con 40 ejemplos por clase.

Descargue el conjunto de datos y extráigalo.

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

Explore los datos

Los metadatos para cada archivo se especifica en el archivo csv en ./datasets/ESC-50-master/meta/esc50.csv

y todos los archivos de audio están en ./datasets/ESC-50-master/audio/

Va a crear un pandas DataFrame con la asignación y el uso que para tener una visión más clara de los datos.

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

Filtrar los datos

Ahora que los datos se almacenan en la DataFrame , se aplican algunas transformaciones:

  • Filtrar las filas y utilizar sólo el seleccionado clases - dog y cat . Si desea utilizar otras clases, aquí es donde puede elegirlas.
  • Modifique el nombre del archivo para que tenga la ruta completa. Esto facilitará la carga más adelante.
  • Cambie los objetivos para que estén dentro de un rango específico. En este ejemplo, dog se mantendrá en 0 , pero cat se convertirá en 1 en lugar de su valor original de 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)

Cargue los archivos de audio y recupere las incrustaciones

Aquí vamos a aplicar la load_wav_16k_mono y preparar los datos para el modelo WAV.

Al extraer las incrustaciones partir de los datos WAV, se obtiene una matriz de forma (N, 1024) , donde N es el número de fotogramas que se encuentra YAMNet (uno por cada 0,48 segundos de audio).

Su modelo utilizará cada cuadro como una entrada. Por lo tanto, debe crear una nueva columna que tenga un marco por fila. También es necesario ampliar las etiquetas y el fold columna para reflejar estos nuevos adecuada filas.

La expandido fold columna mantiene los valores originales. No puede mezclar fotogramas porque, al realizar las divisiones, podría terminar teniendo partes del mismo audio en diferentes divisiones, lo que haría que sus pasos de validación y prueba fueran menos efectivos.

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

Divide los datos

Utilizará el fold columna para dividir el conjunto de datos en tren, de validación y de prueba.

ESC-50 está dispuesta en cinco uniformemente de tamaño de validación cruzada fold s, de manera que los clips de la misma fuente original son siempre en el mismo fold - encontrar más información en el ESC: conjunto de datos para la clasificación de sonido ambiental papel.

El último paso consiste en eliminar el fold columna del conjunto de datos, ya que no va a utilizarlo durante el entrenamiento.

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 tu modelo

¡Hiciste la mayor parte del trabajo! A continuación, definir una muy simple secuencial modelo con una capa oculta y dos salidas para reconocer los gatos y perros de sonidos.

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

Vamos a ejecutar la evaluate método sobre los datos de prueba para estar seguro de que no hay sobreajuste.

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

¡Lo hiciste!

Prueba tu modelo

A continuación, pruebe su modelo en la incrustación de la prueba anterior utilizando 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

Guarde un modelo que pueda tomar directamente un archivo WAV como entrada

Su modelo funciona cuando le da las incrustaciones como entrada.

En un escenario del mundo real, querrá usar datos de audio como entrada directa.

Para hacer eso, combinará YAMNet con su modelo en un solo modelo que puede exportar para otras aplicaciones.

Para que sea más fácil de usar resultado del modelo, la capa final será un reduce_mean operación. Cuando utilice este modelo para la publicación (sobre el que aprenderá más adelante en el tutorial), necesitará el nombre de la capa final. Si no define uno, TensorFlow definirá automáticamente uno incremental que dificulta la prueba, ya que seguirá cambiando cada vez que entrene el modelo. Cuando usas una operación de TensorFlow sin procesar, no puedes asignarle un nombre. Para abordar esta cuestión, vamos a crear una capa personalizada que se aplica reduce_mean y lo llaman '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

Cargue su modelo guardado para verificar que funciona como se esperaba.

reloaded_model = tf.saved_model.load(saved_model_path)

Y para la prueba final: dados algunos datos sólidos, ¿su modelo devuelve el resultado correcto?

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

Si desea probar su nuevo modelo en una configuración de publicación, puede usar la firma 'serve_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

(Opcional) Algunas pruebas más

El modelo está listo.

Vamos a compararlo con YAMNet en el conjunto de datos de prueba.

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)

Próximos pasos

Ha creado un modelo que puede clasificar sonidos de perros o gatos. Con la misma idea y otro conjunto de datos se puede tratar, por ejemplo, la construcción de un identificador acústica de las aves sobre la base de sus cantos.

¡Comparta su proyecto con el equipo de TensorFlow en las redes sociales!