12월 7일 Women in ML 심포지엄 참석 지금 등록

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

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

TensorFlow.org에서 보기 Google Colab에서 실행 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

얌넷 소개

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

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

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

모델은 클래스 점수, 임베딩(전이 학습에 사용할 것) 및 로그 멜 스펙트로그램 을 포함하여 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
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

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
645103616/Unknown - 47s 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 에 저장되었으므로 몇 가지 변환을 적용합니다.

  • 행을 필터링하고 선택한 클래스( 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은 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)

모델 만들기

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

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)

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-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]

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: Silence (0.500638484954834)
[Your model] The main sound is: cat (0.9981643557548523)

다음 단계

개나 고양이의 소리를 분류할 수 있는 모델을 만들었습니다. 예를 들어, 같은 아이디어와 다른 데이터 세트를 사용하여 새의 노래를 기반으로 새의 음향 식별자를 구축해 볼 수 있습니다.

소셜 미디어에서 TensorFlow 팀과 프로젝트를 공유하세요!