![]() | ![]() | ![]() | ![]() | ![]() |
YAMNetは、事前にトレーニングされたディープニューラルネットワークであり、笑い声、吠え声、サイレンなど、 521のクラスからの音声イベントを予測できます。
このチュートリアルでは、次の方法を学習します。
- 推論のためにYAMNetモデルをロードして使用します。
- YAMNet埋め込みを使用して新しいモデルを作成し、猫と犬の音を分類します。
- モデルを評価してエクスポートします。
TensorFlowおよびその他のライブラリをインポートする
TensorFlow I / Oをインストールすることから始めます。これにより、オーディオファイルをディスクから簡単にロードできるようになります。
pip install tensorflow_io
import os
from IPython import display
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_io as tfio
YAMNetについて
YAMNetは、 MobileNetV1の深さ方向に分離可能な畳み込みアーキテクチャを採用した事前トレーニング済みのニューラルネットワークです。オーディオ波形を入力として使用し、 AudioSetコーパスからの521のオーディオイベントごとに独立した予測を行うことができます。
内部的には、モデルはオーディオ信号から「フレーム」を抽出し、これらのフレームのバッチを処理します。このバージョンのモデルは、0.96秒の長さのフレームを使用し、0.48秒ごとに1つのフレームを抽出します。
このモデルは、 [-1.0, +1.0]
の範囲のシングルチャネル(モノラル)16 kHzサンプルとして表される、任意の長さの波形を含む1次元float32TensorまたはNumPy配列を受け入れます。このチュートリアルには、WAVファイルをサポートされている形式に変換するのに役立つコードが含まれています。
モデルは、クラススコア、埋め込み(転送学習に使用)、およびログメルスペクトログラムを含む3つの出力を返します。詳細については、こちらをご覧ください。
YAMNetの特定の用途の1つは、高レベルの特徴抽出器(1,024次元の埋め込み出力)としての使用です。ベース(YAMNet)モデルの入力フィーチャを使用して、1つの非表示のtf.keras.layers.Dense
レイヤーで構成される浅いモデルにフィードします。次に、大量のラベル付きデータを必要とせずに、オーディオ分類のために少量のデータでネットワークをトレーニングし、エンドツーエンドでトレーニングします。 (これは、詳細については、TensorFlow Hubを使用した画像分類の転移学習に似ています。)
まず、モデルをテストし、オーディオの分類結果を確認します。次に、データ前処理パイプラインを構築します。
TensorFlowハブからのYAMNetの読み込み
Tensorflow Hubから事前にトレーニングされたYAMNetを使用して、サウンドファイルから埋め込みを抽出します。
TensorFlow Hubからモデルをロードするのは簡単です。モデルを選択し、そのURLをコピーして、 load
関数を使用します。
yamnet_model_handle = 'https://tfhub.dev/google/yamnet/1'
yamnet_model = hub.load(yamnet_model_handle)
モデルがロードされたら、 YAMNetの基本的な使用法のチュートリアルに従い、サンプルのWAVファイルをダウンロードして推論を実行できます。
testing_wav_file_name = tf.keras.utils.get_file('miaow_16k.wav',
'https://storage.googleapis.com/audioset/miaow_16k.wav',
cache_dir='./',
cache_subdir='test_data')
print(testing_wav_file_name)
Downloading data from https://storage.googleapis.com/audioset/miaow_16k.wav 221184/215546 [==============================] - 0s 0us/step 229376/215546 [===============================] - 0s 0us/step ./test_data/miaow_16k.wav
オーディオファイルをロードする機能が必要になります。この機能は、後でトレーニングデータを操作するときにも使用されます。 (オーディオファイルとそのラベルの読み取りについて詳しくは、シンプルオーディオ認識をご覧ください。
# Utility functions for loading audio files and making sure the sample rate is correct.
@tf.function
def load_wav_16k_mono(filename):
""" Load a WAV file, convert it to a float tensor, resample to 16 kHz single-channel audio. """
file_contents = tf.io.read_file(filename)
wav, sample_rate = tf.audio.decode_wav(
file_contents,
desired_channels=1)
wav = tf.squeeze(wav, axis=-1)
sample_rate = tf.cast(sample_rate, dtype=tf.int64)
wav = tfio.audio.resample(wav, rate_in=sample_rate, rate_out=16000)
return wav
testing_wav_data = load_wav_16k_mono(testing_wav_file_name)
_ = plt.plot(testing_wav_data)
# Play the audio file.
display.Audio(testing_wav_data,rate=16000)
2022-01-26 08:07:19.084427: W tensorflow_io/core/kernels/audio_video_mp3_kernels.cc:271] libmp3lame.so.0 or lame functions are not available WARNING:tensorflow:Using a while_loop for converting IO>AudioResample WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
クラスマッピングをロードする
YAMNetが認識できるクラス名をロードすることが重要です。マッピングファイルは、 yamnet_model.class_map_path()
にCSV形式で存在します。
class_map_path = yamnet_model.class_map_path().numpy().decode('utf-8')
class_names =list(pd.read_csv(class_map_path)['display_name'])
for name in class_names[:20]:
print(name)
print('...')
Speech Child speech, kid speaking Conversation Narration, monologue Babbling Speech synthesizer Shout Bellow Whoop Yell Children shouting Screaming Whispering Laughter Baby laughter Giggle Snicker Belly laugh Chuckle, chortle Crying, sobbing ...
推論を実行する
YAMNetは、フレームレベルのクラススコア(つまり、フレームごとに521スコア)を提供します。クリップレベルの予測を決定するために、スコアをフレーム全体でクラスごとに集計できます(たとえば、平均または最大集計を使用)。これは、 scores_np.mean(axis=0)
によって以下で実行されます。最後に、クリップレベルで最高スコアのクラスを見つけるには、521個の集計スコアの最大値を取得します。
scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
class_scores = tf.reduce_mean(scores, axis=0)
top_class = tf.argmax(class_scores)
inferred_class = class_names[top_class]
print(f'The main sound is: {inferred_class}')
print(f'The embeddings shape: {embeddings.shape}')
The main sound is: Animal The embeddings shape: (13, 1024)
ESC-50データセット
ESC-50データセット( Piczak、2015年)は、2,000の5秒間の環境オーディオ録音のラベル付きコレクションです。データセットは50のクラスで構成され、クラスごとに40の例があります。
データセットをダウンロードして抽出します。
_ = tf.keras.utils.get_file('esc-50.zip',
'https://github.com/karoldvl/ESC-50/archive/master.zip',
cache_dir='./',
cache_subdir='datasets',
extract=True)
Downloading data from https://github.com/karoldvl/ESC-50/archive/master.zip 645103616/Unknown - 47s 0us/step
データを調べる
各ファイルのメタデータは、。 ./datasets/ESC-50-master/meta/esc50.csv
のcsvファイルで指定されます。
すべてのオーディオファイルは./datasets/ESC-50-master/audio/
にあります
マッピングを使用してパンダのDataFrame
を作成し、それを使用してデータをより明確に表示します。
esc50_csv = './datasets/ESC-50-master/meta/esc50.csv'
base_data_path = './datasets/ESC-50-master/audio/'
pd_data = pd.read_csv(esc50_csv)
pd_data.head()
データをフィルタリングする
データがDataFrame
に保存されたので、いくつかの変換を適用します。
- 行を除外し、選択したクラス(
dog
とcat
)のみを使用します。他のクラスを使用したい場合は、ここで選択できます。 - フルパスを持つようにファイル名を修正します。これにより、後でロードしやすくなります。
- ターゲットを特定の範囲内に変更します。この例では、
dog
は0
のままですが、cat
は元の値の5
ではなく1
になります。
my_classes = ['dog', 'cat']
map_class_to_id = {'dog':0, 'cat':1}
filtered_pd = pd_data[pd_data.category.isin(my_classes)]
class_id = filtered_pd['category'].apply(lambda name: map_class_to_id[name])
filtered_pd = filtered_pd.assign(target=class_id)
full_path = filtered_pd['filename'].apply(lambda row: os.path.join(base_data_path, row))
filtered_pd = filtered_pd.assign(filename=full_path)
filtered_pd.head(10)
オーディオファイルをロードし、埋め込みを取得します
ここでは、 load_wav_16k_mono
を適用し、モデルのWAVデータを準備します。
WAVデータから埋め込みを抽出すると、形状の配列(N, 1024)
が得られます。ここで、 N
はYAMNetが検出したフレーム数(オーディオの0.48秒ごとに1つ)です。
モデルは、各フレームを1つの入力として使用します。したがって、行ごとに1つのフレームを持つ新しい列を作成する必要があります。また、これらの新しい行を適切に反映するには、ラベルとfold
列を展開する必要があります。
展開されたfold
列は、元の値を保持します。分割を実行すると、同じオーディオの一部が異なる分割に含まれる可能性があり、検証とテストの手順の効果が低下する可能性があるため、フレームを混合することはできません。
filenames = filtered_pd['filename']
targets = filtered_pd['target']
folds = filtered_pd['fold']
main_ds = tf.data.Dataset.from_tensor_slices((filenames, targets, folds))
main_ds.element_spec
(TensorSpec(shape=(), dtype=tf.string, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None))
def load_wav_for_map(filename, label, fold):
return load_wav_16k_mono(filename), label, fold
main_ds = main_ds.map(load_wav_for_map)
main_ds.element_spec
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample WARNING:tensorflow:Using a while_loop for converting IO>AudioResample (TensorSpec(shape=<unknown>, dtype=tf.float32, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None))
# applies the embedding extraction model to a wav data
def extract_embedding(wav_data, label, fold):
''' run YAMNet to extract embedding from the wav data '''
scores, embeddings, spectrogram = yamnet_model(wav_data)
num_embeddings = tf.shape(embeddings)[0]
return (embeddings,
tf.repeat(label, num_embeddings),
tf.repeat(fold, num_embeddings))
# extract embedding
main_ds = main_ds.map(extract_embedding).unbatch()
main_ds.element_spec
(TensorSpec(shape=(1024,), dtype=tf.float32, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None))
データを分割する
fold
列を使用して、データセットをトレーニング、検証、およびテストセットに分割します。
ESC-50は、5つの均一なサイズの交差検定fold
に配置されているため、同じ元のソースからのクリップは常に同じfold
になります。詳細については、 ESC:環境サウンド分類のデータセットを参照してください。
最後のステップは、トレーニング中に折り畳み列を使用しないため、データセットからfold
列を削除することです。
cached_ds = main_ds.cache()
train_ds = cached_ds.filter(lambda embedding, label, fold: fold < 4)
val_ds = cached_ds.filter(lambda embedding, label, fold: fold == 4)
test_ds = cached_ds.filter(lambda embedding, label, fold: fold == 5)
# remove the folds column now that it's not needed anymore
remove_fold_column = lambda embedding, label, fold: (embedding, label)
train_ds = train_ds.map(remove_fold_column)
val_ds = val_ds.map(remove_fold_column)
test_ds = test_ds.map(remove_fold_column)
train_ds = train_ds.cache().shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
test_ds = test_ds.cache().batch(32).prefetch(tf.data.AUTOTUNE)
モデルを作成する
あなたはほとんどの仕事をしました!次に、音から猫と犬を認識するために、1つの隠れ層と2つの出力を持つ非常に単純なシーケンシャルモデルを定義します。
my_model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(1024), dtype=tf.float32,
name='input_embedding'),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(len(my_classes))
], name='my_model')
my_model.summary()
Model: "my_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 512) 524800 dense_1 (Dense) (None, 2) 1026 ================================================================= Total params: 525,826 Trainable params: 525,826 Non-trainable params: 0 _________________________________________________________________
my_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="adam",
metrics=['accuracy'])
callback = tf.keras.callbacks.EarlyStopping(monitor='loss',
patience=3,
restore_best_weights=True)
history = my_model.fit(train_ds,
epochs=20,
validation_data=val_ds,
callbacks=callback)
Epoch 1/20 15/15 [==============================] - 6s 49ms/step - loss: 0.7811 - accuracy: 0.8229 - val_loss: 0.4866 - val_accuracy: 0.9125 Epoch 2/20 15/15 [==============================] - 0s 18ms/step - loss: 0.3385 - accuracy: 0.8938 - val_loss: 0.2185 - val_accuracy: 0.8813 Epoch 3/20 15/15 [==============================] - 0s 18ms/step - loss: 0.3091 - accuracy: 0.9021 - val_loss: 0.4290 - val_accuracy: 0.8813 Epoch 4/20 15/15 [==============================] - 0s 18ms/step - loss: 0.5354 - accuracy: 0.9062 - val_loss: 0.2074 - val_accuracy: 0.9125 Epoch 5/20 15/15 [==============================] - 0s 18ms/step - loss: 0.4651 - accuracy: 0.9333 - val_loss: 0.6857 - val_accuracy: 0.8813 Epoch 6/20 15/15 [==============================] - 0s 18ms/step - loss: 0.2489 - accuracy: 0.9167 - val_loss: 0.3640 - val_accuracy: 0.8750 Epoch 7/20 15/15 [==============================] - 0s 17ms/step - loss: 0.2020 - accuracy: 0.9292 - val_loss: 0.2158 - val_accuracy: 0.9125 Epoch 8/20 15/15 [==============================] - 0s 16ms/step - loss: 0.4550 - accuracy: 0.9208 - val_loss: 0.9893 - val_accuracy: 0.8750 Epoch 9/20 15/15 [==============================] - 0s 17ms/step - loss: 0.3434 - accuracy: 0.9354 - val_loss: 0.2670 - val_accuracy: 0.8813 Epoch 10/20 15/15 [==============================] - 0s 17ms/step - loss: 0.2864 - accuracy: 0.9208 - val_loss: 0.5122 - val_accuracy: 0.8813
過剰適合がないことを確認するために、テストデータに対してevaluate
メソッドを実行してみましょう。
loss, accuracy = my_model.evaluate(test_ds)
print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 9ms/step - loss: 0.2526 - accuracy: 0.9000 Loss: 0.25257644057273865 Accuracy: 0.8999999761581421
できたね!
モデルをテストする
次に、YAMNetのみを使用して、前のテストの埋め込みでモデルを試してください。
scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
result = my_model(embeddings).numpy()
inferred_class = my_classes[result.mean(axis=0).argmax()]
print(f'The main sound is: {inferred_class}')
The main sound is: cat
WAVファイルを入力として直接受け取ることができるモデルを保存します
モデルに埋め込みを入力として指定すると、モデルが機能します。
実際のシナリオでは、オーディオデータを直接入力として使用する必要があります。
これを行うには、YAMNetをモデルと組み合わせて、他のアプリケーションにエクスポートできる単一のモデルにします。
モデルの結果を使いやすくするために、最後のレイヤーはreduce_mean
操作になります。このモデルを提供に使用する場合(チュートリアルの後半で学習します)、最終レイヤーの名前が必要になります。定義しない場合、TensorFlowはインクリメンタルを自動定義するため、モデルをトレーニングするたびに変化し続けるため、テストが困難になります。生のTensorFlow操作を使用する場合、名前を割り当てることはできません。この問題に対処するには、 reduce_mean
を適用するカスタムレイヤーを作成し、それを'classifier'
と呼びます。
class ReduceMeanLayer(tf.keras.layers.Layer):
def __init__(self, axis=0, **kwargs):
super(ReduceMeanLayer, self).__init__(**kwargs)
self.axis = axis
def call(self, input):
return tf.math.reduce_mean(input, axis=self.axis)
saved_model_path = './dogs_and_cats_yamnet'
input_segment = tf.keras.layers.Input(shape=(), dtype=tf.float32, name='audio')
embedding_extraction_layer = hub.KerasLayer(yamnet_model_handle,
trainable=False, name='yamnet')
_, embeddings_output, _ = embedding_extraction_layer(input_segment)
serving_outputs = my_model(embeddings_output)
serving_outputs = ReduceMeanLayer(axis=0, name='classifier')(serving_outputs)
serving_model = tf.keras.Model(input_segment, serving_outputs)
serving_model.save(saved_model_path, include_optimizer=False)
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model. WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model. 2022-01-26 08:08:33.807036: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them. INFO:tensorflow:Assets written to: ./dogs_and_cats_yamnet/assets INFO:tensorflow:Assets written to: ./dogs_and_cats_yamnet/assets
tf.keras.utils.plot_model(serving_model)
保存したモデルをロードして、期待どおりに機能することを確認します。
reloaded_model = tf.saved_model.load(saved_model_path)
そして最後のテストでは、いくつかのサウンドデータが与えられた場合、モデルは正しい結果を返しますか?
reloaded_results = reloaded_model(testing_wav_data)
cat_or_dog = my_classes[tf.argmax(reloaded_results)]
print(f'The main sound is: {cat_or_dog}')
The main sound is: cat
新しいモデルをサービング設定で試してみたい場合は、「serving_default」シグニチャを使用できます。
serving_results = reloaded_model.signatures['serving_default'](testing_wav_data)
cat_or_dog = my_classes[tf.argmax(serving_results['classifier'])]
print(f'The main sound is: {cat_or_dog}')
The main sound is: cat
(オプション)さらにいくつかのテスト
モデルの準備が整いました。
テストデータセットのYAMNetと比較してみましょう。
test_pd = filtered_pd.loc[filtered_pd['fold'] == 5]
row = test_pd.sample(1)
filename = row['filename'].item()
print(filename)
waveform = load_wav_16k_mono(filename)
print(f'Waveform values: {waveform}')
_ = plt.plot(waveform)
display.Audio(waveform, rate=16000)
./datasets/ESC-50-master/audio/5-214759-A-5.wav WARNING:tensorflow:Using a while_loop for converting IO>AudioResample WARNING:tensorflow:Using a while_loop for converting IO>AudioResample Waveform values: [ 3.2084468e-09 -7.7704687e-09 -1.2222010e-08 ... 2.2788899e-02 1.0315948e-02 -3.4766860e-02]
# Run the model, check the output.
scores, embeddings, spectrogram = yamnet_model(waveform)
class_scores = tf.reduce_mean(scores, axis=0)
top_class = tf.argmax(class_scores)
inferred_class = class_names[top_class]
top_score = class_scores[top_class]
print(f'[YAMNet] The main sound is: {inferred_class} ({top_score})')
reloaded_results = reloaded_model(waveform)
your_top_class = tf.argmax(reloaded_results)
your_inferred_class = my_classes[your_top_class]
class_probabilities = tf.nn.softmax(reloaded_results, axis=-1)
your_top_score = class_probabilities[your_top_class]
print(f'[Your model] The main sound is: {your_inferred_class} ({your_top_score})')
[YAMNet] The main sound is: Silence (0.500638484954834) [Your model] The main sound is: cat (0.9981643557548523)
次のステップ
犬や猫の音を分類できるモデルを作成しました。同じアイデアと異なるデータセットを使用して、たとえば、鳥の歌声に基づいて鳥の音響識別子を作成することを試みることができます。
プロジェクトをソーシャルメディアでTensorFlowチームと共有してください!