完整版请微信关注“大数据技术宅”

序言:语音识别作为人工智能领域重要研究方向,近几年发展迅猛,其中RNN的贡献尤为突出。RNN设计的目的就是让神经网络可以处理序列化的数据。本文笔者将陪同小伙伴们一块儿踏上语音识别之梦幻旅途,相信此处风景独好。

内容目录

环境准备

RNN与LSTM介绍RNNLSTM语音识别介绍声学特征提取声学特征转换成音素(声学模型)音素转文本(语言模型+解码)语音识别简单实现提取WAV文件中特征将WAV文件对应的文本文件转换成音素分类定义双向LSTM

模型训练和测试

环境准备

1、win10
2、python3.6.4
3、pip3
4、tensorflow1.12.0
(在运行代码的时候如果显示缺少python模块,直接用pip3安装即可)

RNN与LSTM介绍

循环神经网络(RNN)是神经网络模型中的一种,其中部分神经元的连接组成了有向环,有向环使得RNN中出现了内部状态或带记忆的结构,赋予了RNN对动态序列进行建模的能力。在接下来的两小节中笔者将详细的介绍一下RNN,以及RNN的变种长短期记忆(Long Short Term Memory,LSTM)网络。

RNN

图1中描绘了一个简单循环神经网络,叫做Elman网络,一共包含三层:输入层(input)、隐藏层(hidden)以及输出层(output)。context unit用来存储上一次的隐藏层的值,与下一次的输入一起输入到隐藏层(图中实线表示直接复制,虚线表示需要通过学习获得)。Elman网络和乔丹网络是循环神经网络中最简单的形态,本文只介绍Elman网络,感兴趣的读者可自行查阅乔丹网络。

图1 Elman网络

Elman网络的数学表达式如下:
假设:

  • x(t):在t时间点的输入向量;

  • h(t):在t时间点的隐藏向量;

  • y(t):在t时间点的输出向量;

  • W、U和b:参数矩阵;

  • sigma(h)和sigma(y):激活函数。

隐藏层向量和输出层向量可以表示为:


LSTM

长短期记忆(Long Short Term Memory,LSTM)是RNN的一种,最早由Hochreiter和Schmidhuber(1977)年提出,该模型克服了一下RNN的不足,通过刻意的设计来避免长期依赖的问题。现在很多大公司的翻译和语音识别技术核心都以LSTM为主。下边就详细的介绍一下LSTM的构成。图2描绘了LSTM单元的结构。

图2  LSTM单元结构

为了避免RNN中梯度消失和梯度爆炸的问题,LSTM相对于普通RNN单元有比较大的区别,主要的核心思想是:
①采用叫“细胞状态”(state)的通道贯穿整个时间序列,如图3中从C(t-1)到C(t),这条线上只有乘法操作和加法操作。
②通过设计“门”的结构来去除或者增加信息到细胞状态,LSML中有三个门,分别是“忘记门”、“输入门”和“输出门”。

图3 “细胞状态”通道示意图

下边详细阐述一下LSML中的三个门。
(1)忘记门
       图4中红色加粗部分为LSML单元中“忘记门”的位置,“忘记门”决定之前状态中的信息有多少应该舍弃。它会输出一个0和1之间的数,代表C(t-1)中保留的部分。“忘记门”的计算公式如下:

“忘记门”的输入是x(t),上一时刻的隐藏层输出h(t-1)、W(f)和U(f)是“忘记门的参数”,需要通过训练获取。

图4 “忘记门”

(2)输入门

图5中红色加粗部分为“输入门”,输入门决定什么样的输入信息应该保留在“细胞状态”C(t)中。它会读取h(t-1)和x(t)的内容,其计算公式为:


其中输入是h(t-1)和x(t),W(i)、U(i)、W(c)、U(c)是要训练的参数。

图5 “输入门”

接下来,研究一下“细胞状态”的值是如何更新的。首先经过忘记门,算出旧的细胞状态中有多少被遗弃,接着输入门将所得的结果加入到细胞状态,表示新的输入信息中有多少加入到细胞状态中。计算公式如下:

细胞状态的更新过程如图6所示:

图6 “细胞状态”更新

(3) 输出门

在细胞状态更新之后,将会基于细胞状态计算输出。首先输入数据h(t-1)和x(t),通过sigmoid激活函数得到“输出门”的值。然后,把细胞状态经过tanh处理,并与输出门的值相乘得到细胞的输出结果。输出门的公式如下:


输出门的计算流程如图7红色加粗部分所示:

图7 “输出门”

语音识别介绍

语音识别的最主要过程是:(1)从声音波形中提取声学特征;(2)将声学特征转换成发音的因素;(3)使用语言模型等解码技术转变成我们能读懂的文本。语音识别系统的典型结构如图8所示:

图8 语音识别结构

声学特征提取

声音实际上一种波,原始的音频文件叫WAV文件,WAV文件中存储的除了一个文件头以外,就是声音波形的一个个点。如图9所示:

图9 声音波形示意图

要对声音进行分析,首先对声音进行分帧,把声音切分成很多小的片段,帧与帧之间有一定的交叠,如图10,每一帧长度是25ms,帧移是10ms,两帧之间有25-10=15ms的交叠。

图10 帧切割图

分帧后,音频数据就变成了很多小的片段,然后针对小片段进行特征提取,常见的提取特征的方法有:线性预测编码(Linear Predictive Coding,LPC),梅尔频率倒谱系数(Mel-frequency Cepstrum),把一帧波形变成一个多维向量的过程就是声学特征提取。

声学特征转换成音素(声学模型)

音素是人发音的基本单位。对于英文,常用的音素是一套39个音素组成的集合。对于汉语,基本就是汉语拼音的生母和韵母组成的音素集合。本文例子中LSTM+CTC神经网络就是声学特征转换成音素这个阶段,该阶段的模型被称为声学模型。

音素转文本(语言模型+解码)

得到声音的音素序列后,就可以使用语言模型等解码技术将音素序列转换成我们可以读懂的文本。解码过程对给定的音素序列和若干假设词序列计算声学模型和语言模型分数,将总体输出分数最高的序列作为识别的结果(这部分是比较复杂的,感兴趣的读者可以查阅相关资料)。

语音识别简单实现

本文通过一个简单的例子演示如何用tensorflow的LSTM+CTC完成一个端到端的语音识别,为了简化操作,本例子中的语音识别只训练一句话,这句话中的音素分类也简化成对应的字母(与真实因素的训练过程原理一致)。计算过程如下图所示:

提取WAV文件中特征

首先读者肯定会有疑问?什么是WAV文件?笔者在此简单的介绍一下,WAV格式是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持,是一种无损的音频数据存放格式。

本文在读取WAV的特征数据后,采用python_speech_features包中的方法来读取文件的MFCC特征,详细代码如下:

def get_audio_feature():
  '''
  获取wav文件提取mfcc特征之后的数据
  '''
  audio_filename = "audio.wav"
  #读取wav文件内容,fs为采样率, audio为数据
  fs, audio = wav.read(audio_filename)   #提取mfcc特征
  inputs = mfcc(audio, samplerate=fs)
  # 对特征数据进行归一化,减去均值除以方差
  feature_inputs = np.asarray(inputs[np.newaxis, :])
  feature_inputs = (feature_inputs - np.mean(feature_inputs))/np.std(feature_inputs)     #特征数据的序列长度
  feature_seq_len = [feature_inputs.shape[1]]
  return feature_inputs, feature_seq_len

函数的返回值feature_seq_len表示这段语音被分割成了多少帧,一帧数据计算出一个13维长度的特征值。返回值feature_inputs是一个二维矩阵,矩阵行数是feature_seq_len长度,列数是13。

将WAV文件对应的文本文件转换成音素分类

本文音素的数量是28,分别对应26个英文字母、空白符和没有分到类情况。WAV文件对应的文本文件的内容是she had your dark suit in greasy wash water all year。现在把这句话转换成整数表示的序列,空白用0表示,a-z分别用数字1-26表示,则转换的结果为:[19 8 5 0 8 1 4 0 25 15 21 18 0 4 1 18 110 19 21 9 20 0 9 14 0 7 18 5 1 19 25 0 231 19 8 0 23 1 20 5 18 0 1 12 12 0 25 5 118],最后将整个序列转换成稀疏三元组结构,这样就可以直接用在tensorflow的tf.sparse_placeholder上。转换代码如下:

with open(target_filename, 'r') as f:
    #原始文本为“she had your dark suit in greasy wash water all year”
    line = f.readlines()[0].strip()
    targets = line.replace(' ', '  ')
    targets = targets.split(' ')
    targets = np.hstack([SPACE_TOKEN if x == '' else list(x) for x in targets])
    targets = np.asarray([SPACE_INDEX if x == SPACE_TOKEN else ord(x) - FIRST_INDEX
                      for x in targets])
    # 将列表转换成稀疏三元组
    train_targets = sparse_tuple_from([targets])
    print(train_targets)
  return train_targets

定义双向LSTM

定义双向LSTM及LSTM之后的特征映射的代码如下:

 def inference(inputs, seq_len):
  #定义一个向前计算的LSTM单元,40个隐藏单元
  cell_fw = tf.contrib.rnn.LSTMCell(num_hidden, 
                        initializer=tf.random_normal_initializer(
                                        mean=0.0, stddev=0.1),
                        state_is_tuple=True)   # 组成一个有2个cell的list
  cells_fw = [cell_fw] * num_layers
  # 定义一个向后计算的LSTM单元,40个隐藏单元
  cell_bw = tf.contrib.rnn.LSTMCell(num_hidden, 
                        initializer=tf.random_normal_initializer(
                                        mean=0.0, stddev=0.1),
                        state_is_tuple=True)
  # 组成一个有2个cell的list
  cells_bw = [cell_bw] * num_layers
  outputs, _, _ = tf.contrib.rnn.stack_bidirectional_dynamic_rnn(cells_fw,
                                                                 cells_bw,
                                                                 inputs,
                                                               dtype=tf.float32,
                                                        sequence_length=seq_len)   shape = tf.shape(inputs)
  batch_s, max_timesteps = shape[0], shape[1]
  outputs = tf.reshape(outputs, [-1, num_hidden])   W = tf.Variable(tf.truncated_normal([num_hidden,
                                         num_classes],
                                        stddev=0.1))   b = tf.Variable(tf.constant(0., shape=[num_classes]))
  # 进行全连接线性计算
  logits = tf.matmul(outputs, W) + b
  # 将全连接计算的结果,由宽度40变成宽度80,
  # 即最后的输入给CTC的数据宽度必须是26+2的宽度
  logits = tf.reshape(logits, [batch_s, -1, num_classes])
  # 转置,将第一维和第二维交换。
  # 变成序列的长度放第一维,batch_size放第二维。
  # 也是为了适应Tensorflow的CTC的输入格式
  logits = tf.transpose(logits, (1, 0, 2))
  return logits

模型训练和测试

最后将读取数据、构建LSTM+CTC网络及训练过程结合起来,在完成500次迭代训练后,进行测试,并将结果输出,部分代码如下(完整代码,请读者关注本公众号“大数据技术宅”,输入“语音识别demo”获取):

def main():
  # 输入特征数据,形状为:[batch_size, 序列长度,一帧特征数]
  inputs = tf.placeholder(tf.float32, [None, None, num_features])

# 输入数据的label,定义成稀疏sparse_placeholder会生成稀疏的tensor:SparseTensor
  # 这个结构可以直接输入给ctc求loss
  targets = tf.sparse_placeholder(tf.int32)

# 序列的长度,大小是[batch_size]大小
  # 表示的是batch中每个样本的有效序列长度是多少
  seq_len = tf.placeholder(tf.int32, [None])

# 向前计算网络,定义网络结构,输入是特征数据,输出提供给ctc计算损失值。
  logits = inference(inputs, seq_len)

# ctc计算损失
  # 参数targets必须是一个值为int32的稀疏tensor的结构:tf.SparseTensor
  # 参数logits是前面lstm网络的输出
  # 参数seq_len是这个batch的样本中,每个样本的序列长度。
  loss = tf.nn.ctc_loss(targets, logits, seq_len)
  # 计算损失的平均值
  cost = tf.reduce_mean(loss)

训练过程及结果如下图:

从上图训练结果可以清洗的看出经过500次的迭代训练,语音文件基本已经可以完全识别,本例只演示了一个简单的LSTM+CTC的端到端的训练,实际的语音识别系统还需要大量训练样本以及将音素转换成文本的解码过程。后续文章中,笔者会继续深入语音识别。
       最后,在2019开年之际,笔者祝各位爱学习的小哥哥,小姐姐,骑猪当先,万猪奔腾,猪年行大运,发大财。

阅读本文的人还看了:


大数据利器Hive

BP神经网络

初识TensorFlow

GAN模型生成手写字

深度学习(Deep Learning)资料大全(不断更新)

OpenCV+TensorFlow实现自定义手写图像识别

用TensorFlow教你做手写字识别(准确率94.09%)

Storm环境搭建(分布式集群)

机器学习——逻辑回归

扫码关注.大数据技术宅

语音识别(LSTM+CTC)的更多相关文章

  1. tensorflow LSTM+CTC使用详解

    最近用tensorflow写了个OCR的程序,在实现的过程中,发现自己还是跳了不少坑,在这里做一个记录,便于以后回忆.主要的内容有lstm+ctc具体的输入输出,以及TF中的CTC和百度开源的warp ...

  2. 以lstm+ctc对汉字识别为例对tensorflow 中的lstm,ctc loss的调试

    #-*-coding:utf8-*- __author = "buyizhiyou" __date = "2017-11-21" ''' 单步调试,结合汉字的识 ...

  3. 语音识别中的CTC算法的基本原理解释

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文作者:罗冬日 目前主流的语音识别都大致分为特征提取,声学模型,语音模型几个部分.目前结合神经网络的端到端的声学模型训练方法主要CTC和基 ...

  4. CNN+BLSTM+CTC的验证码识别从训练到部署

    项目地址:https://github.com/kerlomz/captcha_trainer 1. 前言 本项目适用于Python3.6,GPU>=NVIDIA GTX1050Ti,原mast ...

  5. 【转】CNN+BLSTM+CTC的验证码识别从训练到部署

    [转]CNN+BLSTM+CTC的验证码识别从训练到部署 转载地址:https://www.jianshu.com/p/80ef04b16efc 项目地址:https://github.com/ker ...

  6. [验证码识别技术] 字符型验证码终结者-CNN+BLSTM+CTC

    验证码识别(少样本,高精度)项目地址:https://github.com/kerlomz/captcha_trainer 1. 前言 本项目适用于Python3.6,GPU>=NVIDIA G ...

  7. 【OCR技术系列之七】端到端不定长文字识别CRNN算法详解

    在以前的OCR任务中,识别过程分为两步:单字切割和分类任务.我们一般都会讲一连串文字的文本文件先利用投影法切割出单个字体,在送入CNN里进行文字分类.但是此法已经有点过时了,现在更流行的是基于深度学习 ...

  8. ANN:DNN结构演进History—LSTM_NN

    前言 语音识别和动作识别(Action.Activities)  等一些时序问题,通过微分方式可以视为模式识别方法中的变长模式识别问题.语音识别的基元为音素.音节,字母和句子模式是在时间轴上的变长序列 ...

  9. [C5W3] Sequence Models - Sequence models & Attention mechanism

    第三周 序列模型和注意力机制(Sequence models & Attention mechanism) 基础模型(Basic Models) 在这一周,你将会学习 seq2seq(sequ ...

随机推荐

  1. JS获取键盘事件

    <script type="text/javascript" language=JavaScript charset="UTF-8"> docume ...

  2. go语言基础之字符串遍历

    Go的字符串遍历,有两种方式: utf-8遍历 unicode遍历 package main import "fmt" func main() { str := "Hel ...

  3. 判断js中的数据类型的几种方法

    判断js中的数据类型有一下几种方法:typeof.instanceof. constructor. prototype. $.type()/jquery.type(),接下来主要比较一下这几种方法的异 ...

  4. pip和cmd常用命令

    1.pip常用命令 显示模块的详情  pip    show 安装模块   pip    install    模块名称 卸载模块    pip    uninstall    模块名称 查看当前环境 ...

  5. python os.walk()方法--遍历当前目录的方法

    前记:有个奇妙的想法并想使用代码实现,发现了一个坑,百度了好久也没发现的"填坑"的文章~~~~~~~~~ 那就由我来填 os.walk()支持相对路径 例如 os.walk(&qu ...

  6. CentOS 7 安装配置 OpenVPN 客户端

    安装 epel yum 源: $ rpm -ivh http://mirrors.sohu.com/fedora-epel/6/x86_64/epel-release-6-8.noarch.rpm $ ...

  7. spring的定时器

    一:基于xml配置的方式 1:编写普通的pojo 类 package com.aflyun.web.task; import org.springframework.stereotype.Compon ...

  8. Springboot Selenide UI 自动化测试

    标题中的Selenide 并没有拼错,确实不是selenium Selenium做UI自动化,可以参考我其他的blog: Selenium做自动化最好要封装起来,否则对于元素的等待,页面的加载会使得自 ...

  9. BBS论坛(二十一)

    21.1.编辑轮播图功能完成 (1)cms_banners.html 把属性绑定到<tr>上面,方便找到各属性的值 <tbody> {% for banner in banne ...

  10. 微信小程序使用nginx方向代理实现内嵌非业务域名

    通过上一篇文章简单介绍了一下微信小程序的新组件<web-view>,实现了在小程序中内嵌业务域名的功能.但是boss要的小程序中不仅想内嵌业务域名,还想要打开一些非业务域名(无法操作web ...