#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#

16、Bi-RNN网络

数据准备好了,接着就应该搭建网络了,我们这里使用Bi-RNN网络,前面的博客中没有介绍这个网络,所以现在先来介绍一下这个网络。

Bi-RNN网络,又叫双向RNN网络,它采用了两个方向的RNN网络,如下图所示,

RNN网络擅长处理连续的数据,所以将正反两个方向的网络结合,就不仅可以学习它的正向规律,还可以学习它的反向规律,这样就比单个循环网络拥有更高的拟合度。

Bi-RNN跟RNN网络非常类似,只是在正向传播的基础上,再进行一次反向传播,且这两个都连接同一个输出层。

17、CTC

还得插讲一下其他内容,直接上代码的话会一脸懵逼。CTC(Connectionist Temporal Classification)是语音识别中的一个关键技术,通过增加一个额外的Symbol代表NULL来解决叠字的问题。

在基于连续的时间序列分类任务中,常用CTC的方法。

该方法主要体现在处理loss值上,通过对序列对不上的label添加blank(空)的方式,将预测的输出值与给定的label值在时间序列上对齐,再求出具体损失。如果以后用机会我再专门的研究这个,大家也可以自行百度,我们这里只要知道怎么使用它就可以了。

18、CTC loss

计算CTC loss在Tensorflow中封装成了ctc_loss函数,该函数的作用就是按照序列来处理输出标签和标注标签之间的损失。函数原型如下,

ctc_loss(labels, inputs, sequence_length,preprocess_collapse_repeated=False,ctc_merge_repeated=True,ignore_longer_outputs_than_inputs=False, time_major=True)

其中,

labels:是一个int32类型的稀疏矩阵张量(SparseTensor)。什么是稀疏矩阵等下再讲。

inputs:经过RNN后输出的标签预测值,是三维的浮点型张量,如果time_major=True,则它的形状为[max_time,batch_size,num_classes],否则为[batch_size,max_time,num_classes]。

sequence_lenght:序列长度

preprocess_collapse_repeated:是否需要预处理,将重复的label合并成一个label。

ctc_merge_repeated:在计算时,是否将每个non_blank重复的label当成单独的label来解释。

当取批次样本进行训练时,还需要对ctc_loss的返回值求均值,这个才是最终的loss。

上面参数中,需要注意的是inputs参数中的num_classes,如果样本中有classes个分类,那么,num_classes=classes+1,即num_classes要比classes多出一个分类,用来存放blank类。在后面实现的代码中就知道这点了。

19levenshtein距离

Levenshtein距离,也叫编辑距离(Edit Distance),指两个字符串之间,由一个转成另一个所需要的最少的编辑操作次数。编辑操作指的是,将一个字符替换成另一个字符、插入或者删除一个字符。编辑距离越小,说明两个字符串之间的相似度最大。

在Tensorflow中,编辑距离的计算被封装成对两个稀疏矩阵的操作,函数原型如下,

edit_distance(hypothesis, truth, normalize=True, name="edit_distance")

其中,

hypothesis:SparseTensor类型,为预测的序列结果。

truth:SparseTensor类型,为真实的序列结果。

normalize:求出来的编辑距离除以真实序列长度。

name:名字

返回值:R-1维的DenseTensor,包含每个序列的编辑距离。

20、CTC decoder

虽然输入ctc_loss中的inputs是我们的预测结果,但是这个结果却是带有空标签的(blank),而且是一个与时间序列强对应的输出。实际上我们需要的是一个转化好的,类似原始标注标签一个的输出。这时,我们可以使用CTC decoder,经过它对预测结果加工后,就可以与标准标签进行损失loss的运算了。

TensorFlow中,CTC decoder有两个函数,如下所示,

21、定义占位符

现在可以开始搭建网络模型了,我们将其封装成BiRNN类,我们在构造函数中保存一些传进来的参数并定义占位符,代码如下,

class BiRNN():def __init__(self, features, contexts, batch_size, hidden, cell_dim, stddev, keep_dropout_rate, relu_clip, character, save_path, learning_rate):self.features = featuresself.batch_size = batch_sizeself.contexts = contextsself.hidden = hiddenself.stddev = stddevself.keep_dropout_rate = keep_dropout_rateself.relu_clip = relu_clipself.cell_dim = cell_dimself.learning_rate = learning_rate# input 为输入音频数据,由前面分析可知,它的结构是[batch_size, amax_stepsize, features + (2 * features * contexts)]#其中,batch_size是batch的长度,amax_stepsize是时序长度,n_input + (2 * features * contexts)是MFCC特征数,#batch_size是可变的,所以设为None,由于每一批次的时序长度不固定,所有,amax_stepsize也设为Noneself.input = tf.placeholder(tf.float32, [None, None, features + (2 * features * contexts)], name='input')# label 保存的是音频数据对应的文本的系数张量,所以用sparse_placeholder创建一个稀疏张量self.label = tf.sparse_placeholder(tf.int32, name='label')#seq_length保存的是当前batch数据的时序长度self.seq_length = tf.placeholder(tf.int32, [None], name='seq_length')#keep_dropout则是dropout的参数self.keep_dropout = tf.placeholder(tf.float32, name='keep_dropout')

22、构建网络模型

网络模型的话,先使用3个全连接层网络,然后经过一个Bi-RNN网络,最后再连接两个全连接层,且都带有dropout层。激活函数的话,使用带截断的Relu,截断值设置为20。

模型的shape变换有点多,我们输入的数据的结构是3维的,

[batch_size, amax_stepsize, n_input + (2 * n_input * n_context)]

我们要将它变成2维的,才能传入全连接层,

[amax_stepsize * batch_size, n_input + 2 * n_input * n_context]

全连接层到Bi-RNN网络时,又得转成3维的,

[amax_stepsize, batch_size, 2*n_cell_dim]

然后又得转成2维的,传入全连接层,

[amax_stepsize * batch_size, 2 * n_cell_dim]

最后,又得将2维的转成3维的输出,

[amax_stepsize, batch_size, n_character]

代码如下,

def network_init(self, input, character):# batch_x_shape: [batch_size, amax_stepsize, n_input + 2 * n_input * contexts]batch_x_shape = tf.shape(input)# 将输入转成时间序列优先input = tf.transpose(input, [1, 0, 2])# 再转成2维传入第一层# [amax_stepsize * batch_size, n_input + 2 * n_input * contexts]input = tf.reshape(input, [-1, self.features + 2 * self.features * self.contexts])# 使用clipped RELU activation and dropout.# 1st layerwith tf.name_scope('fc1'):b1 = variable_on_cpu('b1', [self.hidden], tf.random_normal_initializer(stddev=self.stddev))        h1 = variable_on_cpu('h1', [self.features + 2 * self.features * self.contexts, self.hidden],tf.random_normal_initializer(stddev=self.stddev))layer_1 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(input, h1), b1)), self.relu_clip)layer_1 = tf.nn.dropout(layer_1, self.keep_dropout)# 2nd layerwith tf.name_scope('fc2'):b2 = variable_on_cpu('b2', [self.hidden], tf.random_normal_initializer(stddev=self.stddev))h2 = variable_on_cpu('h2', [self.hidden, self.hidden], tf.random_normal_initializer(stddev=self.stddev))layer_2 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(layer_1, h2), b2)), self.relu_clip)layer_2 = tf.nn.dropout(layer_2, self.keep_dropout)# 3rd layerwith tf.name_scope('fc3'):b3 = variable_on_cpu('b3', [2 * self.hidden], tf.random_normal_initializer(stddev=self.stddev))h3 = variable_on_cpu('h3', [self.hidden, 2 * self.hidden], tf.random_normal_initializer(stddev=self.stddev))layer_3 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(layer_2, h3), b3)), self.relu_clip)layer_3 = tf.nn.dropout(layer_3, self.keep_dropout)# 双向rnnwith tf.name_scope('lstm'):# Forward direction cell:lstm_fw_cell = tf.contrib.rnn.BasicLSTMCell(self.cell_dim, forget_bias=1.0, state_is_tuple=True)lstm_fw_cell = tf.contrib.rnn.DropoutWrapper(lstm_fw_cell,input_keep_prob=self.keep_dropout)# Backward direction cell:lstm_bw_cell = tf.contrib.rnn.BasicLSTMCell(self.cell_dim, forget_bias=1.0, state_is_tuple=True)lstm_bw_cell = tf.contrib.rnn.DropoutWrapper(lstm_bw_cell,input_keep_prob=self.keep_dropout)# `layer_3`  `[amax_stepsize, batch_size, 2 * cell_dim]`layer_3 = tf.reshape(layer_3, [-1, batch_x_shape[0], 2 * self.cell_dim])outputs, _ = 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=self.seq_length)# 连接正反向结果[amax_stepsize, batch_size, 2 * n_cell_dim]outputs = tf.concat(outputs, 2)# to a single tensor of shape [amax_stepsize * batch_size, 2 * n_cell_dim]outputs = tf.reshape(outputs, [-1, 2 * self.hidden])with tf.name_scope('fc5'):b5 = variable_on_cpu('b5', [self.hidden], tf.random_normal_initializer(stddev=self.stddev))h5 = variable_on_cpu('h5', [(2 * self.hidden), self.hidden], tf.random_normal_initializer(stddev=self.stddev))layer_5 = tf.minimum(tf.nn.relu(tf.add(tf.matmul(outputs, h5), b5)), self.relu_clip)layer_5 = tf.nn.dropout(layer_5, self.keep_dropout)with tf.name_scope('fc6'):# 全连接层用于softmax分类b6 = variable_on_cpu('b6', [character], tf.random_normal_initializer(stddev=self.stddev))h6 = variable_on_cpu('h6', [self.hidden, character], tf.random_normal_initializer(stddev=self.stddev))layer_6 = tf.add(tf.matmul(layer_5, h6), b6)# 将2维[amax_stepsize * batch_size, character]转成3维 time-major [amax_stepsize, batch_size, character].self.pred = tf.reshape(layer_6, [-1, batch_x_shape[0], character], name='pred')

23、定义损失函数和优化器

前面也说了,语音识别属于时序分类任务,要使用ctc_loss来计算损失。

    #损失函数def loss_init(self):# 使用ctc loss计算损失self.loss = tf.reduce_mean(ctc_ops.ctc_loss(self.label, self.pred, self.seq_length))

而优化器还是使用梯度下降法AdamOptimizer。

    #优化器def optimizer_init(self):# 优化器        self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(self.loss)

24、使用CTC decoder和计算编辑距离

这里使用ctc_beam_search_decoder函数对预测结果进行解码,它返回值decoded是一个只有一个元素的数组,所以,使用edit_distance函数计算编辑距离时,我们应该传入的是decoded[0]。最后,对编辑距离取均值,求平均错误率,代码如下,

def accuracy_init(self):# 使用CTC decoderwith tf.name_scope("decode"):self.decoded, _ = ctc_ops.ctc_beam_search_decoder(self.pred, self.seq_length, merge_repeated=False)# 计算编辑距离with tf.name_scope("accuracy"):distance = tf.edit_distance(tf.cast(self.decoded[0], tf.int32), self.label)# 计算label error rate (accuracy)self.label_error_rate = tf.reduce_mean(distance, name='label_error_rate')

25、建立session

我们先在构造函数调用上面定义的各种初始化函数,然后再创建session,并且实现如果我们在训练模型过程中中断了训练,再次运行程序时,它还能从我们保存的模型中继续训练的功能。代码如下,

        #创建会话self.sess = tf.Session()#需要保存模型,所以获取saverself.saver = tf.train.Saver(max_to_keep=1)#模型保存地址self.save_path = save_path#如果该目录不存在,新建if os.path.exists(self.save_path) == False:os.mkdir(self.save_path)#初始化self.sess.run(tf.global_variables_initializer())# 没有模型的话,就重新初始化cpkt = tf.train.latest_checkpoint(self.save_path)self.start_epoch = 0if cpkt != None:self.saver.restore(self.sess, cpkt)ind = cpkt.find("-")self.start_epoch = int(cpkt[ind + 1:])

26、run函数

接着,我们在run函数中实现训练模型的代码,并且验证模型的准确率,和边训练边保存模型的功能,代码如下,

def run(self, batch, source, source_lengths, sparse_labels, words, epoch):feed = {self.input: source, self.seq_length: source_lengths, self.label: sparse_labels, self.keep_dropout: self.keep_dropout_rate}# loss optimizer ;loss, _ = self.sess.run([self.loss, self.optimizer], feed_dict=feed)# 验证模型的准确率,比较耗时,我们训练的时候全力以赴,所以这里先不跑# if (batch + 1) % 1 == 0:#     feed2 = {self.input: source, self.seq_length: source_lengths, self.label: sparse_labels, self.keep_dropout: 1.0}#     decoded, label_error_rate = self.sess.run([self.decoded[0], self.label_error_rate], feed_dict=feed2)        #     dense_decodeds = tf.sparse_tensor_to_dense(decoded, default_value=0).eval(session=self.sess)#     dense_original_labels = sparse_tuple_to_text(sparse_labels, words)#     counter = 0            #     print('Label err rate: ', label_error_rate)#     for dense_original_label, dense_decoded in zip(dense_original_labels, dense_decodeds):#         # convert to strings#         decoded_str = dense_to_text(dense_decoded, words)                 #         print('Original: {}'.format(dense_original_label))#         print('Decoded:  {}'.format(decoded_str))#         print('------------------------------------------')#         counter = counter + 1#每训练100次保存一下模型if (batch + 1) % 100 == 0:self.saver.save(self.sess, os.path.join(self.save_path + "birnn_speech_recognition.cpkt"), global_step=epoch)return loss

27、train

一切都准备好了,我们现在就来调用它们实现模型的训练,这一步就比较简单了,我就直接上代码了,代码如下,

from audio_processor import AudioProcessor
import tensorflow as tf
import time
import numpy as np
from birnn import BiRNN
import os
# 梅尔倒谱系数的个数
features = 26
# 对于每个时间序列,要包含上下文样本的个数
contexts = 9
# batch大小
batch_size = 8stddev = 0.046875hidden = 1024
cell_dim = 1024keep_dropout_rate = 0.95
relu_clip = 20wav_path = 'dataset/data_thchs30/train'
tran_path = 'dataset/data_thchs30/data'save_path = 'model/'#迭代次数
epochs = 200learning_rate = 0.001def main(argv=None):if not os.path.exists(wav_path) or not os.path.exists(tran_path):print('目录', wav_path, '或', tran_path, "不存在!")returnprocessor = AudioProcessor(wav_path, tran_path, features, contexts)words, words_size = processor.get_property()birnn = BiRNN(features, contexts, batch_size, hidden, cell_dim, stddev, keep_dropout_rate, relu_clip, words_size+1, save_path, learning_rate)print('Run training epoch')start_epoch = birnn.get_property()for epoch in range(epochs):  # 样本集迭代次数epoch_start_time = time.time()if epoch < start_epoch:continueprint("epoch start:", epoch, "total epochs= ", epochs)        batches_per_epoch = processor.batches_per_epoch(batch_size)print("total loop ", batches_per_epoch, "in one epoch,", batch_size, "items in one loop")next_index = 0        #######################run batch####for batch in range(batches_per_epoch):  # 一次batch_size,取多少次            next_index, source, source_lengths, sparse_labels = processor.next_batch(next_index, batch_size)batch_loss = birnn.run(batch, source, source_lengths, sparse_labels, words, epoch)epoch_duration = time.time() - epoch_start_timelog = 'Epoch {}/{}, batch:{}, batch_loss: {:.3f}, time: {:.2f} sec'print(log.format(epoch, epochs, batch, batch_loss, epoch_duration))if __name__ == '__main__':tf.app.run()

运行上面的代码,运行结果如下,

这样就开始训练模型了,我们可以看到,损失batch_loss在慢慢的下降,说明模型正在“学习”了,静等代码执行完毕吧,这个运行时间因机器而异,尽量使用高性能的显卡,我会上传我训练好的模型,大家可以在这基础上继续训练,或者测试。

运行结果:

可以看到,loss达到了个位数,甚至零点几,这个效果已经不错了。我们将训练时注释了的这段代码打开,再运行看看,

        # 验证模型的准确率,比较耗时,我们训练的时候全力以赴,所以这里先不跑if (batch + 1) % 1 == 0:            feed2 = {self.input: source, self.seq_length: source_lengths, self.label: sparse_labels, self.keep_dropout: 1.0}        decoded, label_error_rate = self.sess.run([self.decoded[0], self.label_error_rate], feed_dict=feed2)        dense_decodeds = tf.sparse_tensor_to_dense(decoded, default_value=0).eval(session=self.sess)dense_original_labels = sparse_tuple_to_text(sparse_labels, words)        counter = 0            print('Label err rate: ', label_error_rate)for dense_original_label, dense_decoded in zip(dense_original_labels, dense_decodeds):# convert to stringsdecoded_str = dense_to_text(dense_decoded, words)                 print('Original: {}'.format(dense_original_label))print('Decoded:  {}'.format(decoded_str))print('------------------------------------------')counter = counter + 1

运行结果,

28、历史遗留问题

如果以前看过我这篇语音识别博客的就知道,当时训练结束以后有如下问题,

--------------------------------补充-------------------------

20181102

训练了两天两夜,贴上训练结果,

可以看到,准确率还是可以了,但是,句子后面怎么有一堆的“龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚龚”呢??先留下疑问,后续找到问题我们再更新博客。

-------------------------------------------------------------

其实出现这个问题并不是模型的问题,而是因为以前代码中,我在调用TensorFlow的tf.sparse_tensor_to_dense函数将批量的稀疏矩阵还原为稠密矩阵时,出现了像上一篇博客中,我们将稀疏表示还原为文字时出现的问题,截图如下,

(在稀疏表示还原成文字的时候,“盎然”后面多了几个空格,这是因为批量处理时,矩阵的shape是统一大小的,所以对于比较短的句子,就会在句子后面“补零”了。)

而我在调用TensorFlow的tf.sparse_tensor_to_dense函数时将默认值设置成了-1,这样的话就导致稀疏矩阵中的“零值”都被设置成了-1。而“龚”字又刚好在我们words列表中的最后一个字,

所以在进行批量语音识别时才会出现一堆“龚”字了。

解决的方法就是将default_value设为0,这样的话对于比较短的句子,它后面补的也只是空格而已。

29、完整代码

完整代码链接如下,

https://mianbaoduo.com/o/bread/ZZyWm58=

下一讲,我们将上面训练好的模型进行固化和应用。

TensorFlow入门教程(19)语音识别(下)相关推荐

  1. TensorFlow入门教程(18)语音识别(中)

    # #作者:韦访 #博客:https://blog.csdn.net/rookie_wei #微信:1007895847 #添加微信的备注一下是CSDN的 #欢迎大家一起学习 # 6.提取音频数据的M ...

  2. TensorFlow入门教程:1:安装和第一个例子程序

    TensorFlow™ 是Google开源的一个采用数据流图用于数值计算的开源库.截止到目前为止在github上已经获得超过6万个Star,已经成为深度学习和机器学习方面最为受欢迎的项目,炙手可热.这 ...

  3. Tensorflow 入门教程

    Tensorflow 入门教程  http://tensornews.cn/ 深度学习发展史 特征工程 深度学习之激活函数 损失函数 反向传播算法 [上] 反向传播算法 [下] Tensorflow ...

  4. (转)tensorflow入门教程(二十六)人脸识别(上)

    https://blog.csdn.net/rookie_wei/article/details/81676177 1.概述 查看全文 http://www.taodudu.cc/news/show- ...

  5. TensorFlow入门教程(1)安装、基础、Tensorboard

    TensorFlow入门教程 本教程适合人群: - 会些python但不是特别精通 - 机器学习的初学者 本教程预计耗时: - 2-3小时 本教程预计效果: - 掌握TensorFlow的基础操作 - ...

  6. Arduino可穿戴开发入门教程Windows平台下安装Arduino IDE

    Arduino可穿戴开发入门教程Windows平台下安装Arduino IDE Windows平台下安装Arduino IDE Windows操作系统下可以使用安装向导和压缩包形式安装.下面详细讲解这 ...

  7. python十大操作方法_python最佳入门教程(19): 列表操作方法

    1 教程引言 本教程基于python3.x, 是针对初学者的一系列python入门教程,在知乎上常有人问我计算机该怎么学,如何自学编程,笔者也是通过自学编程而进入IT这一行业的,回顾入行的这几年,从音 ...

  8. tensorflow官方文档_开源分享:最好的TensorFlow入门教程

    如果一门技术的学习曲线过于陡峭,那么我们在入门时的场景往往是,一鼓作气,没入门,再而衰,三而竭.演绎一出从入门到放弃的败走麦城. 今天发现一个入门TensorFlow的宝藏,迫不及待的分享给大家.这个 ...

  9. 比官方更简洁的Tensorflow入门教程

    声明: 参考自Python TensorFlow Tutorial – Build a Neural Network,本文简化了文字部分 文中有很多到官方文档的链接,毕竟有些官方文档是中文的,而且写的 ...

最新文章

  1. android fersco 框架,Android Fresco框架的简单使用
  2. Android应用坐标系统全面详解
  3. 【任务脚本】0619京东叠蛋糕开奖好评,简单统计等级和红包关系,坐等淘宝开奖,更新汇总战绩...
  4. tomcat跳转报错_微信小程序开发:使用reLaunch跳转时报错的解决步骤
  5. 12个便于web设计及开发的在线工具
  6. 【网络安全面试题】——如何渗透测试文件目录穿越
  7. MATLAB实现频数直方图——hist的使用
  8. 信息课为什么不叫计算机课,你理解的互联网是这样的吗?为什么叫加入互联网?...
  9. java web相对路径_java(Web)中相对路径,绝对路径问题总结
  10. 一款开源的截图神器,支持 macOS/Windows/Linux
  11. IP子网划分【网工复习专题】2022.5.8
  12. 2005年上半年软件评测师试题和答案
  13. 修复cdn服务器连接异,cdn服务器连接异常怎么处理
  14. Micheal Jackson 离开了我们
  15. BitLocker加密怎么解除?
  16. 项目一:家庭记账软件
  17. 使用Mysql函数生成指定的自增序列号
  18. 天气变冷了,用Python给爸妈制作一个天气提醒小助手
  19. SE14 调整并激活数据库
  20. 优思学院|测量系统分析(MSA)中的偏倚和线性是什么?

热门文章

  1. python爬取游天下苏州短租网数据(bs4)
  2. XKT-510和T3168和XKT-511芯片的无线供电方案原理
  3. 深圳学区房购买 查查吧学区地图
  4. 比较实用的几种端口入侵
  5. 毕业时制作的游戏demo
  6. 使用pywinrm远程控制windows系统
  7. 双系统如何远程切换设置
  8. ASP初学者的10个编程技巧
  9. vocab 文本_fastNLP中的Vocabulary
  10. 滚动代码瀑布流代码效果国际站店铺装修代码生成器在线编辑器