לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

זיהוי שמע פשוט: זיהוי מילות מפתח

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

מדריך זה יראה לכם כיצד לבנות רשת זיהוי דיבור בסיסית המזהה עשר מילים שונות. חשוב לדעת שמערכות דיבור ושמע אמיתי הן הרבה יותר מורכבות, אך כמו MNIST לתמונות, זה אמור לתת לך הבנה בסיסית של הטכניקות המעורבות. לאחר שתסיים את ההדרכה הזו, יהיה לך דגם שמנסה לסווג קליפ שמע שנייה אחד כ- "למטה", "עבור", "שמאלה", "לא", "ימינה", "עצור", "למעלה " וכן".

להכין

ייבא מודולים ותלות נחוצים.

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)

ייבא את מערך פקודות הדיבור

תכתוב סקריפט להורדת חלק ממערך הנתונים של פקודות דיבור . מערך הנתונים המקורי מורכב מיותר מ -105,000 קבצי שמע WAV של אנשים שאומרים שלושים מילים שונות. נתונים אלה נאספו על ידי גוגל ושוחררו ברישיון CC BY.

אתה תשתמש בחלק ממערך הנתונים כדי לחסוך זמן עם טעינת הנתונים. חלץ את mini_speech_commands.zip אותו באמצעות ממשק ה- API של tf.data .

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

בדוק נתונים סטטיסטיים בסיסיים אודות מערך הנתונים.

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

חלץ את קבצי השמע לרשימה וטלטל אותם.

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/up/50f55535_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 כ- Tensor ואת קצב הדגימה.

קובץ 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 לבניית מערך האימונים שלך לחילוץ זוגות 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

ספקטרוגרמה

תוכלו להמיר את צורת הגל לספקטרוגרמה, המציגה שינויי תדרים לאורך זמן וניתנת לייצוג כתמונת דו מימד. ניתן לעשות זאת על ידי החלת טרנספורמציית פורייה קצרה (STFT) להמרת האודיו לתחום תדר הזמן.

טרנספורמציה של פורייה ( tf.signal.fft ) ממירה אות לתדרי הרכיב שלה, אך מאבדת מידע כל הזמן. ה- STFT ( tf.signal.stft .

STFT מייצר מערך של מספרים מורכבים המייצגים גודל ומשלב. עם זאת, תצטרך רק את גודל הדרכה זו, שניתן להפיק באמצעות יישום tf.abs על הפלט של tf.signal.stft .

בחר frame_length ו frame_step פרמטרים כגון כי ספקטרוגרמה שנוצר "תמונה" הוא כמעט מרובע. לקבלת מידע נוסף על בחירת פרמטרים STFT, אתה יכול להתייחס לסרטון זה על עיבוד אותות שמע.

אתה גם רוצה שצורות הגל יהיו באותו אורך, כך שכאשר תמיר אותו לתמונת ספקטרוגרמה, התוצאות יהיו בעלות מידות דומות. ניתן לעשות זאת פשוט על ידי ריפוד אפס של קטעי השמע שהם קצרים משנייה אחת.

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: up
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).
  log_spec = np.log(spectrogram.T)
  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.6/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 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.6/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__':
/home/kbuilder/.local/lib/python3.6/site-packages/ipykernel_launcher.py:4: RuntimeWarning: divide by zero encountered in log
  after removing the cwd from sys.path.

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() ופעולות אחזור prefetch() כדי להפחית את חביון הקריאה בזמן אימון המודל.

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

עבור המודל, תשתמש ברשת עצבית פשוטה (CNN), מכיוון שהפכת את קבצי השמע לתמונות ספקטרוגרמה. המודל כולל גם שכבות עיבוד מקדימות נוספות:

  • שכבת Resizing לשינוי 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 [==============================] - 16s 45ms/step - loss: 1.9259 - accuracy: 0.2930 - val_loss: 1.3422 - val_accuracy: 0.5638
Epoch 2/10
100/100 [==============================] - 0s 4ms/step - loss: 1.2773 - accuracy: 0.5598 - val_loss: 0.9881 - val_accuracy: 0.6812
Epoch 3/10
100/100 [==============================] - 0s 4ms/step - loss: 0.9726 - accuracy: 0.6546 - val_loss: 0.7903 - val_accuracy: 0.7362
Epoch 4/10
100/100 [==============================] - 0s 4ms/step - loss: 0.7634 - accuracy: 0.7252 - val_loss: 0.6914 - val_accuracy: 0.7550
Epoch 5/10
100/100 [==============================] - 0s 4ms/step - loss: 0.6580 - accuracy: 0.7596 - val_loss: 0.6356 - val_accuracy: 0.7975
Epoch 6/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5770 - accuracy: 0.7912 - val_loss: 0.5791 - val_accuracy: 0.8062
Epoch 7/10
100/100 [==============================] - 0s 4ms/step - loss: 0.5080 - accuracy: 0.8128 - val_loss: 0.5495 - val_accuracy: 0.8163
Epoch 8/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4813 - accuracy: 0.8282 - val_loss: 0.5315 - val_accuracy: 0.8087
Epoch 9/10
100/100 [==============================] - 0s 4ms/step - loss: 0.4303 - accuracy: 0.8467 - val_loss: 0.4886 - val_accuracy: 0.8263
Epoch 10/10
100/100 [==============================] - 0s 4ms/step - loss: 0.3820 - accuracy: 0.8646 - val_loss: 0.5023 - val_accuracy: 0.8413

בואו לבדוק את עקומות האימון ואיבוד האימות כדי לראות כיצד המודל שלכם השתפר במהלך האימון.

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

הצג מטריצת בלבול

מטריצת בלבול מועילה לראות עד כמה המודל הצליח בכל אחת מהפקודות בערכת הבדיקה.

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.