CTC是什么,有什么用?
CTC(Connectionist Temporal Classification),用来解决输入序列和输出序列难以一一对应的问题。
在语音识别中,我们希望音频中的音素和翻译后的字符可以一一对应。但是对齐是一个很困难的事,有人说话快,有人说话慢,每个人说话快慢不同,手动对齐太耗时。
在OCR中,使用RNN时,RNN的每一个输出要对应字符图像中的每一个位置,要手工做这样的标记工作量太大,而且图像中的字符数量不同,字体样式不容,大小不同,导致输出不一定能和每一个字符一一对应。
CTC基本原理
假设一个RNN,用r表示RNN的参数,则RNN可以表示为一个函数:y=Nw(x)。
定义输入x的时间步长为T,每个时间步长的特征维度记作m,表示m维特征。
x=(x1,x2,...,xT) xt=(x1t,x2t,...,xmt)
输出时间步也为T,和输入可以一一对应,每个时间步的输出维度作为n,表示n维的输出,实际上是n个概率。
y=(y1,y2,...,yT) yt=(y1t,y2t,...,ymt)
假设要对26个英文字符进行识别,考虑到有些位置没有字符,定义一个 blank(用 - 代替) 作为空白符加入到字符集合中,L′={a,b,c,...,z}∪{−}=L∪{−},那么对于RNN而言每个
时间步长的输出维度n就是27,表示27个字符在时间步长上输出的概率。
如果根据这些概率进行选取,每个时间步选取一个元素,就可以得到输出序列,其输出空间可以记作L′T。
定义一个转换函数B,对RNN的输出序列进行变换,变换成真实的输出,把连续的相同字符删减为1个并删去空白符。如下:
B(π1)=B(−−stta−t−−−e)=state B(π2)=B(sst−aaa−tee−)=state B(π3)=B(−−sttaa−tee−)=state B(π4)=B(sst−aa−t−−−e)=state
其中π表示RNN的一种输出序列。当我们在优化RNN时,需要最大化以下概率,即给定输入x的情况下,输出为l的概率,l表示真实的输出,对下式取负号,就可以使用梯度下降求最小。
p(l∣x)=B(π)=l∑p(π∣x)假设时间步之间输出独立,那么对任意一个输出序列π的概率计算如下:p(π∣x)=t=1∏Tyπtt其中下标πt表示的是,输出序列在t时间步选取元素对应的索引,比如该序列在第一时间步选取的元素是a,那么的到值就是1,。选取的是z,那么得到的值就是26,。选取的是空白符,那么得到的值就是27。
对于某一个真实的输出,如state,是有过个RNN输出序列通过B转换得到,这些序列都是我们想要的结果,我们要给定x,这些输出序列的概率加起来最大。如果逐条遍历,时间复杂度使指数级,因为有T个位置,每个位置有n种选择(字符集合的大小),那么就有nT种可能,因此CTC使用HMM中的前向-后向算法来计算。
CTC中的前向后向算法
由于真实输出l是一个序列,序列可以通过一个路径图中的一条路径来表示,我们也称输出序列l为路径l。定义路径l′为:在路径l每两个元素之间以及头尾插入空白符。如下:l=state l′=−s−t−a−t−e−对某个时间步长的某个字符求导(这里用k表示字符集合中的某个字符或者字符索引)恰好是与概率ykt相关的路径。∂ykt∂p(l∣x)=∂ykt∂∑B(π)=l,πt=kp(π∣x)以前面的π1,π2,π3,π4为例子,简单绘制如下示意图:

4条路径都在t=6时经过了字符a,观察4条路径,可以得到如下式子。
π1=b=b1:5+a6+b7:12π2=r=r1:5+a6+r7:12π3=b1:5+a6+r7:12π4=r1:5+a6+b7:12p(π1,π2,π3,π4∣x)=y1−⋅y2−⋅y3⋅y4+y5t⋅y6⋅y7−⋅y8+y9−y10−y11−y12e+y1s⋅y2s⋅y3t⋅y4−y5⋅y6⋅y7a⋅y8−y9t⋅y10⋅y11e⋅y12+y1−⋅y2−⋅y3⋅y4+y5t5⋅y6⋅y7⋅y8−y9t9⋅y10⋅y11e−y12−+y1s⋅y2−x3s3⋅y4+y5t5+y6a7⋅y7⋅y8−y9t9⋅y10⋅y11e⋅y12−+y1s⋅y2s⋅y3t⋅y4−ya5⋅y6⋅y7−y8⋅9−y10−y11−y12e
令: forward =p(b1:5+r1:5∣x)=y1⋅y2−y3⋅y4⋅t5+y1⋅y2⋅y2⋅y3⋅y4−y5a backward =p(b7:12+r7:12∣x)=y7−⋅yt8⋅y9−⋅y10−⋅ye11+ya7⋅y−8⋅yt9⋅ye10⋅ye11⋅y12
那么可以做如下表示:
p(π1,π2,π3,π4∣x)= forward ⋅yat⋅ backward 上述forward和backward只包含了四条路径,如果推广一下forward和backward的含义,考虑所有路径,可以做如下表示:B(π)=l,π6=a∑p(π∣x)= forward ⋅yat⋅ backward 定义forward为αt(lk′),表示t时刻经过lk′字符的路径概率中1-t的概率之和,式子定义如下:αt(lk′)=B(π)=l,πt=lk′∑t′=1∏tyπt′t′
t=1时,符号只能是空白符或者l1,可以得到以下初始条件:
α1(−)α1(l1)α1(lt)=0,∀t>1=y1−=yl11

观察上图可以看出,如果t=6时字符是a,那么t=5时只能是字符a,t,空白符三选一。
可以得到如下递推关系:α6(a)=(α5(a)+α5(t)+α5(−))⋅ya6更一般:αt(lk′)=(αt−1(lk′)+αt−1(lk−1′)+αt−1(−))⋅ylkt
定义backward为βt(s),表示t时刻经过lk′字符的路径概率中t~T的概率之和,式子如下:βt(lk′)=B(π)=l,πt=lk′∑t′=t∏Tyπt′t′t=T时,符号只能是空白符或者l∣l′∣−1,可以得到以下初始条件:βT(−)βT(l∣l′∣−1′)βT(l∣l′∣−i)=y−T=yl∣l′∣−1′T=0,∀i>1
同理,可以得到如下递归条件:βt(lk′)=(βt+1(lk′)+βt+1(lk+1′)+βt+1(−))⋅ylkttk
根据forward和backward的式子定义,它们相乘可以得到:αt(lk′)βt(lk′)=B(π)=l,πt=lkt∑ylktt=1∏Tyπtt
又因为p(l∣x)对lk′求导时,只跟那些πt=lk′的路径有关,那么求导时可以简写如下式子:p(l∣x)=B(π)=l,πt=lk′∑p(π∣x)=B(π)=l,πt=lk′∑t=1∏Tyπtt结合上面两个式子,得到:p(l∣x)=B(π)=l,πt=lk′∑ylk′tαt(lk′)βt(lk′)最后可以得到求导式(这里用k来表示字符,和lk′)的含义相同:∂ykt∂p(l∣x)=∂ykt∂∑B(π)=l,πt=kyktαt(k)βt(k)求导式里,forward和backward可以用前面的dp递推公式计算出来,时间复杂度是nT,大大减小了计算量。
这样对RNN的输出y求导之后,再根据y对RNN里面的权重参数w进行链式求导,就可以使用梯度下降的方法来更新参数了。
CTC预测
一种方法是Best Path search。计算概率最大的一条输出序列,但是这样没有考虑多个输出序列对应一个真实输出。如:[s,s,−]和[s,s,s]的概率比[s,t,a]低,但是他们的概率之和会高于[s,t,a]。
第二种方法,是Beam Search。假设指定B=3,预测过程如下图所示。在第一时间步长选取概率最大的三个字符,然后在第二个时间步也选取概率最大的三个字符,两两组合可以组合成9个序列,这些序列在B转换之后会得到一些相同输出,把具有相同输出的序列进行合并,比如有三个序列都可以转换为a,把他们合并,计算出概率最大的三个序列,然后继续和下一个时间步长的字符进行相同的合并。
有一点需要注意的是合并相同字符,比如上图,T=3时,第一个前缀序列a,在跟相同字符a合并的时候,除了产生a之外,还会产生一个aa的有效输出,这是因为这个前缀序列a在T=2的时候曾经把空白符号合并掉了,实际上这个前缀序列a后面是跟着一个空白符的,所以它在跟相同字符a合并的时候中间是有一个隐藏的空白符,合并之后得到的应该是两个a。
因此合并相同字符的时候,如果要合并成aa,需要统计在这之前以空白符结尾的那些序列的概率,如果要合并成a,计算的是不以空白符结尾的那些序列的概率。出于这个事实,我们需要跟踪前两处的输出,以便后续的合并运算。
CTC的性质
- 条件独立假设。CTC做了一个假设是不同时间步的输出之间是独立的。这个假设对于很多序列问题来说并不成立,输出序列之间往往存在联系。
- 单调对齐。CTC只允许单调对齐,在语音识别中可能是有效的,但是在机器翻译中,比如目标语句中的一些比较后的词,可能与源语句中前面的一些词对应,这个CTC无法做到。
- 多对一映射。CTC的输入和输出是多对一的关系。这意味着输出长度不能超过输入长度,这在手写字体识别或者语音中不是什么问题,因为通常输入都会大于输出,但是对于输出长度大于输入长度的问题,CTC就无法处理。
参考文章
CTC介绍