تشخیص ساده صدا: تشخیص کلمات کلیدی

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در GitHub دانلود دفترچه یادداشت

این آموزش نحوه پیش پردازش فایل های صوتی در فرمت WAV و ساخت و آموزش یک مدل اصلی تشخیص گفتار خودکار (ASR) برای تشخیص ده کلمه مختلف را نشان می دهد. شما از بخشی از مجموعه دستورات گفتار استفاده خواهید کرد ( واردن، 2018 )، که شامل کلیپ های صوتی کوتاه (یک ثانیه یا کمتر) از دستورات، مانند "پایین"، "برو"، "چپ"، "نه"، "است. راست، «ایست»، «بالا» و «بله».

سیستم های تشخیص گفتار و صدا در دنیای واقعی پیچیده هستند. اما، مانند طبقه‌بندی تصویر با مجموعه داده‌های MNIST ، این آموزش باید به شما درک اساسی از تکنیک‌های مربوطه بدهد.

برپایی

ماژول ها و وابستگی های لازم را وارد کنید. توجه داشته باشید که در این آموزش از seaborn برای تجسم استفاده خواهید کرد.

import os
import pathlib

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

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

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

مجموعه داده دستورات گفتار کوچک را وارد کنید

برای صرفه جویی در زمان با بارگیری داده ها، با نسخه کوچکتری از مجموعه داده دستورات گفتار کار خواهید کرد. مجموعه داده اصلی شامل بیش از 105000 فایل صوتی در فرمت فایل صوتی WAV (شکل موج) از افرادی است که 35 کلمه مختلف می گویند. این داده ها توسط Google جمع آوری شده و تحت مجوز CC BY منتشر شده است.

فایل mini_speech_commands.zip حاوی مجموعه داده های کوچکتر Speech Commands را با tf.keras.utils.get_file دانلود و استخراج کنید:

DATASET_PATH = 'data/mini_speech_commands'

data_dir = pathlib.Path(DATASET_PATH)
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

کلیپ‌های صوتی مجموعه داده در هشت پوشه مربوط به هر فرمان گفتاری ذخیره می‌شوند: no ، yes ، down ، go ، left ، up ، right و stop :

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

کلیپ های صوتی را در لیستی به نام نام filenames ها استخراج کنید و آن را به هم بزنید:

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/yes/db72a474_nohash_0.wav', shape=(), dtype=string)

با استفاده از نسبت 80:10:10، filenames را به مجموعه‌های آموزشی، اعتبارسنجی و آزمایشی تقسیم کنید:

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

فایل های صوتی و برچسب های آنها را بخوانید

در این بخش، مجموعه داده را از قبل پردازش می‌کنید و تانسورهای رمزگشایی شده برای شکل‌های موج و برچسب‌های مربوطه ایجاد می‌کنید. توجه داشته باشید که:

  • هر فایل WAV حاوی داده های سری زمانی با تعدادی نمونه در ثانیه است.
  • هر نمونه نشان دهنده دامنه سیگنال صوتی در آن زمان خاص است.
  • در یک سیستم 16 بیتی ، مانند فایل های WAV در مجموعه داده دستورات گفتاری کوچک، مقادیر دامنه از 32768- تا 32767 متغیر است.
  • نرخ نمونه برای این مجموعه داده 16 کیلوهرتز است.

شکل تانسور بازگردانده شده توسط tf.audio.decode_wav [samples, channels] است، که در آن channels 1 برای مونو یا 2 برای استریو است. مجموعه داده دستورات گفتار کوچک فقط شامل ضبط‌های تک است.

test_file = tf.io.read_file(DATASET_PATH+'/down/0a9f9af7_nohash_0.wav')
test_audio, _ = tf.audio.decode_wav(contents=test_file)
test_audio.shape
TensorShape([13654, 1])

اکنون، بیایید تابعی را تعریف کنیم که فایل های صوتی WAV خام مجموعه داده را به تانسورهای صوتی از قبل پردازش می کند:

def decode_audio(audio_binary):
  # Decode WAV-encoded audio files to `float32` tensors, normalized
  # to the [-1.0, 1.0] range. Return `float32` audio and a sample rate.
  audio, _ = tf.audio.decode_wav(contents=audio_binary)
  # Since all the data is single channel (mono), drop the `channels`
  # axis from the array.
  return tf.squeeze(audio, axis=-1)

تابعی را تعریف کنید که با استفاده از دایرکتوری های والد برای هر فایل برچسب ایجاد می کند:

  • مسیرهای فایل را به tf.RaggedTensor s (تانسورهایی با ابعاد ناهموار - با برش هایی که ممکن است طول های متفاوتی داشته باشند) تقسیم کنید.
def get_label(file_path):
  parts = tf.strings.split(
      input=file_path,
      sep=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]

تابع کمکی دیگری را تعریف کنید - get_waveform_and_label - که همه را با هم جمع می کند:

  • ورودی نام فایل صوتی 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

مجموعه آموزشی را برای استخراج جفت‌های برچسب صوتی بسازید:

بعداً با استفاده از روشی مشابه، مجموعه‌های اعتبارسنجی و آزمایش را خواهید ساخت.

AUTOTUNE = tf.data.AUTOTUNE

files_ds = tf.data.Dataset.from_tensor_slices(train_files)

waveform_ds = files_ds.map(
    map_func=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

تبدیل شکل موج به طیف نگار

شکل موج در مجموعه داده در حوزه زمان نمایش داده می شود. در مرحله بعد، با محاسبه تبدیل فوریه کوتاه مدت (STFT) شکل موج ها را از سیگنال های حوزه زمان به سیگنال های حوزه فرکانس زمانی تبدیل می کنید تا شکل موج ها را به طیف نگار تبدیل کنید، که تغییرات فرکانس را در طول زمان نشان می دهد و می تواند به صورت تصاویر دو بعدی نمایش داده می شود. برای آموزش مدل، تصاویر طیف‌گرام را وارد شبکه عصبی خود می‌کنید.

تبدیل فوریه ( tf.signal.fft ) یک سیگنال را به فرکانس های جزء آن تبدیل می کند، اما تمام اطلاعات زمانی را از دست می دهد. در مقایسه، STFT ( tf.signal.stft ) سیگنال را به پنجره‌های زمان تقسیم می‌کند و یک تبدیل فوریه را روی هر پنجره اجرا می‌کند، مقداری اطلاعات زمانی را حفظ می‌کند و یک تانسور دو بعدی را برمی‌گرداند که می‌توانید کانولوشن‌های استاندارد را روی آن اجرا کنید.

یک تابع کاربردی برای تبدیل شکل موج به طیف نگار ایجاد کنید:

  • طول شکل موج ها باید یکسان باشد، به طوری که وقتی آنها را به طیف نگار تبدیل می کنید، نتایج دارای ابعاد مشابه باشند. این کار را می‌توان با صفر کردن کلیپ‌های صوتی کوتاه‌تر از یک ثانیه (با استفاده از tf.zeros ) انجام داد.
  • هنگام فراخوانی tf.signal.stft ، پارامترهای frame_length و frame_step را طوری انتخاب کنید که طیف‌گرام ایجاد شده "تصویر" تقریباً مربع باشد. برای اطلاعات بیشتر در مورد انتخاب پارامترهای STFT، به این ویدیوی Coursera در مورد پردازش سیگنال صوتی و STFT مراجعه کنید.
  • STFT آرایه ای از اعداد مختلط را تولید می کند که نشان دهنده قدر و فاز است. با این حال، در این آموزش شما فقط از مقدار استفاده می کنید که می توانید با اعمال tf.abs در خروجی tf.signal.stft به دست آورید.
def get_spectrogram(waveform):
  # Zero-padding for an audio waveform with less than 16,000 samples.
  input_len = 16000
  waveform = waveform[:input_len]
  zero_padding = tf.zeros(
      [16000] - tf.shape(waveform),
      dtype=tf.float32)
  # Cast the waveform tensors' dtype to float32.
  waveform = tf.cast(waveform, dtype=tf.float32)
  # Concatenate the waveform with `zero_padding`, which ensures all audio
  # clips are of the same length.
  equal_length = tf.concat([waveform, zero_padding], 0)
  # Convert the waveform to a spectrogram via a STFT.
  spectrogram = tf.signal.stft(
      equal_length, frame_length=255, frame_step=128)
  # Obtain the magnitude of the STFT.
  spectrogram = tf.abs(spectrogram)
  # Add a `channels` dimension, so that the spectrogram can be used
  # as image-like input data with convolution layers (which expect
  # shape (`batch_size`, `height`, `width`, `channels`).
  spectrogram = spectrogram[..., tf.newaxis]
  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: yes
Waveform shape: (16000,)
Spectrogram shape: (124, 129, 1)
Audio playback

حالا تابعی را برای نمایش یک طیف نگار تعریف کنید:

def plot_spectrogram(spectrogram, ax):
  if len(spectrogram.shape) > 2:
    assert len(spectrogram.shape) == 3
    spectrogram = np.squeeze(spectrogram, axis=-1)
  # Convert the frequencies to log scale and transpose, so that the time is
  # represented on the x-axis (columns).
  # Add an epsilon to avoid taking a 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()

png

اکنون، تابعی را تعریف کنید که مجموعه داده شکل موج را به طیف‌نگارها و برچسب‌های مربوط به آنها به عنوان شناسه‌های عدد صحیح تبدیل می‌کند:

def get_spectrogram_and_label_id(audio, label):
  spectrogram = get_spectrogram(audio)
  label_id = tf.argmax(label == commands)
  return spectrogram, label_id

نقشه get_spectrogram_and_label_id در عناصر مجموعه داده با Dataset.map :

spectrogram_ds = waveform_ds.map(
  map_func=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(spectrogram.numpy(), ax)
  ax.set_title(commands[label_id.numpy()])
  ax.axis('off')

plt.show()

png

مدل را بسازید و آموزش دهید

پیش پردازش مجموعه آموزشی را در مجموعه های اعتبار سنجی و آزمایش تکرار کنید:

def preprocess_dataset(files):
  files_ds = tf.data.Dataset.from_tensor_slices(files)
  output_ds = files_ds.map(
      map_func=get_waveform_and_label,
      num_parallel_calls=AUTOTUNE)
  output_ds = output_ds.map(
      map_func=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)

عملیات Dataset.cache و Dataset.prefetch را برای کاهش تأخیر خواندن در حین آموزش مدل اضافه کنید:

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

برای مدل، از یک شبکه عصبی کانولوشنال ساده (CNN) استفاده خواهید کرد، زیرا فایل‌های صوتی را به تصاویر طیف‌نگاری تبدیل کرده‌اید.

مدل tf.keras.Sequential شما از لایه های پیش پردازش Keras زیر استفاده می کند:

  • tf.keras.layers.Resizing : برای پایین آوردن نمونه ورودی برای فعال کردن مدل برای آموزش سریعتر.
  • tf.keras.layers.Normalization : برای عادی سازی هر پیکسل در تصویر بر اساس میانگین و انحراف استاندارد آن.

برای لایه Normalization ، روش adapt آن ابتدا باید روی داده های آموزشی فراخوانی شود تا آمار کل (یعنی میانگین و انحراف استاندارد) محاسبه شود.

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

# Instantiate the `tf.keras.layers.Normalization` layer.
norm_layer = layers.Normalization()
# Fit the state of the layer to the spectrograms
# with `Normalization.adapt`.
norm_layer.adapt(data=spectrogram_ds.map(map_func=lambda spec, label: spec))

model = models.Sequential([
    layers.Input(shape=input_shape),
    # Downsample the input.
    layers.Resizing(32, 32),
    # Normalize.
    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 (Normalizatio  (None, 32, 32, 1)        3         
 n)                                                              
                                                                 
 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
_________________________________________________________________

مدل Keras را با بهینه ساز Adam و از دست دادن آنتروپی متقابل پیکربندی کنید:

model.compile(
    optimizer=tf.keras.optimizers.Adam(),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'],
)

این مدل را بیش از 10 دوره برای اهداف نمایشی آموزش دهید:

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 [==============================] - 6s 41ms/step - loss: 1.7503 - accuracy: 0.3630 - val_loss: 1.2850 - val_accuracy: 0.5763
Epoch 2/10
100/100 [==============================] - 0s 5ms/step - loss: 1.2101 - accuracy: 0.5698 - val_loss: 0.9314 - val_accuracy: 0.6913
Epoch 3/10
100/100 [==============================] - 0s 5ms/step - loss: 0.9336 - accuracy: 0.6703 - val_loss: 0.7529 - val_accuracy: 0.7325
Epoch 4/10
100/100 [==============================] - 0s 5ms/step - loss: 0.7503 - accuracy: 0.7397 - val_loss: 0.6721 - val_accuracy: 0.7713
Epoch 5/10
100/100 [==============================] - 0s 5ms/step - loss: 0.6367 - accuracy: 0.7741 - val_loss: 0.6061 - val_accuracy: 0.7975
Epoch 6/10
100/100 [==============================] - 0s 5ms/step - loss: 0.5650 - accuracy: 0.7987 - val_loss: 0.5489 - val_accuracy: 0.8125
Epoch 7/10
100/100 [==============================] - 0s 5ms/step - loss: 0.5099 - accuracy: 0.8183 - val_loss: 0.5344 - val_accuracy: 0.8238
Epoch 8/10
100/100 [==============================] - 0s 5ms/step - loss: 0.4560 - accuracy: 0.8392 - val_loss: 0.5194 - val_accuracy: 0.8288
Epoch 9/10
100/100 [==============================] - 0s 5ms/step - loss: 0.4101 - accuracy: 0.8547 - val_loss: 0.4809 - val_accuracy: 0.8388
Epoch 10/10
100/100 [==============================] - 0s 5ms/step - loss: 0.3905 - accuracy: 0.8589 - val_loss: 0.4973 - val_accuracy: 0.8363

بیایید منحنی‌های از دست دادن آموزش و اعتبارسنجی را ترسیم کنیم تا بررسی کنیم که مدل شما در طول آموزش چگونه بهبود یافته است:

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: 85%

نمایش یک ماتریس سردرگمی

از یک ماتریس سردرگمی استفاده کنید تا بررسی کنید که مدل در طبقه بندی هر یک از دستورات در مجموعه آزمایشی چقدر خوب عمل کرده است:

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 نشان می‌دهد. برای کسب اطلاعات بیشتر، منابع زیر را در نظر بگیرید: