其他分享
首页 > 其他分享> > ASR语音转文字模型——项目落地

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