Einfache Audioerkennung: Schlüsselwörter erkennen

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial zeigt Ihnen, wie Sie ein einfaches Spracherkennungsnetzwerk aufbauen, das zehn verschiedene Wörter erkennt. Es ist wichtig zu wissen, dass echte Sprach- und Audioerkennungssysteme viel komplexer sind, aber wie MNIST für Bilder sollte es Ihnen ein grundlegendes Verständnis der beteiligten Techniken vermitteln. Sobald Sie dieses Tutorial abgeschlossen haben, haben Sie ein Modell, das versucht, einen einsekündigen Audioclip als "down", "go", "left", "no", "right", "stop", "up" zu klassifizieren " und ja".

Installieren

Importieren Sie die erforderlichen Module und Abhängigkeiten.

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)

Importieren Sie den Datensatz für Sprachbefehle

Sie werden ein Skript schreiben , um einen Teil des zum Download Sprachbefehle Datensatzes . Der ursprüngliche Datensatz besteht aus über 105.000 WAV-Audiodateien von Menschen, die dreißig verschiedene Wörter sagen. Diese Daten wurden von Google gesammelt und unter einer CC BY-Lizenz veröffentlicht.

Sie verwenden einen Teil des Datasets, um beim Laden von Daten Zeit zu sparen. Extrahieren der mini_speech_commands.zip und lädt es bei der Verwendung der tf.data API.

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 [==============================] - 1s 0us/step

Überprüfen Sie grundlegende Statistiken zum Datensatz.

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

Extrahieren Sie die Audiodateien in eine Liste und mischen Sie sie.

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/go/5eb5fc74_nohash_0.wav', shape=(), dtype=string)

Teilen Sie die Dateien im Verhältnis 80:10:10 in Trainings-, Validierungs- und Testsätze auf.

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

Lesen von Audiodateien und deren Labels

Die Audiodatei wird zunächst als Binärdatei gelesen, die Sie in einen numerischen Tensor umwandeln möchten.

Um eine Audiodatei zu laden, werden Sie verwenden tf.audio.decode_wav , die die WAV-codierte Audio als Tensor und die Abtastrate zurück.

Eine WAV-Datei enthält Zeitreihendaten mit einer festgelegten Anzahl von Abtastungen pro Sekunde. Jedes Sample repräsentiert die Amplitude des Audiosignals zu diesem bestimmten Zeitpunkt. In einem 16-Bit - System, wie die Dateien in mini_speech_commands reichen die Werte von 32768 bis 32767. Die Abtastrate zu diesem Datensatz 16kHz sind. Man beachte , dass tf.audio.decode_wav werden die Werte auf den Bereich [-1.0, 1.0] normalisieren.

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

Das Label für jede WAV-Datei ist ihr übergeordnetes Verzeichnis.

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]

Lassen Sie uns eine Methode definieren, die den Dateinamen der WAV-Datei aufnimmt und ein Tupel ausgibt, das Audio und Labels für das überwachte Training enthält.

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

Sie gelten jetzt process_path Ihren Trainingssatz zu bauen , um die audio-Label - Paare zu extrahieren und die Ergebnisse zu überprüfen. Sie erstellen die Validierungs- und Testsets später mit einem ähnlichen Verfahren.

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)

Sehen wir uns einige Audiowellenformen mit ihren entsprechenden Bezeichnungen an.

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

Spektrogramm

Sie wandeln die Wellenform in ein Spektrogramm um, das Frequenzänderungen über die Zeit zeigt und als 2D-Bild dargestellt werden kann. Dies kann durch Anwenden der Kurzzeit-Fourier-Transformation (STFT) erfolgen, um das Audio in den Zeit-Frequenz-Bereich umzuwandeln.

Eine Fourier - Transformation ( tf.signal.fft ) wandelt ein Signal in seine Teilfrequenzen, aber verliert alle Zeitinformationen. Die STFT ( tf.signal.stft ) teilt das Signal in Zeitfenster und führt eine Fourier - Transformation an jedem Fenster verwandeln, einige Zeit Informationen zu bewahren und eine 2D - Tensor Rückkehr , dass Sie Standard - Faltungen auf ausführen können.

STFT erzeugt ein Array komplexer Zahlen, die Betrag und Phase darstellen. Allerdings werden Sie brauchen nur die Größe für dieses Tutorial, das durch die Anwendung abgeleitet werden kann tf.abs am Ausgang des tf.signal.stft .

Wählen Sie frame_length und frame_step Parameter , so dass die erzeugte Spektrogramm „Bild“ ist fast quadratisch. Weitere Informationen über die STFT Parameter Wahl, können Sie sich auf dieses Video auf Audio-Signalverarbeitung.

Sie möchten auch, dass die Wellenformen die gleiche Länge haben, damit die Ergebnisse beim Konvertieren in ein Spektrogrammbild ähnliche Abmessungen haben. Dies kann durch einfaches Null-Auffüllen der Audioclips, die kürzer als eine Sekunde sind, erfolgen.

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

Als nächstes untersuchen Sie die Daten. Vergleichen Sie die Wellenform, das Spektrogramm und das tatsächliche Audio eines Beispiels aus dem Datensatz.

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: go
Waveform shape: (8192,)
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]
  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)


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()
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log
  after removing the cwd from sys.path.
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:9: 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.
  if __name__ == '__main__':

png

Transformieren Sie nun das Wellenform-Dataset so, dass es Spektrogrammbilder und die entsprechenden Beschriftungen als ganzzahlige IDs enthält.

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)

Untersuchen Sie die Spektrogramm-"Bilder" für verschiedene Proben des Datensatzes.

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()
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log
  after removing the cwd from sys.path.
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:9: 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.
  if __name__ == '__main__':

png

Modell bauen und trainieren

Jetzt können Sie Ihr Modell bauen und trainieren. Zuvor müssen Sie jedoch die Vorverarbeitung des Trainingssatzes für die Validierungs- und Testsätze wiederholen.

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)

Stapeln Sie die Trainings- und Validierungssätze für das Modelltraining.

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

In Daten - Set - cache() und prefetch() Operationen Leselatenz zu verringern , während das Modell zu trainieren.

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

Für das Modell verwenden Sie ein einfaches Convolutional Neural Network (CNN), da Sie die Audiodateien in Spektrogrammbilder umgewandelt haben. Das Modell verfügt außerdem über die folgenden zusätzlichen Vorverarbeitungsebenen:

  • Ein Resizing der Resizing Schicht die Eingabe in dem Downsampling , das Modell zu ermöglichen , schneller zu trainieren.
  • Ein Normalization jedes Pixel in dem Bild auf seinem Mittelwert und die Standardabweichung basierend zu normalisieren.

Für die Normalization seine adapt würde Methode zuerst auf den Trainingsdaten , um genannt werden muß , aggregierte Statistiken zu berechnen (dh Mittelwert und Standardabweichung).

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)
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: "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 [==============================] - 6s 38ms/step - loss: 1.7314 - accuracy: 0.3739 - val_loss: 1.3301 - val_accuracy: 0.5387
Epoch 2/10
100/100 [==============================] - 0s 4ms/step - loss: 1.1985 - accuracy: 0.5773 - val_loss: 0.9609 - val_accuracy: 0.6862
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.9329 - accuracy: 0.6752 - val_loss: 0.7923 - val_accuracy: 0.7350
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7678 - accuracy: 0.7280 - val_loss: 0.6767 - val_accuracy: 0.7700
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6529 - accuracy: 0.7670 - val_loss: 0.6235 - val_accuracy: 0.7850
Epoch 6/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5823 - accuracy: 0.7880 - val_loss: 0.5722 - val_accuracy: 0.7987
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5088 - accuracy: 0.8180 - val_loss: 0.5437 - val_accuracy: 0.8062
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4687 - accuracy: 0.8342 - val_loss: 0.5667 - val_accuracy: 0.7987
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4238 - accuracy: 0.8509 - val_loss: 0.5144 - val_accuracy: 0.8350
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.3966 - accuracy: 0.8633 - val_loss: 0.4949 - val_accuracy: 0.8263

Sehen wir uns die Trainings- und Validierungsverlustkurven an, um zu sehen, wie sich Ihr Modell während des Trainings verbessert hat.

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

png

Bewerten Sie die Leistung des Testsatzes

Lassen Sie uns das Modell auf dem Testsatz ausführen und die Leistung überprüfen.

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%

Zeigen Sie eine Verwirrungsmatrix an

Eine Verwirrungsmatrix ist hilfreich, um zu sehen, wie gut das Modell bei jedem der Befehle im Testsatz abgeschnitten hat.

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

Inferenz für eine Audiodatei ausführen

Überprüfen Sie abschließend die Vorhersageausgabe des Modells mit einer Eingabe-Audiodatei, in der jemand "Nein" sagt. Wie gut schneidet Ihr Modell ab?

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

Sie können sehen, dass Ihr Modell den Audiobefehl sehr deutlich als "Nein" erkannt hat.

Nächste Schritte

In diesem Tutorial wurde gezeigt, wie Sie eine einfache Audioklassifizierung mithilfe eines neuronalen Faltungsnetzwerks mit TensorFlow und Python durchführen können.