Ajuda a proteger a Grande Barreira de Corais com TensorFlow em Kaggle Junte Desafio

Reconhecimento de áudio simples: como reconhecer palavras-chave

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial demonstra como pré-processar arquivos de áudio no formato WAV e construir e treinar um básica reconhecimento automático de fala modelo (ASR) para o reconhecimento de palavras dez diferentes. Você vai usar uma parte do conjunto de dados Comandos de Voz ( Warden, 2018 ), que contém curto (um segundo ou menos) clipes de áudio de comandos, como "baixo", "vai", "esquerda", "não", " certo "," pare "," para cima "e" sim ".

Discurso do mundo real e reconhecimento de áudio sistemas são complexos. Mas, como classificação de imagens com o conjunto de dados MNIST , este tutorial deve dar-lhe uma compreensão básica das técnicas envolvidas.

Configurar

Importe os módulos e dependências necessários. Note-se que você estará usando Seaborn para visualização neste tutorial.

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)

Importe o conjunto de dados do mini Speech Commands

Para economizar tempo com o carregamento de dados, você trabalhará com uma versão menor do conjunto de dados Speech Commands. A base de dados original consiste de mais de 105.000 arquivos de áudio no WAV (Waveform) formato de arquivo de áudio de pessoas dizendo 35 palavras diferentes. Esses dados foram coletados pelo Google e divulgados sob uma licença CC BY.

Faça o download e extrair o mini_speech_commands.zip arquivo contendo os pequenos conjuntos de dados comandos de voz com 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

Clipes de áudio do conjunto de dados são armazenados em oito pastas correspondentes a cada comando de voz: no , yes , down , go , left , up , right e 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']

Extraia os clipes de áudio em uma lista chamada filenames , e embaralhe-:

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)

Dividir filenames para o treinamento, validação e teste conjuntos utilizando um rácio 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

Leia os arquivos de áudio e suas etiquetas

Nesta seção, você irá pré-processar o conjunto de dados, criando tensores decodificados para as formas de onda e os rótulos correspondentes. Observe que:

  • Cada arquivo WAV contém dados de série temporal com um número definido de amostras por segundo.
  • Cada amostra representa a amplitude do sinal de áudio naquele momento específico.
  • Em um 16-bit sistema, como os arquivos WAV no conjunto de dados mini-Comandos de Voz, os valores de amplitude variam de 32.768 a 32.767.
  • A taxa de amostragem para este conjunto de dados é 16kHz.

A forma do tensor retornado por tf.audio.decode_wav é [samples, channels] , onde channels é 1 para mono ou 2 para estéreo. O conjunto de dados mini Speech Commands contém apenas gravações 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])

Agora, vamos definir uma função que pré-processa os arquivos de áudio WAV brutos do conjunto de dados em tensores de áudio:

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)

Defina uma função que crie rótulos usando os diretórios pais para cada arquivo:

  • Dividir os caminhos de arquivo em tf.RaggedTensor s (tensores com dimensões com RAGGED-fatias que podem ter comprimentos diferentes).
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]

Definir outro ajudante Função- get_waveform_and_label -que reúne tudo:

  • A entrada é o nome do arquivo de áudio WAV.
  • A saída é uma tupla contendo os tensores de áudio e rótulo prontos para o aprendizado supervisionado.
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

Crie o conjunto de treinamento para extrair os pares de rótulos de áudio:

Você construirá os conjuntos de validação e teste usando um procedimento semelhante mais tarde.

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)

Vamos plotar algumas formas de onda de áudio:

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

Converter formas de onda em espectrogramas

As formas de onda no conjunto de dados são representadas no domínio do tempo. Em seguida, você vai transformar as formas de onda dos sinais no domínio do tempo para os sinais de tempo-freqüência-domínio calculando o tempo curto transformada de Fourier (STFT) para converter as formas de onda como espectrogramas , que mostram alterações de frequência ao longo do tempo e pode ser representado como imagens 2D. Você alimentará as imagens do espectrograma em sua rede neural para treinar o modelo.

A transformada de Fourier ( tf.signal.fft ) converte um sinal de suas freqüências componentes, mas perde toda a informação do tempo. Em comparação, STFT ( tf.signal.stft ) divide o sinal em janelas de tempo e executa uma transformada de Fourier em cada janela, preservando algumas informações tempo, e retornando um tensor 2D que você pode executar circunvoluções padrão diante.

Crie uma função de utilidade para converter formas de onda em espectrogramas:

  • As formas de onda precisam ter o mesmo comprimento, de modo que, ao convertê-las em espectrogramas, os resultados tenham dimensões semelhantes. Isso pode ser feito simplesmente zero preenchimento dos clipes de áudio que são mais curtos do que um segundo (usando tf.zeros ).
  • Ao chamar tf.signal.stft , escolher os frame_length e frame_step parâmetros tais que o espectrograma "imagem" gerado é quase quadrado. Para mais informações sobre a escolha parâmetros STFT, referem-se a este vídeo Coursera em processamento de sinais de áudio e STFT.
  • O STFT produz uma série de números complexos que representam magnitude e fase. No entanto, neste tutorial você só vai usar a magnitude, o que você pode derivar aplicando tf.abs na saída do 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

Em seguida, comece a explorar os dados. Imprima as formas da forma de onda tensorizada de um exemplo e o espectrograma correspondente e reproduza o áudio original:

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

Agora, defina uma função para exibir um espectrograma:

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)

Trace a forma de onda do exemplo ao longo do tempo e o espectrograma correspondente (frequências ao longo do tempo):

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

Agora, defina uma função que transforma o conjunto de dados da forma de onda em espectrogramas e seus rótulos correspondentes como IDs inteiros:

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

Mapa get_spectrogram_and_label_id através elementos do conjunto de dados com Dataset.map :

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

Examine os espectrogramas para diferentes exemplos do conjunto de dados:

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

Construir e treinar o modelo

Repita o pré-processamento do conjunto de treinamento nos conjuntos de validação e teste:

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)

Lote os conjuntos de treinamento e validação para o treinamento do modelo:

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

Adicionar Dataset.cache e Dataset.prefetch operações para reduzir a latência de leitura durante o treinamento do modelo:

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

Para o modelo, você usará uma rede neural convolucional (CNN) simples, uma vez que transformou os arquivos de áudio em imagens de espectrograma.

Seu tf.keras.Sequential modelo usará as seguintes camadas de pré-processamento Keras:

Para a Normalization camada, a sua adapt método teria primeiro de ser chamado os dados de treino a fim de calcular as estatísticas agregados (isto é, a média e o desvio padrão).

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
_________________________________________________________________

Configure o modelo Keras com o otimizador Adam e a perda de entropia cruzada:

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

Treine o modelo em 10 épocas para fins de demonstração:

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

Vamos plotar as curvas de perda de treinamento e validação para verificar como seu modelo melhorou durante o treinamento:

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

png

Avalie o desempenho do modelo

Execute o modelo no conjunto de teste e verifique o desempenho do modelo:

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%

Exibir uma matriz de confusão

Use uma matriz de confusão para verificar quão bem o modelo se classificar cada um dos comandos do conjunto de teste:

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

Executar inferência em um arquivo de áudio

Finalmente, verifique a saída de previsão do modelo usando um arquivo de áudio de entrada de alguém dizendo "não". Qual é o desempenho do seu 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

Como a saída sugere, seu modelo deve ter reconhecido o comando de áudio como "não".

Próximos passos

Este tutorial demonstrou como realizar uma classificação de áudio simples / reconhecimento automático de fala usando uma rede neural convolucional com TensorFlow e Python. Para saber mais, considere os seguintes recursos: