Eine Frage haben? Verbinden Sie sich mit der Community im TensorFlow Forum Visit Forum

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".

Einrichten

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 schreiben ein Skript, um einen Teil des Datasets für Sprachbefehle herunterzuladen. 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 Sie die mini_speech_commands.zip und laden Sie sie mit 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, verwenden Sie tf.audio.decode_wav , das das WAV-codierte Audio als Tensor und die Abtastrate zurückgibt.

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 den Dateien in mini_speech_commands , reichen die Werte von -32768 bis 32767. Die Abtastrate für diesen Datensatz beträgt 16 kHz. Beachten Sie, dass tf.audio.decode_wav die Werte auf den Bereich [-1.0, 1.0] normalisiert.

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 wenden nun process_path an, um Ihren Trainingssatz zu erstellen, 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 Komponentenfrequenzen um, verliert jedoch alle Zeitinformationen. Die STFT ( tf.signal.stft ) teilt das Signal in Zeitfenster auf und führt in jedem Fenster eine Fourier-Transformation durch, wobei einige Zeitinformationen erhalten tf.signal.stft und ein 2D-Tensor zurückgegeben wird, auf dem Sie Standardfaltungen ausführen können.

STFT erzeugt ein Array komplexer Zahlen, die Betrag und Phase darstellen. Für dieses Tutorial benötigen Sie jedoch nur die Größe, die durch Anwenden von tf.abs auf die Ausgabe von tf.signal.stft .

Wählen frame_length Parameter frame_length und frame_step so, dass das erzeugte Spektrogramm "Bild" fast quadratisch ist. Weitere Informationen zur Auswahl der STFT-Parameter finden Sie in diesem Video zur Audiosignalverarbeitung.

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)

Fügen Sie die Vorgänge dataset cache() und prefetch() , um die Leselatenz beim Trainieren des Modells zu reduzieren.

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:

  • Eine Resizing zum Downsampling der Eingabe, damit das Modell schneller trainiert werden kann.
  • Eine Normalization , um jedes Pixel im Bild basierend auf seinem Mittelwert und seiner Standardabweichung zu normalisieren.

Für die Normalization müsste zunächst ihre adapt für die Trainingsdaten aufgerufen werden, um aggregierte Statistiken (dh Mittelwert und Standardabweichung) zu berechnen.

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.