如何使用MFCC功能训练神经网络进行语音识别

时间:2019-12-23 12:54:45

标签: python

我有一个音频数据库,并且我正在使用Librosa和MFCCs算法进行语音识别。我有20个来自MFCC算法的功能输出,但是现在我不知道如何将其作为算法的输入。所以我的模型是:

import tensorflow as tf
import tensorflow.keras as tfk
import tensorflow.keras.layers as tfkl

cnn_model = tfk.Sequential(name='CNN_model')
cnn_model.add(tfkl.Conv1D(filters= 225, kernel_size= 11, padding='same', activation='relu', input_shape=(4500,9000, 3)))
cnn_model.add(tfkl.BatchNormalization())
cnn_model.add(tfkl.Bidirectional(tfkl.GRU(200, activation='relu', return_sequences=True, implementation=0)))
cnn_model.add(tfkl.Dropout(0.2))
cnn_model.add(tfkl.BatchNormalization())
cnn_model.add(tfkl.TimeDistributed(tfkl.Dense(20)))
cnn_model.add(tfkl.Dropout(0.2))
cnn_model.add(tfkl.Softmax())
cnn_model.compile(loss='mae', optimizer='Adam', metrics=['mae'])

cnn_model.summary()

,输入为

In: X_train_reshape[0].shape
Out: (1500, 20)

我尝试了很多事情,但是总是出现尺寸错误。 ValueError Error when checking input: expected conv1d_input to have 3 dimensions, but got array with shape (64, 256, 256, 3)

1 个答案:

答案 0 :(得分:1)

我觉得您没有将其视为典型的语音识别问题。因为我发现您的方法有几种奇怪的选择。

我注意到的问题

SoftMax之前的退出

这就是算法的尾巴,

cnn_model.add(tfkl.TimeDistributed(tfkl.Dense(20)))
cnn_model.add(tfkl.Dropout(0.2))
cnn_model.add(tfkl.Softmax())

我个人并没有在最后一层使用过使用dropout的人。所以我会摆脱它。因为辍学随机切换神经元。但是您想随时打开所有输出节点。

损失函数

通常,CTC用于优化语音识别模型。我(个人)还没有看到有人使用mae作为语音模型的损失。因为,您的输入数据和标签数据通常具有未对齐的时间维度。这意味着,并不总是有对应于预测的每个时间步长的标签。这就是CTC损失大放异彩的地方。这可能就是您要用于此模型的内容(除非您100%确定每个预测都有标签,并且它们完全对齐)。

话虽如此,损失取决于您要解决的问题。但我将提供一个示例,说明如何使用此损失解决此问题。

一个有效的例子

数据集

为了显示一个有效的示例,我将使用this语音数据集。我之所以选择它是因为,由于问题的简单性,我可以很快获得良好的结果。

  • 输入:音频
  • 输出:标签0-9

MFCC转换

然后,您可以在音频文件上执行MFCC,您将获得以下热图。因此,正如我之前说的,这将是一个2D矩阵(n_mfcc, timesteps)大小的数组。有了批次尺寸,它变成(batch size, n_mfcc, timesteps)

enter image description here

以下是可视化以上内容的方法。此处,y是通过librosa.core.load()函数加载的音频。

y = audios[aid][1][0]
sr = audios[aid][1][1]
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20)
print(mfcc.shape)

plt.figure(figsize=(6, 4))
librosa.display.specshow(mfcc, x_axis='time')
plt.colorbar()
plt.title('MFCC')
plt.tight_layout()

创建训练/测试数据

接下来,您可以创建训练和测试数据。这是我创建的。

  • train_data-一个(sample size, timesteps, n_mfcc)大小的数组
  • train_labels =一个(sample size, timesteps, num_classes)大小的数组
  • train_inp_lengths-一个(sample size,)大小的数组(用于CTC丢失)
  • train_seq_lengths-一个(sample size,)`大小数组(用于CTC丢失)

  • test_data-一个(sample size, timesteps, n_mfcc)大小的数组

  • test_labels =一个(sample size, timesteps, num_classes+1)大小的数组
  • test_inp_lengths-一个(sample size,)`大小的数组(用于CTC丢失)
  • test_seq_lengths-一个(sample size,)`大小的数组(用于CTC丢失)

我正在使用以下映射将字符转换为数字

alphabet = 'abcdefghijklmnopqrstuvwxyz '
a_map = {} # map letter to number
rev_a_map = {} # map number to letter
for i, a in enumerate(alphabet):
  a_map[a] = i
  rev_a_map[i] = a

label_map = {0:'zero', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five', 6:'six', 7: 'seven', 8: 'eight', 9:'nine'}

没什么要注意的。

  • 请注意,mfcc操作将返回(n_mfcc, time)。您必须进行轴置换才能使其变为(time, n_mfcc)格式。这样卷积就发生在时间维度上。
  • 我还必须确保标签具有与输入完全相同的时间步长(对于ctc_loss而言这不是必需的)。但是是由keras模型定义强制执行的要求。这是通过在每个字符序列的末尾添加空格来完成的。

定义模型

我已从顺序API更改为功能性API,因为我需要包括多个输入层才能使此ctc_loss起作用。此外,我摆脱了最后一个辍学层。

def ctc_loss(inp_lengths, seq_lengths):
    def loss(y_true, y_pred):
        l = tf.reduce_mean(K.ctc_batch_cost(tf.argmax(y_true, axis=-1), y_pred, inp_lengths, seq_lengths))        
        return l            
    return loss

K.clear_session()
inp = tfk.Input(shape=(10,50))
inp_len = tfk.Input(shape=(1))
seq_len = tfk.Input(shape=(1))
out = tfkl.Conv1D(filters= 128, kernel_size= 5, padding='same', activation='relu')(inp)
out = tfkl.BatchNormalization()(out)
out = tfkl.Bidirectional(tfkl.GRU(128, return_sequences=True, implementation=0))(out)
out = tfkl.Dropout(0.2)(out)
out = tfkl.BatchNormalization()(out)
out = tfkl.TimeDistributed(tfkl.Dense(27, activation='softmax'))(out)
cnn_model = tfk.models.Model(inputs=[inp, inp_len, seq_len], outputs=out)
cnn_model.compile(loss=ctc_loss(inp_lengths=inp_len , seq_lengths=seq_len), optimizer='Adam', metrics=['mae'])

训练模型

然后您只需致电

cnn_model.fit([train_data, train_inp_lengths, train_seq_lengths], train_labels, batch_size=64, epochs=20)

给了,

Train on 900 samples
Epoch 1/20
900/900 [==============================] - 3s 3ms/sample - loss: 11.4955 - mean_absolute_error: 0.0442
Epoch 2/20
900/900 [==============================] - 2s 2ms/sample - loss: 4.1317 - mean_absolute_error: 0.0340
...
Epoch 19/20
900/900 [==============================] - 2s 2ms/sample - loss: 0.1162 - mean_absolute_error: 0.0275
Epoch 20/20
900/900 [==============================] - 2s 2ms/sample - loss: 0.1012 - mean_absolute_error: 0.0277

根据模型进行预测

y = cnn_model.predict([test_data, test_inp_lengths, test_seq_lengths])

n_ids = 5

for pred, true in zip(y[:n_ids,:,:], test_labels[:n_ids,:,:]):
  pred_ids = np.argmax(pred,axis=-1)
  true_ids = np.argmax(true, axis=-1)
  print('pred > ',[rev_a_map[tid] for tid in pred_ids])
  print('true > ',[rev_a_map[tid] for tid in true_ids])

这给出了

pred >  ['e', ' ', 'i', 'i', 'i', 'g', 'h', ' ', ' ', 't']
true >  ['e', 'i', 'g', 'h', 't', ' ', ' ', ' ', ' ', ' ']

pred >  ['o', ' ', ' ', 'n', 'e', ' ', ' ', ' ', ' ', ' ']
true >  ['o', 'n', 'e', ' ', ' ', ' ', ' ', ' ', ' ', ' ']

pred >  ['s', 'e', ' ', ' ', ' ', ' ', ' ', ' ', 'v', 'e']
true >  ['s', 'e', 'v', 'e', 'n', ' ', ' ', ' ', ' ', ' ']

pred >  ['z', 'e', ' ', ' ', ' ', ' ', ' ', 'r', 'o', ' ']
true >  ['z', 'e', 'r', 'o', ' ', ' ', ' ', ' ', ' ', ' ']

pred >  ['n', ' ', ' ', 'i', 'i', 'n', 'e', ' ', ' ', ' ']
true >  ['n', 'i', 'n', 'e', ' ', ' ', ' ', ' ', ' ', ' ']

要摆脱重复的字母和空格,请使用ctc_decode函数,如下所示。

y = cnn_model.predict([test_data, test_inp_lengths, test_seq_lengths])

sess = K.get_session()
pred = sess.run(tf.keras.backend.ctc_decode(y, test_inp_lengths[:,0]))

rev_a_map[-1] = '-'

for pred, true in zip(pred[0][0][:n_ids,:], test_labels[:n_ids,:,:]):
  print(pred.shape)  
  true_ids = np.argmax(true, axis=-1)
  print('pred > ',[rev_a_map[tid] for tid in pred])
  print('true > ',[rev_a_map[tid] for tid in true_ids])

给了,

pred >  ['e', 'i', 'g', 'h', 't']
true >  ['e', 'i', 'g', 'h', 't', ' ', ' ', ' ', ' ', ' ']

pred >  ['o', 'n', 'e', '-', '-']
true >  ['o', 'n', 'e', ' ', ' ', ' ', ' ', ' ', ' ', ' ']

pred >  ['s', 'e', 'i', 'v', 'n']
true >  ['s', 'e', 'v', 'e', 'n', ' ', ' ', ' ', ' ', ' ']

pred >  ['z', 'e', 'r', 'o', '-']
true >  ['z', 'e', 'r', 'o', ' ', ' ', ' ', ' ', ' ', ' ']

pred >  ['n', 'i', 'n', 'e', '-']
true >  ['n', 'i', 'n', 'e', ' ', ' ', ' ', ' ', ' ', ' ']
  • 请注意,我已经添加了新标签-1。这是ctc_decode函数所添加的用来表示任何空格的东西。