谈谈语音识别中的CTC

567 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文参考了很多优秀的文章,本文其实是自己理解后的复述或者输出,有不对的地方欢迎指正。 yudonglee.me/ctc-explain…

distill.pub/2017/ctc/

1、背景介绍

学习CTC之前,先来谈谈为什么会有人创造CTC出来。在CTC出现之前人们普遍使用RNN来解决问题,但RNN需要对输入和输出的映射关系提前进行标注,根据标注数据和输出序列的差异建立损失函数。又因为语音数据天然不可分割,要想对连续语音数据进行标注是很花时间和金钱的,对于超巨大语音数据进行标注几乎不可能。因此传统RNN的方式就不可行了,想让模型进行端到端地进行语音识别任务就成了一个问题。

image.png Connectionist Temporal Classification(CTC)是DeepMind研究员Alex Graves等人在ICML 2006上提出的一种端到端的RNN训练方法,其主要思路可以分为三点:

  • 它扩展了RNN的输出层,在输出序列和最终label之间加了一个多对一的映射关系(后面会讲为什么多对一),并在此基础上定义了CTC Loss函数
  • 它借鉴了HMM(Hidden Markov Model)的Forward-Backward算法思路,利用动态规划算法有效地计算CTC Loss函数及其导数,从而解决了RNN端到端训练的问题
  • 最后,结合CTC Decoding算法RNN可以有效地对序列数据进行端到端的预测 注意一点,RNN是直接计算输出序列和最终label之间差距来定义损失函数的,这种方法需要预先标注的原因是输出序列输出的每一帧必须知道这里本来应该输出什么(ground truth),否则就无法进行反向传播修正模型。而CTC巧妙就巧妙在输出序列和最终标签加了一个多对一的映射关系。

2、一个例子

语音识别的目标是把一段音频转成文字,也就是Speech to text(STT)任务,传统语音识别是这样的架构:

image.png 其中X=[x1,x2,...,xT]X=[x^1,x^2,...,x^T]为音频输入,假设只有一句话,经过特征表示会变成一个二维变量,特征提取可以使用MFCC、FBANK等,如果是MFCC,最后就是一个39维的特征,也可以理解为编码后的结果。然后送入解码器,解码器包括三个部分,发音模型P(QW)P(Q|W),声学模型P(OQ)P(O|Q)和语言模型P(W)构成。其中发音模型中的W表示标签中的词,比如(我,爱,你,中,国),Q是W对应的发音序列,也就是建模单元“wo3 ai4 ni3 zhong1 guo2”。我们重点看声学模型,其中O就是上面说的39维的特征表示;通过训练可以使模型学到音频信号和文本发音的关系。如果训练样本中已经 “分割” 好音频,并标注好它和音节的对应关系,则 RNN 模型如下:

image.png

但是前面也说了,对音频分割并标注成本很大,所以并不现实。事实上,通常使用25ms为一帧,每次移动10ms。假设是MFCC特征,并且有九帧,则特征被表示为9*39维。而一帧很有可能什么内容都没有,亦或者和上一帧输出的内容一样,则需要去除相同元素和间隔符,才能得到最终的音节序列:“wo3 ai4 ni3 zhong1 guo2”。

image.png

可以看出输出序列和最终label是多对一的映射关系。RNN的本质是对P(zx)P(z|x)建模,x是输入序列,z是label,o是输出序列。如果我们能求出当前label对应的所有的输出序列,那么对所有输出序列的概率进行求和就可以等价于P(zx)P(z|x),即𝒑(𝒛𝒙)=P(ox)𝒑(𝒛│𝒙)=\sum P (o|x)。经过以上的映射转换,解决了端到端训练的问题,RNN 模型实际上是对映射到最终 label 的输出序列的空间建模。而问题就在于穷举所有输出序列o的开销太大了,如果句子比较长,那么这个计算开销是无法预估的。接下来正式介绍CTC。

3、问题定义

以 RNN 声学模型为例子,建模的目标是通过训练得到一个 RNN 模型,使其满足:

本质上是最大似然预估, S 是训练数据集,X 和 Z 分别是输入空间(由音频信号向量序列组成的集合)和目标空间(由声学模型建模单元序列组成的集合),L 是由输出的字符集(声学建模单元的集合),且 x 的序列长度大于或等于 z 的序列长度。(链接一作者这里写成了小于,估计是笔误)

4、RNN输出层扩展

接下来,在介绍如何计算 Loss 函数之前,我们需要对 RNN 输出层做一个简单的扩展。 如下图,为了便于读者理解,简化了 RNN 的结构,只有单向的一层 LSTM,把声学建模单元选择为字母 {a-z},并对建模单元字符集做了扩展,且定义了从输出层到最终 label 序列的多对一映射函数,使得 RNN 输出层能映射到最终的 label 序列。

所以,如果要计算𝒑(𝒛│𝒙),可以累加其对应的全部输出序列 (也即映射到最终 label 的 “路径”) 的概率即可,如下图。

注意一下,RNN原本是不存在多对一空间映射的,正是这种巧妙的映射解决了端到端的问题,也将逐帧数据标注任务化解为了求label对应的所有输出序列o的概率之和,这很重要。

5、CTC损失函数定义

如下图,基于 RNN 条件独立假设(其实是有问题的,这样会忽略上下文信息,green就可能会输出graen,这样就需要外接一个语言模型去纠偏),即可得到 CTC Loss 函数的定义:

上图中P(πx)P(\pi |x)是给定输入序列x求产生路径π\pi的概率(就是输出一条对应label的合法输出序列),比如label是“hello”,π\pi就可能是“h_ee_l_llo”。P(zx)P(z|x)是对所有合法输出序列进行求和,B1B^{-1}就是上面说到的合并重复的,去掉空格符的函数。

假定选择单层 LSTM 为 RNN 结构,则最终的模型结构如下图:

6. CTC Loss 函数计算

由于直接暴力计算 𝒑(𝒛│𝒙) 的复杂度非常高,作者借鉴 HMM 的 Forward-Backward 算法思路,利用动态规划算法求解。

如下图,为了更形象表示问题的搜索空间,用 X 轴表示时间序列, Y 轴表示输出序列,并把输出序列做标准化处理,输出序列中间和头尾都加上 blank,用 ll 表示最终标签,ll'表示扩展后的形式,则由 2l|l| + 1 = 2l|l'|,比如:l=applel=apple => l'=_a_p_p_l_e_

图中并不是所有的路径都是合法路径,所有的合法路径需要遵循一些约束,如下图:

所以,依据以上约束规则,遍历所有映射为 “apple” 的合法路径,最终时序 T=8,标签 labeling=“apple” 的全部路径如下图:

\

接下来,如何计算这些路径的概率总和?暴力遍历?分而治之?作者借鉴 HMM 的 Forward-Backward 算法思路,利用动态规划算法求解,可以将路径集合分为前向和后向两部分,如下图所示:

通过动态规划求解出前向概率之后,可以用前向概率来计算 CTC Loss 函数,如下图:

\

类似地方式,我们可以定义反向概率,并用反向概率来计算 CTC Loss 函数,如下图:

去掉箭头方向,把前向概率和后向概率结合起来也可以计算 CTC Loss 函数,这对于后面 CTC Loss 函数求导计算是十分重要的一步,如下图所示:

总结一下,根据前向概率计算 CTC Loss 函数,得到以下结论:

根据后向概率计算 CTC Loss 函数,得到以下结论:

根据任意时刻的前向概率和后向概率计算 CTC Loss 函数,得到以下结论:

至此,我们已经得到 CTC Loss 的有效计算方法,接下来对其进行求导

7. CTC Loss 函数求导

我们先回顾下 RNN 的网络结构,如下图飘红部分是 CTC Loss 函数求导的核心部分:

CTC Loss 函数相对于 RNN 输出层元素的求导过程如下图所示:

8. 总结

总结一下,本篇通过 RNN 声学模型的例子引出了问题背景,并通过实际例子一步一步的介绍如何定义和计算 CTC Loss 函数,最终通过反向传播算法完成对 RNN 模型的端到端训练过程。