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

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

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

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関数を使用し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
./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)
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)
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
645701632/Unknown - 41s 0us/step

データを調べる

各ファイルのメタデータは、。 ./datasets/ESC-50-master/meta/esc50.csv csvファイルで指定されます./datasets/ESC-50-master/meta/esc50.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に保存されたDataFrame 、いくつかの変換を適用します。

  • 行を除外し、選択したクラス( dogcatのみを使用します。他のクラスを使用する場合は、ここで選択できます。
  • ファイル名を修正してフルパスにします。これにより、後でロードしやすくなります。
  • 特定の範囲内になるようにターゲットを変更します。この例では、 dog0ままですが、 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は、同じ元のソースからのクリップが常に同じfoldあるように、5つの均一なサイズの交差検定foldに配置されます。詳細については、 ESC:環境音分類のデータセットペーパーを参照してください。

最後のステップは、トレーニング中に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 [==============================] - 5s 25ms/step - loss: 0.7833 - accuracy: 0.8000 - val_loss: 0.6789 - val_accuracy: 0.8687
Epoch 2/20
15/15 [==============================] - 0s 16ms/step - loss: 0.5082 - accuracy: 0.8958 - val_loss: 0.3775 - val_accuracy: 0.8813
Epoch 3/20
15/15 [==============================] - 0s 17ms/step - loss: 0.3210 - accuracy: 0.8750 - val_loss: 0.5043 - val_accuracy: 0.8750
Epoch 4/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2146 - accuracy: 0.9021 - val_loss: 0.3757 - val_accuracy: 0.8750
Epoch 5/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2113 - accuracy: 0.9062 - val_loss: 0.2740 - val_accuracy: 0.8750
Epoch 6/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2672 - accuracy: 0.9167 - val_loss: 0.4483 - val_accuracy: 0.8750
Epoch 7/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2386 - accuracy: 0.9333 - val_loss: 0.5775 - val_accuracy: 0.8687
Epoch 8/20
15/15 [==============================] - 0s 17ms/step - loss: 0.1639 - accuracy: 0.9229 - val_loss: 0.4539 - val_accuracy: 0.8750
Epoch 9/20
15/15 [==============================] - 0s 18ms/step - loss: 0.3539 - accuracy: 0.9250 - val_loss: 0.2091 - val_accuracy: 0.9187
Epoch 10/20
15/15 [==============================] - 0s 18ms/step - loss: 0.2705 - accuracy: 0.9271 - val_loss: 0.2505 - val_accuracy: 0.9062
Epoch 11/20
15/15 [==============================] - 0s 17ms/step - loss: 0.2582 - accuracy: 0.9312 - val_loss: 0.2182 - val_accuracy: 0.9250

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

loss, accuracy = my_model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
5/5 [==============================] - 0s 4ms/step - loss: 0.6575 - accuracy: 0.8125
Loss:  0.657511293888092
Accuracy:  0.8125

できたね!

モデルをテストする

次に、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.
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)
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: Animal (0.9570276141166687)
[Your model] The main sound is: dog (0.9999711513519287)

次のステップ

犬や猫の音を分類できるモデルを作成しました。同じアイデアと異なるデータセットを使用して、たとえば、鳥の歌声に基づいて鳥の音響識別子を作成することを試みることができます。

ソーシャルメディアでTensorFlowチームとプロジェクトを共有してください!