CTC是一种端到端的语音识别技术,他避免了需要字或者音素级别的标注,只需要句子级别的标注就可以进行训练,感觉非常巧妙,也很符合神经网络浪潮人们的习惯。特别是LSTM+CTC相较于之前的DNN+HMM,LSTM能够更好的捕捉输入中的重要的点(LSTM随着状态数目增加参数呈线性增加,而HMM会平方增加),CTC打破了隐马尔科夫的假设,把整个模型从静态分类变成了序列分类。

语音识别的评价指标

在语音识别中,在数据集 S S S上评价模型 h h h的好坏一般用标签错误率(Label Error Rate): L E R ( h , S ) = 1 ∣ S ∣ ∑ ( x , z ) ∈ S E D ( h ( x ) , z ) ∣ z ∣ LER(h,S)=\frac{1}{|S|}\sum_{(x,z)\in S}\frac{ED(h(x),z)}{|z|} LER(h,S)=∣S∣1​∑(x,z)∈S​∣z∣ED(h(x),z)​, E D ( p , q ) ED(p,q) ED(p,q)表示 p p p和 q q q两个序列的编辑距离。

语音识别模型

在语音识别中,提取语音信号的MFCC特征 x x x,经过神经网络或者GMM处理后经过一个softmax层得到一个每个音素的后验概率 y y y, y y y的类别有 ∣ L ∣ + 1 |L|+1 ∣L∣+1种, L L L是可能出现的字符,加1为建个符。定义 B B B为简单的压缩变换,把路径 π \pi π(路径就是一种音素出现的路线)中相邻相同的音素合并,空音素去掉,再特征 x x x下定序列 l l l出现的条件概率为:

p ( l ∣ x ) = ∑ π = ∈ B − 1 ( l ) p ( π ∣ x ) p(l|x)=\sum_{\pi=\in B^{-1}(l)}p(\pi|x) p(l∣x)=π=∈B−1(l)∑​p(π∣x)

前向后向算法(Forward-Backward Algorithm)

定义符号 l p : q l_{p:q} lp:q​表示符号序列KaTeX parse error: Expected '}', got 'EOF' at end of input: …..l_{q-1},l_{q},容易得知,要想使得路径 B ( π ′ ) B(\pi') B(π′)满足一定的 l l l, π \pi π路线上的状态跳转需要满组 l ′ l' l′的先后顺序,不同的符号之间可以插入blank。

定义前向变量 α ( t , u ) \alpha(t,u) α(t,u): α t ( t , u ) = ∑ π ∈ N T , B ( π 1 : t ) = 1 1 : u ∏ t ′ = 1 t y π t ′ t ′ \alpha_t(t,u)=\sum_{\pi\in N^T,B(\pi_{1:t})=1_{1:u}}\prod_{t'=1}^ty_{\pi_{t'}}^{t'} αt​(t,u)=∑π∈NT,B(π1:t​)=11:u​​∏t′=1t​yπt′​t′​

α ( t , s ) \alpha(t,s) α(t,s)可以递推的用 α ( t − 1 , s ) , α ( t − 1 , s − 1 ) \alpha(t-1,s),\alpha(t-1,s-1) α(t−1,s),α(t−1,s−1)计算。

为了方便起见,我们在 l l l相邻标签之间插入了空白(blank),在开始和末尾也加入了空白,这样我们用 l ′ l' l′表示这个新的标记, l ′ l' l′的长度就为 2 ∣ l ∣ + 1 2|l|+1 2∣l∣+1。在计算 l ′ l' l′前缀的概率中,我们允许空白和非空白标签之间转移,那么我么有动态规划的初始条件:

α ( 1 , 1 ) = y b 1 \alpha(1,1)=y_b^1 α(1,1)=yb1​

α ( 1 , 2 ) = y l 1 1 \alpha(1,2)=y_{l_1}^1 α(1,2)=yl1​1​

α ( 1 , u ) = 0 , u > 2 \alpha(1,u)=0,u>2 α(1,u)=0,u>2

α ( t , 0 ) = 0 \alpha(t,0)=0 α(t,0)=0

α ( t , u ) = 0 , u &lt; U ′ − 2 ( T − t ) − 1 \alpha(t,u)=0,u&lt;U'-2(T-t)-1 α(t,u)=0,u<U′−2(T−t)−1

U ′ U' U′表示序列 2 ∣ l 1 : u ∣ + 1 2|l_{1:u}|+1 2∣l1:u​∣+1。

递归算式:

α ( t , u ) = y l u ′ t ∑ i = f ( u ) u α ( t − 1 , i ) \alpha(t,u)=y_{l'_u}^t\sum_{i=f(u)}^u\alpha(t-1,i) α(t,u)=ylu′​t​i=f(u)∑u​α(t−1,i)

f ( u ) = { u − 1 i f l u ′ = b l a n k o r l u − 2 ′ = l u ′ u − 2 o t h e r w i s e f(u)=\{\begin{array}{} u-1 &amp; if~l'_u=blank~or~l'_{u-2}=l'_u\\u-2 &amp; otherwise \end{array} f(u)={u−1u−2​if lu′​=blank or lu−2′​=lu′​otherwise​

简单地说就是现在如果状态是blank,那么上一个状态有可能是blank或者是上一个字符,如果现在的状态是字符,那他上一个状态可能是相同的字符,可能是blank,也可能是上一个非blank字符,但如果现在的字符与上一个非空字符相同,那意味着现在的状态不能直接从上一个非空字符跳过来,必须隔一个blank,所以只能从blank和相同的字符跳过来。

相似的定义后向变量 β ( t , u ) \beta(t,u) β(t,u): α t ( t , s ) = ∑ π ∈ N T , B ( π t : T ) = 1 u : U ∏ t ′ = t + 1 T y π t ′ t ′ \alpha_t(t,s)=\sum_{\pi\in N^T,B(\pi_{t:T})=1_{u:U}}\prod_{t'=t+1}^{T}y_{\pi_{t'}}^{t'} αt​(t,s)=∑π∈NT,B(πt:T​)=1u:U​​∏t′=t+1T​yπt′​t′​

依然用动态规划来求解,动态规划的初始条件:

β ( T , U ′ ) = 1 \beta(T,U')=1 β(T,U′)=1

β ( T , U ′ − 1 ) = 1 \beta(T,U'-1)=1 β(T,U′−1)=1

β ( T , u ) = 0 , u &lt; U ′ − 1 \beta(T,u)=0,u&lt;U'-1 β(T,u)=0,u<U′−1

β ( t , u ) = 0 , u &gt; 2 t \beta(t, u)=0,u&gt;2t β(t,u)=0,u>2t

递归算式:

β ( t , u ) = ∑ i = u g ( u ) β ( t + 1 , i ) y l t u t ′ + 1 \beta(t,u)=\sum_{i=u}^{g(u)}\beta(t+1,i)y^{t'+1}_{l^u_t} β(t,u)=i=u∑g(u)​β(t+1,i)yltu​t′+1​

g ( u ) = { u + 1 i f l u ′ = b l a n k o r l u + 2 ′ = l u ′ u + 2 o t h e r w i s e g(u)=\{\begin{array}{} u+1 &amp; if~l'_u=blank~or~l'_{u+2}=l'_u\\u+2 &amp; otherwise \end{array} g(u)={u+1u+2​if lu′​=blank or lu+2′​=lu′​otherwise​

在实际中,上述递归很快就会导致计算机出现下溢。避免下溢的一种好方法是先取对数,在计算结束时仅取幂以找到真实概率。在这种情况下,一个有用的等式是

l n ( a + b ) = l n a + l n ( 1 + e l n b − l n a ) ln(a+b)=ln~a+ln(1+e^{ln~b-ln~a}) ln(a+b)=ln a+ln(1+eln b−ln a)

这样就允许前向和后向变量相加,同时保留取对数。但是这种方法的稳定性较差,并且对于很长的序列可能会失效。

Loss Function

the CTC loss函数 L ( S ) L(S) L(S)被定义为训练集 S S S种正确标签的negative log probability:

L ( S ) = − l n ∏ ( x , z ) ∈ S = − ∑ ( x , z ) ∈ S l n p ( z ∣ x ) L(S)=-ln\prod_{(x,z)\in S}=-\sum_{(x,z)\in S}ln~p(z|x) L(S)=−ln(x,z)∈S∏​=−(x,z)∈S∑​ln p(z∣x)

这个损失函数是可微的,方便加入到神经网络中进行反向传播。我们定义example loss为

L ( x , z ) = − l n p ( z ∣ x ) L(x,z)=-ln~p(z|x) L(x,z)=−ln p(z∣x)

那么:

L ( S ) = ∑ ( x , z ) ∈ S L ( x , z ) L(S)=\sum_{(x,z)\in S}L(x,z) L(S)=(x,z)∈S∑​L(x,z)

∂ L ( S ) ∂ w = ∑ ( x , z ) ∈ S ∂ L ( x , z ) ∂ w \frac{\partial L(S)}{\partial w}=\sum_{(x,z)\in S}\frac{\partial L(x,z)}{\partial w} ∂w∂L(S)​=(x,z)∈S∑​∂w∂L(x,z)​。

上一小节中,我们得到了前向后向变量,我们令 l = z l=z l=z,易知:

α ( t , u ) β ( t , u ) = ∑ π t = z u , B ( π ) = z ∏ t = 1 T y π t t = ∑ π t = z u , B ( π ) = z p ( π ∣ x ) \alpha(t,u)\beta(t,u)=\sum_{\pi_t=z_u,B(\pi)=z}\prod_{t=1}^Ty_{\pi_t}^t=\sum_{\pi_t=z_u,B(\pi)=z}p(\pi|x) α(t,u)β(t,u)=πt​=zu​,B(π)=z∑​t=1∏T​yπt​t​=πt​=zu​,B(π)=z∑​p(π∣x)

那么对于任意时刻 t ∈ [ 1 , T ] t\in [1,T] t∈[1,T],我们有

p ( z ∣ x ) = ∑ u = 1 ∣ z ′ ∣ α ( t , u ) β ( t , u ) p(z|x)=\sum_{u=1}^{|z'|}\alpha(t,u)\beta(t,u) p(z∣x)=u=1∑∣z′∣​α(t,u)β(t,u)

也就是说

L ( x , z ) = − l n ∑ u = 1 ∣ z ′ ∣ α ( t , u ) β ( t , u ) L(x,z)=-ln~\sum_{u=1}^{|z'|}\alpha(t,u)\beta(t,u) L(x,z)=−ln u=1∑∣z′∣​α(t,u)β(t,u)

Loss Gradient

从前面的推导可得:

∂ L ( x , z ) ∂ y k t = − ∂ l n p ( z ∣ x ) ∂ y k t = − 1 p ( z ∣ x ) ∂ p ( z ∣ x ) ∂ y k t \frac{\partial L(x,z)}{\partial y_k^t}=-\frac{\partial ln~p(z|x)}{\partial y_k^t}=-\frac{1}{p(z|x)}\frac{\partial p(z|x)}{\partial y_k^t} ∂ykt​∂L(x,z)​=−∂ykt​∂ln p(z∣x)​=−p(z∣x)1​∂ykt​∂p(z∣x)​

因为网络输出不会相互影响,求解 ∂ p ( z ∣ x ) ∂ y k t \frac{\partial p(z|x)}{\partial y_k^t} ∂ykt​∂p(z∣x)​我们只需要考虑那些在时间 t t t经过标签 k k k的路径。因为 k k k有可能没有出现在 z z z中,对于这些 k k k,我们令 ∂ p ( z ∣ x ) ∂ y k t = 0 \frac{\partial p(z|x)}{\partial y_k^t}=0 ∂ykt​∂p(z∣x)​=0。对于 k k k也可能多次出现在 z z z中,我们定义 B ( z , k ) = u ∣ z u ′ = k B(z,k)={u|z'_u=k} B(z,k)=u∣zu′​=k,易得:

∂ p ( z ∣ x ) ∂ y k t = 1 y k t ∑ u ∈ B ( z , k ) α ( t , u ) β ( t , u ) \frac{\partial p(z|x)}{\partial y_k^t}=\frac{1}{y_k^t}\sum_{u\in B(z,k)}\alpha(t,u)\beta(t,u) ∂ykt​∂p(z∣x)​=ykt​1​u∈B(z,k)∑​α(t,u)β(t,u)

那么:

∂ L ( x , z ) ∂ y k t = − 1 p ( z ∣ x ) ⋅ y k t ∑ u ∈ B ( z , k ) α ( t , u ) β ( t , u ) \frac{\partial L(x,z)}{\partial y_k^t}=-\frac{1}{p(z|x)\cdot y_k^t}\sum_{u\in B(z,k)}\alpha(t,u)\beta(t,u) ∂ykt​∂L(x,z)​=−p(z∣x)⋅ykt​1​u∈B(z,k)∑​α(t,u)β(t,u)

那么对于softmax之前模型的输出 a k t a_k^t akt​的反向传播梯度为:

∂ L ( x , z ) ∂ a k t = − ∑ k ′ ∂ L ( x , z ) ∂ y k ′ t ∂ y k ′ t ∂ a k t \frac{\partial L(x,z)}{\partial a_k^t}=-\sum_{k'}\frac{\partial L(x,z)}{\partial y_{k'}^t}\frac{\partial y_{k'}^t}{\partial a_k^t} ∂akt​∂L(x,z)​=−k′∑​∂yk′t​∂L(x,z)​∂akt​∂yk′t​​

我们知道:

y k t = e a k t ∑ k ′ e a k ′ t y_k^t=\frac{e^{a_k^t}}{\sum_{k'}e^{a_{k'}^t}} ykt​=∑k′​eak′t​eakt​​

∂ y k ′ t ∂ a k t = y k ′ t δ k k ′ − y k ′ t y k t \frac{\partial y_{k'}^t}{\partial a_k^t}=y_{k'}^t\delta_{kk'}-y_{k'}^ty_{k}^t ∂akt​∂yk′t​​=yk′t​δkk′​−yk′t​ykt​

综上可得:

∂ L ( x , z ) ∂ a k t = y k t − 1 p ( z ∣ x ) ∑ u ∈ B ( z , k ) α ( t , u ) β ( t , u ) \frac{\partial L(x,z)}{\partial a_k^t}=y_k^t-\frac{1}{p(z|x)}\sum_{u\in B(z,k)}\alpha(t,u)\beta(t,u) ∂akt​∂L(x,z)​=ykt​−p(z∣x)1​u∈B(z,k)∑​α(t,u)β(t,u)

可以理解为现在的输出 y k t y_k^t ykt​与根据前向后向概率求出的现在应该出现的概率之差。

实验中我们可以得知输出总是spike的形式出现,大部分被blank间隔。

decoder

分类器的输出应该是在给定输入序列下的最可能的标记输出

h ( x ) = a r g m a x l p ( l ∣ x ) h(x)=argmax_{l}p(l|x) h(x)=argmaxl​p(l∣x)

这是一个NP问题,下面是两个近似的算法,在实践中给出了良好的结果。

最佳路径解码(best path decoding)

基于的假设是最可能的路径会对应最可能表标签

h ( x ) = B ( π ∗ ) , s . t . , π ∗ = a r g m a x π p ( π ∣ x ) h(x)=B(\pi^*),s.t.,\pi^*=argmax_{\pi}p(\pi|x) h(x)=B(π∗),s.t.,π∗=argmaxπ​p(π∣x)

最佳路径解码非常容易计算(计算方法如下图),但是它不能保证找到最有可能的 l l l(因为不同的 π \pi π有可能对应相同的 l l l,加起来可能与最佳路径结果不同)。

前缀查找解码(prefix search decoding)

通过修改前向后向算法(forward-backward algorithm),我们可以高效的计算出标记前缀的后继拓展的概率。前缀查找解码(prefix search decoding)是一种剪枝的算法,剪枝主要在两个方面,一是同路径不重复计算,二是不可能状态不再搜索。算法流程如下:

翻译一下:
l ∗ l^* l∗表示当前找到最好的的序列, p ∗ p^* p∗表示以 p ∗ p^* p∗为开头的序列可能性最高,当 p ∗ p^* p∗为开头的序列可能性小于表示当前找到最好的的序列 l ∗ l^* l∗出现的可能性时停止循环,认为 l ∗ l^* l∗就是最有可能的序列。循环过程中,对于最有可能的前缀尝试后面加入每一个可能的字符,然后计算加入这个字符之后新的字符串出现的概率和以新的字符串为前缀的概率,当新的字符串出现的概率大于 l ∗ l^* l∗时更新 l ∗ l^* l∗,把以新的字符串为前缀的概率大于 l ∗ l^* l∗出现的概率的前缀加入带备选集合,循如果发现剩下字符搭配 p ∗ p^* p∗做前缀的概率之和小于 l ∗ l^* l∗出现的概率,停止循环,节约计算。循环完毕后把旧的 p ∗ p^* p∗从备选集合中剔除,再把集合中概率最大的前缀作为 p ∗ p^* p∗。
γ ( p n , t ) \gamma(p_n,t) γ(pn​,t)表示序列 p p p第 t t t个输出非blank, γ ( p b , t ) \gamma(p_b,t) γ(pb​,t)表示序列 p p p第 t t t个输出为blank。

实验结果

tensorflow中的CTC

tf.nn.ctc_loss(labels, inputs, sequence_length)

labels: 标签,一个稀疏矩阵
inputs:模型输出的logits

decoded, _ = tf.nn.ctc_beam_search_decoder(logits, sequence_len, merge_repeated=False)
decoded, _ = tf.nn.ctc_greedy_decoder(logit, sequence_len, merge_repeated=False)

logits:模型输出的logits
sequence_len:实际的长度(tensorflow静态图模型,告诉他实际的音频长度多长)
merge_repeated:把连续输出相同的合并,不同于ctc_merge_repeated,ctc_merge_repeated是把中间没有blank的合并。

音识别模型小型综述

上古时期的方法:元音部分频率共振峰匹配来识别(1952年贝尔实验室的Davis等人发明的特定说话人孤立数字识别系统)

深度神经网络之前的方法:mfcc/plp/fbank->GMM+HMM->DTW(dynamic time warping)

深度学习下的方法(参考了百度语音识别技术负责人的文章):

  • 2011-2012年:发现把GMM换成DNN有30%的提升
  • 2013年:发现调整激活函数Maxout对数据量较少的情况有所改善,对大数据量不一定有显著帮助
  • 2013-2014年:浅层DNN升级为浅层CNN
  • 2014年:开始尝试开始做RNN,尤其是LSTM
  • 2014年底2015年初:Hinton 的博士后 Alex Graves,把以前做手写体识别的LSTM 加 CTC 的系统应用在了语音识别领域,在 TIMIT 上做了结果,紧接着谷歌认为这个很有前途,就开始把这些技术推广在大数据上面。
  • 2015年:研究者发现 CNN 在时间上做卷积和 LSTM 有很多相似之处但各有特点。LSTM 更擅长做整个时间域的信息整合,因为 CNN 有很强的平移的不变性,那么鲁棒性更强
  • 2016年:Deep CNN,WaveNet 等深层 CNN

工程上的一般方法是先用模型 first-pass decoding,再用语言模型 second-pass rescoring 来提高准确率。

实现的乞丐版语音识别模型

dataset: VCTK[https://datashare.is.ed.ac.uk/download/DS_10283_2651.zip]
model: 三层lstm模型
loss function: ctc loss
feature: 13阶mfcc系数+2阶动态因子(39)

git: https://github.com/RDShi/Speech2Text

速度很慢,希望有缘人指点提升数度的方法

参考文献

Graves A, Fernandez S, Gomez F, et al. Connectionist temporal classification: labelling unsegmented sequence data with recurrent neural networks[C] Proceedings of the 23rd international conference on Machine learning. ACM, 2006: 369-376.

Connectionist Temporal Classification(CTC)、音识别模型小型综述和一个简易的语音识别模型的tensorflow实现相关推荐

  1. 一文读懂CRNN+CTC(Connectionist Temporal Classification)文字识别

    先总结一把CTC,下面文档太长: CTC是一种Loss计算方法,用CTC代替Softmax Loss,TF和pytorch都有对CTC的实现,从而解决OCR或者语音识别中序列对齐的问题.CTC特点: ...

  2. CTC的直观理解(Connectionist Temporal Classification连接时序分类),单行文本时序分类识别的端到端方法

    CTC(Connectionist Temporal Classification), ctc擅长单行验证码识别: 两组谷歌验证码示例 ctc可以提高单行文本识别鲁棒性(不同长度不同位置 ).本文用几 ...

  3. CTC介绍(connectionist temporal classification论文翻译)

    1.摘要 我基本是基于论文<Connectionist Temporal Classification: Labelling Unsegmented Sequence Data with Rec ...

  4. CTC(Connectionist Temporal Classification)介绍

    CTC解决什么问题 CTC,Connectionist Temporal Classification,用来解决输入序列和输出序列难以一一对应的问题. 举例来说,在语音识别中,我们希望音频中的音素和翻 ...

  5. 联结主义时间分类(Connectionist temporal classification)的论文笔记

    前言: { 最近在github上更新了一些代码,但没在这里更新文章,这次就在这写一篇论文的阅读笔记. 论文是<Connectionist temporal classification: Lab ...

  6. CMU 11-785 L16 Connectionist Temporal Classification

    Sequence to sequence Sequence goes in, sequence comes out No notion of "time synchrony" be ...

  7. php模拟一个简易的mvc模型

    mvc是生么? mvc是软件设计的一种结构(即模型.视图.控制器) 本篇文章会通过一个简易的webapp来介绍mvc,目录结构如下: mvcDemo     index.php     modules ...

  8. 白话CTC(connectionist temporal classification)算法讲解

    CTC是计算一种损失值,主要的优点是可以对没有对齐的数据进行自动对齐, 主要用在没有事先对齐的序列化数据训练上.比如语音识别.ocr识别等等. CTC算法有个经典的英文的论文,但是相信大多数人和我一样 ...

  9. java如何写电梯代码_JAVA编写的一个简易的电梯模型,完成电梯如何满足乘客上下楼需求(ElevatorTest)...

    [实例简介] [实例截图] [核心代码] package ele; import java.util.LinkedList; import java.util.Queue; public class ...

最新文章

  1. Linux进程ID号--Linux进程的管理与调度(三)
  2. Windows启动文件的详细介绍
  3. 青蛙捉昆虫的html游戏,幼儿园小班体育游戏教案《小青蛙捉害虫》
  4. 2011.8.2号面试
  5. 更新网盘(云存储)功能需求,免费网盘需求,手机数据备份
  6. 计算机类专业综合理论模拟试卷1,山东省2011年高等职业教育对口招生计算机类专业理论综合模拟试题(一)...
  7. OpenCV GrabCut分割的实例(附完整代码)
  8. 程序员修炼之道阅读笔记02
  9. 计算机学业水平测试题及答案初中,初中信息技术学业水平测试——选择题
  10. 弹性分布式数据集RDD
  11. 计算机控制系统编程语言有哪些,PLC编程语言有哪些种类
  12. 【CicadaPlayer】av_rescale_q 学习:转换PTS和Duration
  13. 第十一章:项目风险管理 - (11.6 实施风险应对)
  14. 图像处理与计算机视觉:基础,经典以及最近发展
  15. “计算机无法访问,您可能没有权限使用网络资源”解决方法
  16. 排序算法总结(Python实现)——(一)
  17. 赵征出任尚德机构独立董事 替代俞敏洪席位
  18. 时间序列分析之Holt-Winters的R语言实现
  19. 学校做计算机教室锐捷,云课堂:让学生爱上每一节课
  20. Havel–Hakimi算法学习笔记(哈维尔算法)详细【Python】

热门文章

  1. PWA 应用和原生应用的一些区别
  2. WIN10设置OUTLOOK开机自启
  3. openstack(keypair API)
  4. 【蓝桥杯基础题】2020年省赛填空题—既约分数
  5. BUUCTF Unravel
  6. 硬盘数据恢复的方法有哪些?这五种恢复方法你知道吗
  7. gdal处理tiff文件的小问题
  8. 安装Ubuntu16.04之后的一些系统调整
  9. 区块链+游戏完美融合 天天牛最后一波限领
  10. python输出打印print字符大表情