Введение
В сочетании с содержанием предыдущего урока используйте WaveNet для классификации речи.
принцип
Для каждой функции MFCC выводится распределение вероятностей, которое затем объединяется с алгоритмом CTC для достижения распознавания речи.
Напротив, классификация речи намного проще, потому что для всей последовательности признаков MFCC необходимо вывести только один результат классификации.
Разницу между классификацией речи и распознаванием речи можно сравнить с разницей между классификацией текста и маркировкой последовательности.
В конкретной реализации требуется лишь небольшая модификация сетевой структуры.
данные
Использование типов диалектов iFLYTEK для идентификации данных, предоставленных AI Challenge,challenge.xfyun.cn/, в предварительном туре предоставляется 6 диалектов, а в полуфинале предоставляется 10 диалектов.
Каждый диалект включает 30 человек с 200 единицами обучающих данных, всего 6000 единиц, и 10 человек с 50 единицами данных, всего 500 единиц проверочных данных.
Данные предоставляются в формате pcm, который можно понимать как файл wav после удаления избыточной информации, сохраняется только формат голосовых данных.
выполнить
Ниже приведены данные трех диалектов Чанша, Наньчан и Шанхай в качестве примеров, чтобы представить, как реализовать голосовую классификацию.
загрузить библиотеку
# -*- coding:utf-8 -*-
import numpy as np
import os
from matplotlib import pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
%matplotlib inline
from sklearn.utils import shuffle
import glob
import pickle
from tqdm import tqdm
from keras.models import Model
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Input, Activation, Conv1D, Add, Multiply, BatchNormalization, GlobalMaxPooling1D, Dropout
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
from python_speech_features import mfcc
import librosa
from IPython.display import Audio
import wave
Загрузить файл PCM, всего 1W8 частей обучающих данных, 1,5 тыс. единиц проверочных данных.
train_files = glob.glob('data/*/train/*/*.pcm')
dev_files = glob.glob('data/*/dev/*/*/*.pcm')
print(len(train_files), len(dev_files), train_files[0])
Отсортируйте классификационные метки, соответствующие каждой части голосовых данных.
labels = {'train': [], 'dev': []}
for i in tqdm(range(len(train_files))):
path = train_files[i]
label = path.split('/')[1]
labels['train'].append(label)
for i in tqdm(range(len(dev_files))):
path = dev_files[i]
label = path.split('/')[1]
labels['dev'].append(label)
print(len(labels['train']), len(labels['dev']))
Определите три функции для обработки речи, преобразования pcm в wav и визуализации речи.Из-за разной длины речевых сегментов короткие сегменты менее 1 с удаляются, а длинные сегменты делятся на сегменты не более 3 с.
mfcc_dim = 13
sr = 16000
min_length = 1 * sr
slice_length = 3 * sr
def load_and_trim(path, sr=16000):
audio = np.memmap(path, dtype='h', mode='r')
audio = audio[2000:-2000]
audio = audio.astype(np.float32)
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]
slices = []
for i in range(0, audio.shape[0], slice_length):
s = audio[i: i + slice_length]
if s.shape[0] >= min_length:
slices.append(s)
return audio, slices
def pcm2wav(pcm_path, wav_path, channels=1, bits=16, sample_rate=sr):
data = open(pcm_path, 'rb').read()
fw = wave.open(wav_path, 'wb')
fw.setnchannels(channels)
fw.setsampwidth(bits // 8)
fw.setframerate(sample_rate)
fw.writeframes(data)
fw.close()
def visualize(index, source='train'):
if source == 'train':
path = train_files[index]
else:
path = dev_files[index]
print(path)
audio, slices = load_and_trim(path)
print('Duration: %.2f s' % (audio.shape[0] / sr))
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)
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()
wav_path = 'example.wav'
pcm2wav(path, wav_path)
return wav_path
Audio(visualize(2))
Форма волны и характеристики MFCC, соответствующие диалекту Чанша
После сортировки данных и проверки распределения длины речевых клипов мы наконец получили 18 890 обучающих клипов и 1632 проверочных ролика.
X_train = []
X_dev = []
Y_train = []
Y_dev = []
lengths = []
for i in tqdm(range(len(train_files))):
path = train_files[i]
audio, slices = load_and_trim(path)
lengths.append(audio.shape[0] / sr)
for s in slices:
X_train.append(mfcc(s, sr, numcep=mfcc_dim))
Y_train.append(labels['train'][i])
for i in tqdm(range(len(dev_files))):
path = dev_files[i]
audio, slices = load_and_trim(path)
lengths.append(audio.shape[0] / sr)
for s in slices:
X_dev.append(mfcc(s, sr, numcep=mfcc_dim))
Y_dev.append(labels['dev'][i])
print(len(X_train), len(X_dev))
plt.hist(lengths, bins=100)
plt.show()
Нормализация функций MFCC
samples = np.vstack(X_train)
mfcc_mean = np.mean(samples, axis=0)
mfcc_std = np.std(samples, axis=0)
print(mfcc_mean)
print(mfcc_std)
X_train = [(x - mfcc_mean) / (mfcc_std + 1e-14) for x in X_train]
X_dev = [(x - mfcc_mean) / (mfcc_std + 1e-14) for x in X_dev]
maxlen = np.max([x.shape[0] for x in X_train + X_dev])
X_train = pad_sequences(X_train, maxlen, 'float32', padding='post', value=0.0)
X_dev = pad_sequences(X_dev, maxlen, 'float32', padding='post', value=0.0)
print(X_train.shape, X_dev.shape)
Работа с категориальными метками
from sklearn.preprocessing import LabelEncoder
from keras.utils import to_categorical
le = LabelEncoder()
Y_train = le.fit_transform(Y_train)
Y_dev = le.transform(Y_dev)
print(le.classes_)
class2id = {c: i for i, c in enumerate(le.classes_)}
id2class = {i: c for i, c in enumerate(le.classes_)}
num_class = len(le.classes_)
Y_train = to_categorical(Y_train, num_class)
Y_dev = to_categorical(Y_dev, num_class)
print(Y_train.shape, Y_dev.shape)
Определите итератор, который создает пакеты данных
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):
x, y = shuffle(x, y)
offset = batch_size
X_batch = x[offset - batch_size: offset]
Y_batch = y[offset - batch_size: offset]
yield (X_batch, Y_batch)
Определить модель и обучить ее, а также уменьшить размерность вывода всей последовательности с помощью GlobalMaxPooling1D, что становится стандартной задачей классификации
epochs = 10
num_blocks = 3
filters = 128
drop_rate = 0.25
X = Input(shape=(None, mfcc_dim,), dtype='float32')
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') # batch_size, seq_len, filters
h1 = batchnorm(conv1d(h1, num_class, 1, 1)) # batch_size, seq_len, num_class
h1 = GlobalMaxPooling1D()(h1) # batch_size, num_class
Y = activation(h1, 'softmax')
optimizer = Adam(lr=0.01, clipnorm=5)
model = Model(inputs=X, outputs=Y)
model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])
checkpointer = ModelCheckpoint(filepath='fangyan.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_dev, Y_dev),
validation_steps=len(X_dev) // batch_size,
callbacks=[checkpointer, lr_decay])
Нарисуйте кривую функции потерь и правильную кривую скорости.После 10 раундов обучения правильная скорость тренировочного набора составляет почти 100%, а проверочный набор нестабилен, около 89%.
train_loss = history.history['loss']
valid_loss = history.history['val_loss']
plt.plot(train_loss, label='train')
plt.plot(valid_loss, label='valid')
plt.legend(loc='upper right')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
train_acc = history.history['acc']
valid_acc = history.history['val_acc']
plt.plot(train_acc, label='train')
plt.plot(valid_acc, label='valid')
plt.legend(loc='upper right')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.show()
Причина, по которой результаты проверочного набора неудовлетворительны, может заключаться в том, что обучающих данных недостаточно.Хотя всего имеется 1W8 фрагментов обучающих данных, на самом деле всего 90 говорящих.
С большим количеством говорящих и более разнообразными голосами модель должна иметь возможность изучать более общие черты диалектов.
Сохраните сопоставление между таксономией и названиями диалектов для последующего использования.
with open('resources.pkl', 'wb') as fw:
pickle.dump([class2id, id2class, mfcc_mean, mfcc_std], fw)
Загрузите обученную модель на одну машину и случайным образом выберите речь для классификации.
# -*- coding:utf-8 -*-
import numpy as np
from keras.models import load_model
from keras.preprocessing.sequence import pad_sequences
import librosa
from python_speech_features import mfcc
import pickle
import wave
import glob
with open('resources.pkl', 'rb') as fr:
[class2id, id2class, mfcc_mean, mfcc_std] = pickle.load(fr)
model = load_model('fangyan.h5')
paths = glob.glob('data/*/dev/*/*/*.pcm')
path = np.random.choice(paths, 1)[0]
label = path.split('/')[1]
print(label, path)
mfcc_dim = 13
sr = 16000
min_length = 1 * sr
slice_length = 3 * sr
def load_and_trim(path, sr=16000):
audio = np.memmap(path, dtype='h', mode='r')
audio = audio[2000:-2000]
audio = audio.astype(np.float32)
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]
slices = []
for i in range(0, audio.shape[0], slice_length):
s = audio[i: i + slice_length]
slices.append(s)
return audio, slices
audio, slices = load_and_trim(path)
X_data = [mfcc(s, sr, numcep=mfcc_dim) for s in slices]
X_data = [(x - mfcc_mean) / (mfcc_std + 1e-14) for x in X_data]
maxlen = np.max([x.shape[0] for x in X_data])
X_data = pad_sequences(X_data, maxlen, 'float32', padding='post', value=0.0)
print(X_data.shape)
prob = model.predict(X_data)
prob = np.mean(prob, axis=0)
pred = np.argmax(prob)
prob = prob[pred]
pred = id2class[pred]
print('True:', label)
print('Pred:', pred, 'Confidence:', prob)
Наконец, позвольте мне упомянуть, что, поскольку это трехмерная тензорная классификация, она очень похожа на проблему классификации текста, поэтому можно рассмотреть и другие модели, такие как BiLSTM.
Ссылаться на
- iFLYTEK Dialect Type Recognition AI Challenge:challenge.xfyun.cn/