質問があります? TensorFlowフォーラム訪問フォーラムでコミュニティとつながる

環境音分類のためのYAMNetによる転移学習

TensorFlow.orgで表示 GoogleColabで実行 GitHubで表示ノートブックをダウンロードTFハブモデルを参照してください

YAMNetは、笑い声、吠え声、サイレンなど、 521のクラスからの音声イベントを予測できる音声イベント分類子です。

このチュートリアルでは、次の方法を学習します。

  • 推論のためにYAMNetモデルをロードして使用します。
  • YAMNet埋め込みを使用して新しいモデルを作成し、猫と犬の音を分類します。
  • モデルを評価してエクスポートします。

TensorFlowおよびその他のライブラリをインポートします

TensorFlow I / Oをインストールすることから始めます。これにより、オーディオファイルをディスクから簡単にロードできるようになります。

pip install -q 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_datasets as tfds
import tensorflow_hub as hub
import tensorflow_io as tfio

YAMNetについて

YAMNetは、オーディオ波形を入力として受け取り、 AudioSetオントロジーからの521のオーディオイベントのそれぞれについて独立した予測を行うオーディオイベント分類です。

内部的には、モデルはオーディオ信号から「フレーム」を抽出し、これらのフレームのバッチを処理します。このバージョンのモデルは、長さが0.96秒のフレームを使用し、0.48秒ごとに1つのフレームを抽出します。

モデルは、 [-1.0, +1.0]範囲の単精度16kHzサンプルとして表される、任意の長さの波形を含む1次元float32TensorまたはNumPy配列を受け入れます。このチュートリアルには、 .wavファイルを正しい形式に変換するのに役立つコードが含まれています。

モデルは、クラススコア、埋め込み(転送学習に使用)、およびログメルスペクトログラムを含む3つの出力を返します。詳細については、 こちらをご覧ください。このチュートリアルでは、これらを実際に使用する方法について説明します。

YAMNetの特定の用途の1つは、高レベルの特徴抽出器としてです。YAMNetの1024-D埋め込み出力は、特定のタスクの少量のデータでトレーニングできる別の浅いモデルの入力特徴として使用できます。これにより、多くのラベル付きデータを必要とせず、大規模なモデルをエンドツーエンドでトレーニングすることなく、特殊なオーディオ分類器をすばやく作成できます。

YAMNetの埋め込み出力を転送学習に使用し、その上に1つ以上の高密度レイヤーをトレーニングします。

まず、モデルを試して、オーディオの分類結果を確認します。次に、データ前処理パイプラインを構築します。

TensorFlowハブからのYAMNetの読み込み

Tensorflow HubのYAMNetを使用して、サウンドファイルから埋め込みを抽出します。

TensorFlow Hubからモデルをロードするのは簡単です。モデルを選択し、そのURLをコピーして、 load関数を使用しload

yamnet_model_handle = 'https://tfhub.dev/google/yamnet/1'
yamnet_model = hub.load(yamnet_model_handle)

モデルをロードし、モデルの基本的な使用法のチュートリアルに従って、サンプルの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
./test_data/miaow_16k.wav

オーディオファイルをロードする機能が必要になります。これらは、後でトレーニングデータを操作するときにも使用されます。

# Util functions for loading audio files and ensure the correct sample rate

@tf.function
def load_wav_16k_mono(filename):
    """ read in a waveform file and convert to 16 kHz mono """
    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)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/parallel_for/pfor.py:2382: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/parallel_for/pfor.py:2382: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample

png

クラスマッピングをロードします

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)
infered_class = class_names[top_class]

print(f'The main sound is: {infered_class}')
print(f'The embeddings shape: {embeddings.shape}')
The main sound is: Animal
The embeddings shape: (13, 1024)

ESC-50データセット

ここで詳しく説明されているESC-50データセットは、2000の環境オーディオ録音(それぞれ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
645701632/Unknown - 46s 0us/step

データを調べる

各ファイルのメタデータは、。 ./datasets/ESC-50-master/meta/esc50.csv csvファイルで指定されます./datasets/ESC-50-master/meta/esc50.csv

すべてのオーディオファイルは./datasets/ESC-50-master/audio/

マッピングを使用してパンダデータフレームを作成し、それを使用してデータをより明確に表示します。

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

データをフィルタリングする

データフレーム上のデータを前提として、いくつかの変換を適用します。

  • 行を除外し、選択したクラス(犬と猫)のみを使用します。他のクラスを使用する場合は、ここで選択できます。
  • ファイル名を変更してフルパスにします。これにより、後でロードしやすくなります。
  • ターゲットを特定の範囲内に変更します。この例では、犬は0のままですが、猫は元の値の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つのフレームを持つ新しい列を作成する必要があります。また、これらの新しい行を適切に反映するには、ラベルを展開して列を折りたたむ必要があります。

展開された折り畳み列は元の値を保持します。フレームをミキシングすることはできません。分割を行うと、同じオーディオの一部が異なる分割で終了する可能性があり、検証とテストの手順の効果が低下する可能性があるためです。

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列を使用して、データセットをトレーニング、検証、およびテストに分割します。

フォールド値は、同じ元のwavファイルのファイルが同じ分割で保持されるようにするためのものです。データセットについて説明している紙で詳細を確認できます。

最後のステップは、トレーニングプロセスでfold列を使用しないため、データセットから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()
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
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 28ms/step - loss: 1.0148 - accuracy: 0.8167 - val_loss: 0.7994 - val_accuracy: 0.8687
Epoch 2/20
15/15 [==============================] - 0s 17ms/step - loss: 0.4651 - accuracy: 0.8750 - val_loss: 0.1999 - val_accuracy: 0.9187
Epoch 3/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2734 - accuracy: 0.8938 - val_loss: 0.3368 - val_accuracy: 0.9187
Epoch 4/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3543 - accuracy: 0.9000 - val_loss: 0.5660 - val_accuracy: 0.8687
Epoch 5/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2469 - accuracy: 0.9104 - val_loss: 0.2928 - val_accuracy: 0.8750
Epoch 6/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2248 - accuracy: 0.9250 - val_loss: 0.5726 - val_accuracy: 0.8687
Epoch 7/20
15/15 [==============================] - 0s 16ms/step - loss: 0.2794 - accuracy: 0.9125 - val_loss: 0.3606 - val_accuracy: 0.8813
Epoch 8/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2326 - accuracy: 0.9187 - val_loss: 0.7255 - val_accuracy: 0.8750
Epoch 9/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3952 - accuracy: 0.9312 - val_loss: 0.4447 - val_accuracy: 0.8687

過剰適合がないことを確認するために、テストデータに対してevaluateメソッドを実行してみましょう。

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 3ms/step - loss: 0.8820 - accuracy: 0.8188
Loss:  0.8819669485092163
Accuracy:  0.8187500238418579

できたね!

モデルをテストする

次に、YAMNetのみを使用して、前のテストの埋め込みでモデルを試してください。

scores, embeddings, spectrogram = yamnet_model(testing_wav_data)
result = my_model(embeddings).numpy()

infered_class = my_classes[result.mean(axis=0).argmax()]
print(f'The main sound is: {infered_class}')
The main sound is: cat

入力としてwavファイルを直接受け取ることができるモデルを保存します

モデルは、入力として埋め込みを与えると機能します。

実際の状況では、サウンドデータを直接提供する必要があります。

これを行うには、YAMNetをモデルと組み合わせて、他のアプリケーションにエクスポートできる1つのモデルにします。

モデルの結果を使いやすくするために、最後のレイヤーはreduce_mean操作になります。このモデルをサービングに使用する場合、以下に示すように、最終レイヤーの名前が必要になります。定義しない場合、TFはインクリメンタルを自動定義するため、モデルをトレーニングするたびに変化し続けるため、テストが困難になります。生のtf演算を使用する場合、名前を割り当てることはできません。この問題に対処するには、 reduce_meanを適用するだけのカスタムレイヤーを作成し、それを「分類子」と呼びます。

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

png

保存したモデルをロードして、期待どおりに機能することを確認します。

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-212454-A-0.wav
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
WARNING:tensorflow:Using a while_loop for converting IO>AudioResample
Waveform values: [-8.8849301e-09  2.6603255e-08 -1.1731625e-08 ... -1.3478296e-03
 -1.0509168e-03 -9.1038318e-04]

png

# 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)
infered_class = class_names[top_class]
top_score = class_scores[top_class]
print(f'[YAMNet] The main sound is: {infered_class} ({top_score})')

reloaded_results = reloaded_model(waveform)
your_top_class = tf.argmax(reloaded_results)
your_infered_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_infered_class} ({your_top_score})')
[YAMNet] The main sound is: Animal (0.9570276141166687)
[Your model] The main sound is: dog (0.9998551607131958)

次のステップ

犬や猫の音を分類できるモデルを作成しました。同じアイデアと適切なデータを使用して、たとえば、歌に基づいて鳥の認識機能を構築できます。

あなたが思いついたものを教えてください!あなたのプロジェクトをソーシャルメディアで私たちと共有してください。