환경 소리 분류를 위해 YAMNet을 사용한 전이 학습

TensorFlow.org에서보기 Google Colab에서 실행 GitHub에서보기 노트북 다운로드 TF Hub 모델보기

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 정보

YAMNetMobileNetV1 심도 분리형 컨볼 루션 아키텍처를 사용하는 사전 훈련 된 신경망입니다. 오디오 파형을 입력으로 사용하고 AudioSet 코퍼스의 521 오디오 이벤트 각각에 대해 독립적 인 예측을 할 수 있습니다.

내부적으로 모델은 오디오 신호에서 "프레임"을 추출하고 이러한 프레임의 배치를 처리합니다. 이 모델 버전은 0.96 초 길이의 프레임을 사용하고 0.48 초마다 한 프레임을 추출합니다.

모델은 [-1.0, +1.0] 범위의 단일 채널 (모노) 16kHz 샘플로 표현되는 임의 길이의 파형을 포함하는 1D float32 Tensor 또는 NumPy 배열을받습니다. 이 자습서에는 WAV 파일을 지원되는 형식으로 변환하는 데 도움이되는 코드가 포함되어 있습니다.

모델은 클래스 점수, 임베딩 (전이 학습에 사용) 및 log mel 스펙트로 그램을 포함하여 3 개의 출력을 반환합니다. 여기에서 자세한 내용을 확인할 수 있습니다.

YAMNet의 특정 용도 중 하나는 1,024 차원 임베딩 출력 인 고수준 기능 추출기입니다. 기본 (YAMNet) 모델의 입력 기능을 사용하고 숨겨진 tf.keras.layers.Dense 레이어로 구성된 더 얕은 모델에 입력합니다. 그런 다음 레이블이 지정된 많은 데이터와 종단 간 교육 없이 오디오 분류 위해 소량의 데이터로 네트워크를 교육합니다. (자세한 내용 은 TensorFlow Hub사용한 이미지 분류를위한 전이 학습 과 유사합니다.)

먼저 모델을 테스트하고 오디오 분류 결과를 확인합니다. 그런 다음 데이터 사전 처리 파이프 라인을 구성합니다.

TensorFlow Hub에서 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
./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이 인식 할 수있는 클래스 이름을로드하는 것이 중요합니다. 매핑 파일은 CSV 형식으로 yamnet_model.class_map_path() 에 있습니다.

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 )는 5 초 길이의 환경 오디오 녹음 2,000 개의 레이블이있는 컬렉션입니다. 데이터 세트는 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/audio/

매핑으로 pandas 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 초마다 하나씩).

모델은 각 프레임을 하나의 입력으로 사용합니다. 따라서 행당 하나의 프레임이있는 새 열을 만들어야합니다. 또한 이러한 새 행을 적절하게 반영하도록 레이블과 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 열을 제거하는 것입니다.

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)

모델 만들기

대부분의 작업을 수행했습니다! 다음으로, 소리에서 고양이와 개를 인식하기 위해 하나의 히든 레이어와 두 개의 출력으로 매우 간단한 순차 모델을 정의합니다.

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 팀과 프로젝트를 공유하세요!