ASR语音转文字模型——项目落地
作者:互联网
模型:
PaddlePaddle-DeepSpeech
训练数据集:
Aishell:178小时,16khz,16bit,400人录制,涉及智能家居、无人驾驶、工业生产等11个领域的中文语音库。
Free ST-Chinese-Mandarin-Corpus:500小时,16khz,16bit,855人录制。安静的室内环境下,通过单个碳粒麦克风录取,文本选取网络聊天智能音箱控制等。
THCHS-30:30小时,16khz,30人录制,清华大学30小时中文语音库。安静的办公室环境下,通过单个碳粒麦克风录取,文本选取自大容量的新闻。
DeepSpeech-1300:1300小时中文语音库
效果:
训练集准确率94.5%,测试准确率约80%
注意事项:
1.仅能识别普通话,对于方言、非中文语言无法识别;
2.对于背景声音过大的音频,识别准确率较低,模型训练过程中提供了以下六个数据增强组件,用于缓解噪声干扰问题:音量扰动、速度扰动、移动扰动、在线贝叶斯归一化、噪声干扰(需要背景噪音的音频文件)、脉冲响应(需要脉冲音频文件);
3.模型训练所使用的数据均为单条语句,平均长度在十秒以内,所以模型在测试时对于过长的音频识别准确率很低,但短视频的音频数据长度大多在100秒以上,所以在实际应用过程中,先提取视频音频,然后按30秒的时长对音频进行切割,分段转换成文本,然后在拼起来,输出最终的转换结果;
4.视频转音频所用到的工具是ffmpeg;
模型原理:
论文题目:
Deep Speech 2: End-to-End Speech Recognition in English and Mandarin
论文链接:
https://arxiv.org/pdf/1512.02595.pdf
模型结构:
DeepSpeech是完全End-toEnd的语音识别系统,输入是语音的频谱,输出是字符串,核心技术是CTC算法,核心结构实际上是一个RNN。
模型由5个隐藏层组成,可以分为三个部分:Conv layer, Recurrent layer and FC layer。
前三层为是全连接层,组成Conv layer,对于输入x,我们用 hl 表示第l层,h0 表示输入。对于第1层,在t时刻的输入不只是t时刻的特征xtxt,而且还包括它的前后C帧特征,共计2C+1帧,前三层公式如下:
第四层是Recurrent layer, 是一个双向RNN,第五层会把这个双向RNN的两个输入加起来作为输入:
最后一层是FC layer,是一个全连接层,没有激活函数,用softmax吧输出变成对应每个字符的概率:
模型引入了Batch Normalization,使得模型训练更快更好:
核心代码:
核心代码在DeepSpeech.py中,很多代码都是为了支持多GPU训练的,以下是和网络结构定义相关部分的代码:
def BiRNN(batch_x, seq_length, dropout): # 输入 shape: [batch_size, n_steps, n_input + 2*n_input*n_context] batch_x_shape = tf.shape(batch_x) # 把 `batch_x` Reshape成 `[n_steps*batch_size, n_input + 2*n_input*n_context]`. # 因为第一层期望的输入的rank是2 # 时间主序,这是warpCTC的要求。 batch_x = tf.transpose(batch_x, [1, 0, 2]) # 第一层期望的输入rank是2 # (n_steps*batch_size, n_input + 2*n_input*n_context) batch_x = tf.reshape(batch_x, [-1, n_input + 2*n_input*n_context]) # 输入首先经过3个隐层,激活函数是clipped的ReLU,并且使用dropout # 第一层 b1 = variable_on_worker_level('b1', [n_hidden_1], tf.random_normal_initializer(stddev=FLAGS.b1_stddev)) h1 = variable_on_worker_level('h1', [n_input + 2*n_input*n_context, n_hidden_1], tf.contrib.layers.xavier_initializer(uniform=False)) layer_1 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(batch_x, h1), b1)), FLAGS.relu_clip) layer_1 = tf.nn.dropout(layer_1, (1.0 - dropout[0])) # 第二层 b2 = variable_on_worker_level('b2', [n_hidden_2], tf.random_normal_initializer(stddev=FLAGS.b2_stddev)) h2 = variable_on_worker_level('h2', [n_hidden_1, n_hidden_2], tf.random_normal_initializer(stddev=FLAGS.h2_stddev)) layer_2 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(layer_1, h2), b2)), FLAGS.relu_clip) layer_2 = tf.nn.dropout(layer_2, (1.0 - dropout[1])) # 第三层 b3 = variable_on_worker_level('b3', [n_hidden_3], tf.random_normal_initializer(stddev=FLAGS.b3_stddev)) h3 = variable_on_worker_level('h3', [n_hidden_2, n_hidden_3], tf.random_normal_initializer(stddev=FLAGS.h3_stddev)) layer_3 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(layer_2, h3), b3)), FLAGS.relu_clip) layer_3 = tf.nn.dropout(layer_3, (1.0 - dropout[2])) # LSTM,隐单元是n_cell_dim,遗忘门的bias是1.0 # 前向LSTM: lstm_fw_cell = tf.contrib.rnn.BasicLSTMCell(n_cell_dim, forget_bias=1.0, state_is_tuple=True, reuse=tf.get_variable_scope().reuse) lstm_fw_cell = tf.contrib.rnn.DropoutWrapper(lstm_fw_cell, input_keep_prob=1.0 - dropout[3], output_keep_prob=1.0 - dropout[3], seed=FLAGS.random_seed) # 反向LSTM: lstm_bw_cell = tf.contrib.rnn.BasicLSTMCell(n_cell_dim, forget_bias=1.0, state_is_tuple=True, reuse=tf.get_variable_scope().reuse) lstm_bw_cell = tf.contrib.rnn.DropoutWrapper(lstm_bw_cell, input_keep_prob=1.0 - dropout[4], output_keep_prob=1.0 - dropout[4], seed=FLAGS.random_seed) # 第三层的输出`layer_3` reshape成`[n_steps, batch_size, 2*n_cell_dim]`, # 因为后面期望的输入是`[max_time, batch_size, input_size]`. layer_3 = tf.reshape(layer_3, [-1, batch_x_shape[0], n_hidden_3]) # 把第三层的输出传入双向LSTM outputs, output_states = tf.nn.bidirectional_dynamic_rnn(cell_fw=lstm_fw_cell, cell_bw=lstm_bw_cell, inputs=layer_3, dtype=tf.float32, time_major=True, sequence_length=seq_length) # LSTM的输出包括双向的结果,每一个方向的输出是[n_steps, batch_size, n_cell_dim], # 我们把它拼接起来最后一个时刻的隐状态和最后reshape成[n_steps*batch_size, 2*n_cell_dim] outputs = tf.concat(outputs, 2) outputs = tf.reshape(outputs, [-1, 2*n_cell_dim]) # 第五个隐层 b5 = variable_on_worker_level('b5', [n_hidden_5], tf.random_normal_initializer(stddev=FLAGS.b5_stddev)) h5 = variable_on_worker_level('h5', [(2 * n_cell_dim), n_hidden_5], tf.random_normal_initializer(stddev=FLAGS.h5_stddev)) layer_5 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(outputs, h5), b5)), FLAGS.relu_clip) layer_5 = tf.nn.dropout(layer_5, (1.0 - dropout[5])) # 第六层,没有激活函数,输出是logits b6 = variable_on_worker_level('b6', [n_hidden_6], tf.random_normal_initializer(stddev=FLAGS.b6_stddev)) h6 = variable_on_worker_level('h6', [n_hidden_5, n_hidden_6], tf.contrib.layers.xavier_initializer(uniform=False)) layer_6 = tf.add(tf.matmul(layer_5, h6), b6) # 把logits 从 [n_steps*batch_size, n_hidden_6] # reshape [n_steps, batch_size, n_hidden_6]. # 注意,它和输入是不同的,因为它是时间主序(而输入是batch主序) layer_6 = tf.reshape(layer_6, [-1, batch_x_shape[0], n_hidden_6], name="logits") # Output shape: [n_steps, batch_size, n_hidden_6] return layer_6 |
优化工作:
1. 数据并行化
为了利用GPU的计算能力,一次训练会计算较大的batch(通常上千)。此外DeepSpeech会同时用多个GPU计算一个batch的梯度,然后平均这些梯度来更新参数(使用了 synchronous SGD)。因为序列的长度是变长的,DeepSpeech会把长度类似的训练数据分成组,然后padding成一样的长度。
2. CTC loss function的GPU实现
由于计算CTC loss function 比前后向传播更为复杂,作者实现了一个GPU版本的CTC loss function,而这一实现,减少了10%~20%的训练时间。
3. 内存分配优化
使用动态内存分配给GPU和CPU内存,主要用于存储可变长度话语的激活数据和中间结果。
参考:
【1】https://arxiv.org/pdf/1512.02595.pdf
【2】http://fancyerii.github.io/books/deepspeech/
【3】https://zhuanlan.zhihu.com/p/92305041
【4】https://blog.csdn.net/Code_Mart/article/details/87291644
【5】https://github.com/mozilla/DeepSpeech
标签:ASR,layer,落地,batch,cell,语音,tf,hidden,stddev 来源: https://blog.csdn.net/weixin_39586997/article/details/118358075