Введение
Представьте принцип автоматического распознавания речи (ASR) и реализуйте его с помощью WaveNet.
принцип
Вход ASR — речевой фрагмент, а выход — соответствующее текстовое содержимое.
Общий процесс реализации ASR с использованием Deep Neural Networks (DNN) выглядит следующим образом.
- От сырой речи к акустическим характеристикам
- Введите акустические характеристики в нейронную сеть и выведите соответствующую вероятность
- Вывести текстовую последовательность в соответствии с вероятностью
Обычно используемой акустической характеристикой являются кепстральные коэффициенты частоты Мела (MFCC).woohoo.practical cryptography.com/misc Элла нео…
После разделения исходной речи на небольшие сегменты соответствующие признаки MFCC рассчитываются по каждому сегменту, и может быть получен двумерный массив.
Первое измерение — это количество небольших сегментов, чем длиннее исходная речь, тем больше первое измерение, а второе измерение — это размер функции MFCC.
Получив числовое представление исходной речи, вы можете использовать WaveNet для реализации ASR.
Структура модели WaveNet показана ниже, в основном с использованием многоуровневой каузальной расширенной свертки (Causal Dilated Convolution) и Skip Connections.
Поскольку функции MFCC представляют собой одномерные последовательности, Conv1D используется для свертки.
Причина и следствие означает, что вывод свертки связан только с входом до текущей позиции, то есть он не использует будущие признаки, что можно понимать как сдвиг положения свертки вперед.
Отверстие означает, что свертка выполняется путем прыжка, и после многократного наложения рецептивное поле может быть эффективно расширено, чтобы изучить зависимости между длинными последовательностями.
Количество карт признаков последнего слоя свертки равно размеру словаря.После обработки softmax MFCC, соответствующий каждому маленькому сегменту, может получить распределение вероятностей по всему словарю
Однако количество небольших фрагментов, как правило, больше, чем количество слов в текстовом содержании.Даже если это одно и то же предложение, возможны бесчисленные изменения продолжительности и произношения каждого слова, а также времени паузы между словами. .
В предыдущей сегментации китайских слов длина вероятностной последовательности, выдаваемой моделью, и последовательность меток были одинаковыми, но в ASR они другие.Сходные проблемы включают оптическое распознавание символов (OCR) и т. д.
Здесь алгоритм CTC (временная классификация коннекционистов) используется для вычисления функции потерь,zhuanlan.zhihu.com/p/36488476
данные
Используя следующие данные,www.openslr.org/18/, включая 13388 китайских голосовых файлов и соответствующие текстовые аннотации.
выполнить
Используйте некоторые библиотеки, установите, если нет
pip install python_speech_features librosa
Когда код работает, если он появляетсяNoBackendError
, выполните следующую команду для установкиffmpeg
conda install -c conda-forge ffmpeg
загрузить библиотеку
# -*- coding: utf-8 -*-
from keras.models import Model
from keras.layers import Input, Activation, Conv1D, Lambda, Add, Multiply, BatchNormalization
from keras.optimizers import Adam, SGD
from keras import backend as K
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
%matplotlib inline
import random
import pickle
import glob
from tqdm import tqdm
import os
from python_speech_features import mfcc
import scipy.io.wavfile as wav
import librosa
from IPython.display import Audio
Загрузить пути текстовых выносок и просмотреть
text_paths = glob.glob('data/*.trn')
total = len(text_paths)
print(total)
with open(text_paths[0], 'r', encoding='utf8') as fr:
lines = fr.readlines()
print(lines)
Извлекайте текстовые аннотации и пути к голосовым файлам, сохраняйте китайский язык и удаляйте пробелы.
texts = []
paths = []
for path in text_paths:
with open(path, 'r', encoding='utf8') as fr:
lines = fr.readlines()
line = lines[0].strip('\n').replace(' ', '')
texts.append(line)
paths.append(path.rstrip('.trn'))
print(paths[0], texts[0])
Функция MFCC сохраняет 13 измерений, определяет функцию загрузки голосового файла и удаления тишины на обоих концах, а также функцию визуализации голосового файла.
mfcc_dim = 13
def load_and_trim(path):
audio, sr = librosa.load(path)
energy = librosa.feature.rmse(audio)
frames = np.nonzero(energy >= np.max(energy) / 5)
indices = librosa.core.frames_to_samples(frames)[1]
audio = audio[indices[0]:indices[-1]] if indices.size else audio[0:0]
return audio, sr
def visualize(index):
path = paths[index]
text = texts[index]
print('Audio Text:', text)
audio, sr = load_and_trim(path)
plt.figure(figsize=(12, 3))
plt.plot(np.arange(len(audio)), audio)
plt.title('Raw Audio Signal')
plt.xlabel('Time')
plt.ylabel('Audio Amplitude')
plt.show()
feature = mfcc(audio, sr, numcep=mfcc_dim, nfft=551)
print('Shape of MFCC:', feature.shape)
fig = plt.figure(figsize=(12, 5))
ax = fig.add_subplot(111)
im = ax.imshow(feature, cmap=plt.cm.jet, aspect='auto')
plt.title('Normalized MFCC')
plt.ylabel('Time')
plt.xlabel('MFCC Coefficient')
plt.colorbar(im, cax=make_axes_locatable(ax).append_axes('right', size='5%', pad=0.05))
ax.set_xticks(np.arange(0, 13, 2), minor=False);
plt.show()
return path
Audio(visualize(0))
Исходная форма сигнала и функции MFCC, соответствующие первому голосовому файлу, показаны ниже.
Получите функции MFCC, соответствующие всем голосовым файлам
features = []
for i in tqdm(range(total)):
path = paths[i]
audio, sr = load_and_trim(path)
features.append(mfcc(audio, sr, numcep=mfcc_dim, nfft=551))
print(len(features), features[0].shape)
Нормализация функций MFCC
samples = random.sample(features, 100)
samples = np.vstack(samples)
mfcc_mean = np.mean(samples, axis=0)
mfcc_std = np.std(samples, axis=0)
print(mfcc_mean)
print(mfcc_std)
features = [(feature - mfcc_mean) / (mfcc_std + 1e-14) for feature in features]
Создайте словарь с 2883 разными словами.
chars = {}
for text in texts:
for c in text:
chars[c] = chars.get(c, 0) + 1
chars = sorted(chars.items(), key=lambda x: x[1], reverse=True)
chars = [char[0] for char in chars]
print(len(chars), chars[:100])
char2id = {c: i for i, c in enumerate(chars)}
id2char = {i: c for i, c in enumerate(chars)}
Разделите обучающие данные и тестовые данные и определите функцию для генерации пакетных данных.
data_index = np.arange(total)
np.random.shuffle(data_index)
train_size = int(0.9 * total)
test_size = total - train_size
train_index = data_index[:train_size]
test_index = data_index[train_size:]
X_train = [features[i] for i in train_index]
Y_train = [texts[i] for i in train_index]
X_test = [features[i] for i in test_index]
Y_test = [texts[i] for i in test_index]
batch_size = 16
def batch_generator(x, y, batch_size=batch_size):
offset = 0
while True:
offset += batch_size
if offset == batch_size or offset >= len(x):
data_index = np.arange(len(x))
np.random.shuffle(data_index)
x = [x[i] for i in data_index]
y = [y[i] for i in data_index]
offset = batch_size
X_data = x[offset - batch_size: offset]
Y_data = y[offset - batch_size: offset]
X_maxlen = max([X_data[i].shape[0] for i in range(batch_size)])
Y_maxlen = max([len(Y_data[i]) for i in range(batch_size)])
X_batch = np.zeros([batch_size, X_maxlen, mfcc_dim])
Y_batch = np.ones([batch_size, Y_maxlen]) * len(char2id)
X_length = np.zeros([batch_size, 1], dtype='int32')
Y_length = np.zeros([batch_size, 1], dtype='int32')
for i in range(batch_size):
X_length[i, 0] = X_data[i].shape[0]
X_batch[i, :X_length[i, 0], :] = X_data[i]
Y_length[i, 0] = len(Y_data[i])
Y_batch[i, :Y_length[i, 0]] = [char2id[c] for c in Y_data[i]]
inputs = {'X': X_batch, 'Y': Y_batch, 'X_length': X_length, 'Y_length': Y_length}
outputs = {'ctc': np.zeros([batch_size])}
yield (inputs, outputs)
Определите параметры обучения и структуру модели и начните обучение
epochs = 50
num_blocks = 3
filters = 128
X = Input(shape=(None, mfcc_dim,), dtype='float32', name='X')
Y = Input(shape=(None,), dtype='float32', name='Y')
X_length = Input(shape=(1,), dtype='int32', name='X_length')
Y_length = Input(shape=(1,), dtype='int32', name='Y_length')
def conv1d(inputs, filters, kernel_size, dilation_rate):
return Conv1D(filters=filters, kernel_size=kernel_size, strides=1, padding='causal', activation=None, dilation_rate=dilation_rate)(inputs)
def batchnorm(inputs):
return BatchNormalization()(inputs)
def activation(inputs, activation):
return Activation(activation)(inputs)
def res_block(inputs, filters, kernel_size, dilation_rate):
hf = activation(batchnorm(conv1d(inputs, filters, kernel_size, dilation_rate)), 'tanh')
hg = activation(batchnorm(conv1d(inputs, filters, kernel_size, dilation_rate)), 'sigmoid')
h0 = Multiply()([hf, hg])
ha = activation(batchnorm(conv1d(h0, filters, 1, 1)), 'tanh')
hs = activation(batchnorm(conv1d(h0, filters, 1, 1)), 'tanh')
return Add()([ha, inputs]), hs
h0 = activation(batchnorm(conv1d(X, filters, 1, 1)), 'tanh')
shortcut = []
for i in range(num_blocks):
for r in [1, 2, 4, 8, 16]:
h0, s = res_block(h0, filters, 7, r)
shortcut.append(s)
h1 = activation(Add()(shortcut), 'relu')
h1 = activation(batchnorm(conv1d(h1, filters, 1, 1)), 'relu')
Y_pred = activation(batchnorm(conv1d(h1, len(char2id) + 1, 1, 1)), 'softmax')
sub_model = Model(inputs=X, outputs=Y_pred)
def calc_ctc_loss(args):
y, yp, ypl, yl = args
return K.ctc_batch_cost(y, yp, ypl, yl)
ctc_loss = Lambda(calc_ctc_loss, output_shape=(1,), name='ctc')([Y, Y_pred, X_length, Y_length])
model = Model(inputs=[X, Y, X_length, Y_length], outputs=ctc_loss)
optimizer = SGD(lr=0.02, momentum=0.9, nesterov=True, clipnorm=5)
model.compile(loss={'ctc': lambda ctc_true, ctc_pred: ctc_pred}, optimizer=optimizer)
checkpointer = ModelCheckpoint(filepath='asr.h5', verbose=0)
lr_decay = ReduceLROnPlateau(monitor='loss', factor=0.2, patience=1, min_lr=0.000)
history = model.fit_generator(
generator=batch_generator(X_train, Y_train),
steps_per_epoch=len(X_train) // batch_size,
epochs=epochs,
validation_data=batch_generator(X_test, Y_test),
validation_steps=len(X_test) // batch_size,
callbacks=[checkpointer, lr_decay])
Сохранение моделей и словарей
sub_model.save('asr.h5')
with open('dictionary.pkl', 'wb') as fw:
pickle.dump([char2id, id2char, mfcc_mean, mfcc_std], fw)
Постройте кривую функции потерь во время обучения
train_loss = history.history['loss']
valid_loss = history.history['val_loss']
plt.plot(np.linspace(1, epochs, epochs), train_loss, label='train')
plt.plot(np.linspace(1, epochs, epochs), valid_loss, label='valid')
plt.legend(loc='upper right')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
Загрузите модель и случайным образом распознайте речь в обучающем наборе и тестовом наборе.
from keras.models import load_model
import pickle
with open('dictionary.pkl', 'rb') as fr:
[char2id, id2char, mfcc_mean, mfcc_std] = pickle.load(fr)
sub_model = load_model('asr.h5')
def random_predict(x, y):
index = np.random.randint(len(x))
feature = x[index]
text = y[index]
pred = sub_model.predict(np.expand_dims(feature, axis=0))
pred_ids = K.eval(K.ctc_decode(pred, [feature.shape[0]], greedy=False, beam_width=10, top_paths=1)[0][0])
pred_ids = pred_ids.flatten().tolist()
print('True transcription:\n-- ', text, '\n')
print('Predicted transcription:\n-- ' + ''.join([id2char[i] for i in pred_ids]), '\n')
random_predict(X_train, Y_train)
random_predict(X_test, Y_test)
В обучающей выборке случайным образом выбирается речь, правильный текст и распознанный текст соответственно
- В настоящее время он догоняет институциональные изменения префектуры Сяньян.Первоначальный город Сяньян был изменен на район Цинду, район Сяньян был изменен на город Сяньян.
- В настоящее время он догоняет институциональные изменения префектуры Сяньян.Первоначальный город Сяньян был изменен на район Цинду, район Сяньян был изменен на город Сяньян.
В тестовом наборе случайным образом выбирается речь, правильный текст и распознанный текст соответственно
- Вся партия должна плотно сплотиться вокруг ядра центрального комитета партии, целеустремленно и неуклонно, чтобы создать лучшее будущее.
- Люди на Юге должны объединиться в ядре ЦК партии, быть объединены одним сердцем и единым разумом, учить и бороться вместе, чтобы создать лучшее будущее.
Загрузите обученную модель локально, случайным образом выберите файл записи и идентифицируйте его.
# -*- coding: utf-8 -*-
from keras.models import load_model
from keras import backend as K
import numpy as np
import librosa
from python_speech_features import mfcc
import pickle
import glob
wavs = glob.glob('data/*.wav')
with open('dictionary.pkl', 'rb') as fr:
[char2id, id2char, mfcc_mean, mfcc_std] = pickle.load(fr)
mfcc_dim = 13
model = load_model('asr.h5')
index = np.random.randint(len(wavs))
print(wavs[index])
audio, sr = librosa.load(wavs[index])
energy = librosa.feature.rmse(audio)
frames = np.nonzero(energy >= np.max(energy) / 5)
indices = librosa.core.frames_to_samples(frames)[1]
audio = audio[indices[0]:indices[-1]] if indices.size else audio[0:0]
X_data = mfcc(audio, sr, numcep=mfcc_dim, nfft=551)
X_data = (X_data - mfcc_mean) / (mfcc_std + 1e-14)
print(X_data.shape)
with open(wavs[index] + '.trn', 'r', encoding='utf8') as fr:
label = fr.readlines()[0]
print(label)
pred = model.predict(np.expand_dims(X_data, axis=0))
pred_ids = K.eval(K.ctc_decode(pred, [X_data.shape[0]], greedy=False, beam_width=10, top_paths=1)[0][0])
pred_ids = pred_ids.flatten().tolist()
print(''.join([id2char[i] for i in pred_ids]))
Ссылаться на
- WaveNet: генеративная модель для необработанного аудио:АР Вест V.org/ABS/1609.03…
- Проект VUI Capstone — Udacity:GitHub.com/Ubig city/ain…
- Преобразование речи в текст-WaveNet:GitHub.com/Обычные люди…