간단한 오디오 인식: 키워드 인식

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

이 튜토리얼에서는 10개의 다른 단어를 인식하는 기본 음성 인식 네트워크를 구축하는 방법을 보여줍니다. 실제 음성 및 오디오 인식 시스템은 훨씬 더 복잡하지만 이미지용 MNIST와 마찬가지로 관련된 기술에 대한 기본적인 이해를 제공해야 합니다. 이 자습서를 완료하면 1초 오디오 클립을 "아래로", "이동", "왼쪽", "아니요", "오른쪽", "중지", "위로"로 분류하는 모델을 갖게 됩니다. " 및 "예".

설정

필요한 모듈 및 종속성을 가져옵니다.

import os
import pathlib

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import tensorflow as tf

from tensorflow.keras.layers.experimental import preprocessing
from tensorflow.keras import layers
from tensorflow.keras import models
from IPython import display


# Set seed for experiment reproducibility
seed = 42
tf.random.set_seed(seed)
np.random.seed(seed)

음성 명령 데이터 세트 가져오기

당신은의 일부를 다운로드 할 수있는 스크립트를 작성할 수 있습니다 음성 명령 데이터 세트를 . 원본 데이터 세트는 30개의 다른 단어를 말하는 사람들의 105,000개 이상의 WAV 오디오 파일로 구성됩니다. 이 데이터는 Google에서 수집했으며 CC BY 라이선스에 따라 출시되었습니다.

데이터 로드 시간을 절약하기 위해 데이터세트의 일부를 사용할 것입니다. 추출 mini_speech_commands.zip 하고 사용하여로드 tf.data API를.

data_dir = pathlib.Path('data/mini_speech_commands')
if not data_dir.exists():
  tf.keras.utils.get_file(
      'mini_speech_commands.zip',
      origin="http://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip",
      extract=True,
      cache_dir='.', cache_subdir='data')
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip
182083584/182082353 [==============================] - 1s 0us/step
182091776/182082353 [==============================] - 1s 0us/step

데이터세트에 대한 기본 통계를 확인합니다.

commands = np.array(tf.io.gfile.listdir(str(data_dir)))
commands = commands[commands != 'README.md']
print('Commands:', commands)
Commands: ['no' 'yes' 'left' 'right' 'up' 'stop' 'go' 'down']

오디오 파일을 목록으로 추출하고 섞습니다.

filenames = tf.io.gfile.glob(str(data_dir) + '/*/*')
filenames = tf.random.shuffle(filenames)
num_samples = len(filenames)
print('Number of total examples:', num_samples)
print('Number of examples per label:',
      len(tf.io.gfile.listdir(str(data_dir/commands[0]))))
print('Example file tensor:', filenames[0])
Number of total examples: 8000
Number of examples per label: 1000
Example file tensor: tf.Tensor(b'data/mini_speech_commands/right/a8e25ebb_nohash_0.wav', shape=(), dtype=string)

파일을 각각 80:10:10 비율을 사용하여 훈련, 검증 및 테스트 세트로 분할합니다.

train_files = filenames[:6400]
val_files = filenames[6400: 6400 + 800]
test_files = filenames[-800:]

print('Training set size', len(train_files))
print('Validation set size', len(val_files))
print('Test set size', len(test_files))
Training set size 6400
Validation set size 800
Test set size 800

오디오 파일 및 해당 레이블 읽기

오디오 파일은 처음에 이진 파일로 읽혀지며 이를 수치 텐서로 변환하려고 합니다.

오디오 파일을로드하려면 사용 tf.audio.decode_wav 텐서 샘플 속도와 WAV 인코딩 된 오디오를 반환합니다.

WAV 파일에는 초당 샘플 수가 설정된 시계열 데이터가 포함되어 있습니다. 각 샘플은 특정 시간의 오디오 신호 진폭을 나타냅니다. 16 비트 시스템에서의 파일과 같은 mini_speech_commands , 숫자는 -32768에서 32767이 데이터 세트의 샘플 레이트가 16kHz의 범위이다. 참고 tf.audio.decode_wav 범위 [-1.0, 1.0]에 값을 정규화한다.

def decode_audio(audio_binary):
  audio, _ = tf.audio.decode_wav(audio_binary)
  return tf.squeeze(audio, axis=-1)

각 WAV 파일의 레이블은 상위 디렉토리입니다.

def get_label(file_path):
  parts = tf.strings.split(file_path, os.path.sep)

  # Note: You'll use indexing here instead of tuple unpacking to enable this 
  # to work in a TensorFlow graph.
  return parts[-2]

WAV 파일의 파일 이름을 가져와서 지도 교육을 위한 오디오와 레이블이 포함된 튜플을 출력하는 방법을 정의해 보겠습니다.

def get_waveform_and_label(file_path):
  label = get_label(file_path)
  audio_binary = tf.io.read_file(file_path)
  waveform = decode_audio(audio_binary)
  return waveform, label

이제 적용됩니다 process_path 오디오 레이블 쌍을 추출하고 결과를 확인하기 위해 훈련 세트를 구축 할 수 있습니다. 나중에 유사한 절차를 사용하여 검증 및 테스트 세트를 빌드합니다.

AUTOTUNE = tf.data.AUTOTUNE
files_ds = tf.data.Dataset.from_tensor_slices(train_files)
waveform_ds = files_ds.map(get_waveform_and_label, num_parallel_calls=AUTOTUNE)

해당 레이블이 있는 몇 가지 오디오 파형을 살펴보겠습니다.

rows = 3
cols = 3
n = rows*cols
fig, axes = plt.subplots(rows, cols, figsize=(10, 12))
for i, (audio, label) in enumerate(waveform_ds.take(n)):
  r = i // cols
  c = i % cols
  ax = axes[r][c]
  ax.plot(audio.numpy())
  ax.set_yticks(np.arange(-1.2, 1.2, 0.2))
  label = label.numpy().decode('utf-8')
  ax.set_title(label)

plt.show()

png

스펙트로그램

파형을 시간 경과에 따른 주파수 변화를 보여주고 2D 이미지로 나타낼 수 있는 스펙트로그램으로 변환합니다. 이것은 오디오를 시간-주파수 영역으로 변환하기 위해 단시간 푸리에 변환(STFT)을 적용하여 수행할 수 있습니다.

푸리에 변환 ( tf.signal.fft ) 성분의 주파수로 신호를 변환하고 있지만, 모든 시각 정보를 잃는다. STFT ( tf.signal.stft ) 시간의 창에 신호를 분할하고 푸리에 시간 정보를 보존하고 표준 회선을 실행할 수있는 2 차원 텐서를 반환, 각 창에 변환을 실행합니다.

STFT는 크기와 위상을 나타내는 복소수 배열을 생성합니다. 그러나, 당신은 단지 적용하여 산출 할 수있다이 튜토리얼에 대한 크기해야합니다 tf.abs 의 출력에 tf.signal.stft .

선택 frame_lengthframe_step 생성 된 스펙트로 "이미지"거의 정사각형이되도록 매개 변수를. STFT 매개 변수의 선택에 대한 자세한 내용은, 당신은 참조 할 수 있습니다 이 비디오 오디오 신호 처리합니다.

또한 파형을 동일한 길이로 하여 스펙트로그램 이미지로 변환할 때 결과가 유사한 치수를 갖기를 원합니다. 이것은 1초보다 짧은 오디오 클립을 단순히 제로 패딩하여 수행할 수 있습니다.

def get_spectrogram(waveform):
  # Padding for files with less than 16000 samples
  zero_padding = tf.zeros([16000] - tf.shape(waveform), dtype=tf.float32)

  # Concatenate audio with padding so that all audio clips will be of the 
  # same length
  waveform = tf.cast(waveform, tf.float32)
  equal_length = tf.concat([waveform, zero_padding], 0)
  spectrogram = tf.signal.stft(
      equal_length, frame_length=255, frame_step=128)

  spectrogram = tf.abs(spectrogram)

  return spectrogram

다음으로 데이터를 탐색합니다. 데이터 세트에서 한 예의 파형, 스펙트로그램 및 실제 오디오를 비교합니다.

for waveform, label in waveform_ds.take(1):
  label = label.numpy().decode('utf-8')
  spectrogram = get_spectrogram(waveform)

print('Label:', label)
print('Waveform shape:', waveform.shape)
print('Spectrogram shape:', spectrogram.shape)
print('Audio playback')
display.display(display.Audio(waveform, rate=16000))
Label: right
Waveform shape: (16000,)
Spectrogram shape: (124, 129)
Audio playback

def plot_spectrogram(spectrogram, ax):
  # Convert to frequencies to log scale and transpose so that the time is
  # represented in the x-axis (columns). An epsilon is added to avoid log of zero.
  log_spec = np.log(spectrogram.T+np.finfo(float).eps)
  height = log_spec.shape[0]
  width = log_spec.shape[1]
  X = np.linspace(0, np.size(spectrogram), num=width, dtype=int)
  Y = range(height)
  ax.pcolormesh(X, Y, log_spec)


fig, axes = plt.subplots(2, figsize=(12, 8))
timescale = np.arange(waveform.shape[0])
axes[0].plot(timescale, waveform.numpy())
axes[0].set_title('Waveform')
axes[0].set_xlim([0, 16000])
plot_spectrogram(spectrogram.numpy(), axes[1])
axes[1].set_title('Spectrogram')
plt.show()
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:9: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3.  Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading'].  This will become an error two minor releases later.
  if __name__ == '__main__':

png

이제 스펙트로그램 이미지와 해당 레이블이 정수 ID로 되도록 파형 데이터 세트를 변환합니다.

def get_spectrogram_and_label_id(audio, label):
  spectrogram = get_spectrogram(audio)
  spectrogram = tf.expand_dims(spectrogram, -1)
  label_id = tf.argmax(label == commands)
  return spectrogram, label_id
spectrogram_ds = waveform_ds.map(
    get_spectrogram_and_label_id, num_parallel_calls=AUTOTUNE)

데이터 세트의 다양한 샘플에 대한 스펙트로그램 "이미지"를 검사합니다.

rows = 3
cols = 3
n = rows*cols
fig, axes = plt.subplots(rows, cols, figsize=(10, 10))
for i, (spectrogram, label_id) in enumerate(spectrogram_ds.take(n)):
  r = i // cols
  c = i % cols
  ax = axes[r][c]
  plot_spectrogram(np.squeeze(spectrogram.numpy()), ax)
  ax.set_title(commands[label_id.numpy()])
  ax.axis('off')

plt.show()
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:9: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3.  Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading'].  This will become an error two minor releases later.
  if __name__ == '__main__':

png

모델 빌드 및 학습

이제 모델을 빌드하고 학습할 수 있습니다. 그러나 그렇게 하기 전에 검증 및 테스트 세트에 대해 훈련 세트 전처리를 반복해야 합니다.

def preprocess_dataset(files):
  files_ds = tf.data.Dataset.from_tensor_slices(files)
  output_ds = files_ds.map(get_waveform_and_label, num_parallel_calls=AUTOTUNE)
  output_ds = output_ds.map(
      get_spectrogram_and_label_id,  num_parallel_calls=AUTOTUNE)
  return output_ds
train_ds = spectrogram_ds
val_ds = preprocess_dataset(val_files)
test_ds = preprocess_dataset(test_files)

모델 훈련을 위한 훈련 및 검증 세트를 일괄 처리합니다.

batch_size = 64
train_ds = train_ds.batch(batch_size)
val_ds = val_ds.batch(batch_size)

데이터 세트 추가 cache()prefetch() 모델을 훈련하는 동안 작업은 읽기 대기 시간을 줄일 수 있습니다.

train_ds = train_ds.cache().prefetch(AUTOTUNE)
val_ds = val_ds.cache().prefetch(AUTOTUNE)

모델의 경우 오디오 파일을 스펙트로그램 이미지로 변환했으므로 간단한 CNN(컨볼루션 신경망)을 사용합니다. 이 모델에는 다음과 같은 추가 전처리 레이어도 있습니다.

  • Resizing 층이 더 빨리 훈련 모델을 사용하려면 입력을 다운 샘플링합니다.
  • Normalization 층은 평균과 표준 편차에 기초하여 상기 화상의 각 화소를 정상화한다.

를 들어 Normalization 층의 adapt 방법은 첫째 집계 통계 (예 : 평균과 표준 편차)을 계산하기 위해 훈련 데이터를 호출 할 필요가있다.

for spectrogram, _ in spectrogram_ds.take(1):
  input_shape = spectrogram.shape
print('Input shape:', input_shape)
num_labels = len(commands)

norm_layer = preprocessing.Normalization()
norm_layer.adapt(spectrogram_ds.map(lambda x, _: x))

model = models.Sequential([
    layers.Input(shape=input_shape),
    preprocessing.Resizing(32, 32), 
    norm_layer,
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(64, 3, activation='relu'),
    layers.MaxPooling2D(),
    layers.Dropout(0.25),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(num_labels),
])

model.summary()
Input shape: (124, 129, 1)
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
resizing (Resizing)          (None, 32, 32, 1)         0         
_________________________________________________________________
normalization (Normalization (None, 32, 32, 1)         3         
_________________________________________________________________
conv2d (Conv2D)              (None, 30, 30, 32)        320       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 28, 28, 64)        18496     
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0         
_________________________________________________________________
dropout (Dropout)            (None, 14, 14, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 12544)             0         
_________________________________________________________________
dense (Dense)                (None, 128)               1605760   
_________________________________________________________________
dropout_1 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 8)                 1032      
=================================================================
Total params: 1,625,611
Trainable params: 1,625,608
Non-trainable params: 3
_________________________________________________________________
model.compile(
    optimizer=tf.keras.optimizers.Adam(),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'],
)
EPOCHS = 10
history = model.fit(
    train_ds, 
    validation_data=val_ds,  
    epochs=EPOCHS,
    callbacks=tf.keras.callbacks.EarlyStopping(verbose=1, patience=2),
)
Epoch 1/10
100/100 [==============================] - 13s 40ms/step - loss: 1.7503 - accuracy: 0.3659 - val_loss: 1.3361 - val_accuracy: 0.5600
Epoch 2/10
100/100 [==============================] - 0s 4ms/step - loss: 1.1948 - accuracy: 0.5809 - val_loss: 0.9340 - val_accuracy: 0.7225
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.9253 - accuracy: 0.6773 - val_loss: 0.7738 - val_accuracy: 0.7588
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7543 - accuracy: 0.7341 - val_loss: 0.6993 - val_accuracy: 0.7650
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6473 - accuracy: 0.7719 - val_loss: 0.6500 - val_accuracy: 0.7800
Epoch 6/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5746 - accuracy: 0.7977 - val_loss: 0.6049 - val_accuracy: 0.7975
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5181 - accuracy: 0.8155 - val_loss: 0.5967 - val_accuracy: 0.8200
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4493 - accuracy: 0.8402 - val_loss: 0.5678 - val_accuracy: 0.8100
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4292 - accuracy: 0.8491 - val_loss: 0.5531 - val_accuracy: 0.8175
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.3914 - accuracy: 0.8598 - val_loss: 0.5204 - val_accuracy: 0.8275

훈련 및 검증 손실 곡선을 확인하여 훈련 중에 모델이 어떻게 개선되었는지 알아보겠습니다.

metrics = history.history
plt.plot(history.epoch, metrics['loss'], metrics['val_loss'])
plt.legend(['loss', 'val_loss'])
plt.show()

png

테스트 세트 성능 평가

테스트 세트에서 모델을 실행하고 성능을 확인합시다.

test_audio = []
test_labels = []

for audio, label in test_ds:
  test_audio.append(audio.numpy())
  test_labels.append(label.numpy())

test_audio = np.array(test_audio)
test_labels = np.array(test_labels)
y_pred = np.argmax(model.predict(test_audio), axis=1)
y_true = test_labels

test_acc = sum(y_pred == y_true) / len(y_true)
print(f'Test set accuracy: {test_acc:.0%}')
Test set accuracy: 84%

정오분류표 표시

혼동 행렬은 모델이 테스트 세트의 각 명령에 대해 얼마나 잘 수행했는지 확인하는 데 도움이 됩니다.

confusion_mtx = tf.math.confusion_matrix(y_true, y_pred) 
plt.figure(figsize=(10, 8))
sns.heatmap(confusion_mtx, xticklabels=commands, yticklabels=commands, 
            annot=True, fmt='g')
plt.xlabel('Prediction')
plt.ylabel('Label')
plt.show()

png

오디오 파일에 대한 추론 실행

마지막으로 "아니오"라고 말하는 사람의 입력 오디오 파일을 사용하여 모델의 예측 출력을 확인합니다. 모델의 성능은 어느 정도입니까?

sample_file = data_dir/'no/01bb6a2a_nohash_0.wav'

sample_ds = preprocess_dataset([str(sample_file)])

for spectrogram, label in sample_ds.batch(1):
  prediction = model(spectrogram)
  plt.bar(commands, tf.nn.softmax(prediction[0]))
  plt.title(f'Predictions for "{commands[label[0]]}"')
  plt.show()

png

모델이 오디오 명령을 "아니오"로 매우 명확하게 인식했음을 알 수 있습니다.

다음 단계

이 튜토리얼에서는 TensorFlow 및 Python과 함께 컨벌루션 신경망을 사용하여 간단한 오디오 분류를 수행하는 방법을 보여주었습니다.