![]() | ![]() | ![]() | ![]() |
このチュートリアルでは、10個の異なる単語を認識する基本的な音声認識ネットワークを構築する方法を示します。実際の音声および音声認識システムははるかに複雑であることを知っておくことが重要ですが、画像のMNISTと同様に、関連する手法の基本的な理解が得られるはずです。このチュートリアルを完了すると、1秒のオーディオクリップを「下」、「移動」、「左」、「いいえ」、「右」、「停止」、「上」に分類しようとするモデルができあがります。 " はい"。
セットアップ
必要なモジュールと依存関係をインポートします。
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)
音声コマンドデータセットをインポートする
音声コマンドデータセットの一部をダウンロードするスクリプトを作成します。元のデータセットは、30の異なる単語を言っている人々の105,000を超えるWAVオーディオファイルで構成されています。このデータはGoogleによって収集され、CC BYライセンスの下でリリースされました。自分の声を5分間提供することで、データの改善に役立てることができます。
データセットの一部を使用して、データの読み込みにかかる時間を節約します。 mini_speech_commands.zip
を抽出し、 mini_speech_commands.zip
を使用してtf.data
ます。
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 [==============================] - 2s 0us/step
データセットに関する基本的な統計を確認してください。
commands = np.array(tf.io.gfile.listdir(str(data_dir)))
commands = commands[commands != 'README.md']
print('Commands:', commands)
Commands: ['down' 'yes' 'left' 'right' 'no' 'go' 'stop' 'up']
オーディオファイルをリストに抽出し、シャッフルします。
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/yes/fde2dee7_nohash_0.wav', shape=(), dtype=string)
それぞれ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
オーディオファイルとそのラベルの読み取り
オーディオファイルは、最初はバイナリファイルとして読み取られ、数値テンソルに変換する必要があります。
オーディオファイルをロードするには、 tf.audio.decode_wav
を使用しtf.audio.decode_wav
。これは、WAVでエンコードされたオーディオをテンソルおよびサンプルレートとして返します。
WAVファイルには、1秒あたりのサンプル数が設定された時系列データが含まれています。各サンプルは、その特定の時間におけるオーディオ信号の振幅を表します。 mini_speech_commands
のファイルのように、16ビットシステムでは、値の範囲はmini_speech_commands
です。このデータセットのサンプルレートは16kHzです。 tf.audio.decode_wav
は、値を[-1.0、1.0]の範囲に正規化することに注意してください。
def decode_audio(audio_binary):
audio, _ = tf.audio.decode_wav(audio_binary)
return tf.squeeze(audio, axis=-1)
各WAVファイルのラベルはその親ディレクトリです。
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]
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
次に、 process_path
を適用してトレーニングセットを作成し、オーディオとラベルのペアを抽出して結果を確認します。後で同様の手順を使用して、検証セットとテストセットを作成します。
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)
いくつかのオーディオ波形とそれに対応するラベルを調べてみましょう。
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()
スペクトログラム
波形をスペクトログラムに変換します。スペクトログラムは、時間の経過に伴う周波数の変化を示し、2D画像として表すことができます。これは、短時間フーリエ変換(STFT)を適用して、オーディオを時間-周波数領域に変換することで実行できます。
フーリエ変換( tf.signal.fft
)は、信号をその成分周波数に変換しますが、すべての時間情報を失います。 STFT( tf.signal.stft
)は、信号を時間ウィンドウに分割し、各ウィンドウでフーリエ変換を実行して、時間情報を保持し、標準の畳み込みを実行できる2Dテンソルを返します。
STFTは、大きさと位相を表す複素数の配列を生成します。しかし、あなただけ適用することにより導出することができ、このチュートリアルのための大きさが必要ですtf.abs
の出力にtf.signal.stft
。
選択しframe_length
とframe_step
生成スペクトログラム「画像」は、ほぼ正方形であるというようなパラメータを。 STFTパラメータの選択の詳細については、オーディオ信号処理に関するこのビデオを参照してください。
また、波形の長さを同じにして、スペクトログラム画像に変換したときに結果の寸法が同じになるようにする必要があります。これは、1秒より短いオーディオクリップをゼロパディングするだけで実行できます。
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
次に、データを調べます。データセットからの1つの例の波形、スペクトログラム、および実際のオーディオを比較します。
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: yes Waveform shape: (13375,) 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]
X = np.arange(16000, step=height + 1)
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()
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log after removing the cwd from sys.path. /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:8: 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.
次に、波形データセットを変換して、スペクトログラム画像とそれに対応するラベルを整数IDとして使用します。
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)
データセットのさまざまなサンプルのスペクトログラム「画像」を調べます。
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()
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log after removing the cwd from sys.path. /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/ipykernel_launcher.py:8: 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.
モデルを構築してトレーニングする
これで、モデルを構築してトレーニングできます。ただし、その前に、検証セットとテストセットでトレーニングセットの前処理を繰り返す必要があります。
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)
モデルトレーニングのトレーニングセットと検証セットをバッチ処理します。
batch_size = 64
train_ds = train_ds.batch(batch_size)
val_ds = val_ds.batch(batch_size)
データセットのcache()
およびprefetch()
操作を追加して、モデルのトレーニング中の読み取りレイテンシーを削減します。
train_ds = train_ds.cache().prefetch(AUTOTUNE)
val_ds = val_ds.cache().prefetch(AUTOTUNE)
モデルでは、オーディオファイルをスペクトログラム画像に変換したため、単純な畳み込みニューラルネットワーク(CNN)を使用します。このモデルには、次の追加の前処理レイヤーもあります。
- 入力をダウンサンプリングしてモデルをより高速にトレーニングできるようにする
Resizing
レイヤー。 - 平均と標準偏差に基づいて画像の各ピクセルを正規
Normalization
するための正規Normalization
レイヤー。
Normalization
レイヤーの場合、集計統計(平均と標準偏差)を計算するために、最初にそのadapt
メソッドをトレーニングデータで呼び出す必要があります。
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) 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 [==============================] - 16s 47ms/step - loss: 1.9142 - accuracy: 0.2855 - val_loss: 1.2969 - val_accuracy: 0.5938 Epoch 2/10 100/100 [==============================] - 0s 4ms/step - loss: 1.2597 - accuracy: 0.5581 - val_loss: 0.8918 - val_accuracy: 0.7237 Epoch 3/10 100/100 [==============================] - 0s 4ms/step - loss: 0.9525 - accuracy: 0.6647 - val_loss: 0.7742 - val_accuracy: 0.7337 Epoch 4/10 100/100 [==============================] - 0s 4ms/step - loss: 0.7793 - accuracy: 0.7288 - val_loss: 0.6514 - val_accuracy: 0.7850 Epoch 5/10 100/100 [==============================] - 0s 4ms/step - loss: 0.6715 - accuracy: 0.7639 - val_loss: 0.5986 - val_accuracy: 0.8000 Epoch 6/10 100/100 [==============================] - 0s 4ms/step - loss: 0.5647 - accuracy: 0.7968 - val_loss: 0.5901 - val_accuracy: 0.8075 Epoch 7/10 100/100 [==============================] - 0s 4ms/step - loss: 0.5122 - accuracy: 0.8093 - val_loss: 0.5526 - val_accuracy: 0.8087 Epoch 8/10 100/100 [==============================] - 0s 4ms/step - loss: 0.4780 - accuracy: 0.8298 - val_loss: 0.5166 - val_accuracy: 0.8163 Epoch 9/10 100/100 [==============================] - 0s 4ms/step - loss: 0.4216 - accuracy: 0.8544 - val_loss: 0.4806 - val_accuracy: 0.8438 Epoch 10/10 100/100 [==============================] - 0s 4ms/step - loss: 0.3976 - accuracy: 0.8510 - val_loss: 0.4848 - val_accuracy: 0.8388
トレーニングと検証の損失曲線をチェックして、トレーニング中にモデルがどのように改善されたかを確認しましょう。
metrics = history.history
plt.plot(history.epoch, metrics['loss'], metrics['val_loss'])
plt.legend(['loss', 'val_loss'])
plt.show()
テストセットのパフォーマンスを評価する
テストセットでモデルを実行し、パフォーマンスを確認してみましょう。
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()
オーディオファイルで推論を実行する
最後に、「いいえ」と言っている人の入力オーディオファイルを使用して、モデルの予測出力を確認します。モデルのパフォーマンスはどれくらいですか?
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()
モデルがオーディオコマンドを「いいえ」として非常に明確に認識していることがわかります。
次のステップ
このチュートリアルでは、TensorFlowとPythonで畳み込みニューラルネットワークを使用して簡単な音声分類を行う方法を示しました。
音声分類に転送学習を使用する方法については、YAMNetチュートリアルを使用した音声分類を確認してください。
音声分類用の独自のインタラクティブなウェブアプリを構築するには、 TensorFlow.jsを使用することを検討して ください-転送学習コードラボを使用した音声認識。
TensorFlowには、独自のオーディオベースのプロジェクトを支援するためのオーディオデータの準備と拡張の追加サポートもあります。