データの視覚化と変換
- 15 分
生波形を理解したら、次の手順では、オーディオを分類に役立つ表現に変換します。
波形は時間の経過に伴う振幅を示しています。
スペクトログラムは、時間の経過に伴う頻度の内容を示します。 スペクトログラムを作成するには、波形の小さな重なり合うウィンドウ全体で短時間フーリエ変換 (STFT) を計算します。 TensorFlow は、この操作の tf.signal.stft を提供します。
このワークフローの以前のバージョンでは、スペクトログラムは PNG ファイルとして保存され、イメージとして読み込み戻されました。 更新されたアプローチでは、スペクトログラムがテンソルとして保持されます。 これにより、余分なファイル I/O が回避され、画像のサイズ変更のアーティファクトが回避され、現在の TensorFlow オーディオ チュートリアル パターンと一致します。
バイナリ オーディオ データセットを準備する
前のユニットからダウンロードしたのと同じミニ音声コマンドから開始します。 次のコードは、データセットがまだ存在しない場合はダウンロードし、アーカイブ ハッシュを検証し、Keras 2 と Keras 3 の両方のレイアウト用に抽出されたフォルダーを見つけ、 no フォルダーと yes フォルダーのみを小さなバイナリ データセット ディレクトリにコピーします。
import pathlib
import shutil
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
SOURCE_DATASET_PATH = pathlib.Path("data/mini_speech_commands")
ALT_SOURCE_DATASET_PATH = pathlib.Path("data/mini_speech_commands_extracted/mini_speech_commands")
BINARY_DATASET_PATH = pathlib.Path("data/speech_commands_yes_no")
MINI_SPEECH_COMMANDS_SHA256 = "49650f2341b26d886b46b3f4fb8fed59e30300b17550f1ee4a768b3106cf93a0"
if not SOURCE_DATASET_PATH.exists() and not ALT_SOURCE_DATASET_PATH.exists():
tf.keras.utils.get_file(
"mini_speech_commands.zip",
origin="https://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip",
file_hash=MINI_SPEECH_COMMANDS_SHA256,
hash_algorithm="sha256",
extract=True,
cache_dir=".",
cache_subdir="data",
)
if ALT_SOURCE_DATASET_PATH.exists():
SOURCE_DATASET_PATH = ALT_SOURCE_DATASET_PATH
for label in ("no", "yes"):
target_dir = BINARY_DATASET_PATH / label
target_dir.mkdir(parents=True, exist_ok=True)
for source_file in (SOURCE_DATASET_PATH / label).glob("*.wav"):
target_file = target_dir / source_file.name
if not target_file.exists():
shutil.copy2(source_file, target_file)
予想される出力: このセットアップ コードでは出力は出力されません。
data/speech_commands_yes_no用のサブディレクトリとno用のサブディレクトリを含むyes ディレクトリが作成されます。 TensorFlow 2.16 以降では Keras 3 が使用されます。これは、アーカイブを <archive>_extracted サブディレクトリに抽出します。ヘルパーは両方のレイアウトを処理するため、ユニットの残りの部分はサポートされている TensorFlow バージョンで動作します。
オーディオ ファイルを TensorFlow データセットとして読み込む
tf.keras.utils.audio_dataset_from_directoryを使用して、ディレクトリ構造から TensorFlow データセットを作成します。 ミニ音声コマンド ファイルは 16 kHz でサンプリングされるため、 output_sequence_length=16000 はクリップあたり 16,000 サンプルの固定ウィンドウを選択します。これは、そのサンプル レートでオーディオのちょうど 1 秒です。 16,000サンプル未満のファイルはゼロで埋められ、16,000サンプルを超えるファイルは切り捨てられます。
output_sequence_length引数は、異なるサンプル レートで録音されたオーディオをリサンプリングしません。リサンプリングとは、サウンドの 1 秒ごとに表すサンプルの数を変更するということです。 このワークフローを別のサンプル レートで記録されたファイルに適合させる場合は、読み込む前に 16 kHz にリサンプリングするか、sampling_rateでローダーのtensorflow-io オプションを使用して、結果のテンソル図形がモデル入力と一致していることを確認します。
このコードは明示的に label_mode="int" を設定します。このモデルは、カテゴリのクロス エントロピがスパースな整数クラス ラベルを次のユニット内で使用するからです。 クラス名は、フォルダー名から英数字順に推論されます。
SEED = 42
BATCH_SIZE = 64
tf.random.set_seed(SEED)
np.random.seed(SEED)
train_ds, validation_ds = tf.keras.utils.audio_dataset_from_directory(
directory=BINARY_DATASET_PATH,
label_mode="int",
batch_size=BATCH_SIZE,
validation_split=0.2,
subset="both",
seed=SEED,
output_sequence_length=16000,
)
label_names = np.array(train_ds.class_names)
print("Label names:", label_names)
予想される出力: ローダーは、 no クラスと yes クラス全体で約 2,000 個の WAV ファイルを検索します。 20% の検証分割では、トレーニングには約 1,600 の例が使用され、検証には約 400 個が使用されます。
Found 2000 files belonging to 2 classes.
Using 1600 files for training.
Using 400 files for validation.
Label names: ['no' 'yes']
テスト セットは検証セットとは別に保持します。 ローダーは個別のテスト分割を作成しないため、次のコードでは、ホールドアウト データを分離し、個々のクリップを検証シャードとテスト シャードに分割します。 分割がバッチ レベルではなくクリップ レベルで行われるように、シャーディングの前にバッチ処理を解除します。 検証シャードとテスト シャードが同じ WAV ファイルを繰り返しデコードしないように、ホールドアウト データをキャッシュします。
holdout_ds = validation_ds.unbatch().cache()
val_ds = holdout_ds.shard(num_shards=2, index=0).batch(BATCH_SIZE)
test_ds = holdout_ds.shard(num_shards=2, index=1).batch(BATCH_SIZE)
予想される出力: このコードは出力を出力しません。 このモデルでは、トレーニングに train_ds 、トレーニング中のチューニングに val_ds 、最終評価に test_ds を使用します。
Note
Dataset.shard は、データセットから要素を選択します。 このコードはunbatch()前にshard()を呼び出すので、シャード要素はバッチ全体ではなく個々のオーディオ クリップです。 約 400 個のホールドアウト クリップ、 val_ds 、 test_ds には、バッチ処理後に約 200 個のクリップが含まれます。
Note
このモジュールは、TensorFlow チュートリアルに準拠したコンパクトな分割パターンに従って、この例は小さいままです。 完全な Speech Commands データセットで厳密な評価を行うには、TensorFlow データセットによって提供されるtrain、validation、およびtest分割を使用するか、スピーカー ID で分割します。そのため、同じ話者のクリップはトレーニング データと評価データの両方に表示されません。
このデータセットは、チャネル ディメンションを持つオーディオ テンソルを返します。 これらのファイルはモノラルであるため、スペクトログラムを作成する前に追加のチャネル ディメンションを削除します。
def squeeze(audio, labels):
audio = tf.squeeze(audio, axis=-1)
return audio, labels
train_ds = train_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
test_ds = test_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
for example_audio, example_labels in train_ds.take(1):
print("Audio batch shape:", example_audio.shape)
print("Label batch shape:", example_labels.shape)
予想される出力: 各オーディオ バッチには、最大 64 個の 1 秒の波形が含まれています。
Audio batch shape: (64, 16000)
Label batch shape: (64,)
波形を視覚化する
次のコードは、トレーニング バッチからの 1 つの波形をプロットします。
example_waveform = example_audio[0]
example_label = label_names[example_labels[0].numpy()]
plt.figure(figsize=(12, 4))
plt.plot(example_waveform.numpy())
plt.title(f"Waveform for '{example_label}'")
plt.xlabel("Sample")
plt.ylabel("Amplitude")
plt.xlim([0, 16000])
plt.show()
予想される出力: このプロットには、1 つの no または yes オーディオ クリップの 16,000 サンプルを超える振幅が表示されます。
スペクトログラムを作成する
STFTは、時間領域からの波形を時間周波数表現に変換します。 このモデルでは STFT 値の大きさのみが使用されるため、コードは複雑な STFT 結果に tf.abs 適用されます。 最後の行ではチャネル ディメンションが追加されるため、畳み込みレイヤーでスペクトログラムを画像のような入力として処理できます。
def get_spectrogram(waveform):
spectrogram = tf.signal.stft(
waveform,
frame_length=255,
frame_step=128,
)
spectrogram = tf.abs(spectrogram)
spectrogram = spectrogram[..., tf.newaxis]
return spectrogram
example_spectrogram = get_spectrogram(example_waveform)
print("Spectrogram shape:", example_spectrogram.shape)
予想される出力: 1 秒のオーディオ (16,000 サンプル)、フレーム長 255、フレーム ステップ 128 では、スペクトログラムの形状が (124, 129, 1)。 最初のディメンションは、タイム フレーム数 ( floor((16000 - 255) / 128) + 1 = 124) です。 この数式が適用されるのは、tf.signal.stft が pad_end を既定で False に設定したままにするため、末尾に余分な部分フレームが追加されないためです。 2 番目の次元は周波数ビンの数です。tf.signal.stft 各フレームを FFT の次の 2 の累乗 (この場合は 256) にゼロパディングし、非冗長なビンを fft_length / 2 + 1 = 129 返します。最終的な次元は、畳み込みモデルのために追加されたチャネル軸です。
Spectrogram shape: (124, 129, 1)
スペクトログラムを視覚化する
表示には対数スケールを使用して、より静かな周波数成分を見やすくします。 ゼロ値が負の無限大にならないように、 np.log を呼び出す前に小さな epsilon を追加します。
def plot_spectrogram(spectrogram, ax):
if len(spectrogram.shape) > 2:
spectrogram = np.squeeze(spectrogram, axis=-1)
log_spec = np.log(spectrogram.T + np.finfo(float).eps)
height = log_spec.shape[0]
width = log_spec.shape[1]
time_steps = np.arange(width)
frequency_bins = np.arange(height)
ax.pcolormesh(time_steps, frequency_bins, log_spec)
ax.set_xlabel("Time frame")
ax.set_ylabel("Frequency bin")
fig, axes = plt.subplots(2, figsize=(12, 8))
axes[0].plot(example_waveform.numpy())
axes[0].set_title("Waveform")
axes[0].set_xlim([0, 16000])
plot_spectrogram(example_spectrogram.numpy(), axes[1])
axes[1].set_title("Spectrogram")
plt.suptitle(example_label.title())
plt.show()
予想される出力: 最初のグラフは波形を示しています。 2 番目のグラフは、横軸のタイム フレーム、縦軸の周波数ビン、および大きさを表す色の強度を持つスペクトログラムを示しています。
スペクトログラムのデータセットを作成する
波形データセットをスペクトログラム データセットにマップします。 トレーニング中に入力パイプラインの待機時間を短縮するために、データセットをキャッシュしてプリフェッチします。
def make_spectrogram_dataset(dataset):
return dataset.map(
map_func=lambda audio, label: (get_spectrogram(audio), label),
num_parallel_calls=tf.data.AUTOTUNE,
)
train_spectrogram_ds = make_spectrogram_dataset(train_ds)
val_spectrogram_ds = make_spectrogram_dataset(val_ds)
test_spectrogram_ds = make_spectrogram_dataset(test_ds)
train_spectrogram_ds = train_spectrogram_ds.cache().shuffle(1000, seed=SEED).prefetch(tf.data.AUTOTUNE)
val_spectrogram_ds = val_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE)
test_spectrogram_ds = test_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE)
for spectrograms, labels in train_spectrogram_ds.take(1):
print("Spectrogram batch shape:", spectrograms.shape)
print("Label batch shape:", labels.shape)
予想される出力: モデル対応データセットには、スペクトログラムテンソルと整数ラベルのバッチが含まれています。
Spectrogram batch shape: (64, 124, 129, 1)
Label batch shape: (64,)
自分の知識をチェックする
フィードバック
このページはお役に立ちましたか?
いいえ
このトピックについてサポートが必要ですか?
このトピックの意図を把握したり、理解を深めたりするために Ask Learn を使ってみませんか?