環境音分類のための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の深さ方向に分離可能畳み込みアーキテクチャを。それは、入力としてオーディオ波形を使用してから521のオーディオイベントのそれぞれについて独立に予測することができAudioSetのコーパス。

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

モデルは、任意の長さの波形を含む1-Dのfloat32テンソルまたはnumpyの配列を受け取り、シングルチャンネル(モノラル)のように範囲16 kHzのサンプルを表現[-1.0, +1.0]このチュートリアルには、WAVファイルをサポートされている形式に変換するのに役立つコードが含まれています。

モデルは、クラススコア、(あなたが転移学習のために使用します)埋め込み、およびログメル含む3つの出力、返しスペクトログラムを。あなたはより多くの細部を見つけることができるここに

YAMNetの特定の用途の1つは、高レベルの特徴抽出器としての1,024次元の埋め込み出力です。あなたはベース(YAMNet)モデルの入力機能を使用すると、1つの隠れからなるあなたの浅いモデルにそれらを供給しますtf.keras.layers.Dense層。その後、あなたはラベルされた大量のデータを必要とし、エンド・ツー・エンドを訓練することなく、オーディオ分類のための少量のデータのネットワークを学習します。 (これはと同様であるTensorFlowハブと画像分類のための学習を転送詳細について)。

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

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

あなたはから事前に訓練を受けたYAMNetを使用しようとしているTensorflowハブサウンドファイルからの埋め込みを抽出します。

TensorFlowハブからモデルをロードすると簡単です。その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
./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 )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 - 41s 0us/step

データを調べる

各ファイルのメタデータがで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 、いくつかの変換を適用します。

  • 行と使用アウトフィルタは、選択したクラス- dogcat 。他のクラスを使用する場合は、ここで選択できます。
  • ファイル名を修正してフルパスにします。これにより、後でロードしやすくなります。
  • 特定の範囲内になるようにターゲットを変更します。この例では、 dogのままで0が、 catとなるであろう1の代わりに元の値5
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同じ元のソースからのクリップが同じに常にあるように、S 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()
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チームとプロジェクトを共有してください!