ヘルプKaggleにTensorFlowグレートバリアリーフを保護チャレンジに参加

シンプルな音声認識:キーワードの認識

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示 ノートブックをダウンロード

このチュートリアルでは、WAV形式とビルドで音声ファイルを前処理すると、基本的な訓練する方法を示し、自動音声認識10異なる単語を認識するため(ASR)モデル。あなたがの部分に使用する音声コマンドデータセットウォーデン、2018短い含みます)、(1秒以下)、そのような「ダウン」などのコマンドのオーディオクリップを、「行く」、「いいえ」、「左」、「右」、「停止」、「上」、「はい」。

実世界の音声と音声認識システムが複雑です。しかし、同じようMNISTデータセットと画像分類、このチュートリアルでは、関連する技術の基本的な理解を与える必要があります。

設定

必要なモジュールと依存関係をインポートします。あなたが使うことになることを注意seabornをこのチュートリアルの可視化のために。

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)

ミニ音声コマンドデータセットをインポートする

データの読み込みにかかる時間を節約するために、SpeechCommandsデータセットの小さいバージョンで作業します。元のデータセットはで105,000以上の音声ファイルで構成されてWAV(波形)オーディオファイル形式35種類の言葉を言って人々の。このデータはGoogleによって収集され、CCBYライセンスの下でリリースされました。

ダウンロードして解凍mini_speech_commands.zipと小さい音声コマンドデータセットを含むファイル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

:データセットのオーディオクリップは、各音声コマンドに対応する8つのフォルダに格納されてnoyesdowngoleftupright 、および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']

呼ばれるリストにオーディオクリップを抽出しfilenames 、およびそれをシャッフルします:

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)

分割filenamesはそれぞれ、80:10:10の比率を使用して、トレーニング、検証とテストセットに:

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

オーディオファイルとそのラベルを読む

このセクションでは、データセットを前処理し、波形と対応するラベルのデコードされたテンソルを作成します。ご了承ください:

  • 各WAVファイルには、1秒あたりのサンプル数が設定された時系列データが含まれています。
  • 各サンプルを表す振幅その特定の時点でのオーディオ信号のが。
  • 16ビットのシステム、ミニ音声コマンドデータセット内のWAVファイルのように、振幅値が-32,768から32,767の範囲です。
  • サンプルレートこのデータセットのためには16kHzです。

返されたテンソルの形状tf.audio.decode_wavある[samples, channels]channelsある1モノまたは2ステレオのために。ミニ音声コマンドデータセットには、モノラル録音のみが含まれています。

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])

次に、データセットの生のWAVオーディオファイルをオーディオテンソルに前処理する関数を定義しましょう。

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)

各ファイルの親ディレクトリを使用してラベルを作成する関数を定義します。

  • ファイルパスを分割tf.RaggedTensor S(不揃い寸法-と異なる長さを有していてもよいスライスとのテンソル)。
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]

別のヘルパー機能-定義get_waveform_and_label -つまりは、すべて一緒にそれを置きます:

  • 入力はWAVオーディオファイル名です。
  • 出力は、教師あり学習の準備ができているオーディオテンソルとラベルテンソルを含むタプルです。
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

音声とラベルのペアを抽出するためのトレーニングセットを作成します。

後で同様の手順を使用して、検証セットとテストセットを作成します。

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)

いくつかのオーディオ波形をプロットしてみましょう。

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

波形をスペクトログラムに変換する

データセット内の波形は、時間領域で表されます。次に、あなたは計算することによって、時間-周波数領域の信号に時間領域の信号から波形を変換するよ短時間フーリエ変換(STFT)のように波形を変換するために、スペクトログラムの時間にわたる周波数の変化を示し、することができ、 2D画像として表されます。スペクトログラム画像をニューラルネットワークにフィードして、モデルをトレーニングします。

Aフーリエ変換( tf.signal.fft )その成分周波数に信号を変換するが、全ての時間情報を失います。比較では、STFT( tf.signal.stft )時間のウィンドウに信号を分割し、フーリエ変換は、いくつかの時間情報を保存し、あなたは上の標準畳み込みを実行することができ、2Dテンソルを返す、各ウィンドウ上で変換動作します。

波形をスペクトログラムに変換するためのユーティリティ関数を作成します。

  • 波形は同じ長さである必要があるため、スペクトログラムに変換すると、結果の寸法は同じになります。これは、単に1秒より短い(使用しているオーディオクリップゼロパディングによって行うことができるtf.zeros )。
  • 呼び出すときtf.signal.stft 、選択しframe_lengthframe_step生成スペクトログラム「画像」は、ほぼ正方形であるというようなパラメータを。 STFTパラメータの選択の詳細については、を参照してください。このコーセラ映像音声信号処理およびSTFTに。
  • STFTは、大きさと位相を表す複素数の配列を生成します。しかし、このチュートリアルでは、あなたはあなたが適用して引き出すことができる大きさ、使用しますtf.absの出力に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

次に、データの調査を開始します。一例のテンソル化された波形と対応するスペクトログラムの形状を印刷し、元のオーディオを再生します。

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

次に、スペクトログラムを表示するための関数を定義します。

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)

時間の経過に伴う例の波形と対応するスペクトログラム(時間の経過に伴う周波数)をプロットします。

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

次に、波形データセットをスペクトログラムとそれに対応するラベルに整数IDとして変換する関数を定義します。

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

地図はget_spectrogram_and_label_idとデータセットの要素間でDataset.map

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

データセットのさまざまな例についてスペクトログラムを調べます。

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

モデルを構築してトレーニングする

検証セットとテストセットでトレーニングセットの前処理を繰り返します。

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_size = 64
train_ds = train_ds.batch(batch_size)
val_ds = val_ds.batch(batch_size)

追加Dataset.cacheDataset.prefetchモデルを訓練しながら、読み取り待ち時間を短縮するための操作を:

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

モデルでは、オーディオファイルをスペクトログラム画像に変換したため、単純な畳み込みニューラルネットワーク(CNN)を使用します。

あなたのtf.keras.Sequentialモデルは、以下Keras前処理層を使用します。

  • tf.keras.layers.Resizing :速く訓練するためにモデルを可能にするための入力をダウンサンプリングします。
  • tf.keras.layers.Normalization :その平均値と標準偏差に基づいて、画像内の各画素を正規化します。

以下のためにNormalization層、そのadapt方法は、最初に(、平均値と標準偏差である)集約統計を計算するために、学習データに呼び出される必要があります。

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
_________________________________________________________________

Adamオプティマイザーとクロスエントロピー損失を使用してKerasモデルを構成します。

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

デモンストレーションの目的で、モデルを10エポック以上トレーニングします。

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

トレーニングと検証の損失曲線をプロットして、トレーニング中にモデルがどのように改善されたかを確認しましょう。

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

png

モデルのパフォーマンスを評価する

テストセットでモデルを実行し、モデルのパフォーマンスを確認します。

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%

混同行列を表示する

使用混同行列をモデルがテストセット内の各コマンドを分類しましたどれだけチェックします:

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

オーディオファイルで推論を実行する

最後に、「いいえ」と言っている人の入力オーディオファイルを使用して、モデルの予測出力を確認します。モデルのパフォーマンスはどれくらいですか?

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

出力が示すように、モデルはオーディオコマンドを「no」として認識しているはずです。

次のステップ

このチュートリアルでは、TensorFlowとPythonを使用した畳み込みニューラルネットワークを使用して、簡単な音声分類/自動音声認識を実行する方法を示しました。詳細については、次のリソースを検討してください。