Aide à protéger la Grande barrière de corail avec tensorflow sur Kaggle Rejoignez Défi

Transférer l'apprentissage avec YAMNet pour la classification des bruits environnementaux

Voir sur TensorFlow.org Exécuter dans Google Colab Voir sur GitHub Télécharger le cahier Voir le modèle TF Hub

YAMNet est un réseau de neurones profond pré-formé qui peut prédire les événements audio de 521 cours , comme le rire, les aboiements, ou une sirène.

Dans ce tutoriel, vous apprendrez à :

  • Chargez et utilisez le modèle YAMNet pour l'inférence.
  • Construisez un nouveau modèle en utilisant les intégrations YAMNet pour classer les sons de chat et de chien.
  • Évaluez et exportez votre modèle.

Importer TensorFlow et d'autres bibliothèques

Commencez par installer tensorflow E / S , ce qui rendra plus facile pour vous de charger des fichiers audio de disque.

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

À propos de YAMNet

YAMNet est un réseau de neurones pré-formé qui utilise la MobileNetV1 architecture convolution séparable sens de la profondeur. Il peut utiliser une forme d' onde audio en entrée et faire des prévisions indépendantes pour chacun des 521 événements audio du AudioSet corpus.

En interne, le modèle extrait des "trames" du signal audio et traite des lots de ces trames. Cette version du modèle utilise des trames d'une durée de 0,96 seconde et extrait une trame toutes les 0,48 seconde.

Le modèle accepte un 1-D float32 réseau Tensor ou NumPy contenant une forme d' onde d' une longueur arbitraire, représentée comme un seul canal (mono) des échantillons de 16 kHz dans l'intervalle [-1.0, +1.0] . Ce didacticiel contient du code pour vous aider à convertir les fichiers WAV dans le format pris en charge.

Le modèle renvoie 3 sorties, y compris les scores de classe, incorporations (que vous utiliserez pour l' apprentissage de transfert), et le mel journal spectrogramme . Vous pouvez trouver plus d' informations ici .

Une utilisation spécifique de YAMNet est en tant qu'extracteur de caractéristiques de haut niveau - la sortie d'intégration à 1 024 dimensions. Vous utiliserez la base des caractéristiques d'entrée du modèle (YAMNet) et les intégrer dans votre modèle moins profond consistant en un caché tf.keras.layers.Dense couche. Ensuite, vous formerez le réseau sur une petite quantité de données pour la classification audio sans nécessiter beaucoup de données étiquetées et de bout en bout la formation. (Ceci est similaire à transférer l' apprentissage pour la classification d'images avec tensorflow Hub pour plus d' informations.)

Tout d'abord, vous testerez le modèle et verrez les résultats de la classification audio. Vous construirez ensuite le pipeline de pré-traitement des données.

Chargement de YAMNet depuis TensorFlow Hub

Vous allez utiliser un YAMNet pré-formé à partir tensorflow Hub pour extraire les incorporations des fichiers sonores.

Chargement d' un modèle de tensorflow Hub est simple: choisissez le modèle, copiez son URL, et la load fonction.

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

Avec le modèle chargé, vous pouvez suivre le tutoriel d'utilisation de base YAMNet et télécharger un fichier WAV exemple pour exécuter l'inférence.

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

Vous aurez besoin d'une fonction pour charger les fichiers audio, qui sera également utilisée plus tard lorsque vous travaillerez avec les données d'entraînement. ( En savoir plus sur la lecture des fichiers audio et leurs étiquettes en reconnaissance 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

Charger le mappage de classe

Il est important de charger les noms de classe que YAMNet est capable de reconnaître. Le fichier de mappage est présent à yamnet_model.class_map_path() au format 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
...

Exécuter l'inférence

YAMNet fournit des scores de classe au niveau de la trame (c'est-à-dire 521 scores pour chaque trame). Afin de déterminer les prédictions au niveau du clip, les scores peuvent être agrégés par classe à travers les images (par exemple, en utilisant l'agrégation moyenne ou maximale). Cela se fait par en dessous scores_np.mean(axis=0) . Enfin, pour trouver la classe la mieux notée au niveau du clip, vous prenez le maximum des 521 scores agrégés.

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)

Jeu de données ESC-50

L' ensemble de données ESC-50 ( Piczak, 2015 ) est une collection marquée de 2000 à cinq secondes d' enregistrements audio longue environnement. L'ensemble de données se compose de 50 classes, avec 40 exemples par classe.

Téléchargez le jeu de données et extrayez-le.

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

Explorer les données

Les métadonnées pour chaque fichier est spécifié dans le fichier de format CSV à ./datasets/ESC-50-master/meta/esc50.csv

et tous les fichiers audio sont en ./datasets/ESC-50-master/audio/

Vous allez créer un pandas géants DataFrame avec la cartographie et l' utiliser pour avoir une vision plus claire des données.

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

Filtrer les données

Maintenant que les données sont stockées dans la DataFrame de DataFrame , appliquer des transformations:

  • Filtrer les lignes et utiliser uniquement les classes sélectionnées - dog et cat . Si vous souhaitez utiliser d'autres classes, c'est ici que vous pouvez les choisir.
  • Modifiez le nom du fichier pour avoir le chemin complet. Cela facilitera le chargement plus tard.
  • Modifiez les cibles pour qu'elles se situent dans une plage spécifique. Dans cet exemple, le dog restera à 0 , mais le cat deviendra 1 au lieu de sa valeur initiale 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)

Chargez les fichiers audio et récupérez les intégrations

Ici , vous appliquez le load_wav_16k_mono et préparer les données WAV pour le modèle.

Lors de l' extraction plongement à partir des données WAV, vous obtenez un tableau de forme (N, 1024)N est le nombre de trames qui YAMNet trouvé (un pour 0,48 secondes de l' audio).

Votre modèle utilisera chaque image comme une entrée. Par conséquent, vous devez créer une nouvelle colonne comportant un cadre par ligne. Vous devez également développer les étiquettes et la fold colonne appropriée tenir compte de ces nouvelles lignes.

La élargi fold la colonne conserve les valeurs d' origine. Vous ne pouvez pas mélanger les images car, lors de l'exécution des divisions, vous pourriez vous retrouver avec des parties du même audio sur différentes divisions, ce qui rendrait vos étapes de validation et de test moins efficaces.

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

Diviser les données

Vous utiliserez la fold la colonne de diviser les données en ensembles de train, de validation et de test.

ESC-50 est disposé en cinq validation croisée uniforme taille fold s, de sorte que des clips de la même source d' origine sont toujours dans le même fold - pour en savoir plus dans le CES: ensemble_données Classification son environnement papier.

La dernière étape consiste à retirer le fold colonne de l'ensemble de données puisque vous ne comptez pas utiliser pendant la formation.

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)

Créez votre modèle

Tu as fait le gros du boulot ! Ensuite, définir un très simple séquentiel modèle avec une couche cachée et deux sorties à reconnaître les chats et les chiens de sons.

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

Lançons l' evaluate méthode sur les données de test juste pour être sûr qu'il n'y a pas 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

Tu l'as fait!

Testez votre modèle

Ensuite, essayez votre modèle sur l'intégration du test précédent en utilisant uniquement 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

Enregistrez un modèle qui peut directement prendre un fichier WAV en entrée

Votre modèle fonctionne lorsque vous lui donnez les plongements en entrée.

Dans un scénario réel, vous souhaiterez utiliser des données audio comme entrée directe.

Pour ce faire, vous combinerez YAMNet avec votre modèle en un seul modèle que vous pourrez exporter pour d'autres applications.

Pour le rendre plus facile à utiliser le résultat du modèle, la couche finale sera une reduce_mean opération. Lorsque vous utilisez ce modèle pour le service (que vous découvrirez plus tard dans le didacticiel), vous aurez besoin du nom de la couche finale. Si vous n'en définissez pas un, TensorFlow en définira automatiquement un incrémentiel qui le rendra difficile à tester, car il continuera à changer à chaque fois que vous entraînerez le modèle. Lorsque vous utilisez une opération TensorFlow brute, vous ne pouvez pas lui attribuer de nom. Pour résoudre ce problème, vous allez créer une couche personnalisée qui applique reduce_mean et appelez '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

Chargez votre modèle enregistré pour vérifier qu'il fonctionne comme prévu.

reloaded_model = tf.saved_model.load(saved_model_path)

Et pour le test final : étant donné quelques données sonores, votre modèle renvoie-t-il le bon résultat ?

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 vous souhaitez essayer votre nouveau modèle sur une configuration de diffusion, vous pouvez utiliser la signature « 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

(Facultatif) Quelques tests supplémentaires

Le modèle est prêt.

Comparons-le à YAMNet sur l'ensemble de données de 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)

Prochaines étapes

Vous avez créé un modèle qui permet de classer les sons des chiens ou des chats. Avec la même idée et un ensemble de données différent , vous pouvez, par exemple, la construction d' un identifiant acoustique des oiseaux en fonction de leur chant.

Partagez votre projet avec l'équipe TensorFlow sur les réseaux sociaux !