Bantuan melindungi Great Barrier Reef dengan TensorFlow pada Kaggle Bergabung Tantangan

Pengenalan audio sederhana: Mengenali kata kunci

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Tutorial ini menunjukkan bagaimana untuk preprocess file audio dalam format WAV dan membangun dan melatih dasar pengenalan suara otomatis Model (ASR) untuk mengenali kata-kata yang berbeda sepuluh. Anda akan menggunakan sebagian dari dataset Speech Perintah ( Warden, 2018 ), yang berisi pendek (satu detik atau kurang) klip audio dari perintah, seperti "down", "pergi", "kiri", "tidak", " kanan", "berhenti", "naik" dan "ya".

Pidato dunia nyata dan audio sistem yang kompleks. Tapi, seperti klasifikasi citra dengan dataset MNIST , tutorial ini harus memberikan pemahaman dasar tentang teknik yang terlibat.

Mempersiapkan

Impor modul dan dependensi yang diperlukan. Perhatikan bahwa Anda akan menggunakan Seaborn untuk visualisasi dalam tutorial ini.

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)

Impor kumpulan data Perintah Pidato mini

Untuk menghemat waktu dengan pemuatan data, Anda akan bekerja dengan versi yang lebih kecil dari kumpulan data Perintah Ucapan. The dataset asli terdiri dari lebih dari 105.000 file audio dalam format file WAV (Waveform) Audio orang mengatakan 35 kata-kata yang berbeda. Data ini dikumpulkan oleh Google dan dirilis di bawah lisensi CC BY.

Download dan ekstrak mini_speech_commands.zip file yang berisi lebih kecil dataset Speech Perintah dengan 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

Klip audio dataset disimpan dalam delapan folder sesuai dengan setiap perintah pidato: no , yes , down , go , left , up , right , dan 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']

Ekstrak klip audio ke daftar yang disebut filenames , dan shuffle itu:

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)

Berpisah filenames ke dalam pelatihan, validasi dan uji set menggunakan rasio 80:10:10, masing-masing:

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

Baca file audio dan labelnya

Di bagian ini Anda akan melakukan praproses dataset, membuat tensor yang didekodekan untuk bentuk gelombang dan label yang sesuai. Perhatikan bahwa:

  • Setiap file WAV berisi data deret waktu dengan jumlah sampel yang ditetapkan per detik.
  • Setiap sampel mewakili amplitudo dari sinyal audio pada waktu tertentu.
  • Dalam 16-bit sistem, seperti file WAV di dataset Mini Pidato Perintah, nilai-nilai amplitudo berkisar dari -32.768 ke 32.767.
  • The sample rate untuk dataset ini 16kHz.

Bentuk tensor dikembalikan oleh tf.audio.decode_wav adalah [samples, channels] , di mana channels adalah 1 untuk mono atau 2 untuk stereo. Set data Perintah Ucapan mini hanya berisi rekaman 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])

Sekarang, mari kita definisikan fungsi yang mempraproses file audio WAV mentah dataset menjadi tensor audio:

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)

Tentukan fungsi yang membuat label menggunakan direktori induk untuk setiap file:

  • Membagi path file ke tf.RaggedTensor s (tensor dengan compang-camping dimensi-dengan irisan yang mungkin memiliki panjang yang berbeda).
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]

Tentukan lain pembantu function- get_waveform_and_label -yaitu menempatkan itu semua bersama-sama:

  • Inputnya adalah nama file audio WAV.
  • Outputnya adalah tuple yang berisi tensor audio dan label yang siap untuk pembelajaran terawasi.
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

Bangun set pelatihan untuk mengekstrak pasangan label audio:

Anda akan membuat set validasi dan pengujian menggunakan prosedur serupa nanti.

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)

Mari kita plot beberapa bentuk gelombang audio:

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

Konversi bentuk gelombang ke spektogram

Bentuk gelombang dalam dataset direpresentasikan dalam domain waktu. Berikutnya, Anda akan mengubah bentuk gelombang dari sinyal waktu-domain ke dalam sinyal waktu-frekuensi-domain dengan menghitung waktu singkat Fourier transform (STFT) untuk mengubah bentuk gelombang sebagai spektogram , yang menunjukkan perubahan frekuensi dari waktu ke waktu dan dapat direpresentasikan sebagai gambar 2D. Anda akan memasukkan gambar spektogram ke jaringan saraf Anda untuk melatih model.

Sebuah Fourier transform ( tf.signal.fft ) mengkonversi sinyal ke frekuensi komponen, tetapi kehilangan semua informasi waktu. Sebagai perbandingan, STFT ( tf.signal.stft ) membagi sinyal ke jendela waktu dan menjalankan transformasi Fourier pada setiap jendela, melestarikan beberapa informasi waktu, dan mengembalikan tensor 2D yang Anda dapat menjalankan convolutions standar pada.

Buat fungsi utilitas untuk mengubah bentuk gelombang menjadi spektogram:

  • Bentuk gelombang harus memiliki panjang yang sama, sehingga ketika Anda mengubahnya menjadi spektogram, hasilnya memiliki dimensi yang serupa. Hal ini dapat dilakukan dengan hanya nol-padding klip audio yang lebih pendek dari satu detik (menggunakan tf.zeros ).
  • Saat memanggil tf.signal.stft , memilih frame_length dan frame_step parameter seperti yang dihasilkan spektogram "image" hampir persegi. Untuk informasi lebih lanjut tentang pilihan parameter STFT, lihat video yang Coursera ini pada pemrosesan sinyal audio dan STFT.
  • STFT menghasilkan array bilangan kompleks yang mewakili besaran dan fase. Namun, dalam tutorial ini Anda hanya akan menggunakan besarnya, yang dapat Anda berasal dengan menerapkan tf.abs pada output dari 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

Selanjutnya, mulailah menjelajahi data. Cetak bentuk gelombang tensorisasi satu contoh dan spektogram yang sesuai, dan putar audio aslinya:

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

Sekarang, tentukan fungsi untuk menampilkan spektogram:

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)

Plot contoh bentuk gelombang dari waktu ke waktu dan spektogram yang sesuai (frekuensi dari waktu ke waktu):

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

Sekarang, tentukan fungsi yang mengubah kumpulan data bentuk gelombang menjadi spektogram dan labelnya yang sesuai sebagai ID bilangan bulat:

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

Peta get_spectrogram_and_label_id di elemen dataset dengan Dataset.map :

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

Periksa spektogram untuk berbagai contoh kumpulan data:

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

Bangun dan latih modelnya

Ulangi prapemrosesan set pelatihan pada set validasi dan pengujian:

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)

Batch set pelatihan dan validasi untuk pelatihan model:

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

Menambahkan Dataset.cache dan Dataset.prefetch operasi untuk mengurangi membaca latensi saat pelatihan model:

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

Untuk modelnya, Anda akan menggunakan jaringan saraf convolutional sederhana (CNN), karena Anda telah mengubah file audio menjadi gambar spektogram.

Anda tf.keras.Sequential Model akan menggunakan lapisan Keras preprocessing berikut:

Untuk Normalization lapisan, yang adapt metode pertama akan perlu dipanggil pada data pelatihan untuk menghitung statistik agregat (yaitu, mean dan standar deviasi).

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
_________________________________________________________________

Konfigurasikan model Keras dengan pengoptimal Adam dan kehilangan lintas-entropi:

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

Latih model lebih dari 10 zaman untuk tujuan demonstrasi:

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

Mari kita plot kurva kehilangan pelatihan dan validasi untuk memeriksa bagaimana model Anda telah meningkat selama pelatihan:

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

png

Evaluasi kinerja model

Jalankan model pada set pengujian dan periksa kinerja model:

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%

Tampilkan matriks kebingungan

Gunakan matriks kebingungan untuk memeriksa seberapa baik model itu mengelompokkan masing-masing perintah dalam tes set:

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

Jalankan inferensi pada file audio

Terakhir, verifikasi output prediksi model menggunakan file audio input dari seseorang yang mengatakan "tidak". Seberapa baik performa model Anda?

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

Seperti yang disarankan oleh output, model Anda seharusnya mengenali perintah audio sebagai "tidak".

Langkah selanjutnya

Tutorial ini menunjukkan cara melakukan klasifikasi audio sederhana/pengenalan suara otomatis menggunakan jaringan saraf convolutional dengan TensorFlow dan Python. Untuk mempelajari lebih lanjut, pertimbangkan sumber daya berikut: