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

Reconnaissance audio simple : reconnaître les mots-clés

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Ce tutoriel montre comment prétraiter les fichiers audio au format WAV et construire et former une base de reconnaissance vocale automatique modèle (ASR) pour reconnaître dix mots différents. Vous utiliserez une partie de l' ensemble de données commandes vocales ( Warden, 2018 ), qui contient de courtes (une seconde ou moins) des clips audio de commandes, telles que « vers le bas », « go », « gauche », « non », " à droite", "stop", "haut" et "oui".

Discours dans le monde réel et de reconnaissance audio systèmes sont complexes. Mais, comme la classification d'images avec l'ensemble de données MNIST , ce tutoriel devrait vous donner une compréhension de base des techniques utilisées .

Installer

Importez les modules et dépendances nécessaires. Notez que vous allez utiliser Seaborn pour la visualisation dans ce tutoriel.

import os
import pathlib

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import models
from IPython import display

# Set the seed value for experiment reproducibility.
seed = 42
tf.random.set_seed(seed)
np.random.seed(seed)

Importer le mini jeu de données des commandes vocales

Pour gagner du temps avec le chargement des données, vous travaillerez avec une version plus petite de l'ensemble de données Speech Commands. L' ensemble de données d' origine se compose de plus de 105 000 fichiers audio dans le format de fichier audio WAV (Waveform) des personnes disant 35 mots différents. Ces données ont été collectées par Google et publiées sous une licence CC BY.

Télécharger et extraire le mini_speech_commands.zip fichier contenant les plus petits ensembles de données avec les commandes vocales tf.keras.utils.get_file :

DATASET_PATH = 'data/mini_speech_commands'

data_dir = pathlib.Path(DATASET_PATH)
if not data_dir.exists():
  tf.keras.utils.get_file(
      'mini_speech_commands.zip',
      origin="http://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip",
      extract=True,
      cache_dir='.', cache_subdir='data')
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip
182083584/182082353 [==============================] - 6s 0us/step
182091776/182082353 [==============================] - 6s 0us/step

Clips audio sont stockés dans huit dossiers correspondant à chaque commande vocale de l'ensemble de données: no , yes , down le go left up right stop down , go , left , up , right et stop :

commands = np.array(tf.io.gfile.listdir(str(data_dir)))
commands = commands[commands != 'README.md']
print('Commands:', commands)
Commands: ['yes' 'go' 'left' 'right' 'up' 'down' 'stop' 'no']

Extraire les clips audio dans une liste appelée filenames de filenames , et mélangez - le:

filenames = tf.io.gfile.glob(str(data_dir) + '/*/*')
filenames = tf.random.shuffle(filenames)
num_samples = len(filenames)
print('Number of total examples:', num_samples)
print('Number of examples per label:',
      len(tf.io.gfile.listdir(str(data_dir/commands[0]))))
print('Example file tensor:', filenames[0])
Number of total examples: 8000
Number of examples per label: 1000
Example file tensor: tf.Tensor(b'data/mini_speech_commands/up/05739450_nohash_2.wav', shape=(), dtype=string)

Fractionnés les filenames de filenames dans la formation, la validation et de test en utilisant un rapport 80:10:10, respectivement:

train_files = filenames[:6400]
val_files = filenames[6400: 6400 + 800]
test_files = filenames[-800:]

print('Training set size', len(train_files))
print('Validation set size', len(val_files))
print('Test set size', len(test_files))
Training set size 6400
Validation set size 800
Test set size 800

Lire les fichiers audio et leurs étiquettes

Dans cette section, vous allez prétraiter l'ensemble de données, en créant des tenseurs décodés pour les formes d'onde et les étiquettes correspondantes. Noter que:

  • Chaque fichier WAV contient des données de séries temporelles avec un nombre défini d'échantillons par seconde.
  • Chaque échantillon représente l' amplitude du signal audio à ce moment spécifique.
  • Dans un 16 bits du système, comme les fichiers WAV dans le mini - jeu de données commandes vocales, les valeurs d'amplitude vont de -32768 à 32767.
  • Le taux d'échantillonnage pour cet ensemble de données est 16kHz.

La forme du tenseur renvoyé par tf.audio.decode_wav est [samples, channels] les channels 1 2 [samples, channels] , où les channels est de 1 pour mono ou 2 pour la stéréo. Le jeu de données mini Speech Commands ne contient que des enregistrements mono.

test_file = tf.io.read_file(DATASET_PATH+'/down/0a9f9af7_nohash_0.wav')
test_audio, _ = tf.audio.decode_wav(contents=test_file)
test_audio.shape
TensorShape([13654, 1])

Maintenant, définissons une fonction qui prétraite les fichiers audio WAV bruts de l'ensemble de données en tenseurs audio :

def decode_audio(audio_binary):
  # Decode WAV-encoded audio files to `float32` tensors, normalized
  # to the [-1.0, 1.0] range. Return `float32` audio and a sample rate.
  audio, _ = tf.audio.decode_wav(contents=audio_binary)
  # Since all the data is single channel (mono), drop the `channels`
  # axis from the array.
  return tf.squeeze(audio, axis=-1)

Définissez une fonction qui crée des étiquettes à l'aide des répertoires parents pour chaque fichier :

  • Diviser les chemins de fichiers dans tf.RaggedTensor de (les tenseurs avec des dimensions avec Ragged tranches qui peuvent avoir des longueurs différentes).
def get_label(file_path):
  parts = tf.strings.split(
      input=file_path,
      sep=os.path.sep)
  # Note: You'll use indexing here instead of tuple unpacking to enable this
  # to work in a TensorFlow graph.
  return parts[-2]

Définir une autre aide Fonction- get_waveform_and_label -que met tous ensemble:

  • L'entrée est le nom du fichier audio WAV.
  • La sortie est un tuple contenant les tenseurs audio et d'étiquettes prêts pour l'apprentissage supervisé.
def get_waveform_and_label(file_path):
  label = get_label(file_path)
  audio_binary = tf.io.read_file(file_path)
  waveform = decode_audio(audio_binary)
  return waveform, label

Construisez l'ensemble d'apprentissage pour extraire les paires d'étiquettes audio :

Vous construirez les ensembles de validation et de test en utilisant une procédure similaire plus tard.

AUTOTUNE = tf.data.AUTOTUNE

files_ds = tf.data.Dataset.from_tensor_slices(train_files)

waveform_ds = files_ds.map(
    map_func=get_waveform_and_label,
    num_parallel_calls=AUTOTUNE)

Traçons quelques formes d'onde audio :

rows = 3
cols = 3
n = rows * cols
fig, axes = plt.subplots(rows, cols, figsize=(10, 12))

for i, (audio, label) in enumerate(waveform_ds.take(n)):
  r = i // cols
  c = i % cols
  ax = axes[r][c]
  ax.plot(audio.numpy())
  ax.set_yticks(np.arange(-1.2, 1.2, 0.2))
  label = label.numpy().decode('utf-8')
  ax.set_title(label)

plt.show()

png

Convertir des formes d'onde en spectrogrammes

Les formes d'onde de l'ensemble de données sont représentées dans le domaine temporel. Ensuite, vous transformer les formes d' ondes à partir des signaux dans le domaine temporel dans les signaux temps-fréquence domaine en calculant la Fourier de courte durée (TFCT) pour convertir les formes d' ondes comme spectrogrammes , qui montrent des changements de fréquence au fil du temps et peut être représentés sous forme d'images 2D. Vous alimenterez les images du spectrogramme dans votre réseau de neurones pour entraîner le modèle.

Une transformée de Fourier ( tf.signal.fft ) convertit un signal à ses composantes de fréquences, mais perd toute l' information de temps. En comparaison, STFT ( tf.signal.stft ) divise le signal en fenêtres de temps et exécute une transformée de Fourier sur chaque fenêtre, en conservant des informations de temps et de retourner un tenseur 2D que vous pouvez exécuter convolutions standard sur.

Créez une fonction utilitaire pour convertir les formes d'onde en spectrogrammes :

  • Les formes d'onde doivent avoir la même longueur, de sorte que lorsque vous les convertissez en spectrogrammes, les résultats aient des dimensions similaires. Cela peut se faire simplement par zéro padding les clips audio qui sont plus courtes d'une seconde ( en utilisant tf.zeros ).
  • Lorsque vous appelez tf.signal.stft , choisissez les frame_length et frame_step paramètres tels que le carré est presque spectrogramme « image » générée. Pour plus d' informations sur les paramètres de choix STFT, reportez - vous à cette vidéo Coursera sur le traitement du signal audio et STFT.
  • Le STFT produit un tableau de nombres complexes représentant l'amplitude et la phase. Cependant, dans ce tutoriel , vous ne pouvez utiliser l'ampleur, que vous pouvez obtenir en appliquant tf.abs sur la sortie de tf.signal.stft .
def get_spectrogram(waveform):
  # Zero-padding for an audio waveform with less than 16,000 samples.
  input_len = 16000
  waveform = waveform[:input_len]
  zero_padding = tf.zeros(
      [16000] - tf.shape(waveform),
      dtype=tf.float32)
  # Cast the waveform tensors' dtype to float32.
  waveform = tf.cast(waveform, dtype=tf.float32)
  # Concatenate the waveform with `zero_padding`, which ensures all audio
  # clips are of the same length.
  equal_length = tf.concat([waveform, zero_padding], 0)
  # Convert the waveform to a spectrogram via a STFT.
  spectrogram = tf.signal.stft(
      equal_length, frame_length=255, frame_step=128)
  # Obtain the magnitude of the STFT.
  spectrogram = tf.abs(spectrogram)
  # Add a `channels` dimension, so that the spectrogram can be used
  # as image-like input data with convolution layers (which expect
  # shape (`batch_size`, `height`, `width`, `channels`).
  spectrogram = spectrogram[..., tf.newaxis]
  return spectrogram

Ensuite, commencez à explorer les données. Imprimez les formes de la forme d'onde tensorisée d'un exemple et le spectrogramme correspondant, et lisez l'audio d'origine :

for waveform, label in waveform_ds.take(1):
  label = label.numpy().decode('utf-8')
  spectrogram = get_spectrogram(waveform)

print('Label:', label)
print('Waveform shape:', waveform.shape)
print('Spectrogram shape:', spectrogram.shape)
print('Audio playback')
display.display(display.Audio(waveform, rate=16000))
Label: up
Waveform shape: (11606,)
Spectrogram shape: (124, 129, 1)
Audio playback

Maintenant, définissez une fonction pour afficher un spectrogramme :

def plot_spectrogram(spectrogram, ax):
  if len(spectrogram.shape) > 2:
    assert len(spectrogram.shape) == 3
    spectrogram = np.squeeze(spectrogram, axis=-1)
  # Convert the frequencies to log scale and transpose, so that the time is
  # represented on the x-axis (columns).
  # Add an epsilon to avoid taking a log of zero.
  log_spec = np.log(spectrogram.T + np.finfo(float).eps)
  height = log_spec.shape[0]
  width = log_spec.shape[1]
  X = np.linspace(0, np.size(spectrogram), num=width, dtype=int)
  Y = range(height)
  ax.pcolormesh(X, Y, log_spec)

Tracez la forme d'onde de l'exemple au fil du temps et le spectrogramme correspondant (fréquences au fil du temps) :

fig, axes = plt.subplots(2, figsize=(12, 8))
timescale = np.arange(waveform.shape[0])
axes[0].plot(timescale, waveform.numpy())
axes[0].set_title('Waveform')
axes[0].set_xlim([0, 16000])

plot_spectrogram(spectrogram.numpy(), axes[1])
axes[1].set_title('Spectrogram')
plt.show()

png

Maintenant, définissez une fonction qui transforme l'ensemble de données de forme d'onde en spectrogrammes et leurs étiquettes correspondantes en identifiants entiers :

def get_spectrogram_and_label_id(audio, label):
  spectrogram = get_spectrogram(audio)
  label_id = tf.argmax(label == commands)
  return spectrogram, label_id

Carte get_spectrogram_and_label_id sur les éléments de l'ensemble de données avec Dataset.map :

spectrogram_ds = waveform_ds.map(
  map_func=get_spectrogram_and_label_id,
  num_parallel_calls=AUTOTUNE)

Examinez les spectrogrammes pour différents exemples de l'ensemble de données :

rows = 3
cols = 3
n = rows*cols
fig, axes = plt.subplots(rows, cols, figsize=(10, 10))

for i, (spectrogram, label_id) in enumerate(spectrogram_ds.take(n)):
  r = i // cols
  c = i % cols
  ax = axes[r][c]
  plot_spectrogram(spectrogram.numpy(), ax)
  ax.set_title(commands[label_id.numpy()])
  ax.axis('off')

plt.show()

png

Construire et entraîner le modèle

Répétez le prétraitement de l'ensemble d'apprentissage sur les ensembles de validation et de test :

def preprocess_dataset(files):
  files_ds = tf.data.Dataset.from_tensor_slices(files)
  output_ds = files_ds.map(
      map_func=get_waveform_and_label,
      num_parallel_calls=AUTOTUNE)
  output_ds = output_ds.map(
      map_func=get_spectrogram_and_label_id,
      num_parallel_calls=AUTOTUNE)
  return output_ds
train_ds = spectrogram_ds
val_ds = preprocess_dataset(val_files)
test_ds = preprocess_dataset(test_files)

Regroupez les ensembles d'entraînement et de validation pour l'entraînement du modèle :

batch_size = 64
train_ds = train_ds.batch(batch_size)
val_ds = val_ds.batch(batch_size)

Ajouter Dataset.cache et Dataset.prefetch opérations pour réduire la latence de lecture alors que la formation du modèle:

train_ds = train_ds.cache().prefetch(AUTOTUNE)
val_ds = val_ds.cache().prefetch(AUTOTUNE)

Pour le modèle, vous utiliserez un simple réseau de neurones convolutifs (CNN), puisque vous avez transformé les fichiers audio en images de spectrogramme.

Votre tf.keras.Sequential modèle utilisera les couches de pré - traitement KERAS suivantes:

Pour la Normalization couche, son adapt méthode devrait d' abord être appelé sur les données de formation afin de calculer des statistiques globales (qui est, la moyenne et l'écart - type).

for spectrogram, _ in spectrogram_ds.take(1):
  input_shape = spectrogram.shape
print('Input shape:', input_shape)
num_labels = len(commands)

# Instantiate the `tf.keras.layers.Normalization` layer.
norm_layer = layers.Normalization()
# Fit the state of the layer to the spectrograms
# with `Normalization.adapt`.
norm_layer.adapt(data=spectrogram_ds.map(map_func=lambda spec, label: spec))

model = models.Sequential([
    layers.Input(shape=input_shape),
    # Downsample the input.
    layers.Resizing(32, 32),
    # Normalize.
    norm_layer,
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(64, 3, activation='relu'),
    layers.MaxPooling2D(),
    layers.Dropout(0.25),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(num_labels),
])

model.summary()
Input shape: (124, 129, 1)
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 resizing (Resizing)         (None, 32, 32, 1)         0         
                                                                 
 normalization (Normalizatio  (None, 32, 32, 1)        3         
 n)                                                              
                                                                 
 conv2d (Conv2D)             (None, 30, 30, 32)        320       
                                                                 
 conv2d_1 (Conv2D)           (None, 28, 28, 64)        18496     
                                                                 
 max_pooling2d (MaxPooling2D  (None, 14, 14, 64)       0         
 )                                                               
                                                                 
 dropout (Dropout)           (None, 14, 14, 64)        0         
                                                                 
 flatten (Flatten)           (None, 12544)             0         
                                                                 
 dense (Dense)               (None, 128)               1605760   
                                                                 
 dropout_1 (Dropout)         (None, 128)               0         
                                                                 
 dense_1 (Dense)             (None, 8)                 1032      
                                                                 
=================================================================
Total params: 1,625,611
Trainable params: 1,625,608
Non-trainable params: 3
_________________________________________________________________

Configurez le modèle Keras avec l'optimiseur Adam et la perte d'entropie croisée :

model.compile(
    optimizer=tf.keras.optimizers.Adam(),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'],
)

Entraînez le modèle sur 10 époques à des fins de démonstration :

EPOCHS = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=EPOCHS,
    callbacks=tf.keras.callbacks.EarlyStopping(verbose=1, patience=2),
)
Epoch 1/10
100/100 [==============================] - 13s 40ms/step - loss: 1.7404 - accuracy: 0.3823 - val_loss: 1.3372 - val_accuracy: 0.5550
Epoch 2/10
100/100 [==============================] - 0s 4ms/step - loss: 1.1776 - accuracy: 0.5903 - val_loss: 0.9747 - val_accuracy: 0.6600
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.9027 - accuracy: 0.6778 - val_loss: 0.8096 - val_accuracy: 0.7150
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7647 - accuracy: 0.7316 - val_loss: 0.7325 - val_accuracy: 0.7625
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6492 - accuracy: 0.7658 - val_loss: 0.6697 - val_accuracy: 0.7688
Epoch 6/10
100/100 [==============================] - 0s 5ms/step - loss: 0.5796 - accuracy: 0.7937 - val_loss: 0.6108 - val_accuracy: 0.7875
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5261 - accuracy: 0.8117 - val_loss: 0.5708 - val_accuracy: 0.8087
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4747 - accuracy: 0.8267 - val_loss: 0.5429 - val_accuracy: 0.8163
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4427 - accuracy: 0.8428 - val_loss: 0.5379 - val_accuracy: 0.8175
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.3927 - accuracy: 0.8637 - val_loss: 0.5320 - val_accuracy: 0.8213

Traçons les courbes de perte d'entraînement et de validation pour vérifier comment votre modèle s'est amélioré pendant l'entraînement :

metrics = history.history
plt.plot(history.epoch, metrics['loss'], metrics['val_loss'])
plt.legend(['loss', 'val_loss'])
plt.show()

png

Évaluer les performances du modèle

Exécutez le modèle sur l'ensemble de test et vérifiez les performances du modèle :

test_audio = []
test_labels = []

for audio, label in test_ds:
  test_audio.append(audio.numpy())
  test_labels.append(label.numpy())

test_audio = np.array(test_audio)
test_labels = np.array(test_labels)
y_pred = np.argmax(model.predict(test_audio), axis=1)
y_true = test_labels

test_acc = sum(y_pred == y_true) / len(y_true)
print(f'Test set accuracy: {test_acc:.0%}')
Test set accuracy: 84%

Afficher une matrice de confusion

Utilisez une matrice de confusion pour vérifier à quel point le modèle ne classer chacune des commandes dans l'ensemble de test:

confusion_mtx = tf.math.confusion_matrix(y_true, y_pred)
plt.figure(figsize=(10, 8))
sns.heatmap(confusion_mtx,
            xticklabels=commands,
            yticklabels=commands,
            annot=True, fmt='g')
plt.xlabel('Prediction')
plt.ylabel('Label')
plt.show()

png

Exécuter l'inférence sur un fichier audio

Enfin, vérifiez la sortie de prédiction du modèle à l'aide d'un fichier audio d'entrée de quelqu'un disant « non ». Quelle est la performance de votre modèle ?

sample_file = data_dir/'no/01bb6a2a_nohash_0.wav'

sample_ds = preprocess_dataset([str(sample_file)])

for spectrogram, label in sample_ds.batch(1):
  prediction = model(spectrogram)
  plt.bar(commands, tf.nn.softmax(prediction[0]))
  plt.title(f'Predictions for "{commands[label[0]]}"')
  plt.show()

png

Comme le suggère la sortie, votre modèle devrait avoir reconnu la commande audio comme "non".

Prochaines étapes

Ce didacticiel a montré comment effectuer une classification audio/reconnaissance vocale automatique simple à l'aide d'un réseau de neurones convolutifs avec TensorFlow et Python. Pour en savoir plus, consultez les ressources suivantes :