Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/tutorials/audio/simple_audio.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Reconhecimento de áudio simples: reconhecendo palavras-chave

Este tutorial demonstra como pré-processar arquivos de áudio no formato WAV e construir e treinar um modelo básico de reconhecimento automático de fala (ASR, na sigla em inglês) para reconhecer dez palavras diferentes. Você usará uma parte do dataset Speech Commands (Warden, 2018), que contém clipes de áudio curtos (de um segundo ou menos) de comandos em inglês, como "down", "go", "left", "no", "right", "stop", "up" e "yes".

Os sistemas de reconhecimento de voz e áudio do mundo real são complexos. Mas, assim como a classificação de imagens com o dataset MNIST, este tutorial deve fornecer uma compreensão básica das técnicas envolvidas.

Configuração

Importe os módulos e dependências necessários. Você usará tf.keras.utils.audio_dataset_from_directory (introduzido no TensorFlow 2.10), que ajuda a gerar datasets de classificação de áudio a partir de diretórios de arquivos .wav. Você também precisará do Seaborn para visualização neste tutorial.

!pip install -U -q tensorflow tensorflow_datasets
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 dataset mini Speech Commands

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

Baixe e extraia o arquivo mini_speech_commands.zip que contém os datasets Speech Commands menores 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')

Os clipes de áudio do dataset são armazenados em oito pastas correspondentes a cada comando de fala: no, yes, down, go, left, up, right e stop:

commands = np.array(tf.io.gfile.listdir(str(data_dir))) commands = commands[(commands != 'README.md') & (commands != '.DS_Store')] print('Commands:', commands)

Divididos em diretórios dessa forma, você pode carregar facilmente os dados usando keras.utils.audio_dataset_from_directory.

Os clipes de áudio têm 1 segundo ou menos a 16kHz. O output_sequence_length=16000 preenche os curtos para exatamente 1 segundo (e cortaria os mais longos) para que possam ser facilmente agrupados em lote.

train_ds, val_ds = tf.keras.utils.audio_dataset_from_directory( directory=data_dir, batch_size=64, validation_split=0.2, seed=0, output_sequence_length=16000, subset='both') label_names = np.array(train_ds.class_names) print() print("label names:", label_names)

O dataset agora contém lotes de clipes de áudio e rótulos inteiros. Os clipes de áudio têm o formato (batch, samples, channels) (lote, amostras, canais).

train_ds.element_spec

Este dataset contém apenas áudio de canal único, então use a função tf.squeeze para eliminar o eixo extra:

def squeeze(audio, labels): audio = tf.squeeze(audio, axis=-1) return audio, labels train_ds = train_ds.map(squeeze, tf.data.AUTOTUNE) val_ds = val_ds.map(squeeze, tf.data.AUTOTUNE)

A função utils.audio_dataset_from_directory retorna apenas até duas divisões (splits). É uma boa ideia manter um dataset de testes separado do seu dataset de validação. Idealmente, você o manteria em um diretório separado, mas neste caso você pode usar Dataset.shard para dividir o dataset de validação em duas metades. Observe que a iteração sobre qualquer fragmento carregará todos os dados e manterá apenas sua fração.

test_ds = val_ds.shard(num_shards=2, index=0) val_ds = val_ds.shard(num_shards=2, index=1)
for example_audio, example_labels in train_ds.take(1): print(example_audio.shape) print(example_labels.shape)

Vamos desenhar algumas formas de onda de áudio:

label_names[[1,1,3,0]]
plt.figure(figsize=(16, 10)) rows = 3 cols = 3 n = rows * cols for i in range(n): plt.subplot(rows, cols, i+1) audio_signal = example_audio[i] plt.plot(audio_signal) plt.title(label_names[example_labels[i]]) plt.yticks(np.arange(-1.2, 1.2, 0.2)) plt.ylim([-1.1, 1.1])

Converta formas de onda em espectrogramas

As formas de onda no dataset são representadas no domínio do tempo. A seguir, você transformará as formas de onda dos sinais no domínio do tempo em sinais no domínio da frequência e do tempo, calculando a transformada de Fourier de tempo curto (STFT, da sigla em inglês) para converter as formas de onda em espectrogramas, que mostram mudanças de frequência ao longo do tempo e podem ser representadas como imagens 2D. Você alimentará as imagens do espectrograma em sua rede neural para treinar o modelo.

Uma transformada de Fourier (tf.signal.fft) converte um sinal em seus componentes de frequência, mas perde todas as informações de tempo. Em comparação, 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 de tempo e retornando um tensor 2D no qual você pode executar convoluções padrão.

Crie uma função utilitária para converter formas de onda em espectrogramas:

  • As formas de onda precisam ter o mesmo comprimento, para que, ao convertê-las em espectrogramas, os resultados tenham dimensões semelhantes. Isso pode ser feito simplesmente preenchendo com zero os clipes de áudio com menos de um segundo (usando tf.zeros).

  • Ao chamar tf.signal.stft, escolha os parâmetros frame_length e frame_step de forma que a "imagem" do espectrograma gerado seja quase quadrada. Para obter mais informações sobre a escolha dos parâmetros STFT, consulte este vídeo do Coursera sobre processamento de sinal de áudio e STFT.

  • O STFT produz um array de números complexos representando magnitude e fase. No entanto, neste tutorial você usará apenas a magnitude, que pode ser derivada aplicando tf.abs na saída de tf.signal.stft.

def get_spectrogram(waveform): # Convert the waveform to a spectrogram via a STFT. spectrogram = tf.signal.stft( waveform, 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 i in range(3): label = label_names[example_labels[i]] waveform = example_audio[i] 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))

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)

Desenhe 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.suptitle(label.title()) plt.show()

Agora, crie datasets de espectrograma a partir dos datasets de áudio:

def make_spec_ds(ds): return ds.map( map_func=lambda audio,label: (get_spectrogram(audio), label), num_parallel_calls=tf.data.AUTOTUNE)
train_spectrogram_ds = make_spec_ds(train_ds) val_spectrogram_ds = make_spec_ds(val_ds) test_spectrogram_ds = make_spec_ds(test_ds)

Examine os espectrogramas para diferentes exemplos do dataset:

for example_spectrograms, example_spect_labels in train_spectrogram_ds.take(1): break
rows = 3 cols = 3 n = rows*cols fig, axes = plt.subplots(rows, cols, figsize=(16, 9)) for i in range(n): r = i // cols c = i % cols ax = axes[r][c] plot_spectrogram(example_spectrograms[i].numpy(), ax) ax.set_title(label_names[example_spect_labels[i].numpy()]) plt.show()

Construa e treine o modelo

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

train_spectrogram_ds = train_spectrogram_ds.cache().shuffle(10000).prefetch(tf.data.AUTOTUNE) val_spectrogram_ds = val_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE) test_spectrogram_ds = test_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE)

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

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

  • tf.keras.layers.Resizing: para reduzir a resolução da entrada para permitir que o modelo treine mais rapidamente.

  • tf.keras.layers.Normalization: para normalizar cada pixel da imagem com base em sua média e desvio padrão.

Para a camada Normalization, seu método adapt precisaria primeiro ser chamado nos dados de treinamento para calcular estatísticas agregadas (ou seja, a média e o desvio padrão).

input_shape = example_spectrograms.shape[1:] print('Input shape:', input_shape) num_labels = len(label_names) # 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=train_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()

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 ao longo de 10 épocas para fins de demonstração:

EPOCHS = 10 history = model.fit( train_spectrogram_ds, validation_data=val_spectrogram_ds, epochs=EPOCHS, callbacks=tf.keras.callbacks.EarlyStopping(verbose=1, patience=2), )

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

metrics = history.history plt.figure(figsize=(16,6)) plt.subplot(1,2,1) plt.plot(history.epoch, metrics['loss'], metrics['val_loss']) plt.legend(['loss', 'val_loss']) plt.ylim([0, max(plt.ylim())]) plt.xlabel('Epoch') plt.ylabel('Loss [CrossEntropy]') plt.subplot(1,2,2) plt.plot(history.epoch, 100*np.array(metrics['accuracy']), 100*np.array(metrics['val_accuracy'])) plt.legend(['accuracy', 'val_accuracy']) plt.ylim([0, 100]) plt.xlabel('Epoch') plt.ylabel('Accuracy [%]')

Avalie o desempenho do modelo

Execute o modelo sobre o dataset de testes e verifique o desempenho do modelo:

model.evaluate(test_spectrogram_ds, return_dict=True)

Exiba uma matriz de confusão

Use uma matriz de confusão para verificar o desempenho do modelo ao classificar cada um dos comandos no dataset de teste:

y_pred = model.predict(test_spectrogram_ds)
y_pred = tf.argmax(y_pred, axis=1)
y_true = tf.concat(list(test_spectrogram_ds.map(lambda s,lab: lab)), axis=0)
confusion_mtx = tf.math.confusion_matrix(y_true, y_pred) plt.figure(figsize=(10, 8)) sns.heatmap(confusion_mtx, xticklabels=label_names, yticklabels=label_names, annot=True, fmt='g') plt.xlabel('Prediction') plt.ylabel('Label') plt.show()

Execute inferência num arquivo de áudio

Por fim, verifique o resultado da previsão do modelo usando um arquivo de áudio de entrada de alguém dizendo “no” (não). Qual é o desempenho do seu modelo?

x = data_dir/'no/01bb6a2a_nohash_0.wav' x = tf.io.read_file(str(x)) x, sample_rate = tf.audio.decode_wav(x, desired_channels=1, desired_samples=16000,) x = tf.squeeze(x, axis=-1) waveform = x x = get_spectrogram(x) x = x[tf.newaxis,...] prediction = model(x) x_labels = ['no', 'yes', 'down', 'go', 'left', 'up', 'right', 'stop'] plt.bar(x_labels, tf.nn.softmax(prediction[0])) plt.title('No') plt.show() display.display(display.Audio(waveform, rate=16000))

Como a saída sugere, seu modelo deveria ter reconhecido o comando de áudio como "no".

Exporte o modelo com pré-processamento

O modelo não é muito fácil de usar se você precisar aplicar essas etapas de pré-processamento antes de passar os dados ao modelo para inferência. Portanto, crie uma versão ponta a ponta:

class ExportModel(tf.Module): def __init__(self, model): self.model = model # Accept either a string-filename or a batch of waveforms. # YOu could add additional signatures for a single wave, or a ragged-batch. self.__call__.get_concrete_function( x=tf.TensorSpec(shape=(), dtype=tf.string)) self.__call__.get_concrete_function( x=tf.TensorSpec(shape=[None, 16000], dtype=tf.float32)) @tf.function def __call__(self, x): # If they pass a string, load the file and decode it. if x.dtype == tf.string: x = tf.io.read_file(x) x, _ = tf.audio.decode_wav(x, desired_channels=1, desired_samples=16000,) x = tf.squeeze(x, axis=-1) x = x[tf.newaxis, :] x = get_spectrogram(x) result = self.model(x, training=False) class_ids = tf.argmax(result, axis=-1) class_names = tf.gather(label_names, class_ids) return {'predictions':result, 'class_ids': class_ids, 'class_names': class_names}

Teste a execução do modelo "export":

export = ExportModel(model) export(tf.constant(str(data_dir/'no/01bb6a2a_nohash_0.wav')))

Salve e recarregue o modelo, o modelo recarregado fornece uma saída idêntica:

tf.saved_model.save(export, "saved") imported = tf.saved_model.load("saved") imported(waveform[tf.newaxis, :])

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: