¡Confirme su asistencia a su evento local de TensorFlow Everywhere hoy!
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Reconocimiento de audio simple: reconocimiento de palabras clave

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este tutorial le mostrará cómo construir una red básica de reconocimiento de voz que reconoce diez palabras diferentes. Es importante saber que los sistemas reales de reconocimiento de voz y audio son mucho más complejos, pero al igual que MNIST para imágenes, debería brindarle una comprensión básica de las técnicas involucradas. Una vez que haya completado este tutorial, tendrá un modelo que intenta clasificar un clip de audio de un segundo como "abajo", "ir", "izquierda", "no", "derecha", "detener", "arriba " y si".

Preparar

Importe los módulos y las dependencias necesarios.

import os
import pathlib

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

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


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

Importar el conjunto de datos de Speech Commands

Escribirás un script para descargar una parte del conjunto de datos Speech Commands . El conjunto de datos original consta de más de 105.000 archivos de audio WAV de personas que dicen treinta palabras diferentes. Estos datos fueron recopilados por Google y publicados bajo una licencia CC BY, y usted puede ayudar a mejorarlos contribuyendo con cinco minutos de su propia voz .

Utilizará una parte del conjunto de datos para ahorrar tiempo con la carga de datos. Extraiga el mini_speech_commands.zip y cárguelo usando la API tf.data .

data_dir = pathlib.Path('data/mini_speech_commands')
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 [==============================] - 2s 0us/step

Verifique las estadísticas básicas sobre el conjunto de datos.

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

Extraiga los archivos de audio en una lista y mezcle.

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/yes/fde2dee7_nohash_0.wav', shape=(), dtype=string)

Divida los archivos en conjuntos de entrenamiento, validación y prueba utilizando una proporción de 80:10:10, respectivamente.

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

Leer archivos de audio y sus etiquetas

El archivo de audio se leerá inicialmente como un archivo binario, que querrá convertir en un tensor numérico.

Para cargar un archivo de audio, usará tf.audio.decode_wav , que devuelve el audio codificado en WAV como un tensor y la frecuencia de muestreo.

Un archivo WAV contiene datos de series de tiempo con un número determinado de muestras por segundo. Cada muestra representa la amplitud de la señal de audio en ese momento específico. En un sistema de 16 bits, como los archivos en mini_speech_commands , los valores oscilan entre -32768 y 32767. La frecuencia de muestreo para este conjunto de datos es de 16 kHz. Tenga en cuenta que tf.audio.decode_wav normalizará los valores en el rango [-1.0, 1.0].

def decode_audio(audio_binary):
  audio, _ = tf.audio.decode_wav(audio_binary)
  return tf.squeeze(audio, axis=-1)

La etiqueta de cada archivo WAV es su directorio principal.

def get_label(file_path):
  parts = tf.strings.split(file_path, 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]

Definamos un método que tomará el nombre del archivo WAV y generará una tupla que contenga el audio y las etiquetas para el entrenamiento supervisado.

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

Ahora aplicará process_path para construir su conjunto de entrenamiento para extraer los pares de etiquetas de audio y verificar los resultados. Construirá los conjuntos de validación y prueba mediante un procedimiento similar más adelante.

AUTOTUNE = tf.data.AUTOTUNE
files_ds = tf.data.Dataset.from_tensor_slices(train_files)
waveform_ds = files_ds.map(get_waveform_and_label, num_parallel_calls=AUTOTUNE)

Examinemos algunas formas de onda de audio con sus etiquetas correspondientes.

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

Espectrograma

Convertirá la forma de onda en un espectrograma, que muestra los cambios de frecuencia a lo largo del tiempo y se puede representar como una imagen 2D. Esto se puede hacer aplicando la transformada de Fourier de corta duración (STFT) para convertir el audio en el dominio de tiempo-frecuencia.

Una transformada de Fourier ( tf.signal.fft ) convierte una señal a sus frecuencias componentes, pero pierde toda la información de tiempo. El STFT ( tf.signal.stft ) divide la señal en ventanas de tiempo y ejecuta una transformada de Fourier en cada ventana, conservando algo de información de tiempo y devolviendo un tensor 2D en el que puede ejecutar convoluciones estándar.

STFT produce una matriz de números complejos que representan magnitud y fase. Sin embargo, solo necesitará la magnitud para este tutorial, que puede derivarse aplicando tf.abs en la salida de tf.signal.stft .

Elija los parámetros frame_length y frame_step modo que la "imagen" del espectrograma generada sea casi cuadrada. Para obtener más información sobre la elección de los parámetros STFT, puede consultar este video sobre el procesamiento de la señal de audio.

También desea que las formas de onda tengan la misma longitud, de modo que cuando la convierta en una imagen de espectrograma, los resultados tengan dimensiones similares. Esto se puede hacer simplemente rellenando con cero los clips de audio que tengan una duración inferior a un segundo.

def get_spectrogram(waveform):
  # Padding for files with less than 16000 samples
  zero_padding = tf.zeros([16000] - tf.shape(waveform), dtype=tf.float32)

  # Concatenate audio with padding so that all audio clips will be of the 
  # same length
  waveform = tf.cast(waveform, tf.float32)
  equal_length = tf.concat([waveform, zero_padding], 0)
  spectrogram = tf.signal.stft(
      equal_length, frame_length=255, frame_step=128)

  spectrogram = tf.abs(spectrogram)

  return spectrogram

A continuación, explorará los datos. Compare la forma de onda, el espectrograma y el audio real de un ejemplo del conjunto de datos.

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: yes
Waveform shape: (13375,)
Spectrogram shape: (124, 129)
Audio playback

def plot_spectrogram(spectrogram, ax):
  # Convert to frequencies to log scale and transpose so that the time is
  # represented in the x-axis (columns).
  log_spec = np.log(spectrogram.T)
  height = log_spec.shape[0]
  X = np.arange(16000, step=height + 1)
  Y = range(height)
  ax.pcolormesh(X, Y, log_spec)


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()
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log
  after removing the cwd from sys.path.
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:8: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3.  Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading'].  This will become an error two minor releases later.
  

png

Ahora transforme el conjunto de datos de forma de onda para tener imágenes de espectrograma y sus etiquetas correspondientes como ID enteros.

def get_spectrogram_and_label_id(audio, label):
  spectrogram = get_spectrogram(audio)
  spectrogram = tf.expand_dims(spectrogram, -1)
  label_id = tf.argmax(label == commands)
  return spectrogram, label_id
spectrogram_ds = waveform_ds.map(
    get_spectrogram_and_label_id, num_parallel_calls=AUTOTUNE)

Examine las "imágenes" del espectrograma en busca de diferentes muestras del conjunto de datos.

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(np.squeeze(spectrogram.numpy()), ax)
  ax.set_title(commands[label_id.numpy()])
  ax.axis('off')

plt.show()
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log
  after removing the cwd from sys.path.
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:8: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3.  Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading'].  This will become an error two minor releases later.
  

png

Construye y entrena el modelo

Ahora puede construir y entrenar su modelo. Pero antes de hacer eso, deberá repetir el procesamiento previo del conjunto de entrenamiento en los conjuntos de validación y prueba.

def preprocess_dataset(files):
  files_ds = tf.data.Dataset.from_tensor_slices(files)
  output_ds = files_ds.map(get_waveform_and_label, num_parallel_calls=AUTOTUNE)
  output_ds = output_ds.map(
      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)

Agrupe los conjuntos de capacitación y validación para la capacitación de modelos.

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

Agregue operaciones de cache() y prefetch() conjuntos de datos para reducir la latencia de lectura mientras entrena el modelo.

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

Para el modelo, utilizará una red neuronal convolucional simple (CNN), ya que ha transformado los archivos de audio en imágenes de espectrograma. El modelo también tiene las siguientes capas de preprocesamiento adicionales:

  • Una capa de cambio de Resizing para reducir la resolución de la entrada y permitir que el modelo se entrene más rápido.
  • A Normalization capa para normalizar cada píxel de la imagen basada en su desviación media y estándar.

Para la capa de Normalization , su método de adapt primero necesitaría ser llamado en los datos de entrenamiento para calcular estadísticas agregadas (es decir, desviación estándar y media).

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

norm_layer = preprocessing.Normalization()
norm_layer.adapt(spectrogram_ds.map(lambda x, _: x))

model = models.Sequential([
    layers.Input(shape=input_shape),
    preprocessing.Resizing(32, 32), 
    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 (Normalization (None, 32, 32, 1)         3         
_________________________________________________________________
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
_________________________________________________________________

model.compile(
    optimizer=tf.keras.optimizers.Adam(),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'],
)
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 [==============================] - 16s 47ms/step - loss: 1.9142 - accuracy: 0.2855 - val_loss: 1.2969 - val_accuracy: 0.5938
Epoch 2/10
100/100 [==============================] - 0s 4ms/step - loss: 1.2597 - accuracy: 0.5581 - val_loss: 0.8918 - val_accuracy: 0.7237
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.9525 - accuracy: 0.6647 - val_loss: 0.7742 - val_accuracy: 0.7337
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7793 - accuracy: 0.7288 - val_loss: 0.6514 - val_accuracy: 0.7850
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6715 - accuracy: 0.7639 - val_loss: 0.5986 - val_accuracy: 0.8000
Epoch 6/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5647 - accuracy: 0.7968 - val_loss: 0.5901 - val_accuracy: 0.8075
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5122 - accuracy: 0.8093 - val_loss: 0.5526 - val_accuracy: 0.8087
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4780 - accuracy: 0.8298 - val_loss: 0.5166 - val_accuracy: 0.8163
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4216 - accuracy: 0.8544 - val_loss: 0.4806 - val_accuracy: 0.8438
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.3976 - accuracy: 0.8510 - val_loss: 0.4848 - val_accuracy: 0.8388

Revisemos las curvas de pérdida de entrenamiento y validación para ver cómo ha mejorado su modelo durante el entrenamiento.

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

png

Evaluar el rendimiento del equipo de prueba

Ejecutemos el modelo en el conjunto de prueba y verifiquemos el rendimiento.

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%

Mostrar una matriz de confusión

Una matriz de confusión es útil para ver qué tan bien funcionó el modelo en cada uno de los comandos del conjunto de prueba.

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

Ejecutar inferencia en un archivo de audio

Finalmente, verifique la salida de predicción del modelo usando un archivo de audio de entrada de alguien que diga "no". ¿Qué tan bien funciona su modelo?

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

Puede ver que su modelo reconoció muy claramente el comando de audio como "no".

Próximos pasos

Este tutorial mostró cómo se puede hacer una clasificación de audio simple mediante una red neuronal convolucional con TensorFlow y Python.