Post Views = 5
去年研究了一段时间的语音识别, 出于一些功耗的考虑, 主要精力还是放在了 Spinx 这个传统方法的实现上. HMM 方法的局限性还是挺明显的, 如今语音识别的先进技术基本都是基于 DNN 的. 而 RNN 更是非常适合语音这种序列的处理. 前面在 github 上偶然发现了一个语音识别的学习项目, 里面提供了一些标定过的语音数据, 同时也实现了一些 demo 性质的代码. 不过, 这个项目作者对 TensorFlow 进行了一些封装, 导致代码有一点绕, 其实是不利于初学者学习的. 所以, 我就想使用原生的 TensorFlow api 实现个简单的语音识别程序. 说实话, 我对 RNN 也没有多深入的理解, 所以这里也就不深究原理了. 仅仅从直观的角度来讲, RNN 的结构反应了序列的顺序关系, 所以 RNN 对序列模型有这很好的描述能力. 在《Deep Learning with tensorflow》这本书里, 使用了 RNN 实现 MNIST 数据集的分类模型训练. MNIST 数据集虽然是一个图片数据集, 但如果我们把一行像素看做一个输入向量, 而这些行向量按照顺序就会形成一个序列. 经过实验, 我们可以发现, RNN 也可以很好的完成 MNIST 数据的分类.
1. 语音特征提取
语音特征提取的方法中, MFCC(梅尔频率倒谱系数) 大概是最常见的了. 简单说来, MFCC 就是一个短时的频域特征. 在 Python 中, 我们可以很简单的使用 librosa 这个库实现 MFCC 特征的提取. MFCC 特征的提取过程如下图所示, 首先语音信号按照时间分割成多段; 然后对每段信号进行快速傅里叶变换, 变换之后可以得到一个频谱图; 依据频谱图的能量包络线, 对这个能量包络线进行离散化, 即可得到一个向量. 这个向量便是 MFCC 向量.
2. RNN 模型训练
有了特征, 我们就可以使用 TensorFlow 完成模型的建立和训练了. 其实模型很简单, 就如下图所示, 输入层是 LSTM, 输出层是一层 Softmax. 输出的编码是 one-hot 编码, 输入是一组多维向量, 按时间顺序排列起来的.
由于这个模型是非常简单的, 我就直接贴代码了:
import os
import re
import sys
import wave
import numpy as np
import tensorflow as tf
from tensorflow.contrib import rnn
from random import shuffle
import librosa
path = "data/spoken_numbers_pcm/"
# learning_rate = 0.00001
# training_iters = 300000 #steps
# batch_size = 64
height=20 # mfcc features
width=80 # (max) length of utterance
classes=10 # digits
n_input = 20
n_steps = 80
n_hidden = 128
n_classes = 10
learning_rate = 0.001
training_iters = 100000
batch_size = 50
display_step = 10
x = tf.placeholder("float", [None, n_steps, n_input])
y = tf.placeholder("float", [None, n_classes])
weights = {
'out' : tf.Variable(tf.random_normal([n_hidden, n_classes]))
}
biases = {
'out' : tf.Variable(tf.random_normal([n_classes]))
}
def mfcc_batch_generator(batch_size=10):
# maybe_download(source, DATA_DIR)
batch_features = []
labels = []
files = os.listdir(path)
while True:
# print("loaded batch of %d files" % len(files))
shuffle(files)
for file in files:
if not file.endswith(".wav"): continue
wave, sr = librosa.load(path+file, mono=True)
mfcc = librosa.feature.mfcc(wave, sr)
label = dense_to_one_hot(int(file[0]),10)
labels.append(label)
# print(np.array(mfcc).shape)
mfcc = np.pad(mfcc,((0,0),(0,80-len(mfcc[0]))), mode='constant', constant_values=0)
batch_features.append(np.array(mfcc).T)
if len(batch_features) >= batch_size:
yield np.array(batch_features), np.array(labels)
batch_features = [] # Reset for next batch
labels = []
def dense_to_one_hot(labels_dense, num_classes=10):
return np.eye(num_classes)[labels_dense]
def RNN(x, weights, biases):
x = tf.unstack(x, n_steps, 1)
lstm_cell = rnn.BasicLSTMCell(n_hidden, forget_bias=1.0)
outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
return tf.matmul(outputs[-1], weights['out']) + biases['out']
pred = RNN(x, weights, biases)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
step = 1
while step * batch_size < training_iters:
batch = mfcc_batch_generator(batch_size)
batch_x, batch_y = next(batch)
# print(batch_x.shape)
batch_x = batch_x.reshape((batch_size, n_steps, n_input))
sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
if step % display_step == 0:
acc = sess.run(accuracy, feed_dict={x: batch_x, y: batch_y})
loss = sess.run(cost, feed_dict={x: batch_x, y : batch_y})
print("Iter" + str(step*batch_size) + ", Minibatch Loss =" + \
"{:.6f}".format(loss) + ", Training Accuracy =" + \
"{:.5f}".format(acc))
step += 1
print("Optimization Finished!")
数据集可以到 http://pannous.net/files/ 下载. 对于这个例子, 你可以仅仅下载 spoken_numbers_pcm.tar 文件, 并解压到./data / 目录下即可.
下面这个训练结果, 非常尴尬, 训练集上都只有 80+%. 此外, 我还发现了一个非常有趣的事情. 刚开始, 我把 MFCC 特征的维度和时间片段的维度弄反了, 除此之外其他的东西都差不多. 训练的时候发现训练集上的准确率早早就上 100% 了. 至于为什么,"正确" 的维度下, 反而收敛变慢了, 我怀疑是填补时间序列的锅 (不够 n_step 长的数据被补零了). 这种补零的行为在时间序列下, 会严重地污染数据, 不过这个猜想有待于继续验证了.
来源: https://juejin.im/entry/5a70a5a96fb9a01cbf38a44e