CTC Algorithm Explained Part 1:Training the Network(CTC算法详解之训练篇)

转载本文请注明出处:https://xiaodu.io/ctc-explained作者:yudonglee

现实应用中许多问题可以抽象为序列学习(sequence learning)问题,比如词性标注(POS Tagging)、语音识别(Speech Recognition)、手写字识别(Handwriting Recognition)、机器翻译(Machine Translation)等应用,其核心问题都是训练模型把一个领域的(输入)序列转成另一个领域的(输出)序列。近年来基于RNN的序列到序列模型(sequence-to-sequence models)在序列学习任务中取得了显著的效果提升,本文介绍一种RNN(Recurrent Neural Networks)的端到端训练方法——CTC(Connectionist Temporal Classification)算法,它可以让RNN直接对序列数据进行学习,而无需事先标注好训练数据中输入序列和输出序列的映射关系,打破了RNN应用于语音识别、手写字识别等领域的数据依赖约束,使得RNN模型在序列学习任务中取得更好的应用效果。

本文总共分为五部分来全面阐述CTC算法(本篇为Part 1):
Part 1:Training the Network(训练算法篇),介绍CTC理论原理,包括问题定义、公式推导、算法过程等。Part 1链接
Part 2:Decoding the Network(解码算法篇),介绍CTC Decoding的几种常用算法。Part 2链接
Part 3:CTC Demo by Speech Recognition(CTC语音识别实战篇),基于TensorFlow实现的语音识别代码,包含详细的代码实战讲解。Part 3链接。
Part 4:CTC Demo by Handwriting Recognition(CTC手写字识别实战篇),基于TensorFlow实现的手写字识别代码,包含详细的代码实战讲解。Part 4链接。
Part 5:Conclusion(总结展望篇),总结CTC算法的理论局限性和适用场景,以及近年来相关的最新研究动态。Part 5链接。

接下来,我们先从“问题”的背景说起。

1. 背景介绍

在序列学习任务中,RNN模型对训练样本一般有这样的依赖条件:输入序列和输出序列之间的映射关系已经事先标注好了。比如,在词性标注任务中,训练样本中每个词(或短语)对应的词性会事先标注好,如下图(DT、NN等都是词性的标注,具体含义请参考链接)。由于输入序列和输出序列是一一对应的,所以RNN模型的训练和预测都是端到端的,即可以根据输出序列和标注样本间的差异来直接定义RNN模型的Loss函数,传统的RNN训练和预测方式可直接适用。

然而,在语音识别、手写字识别等任务中,由于音频数据和图像数据都是从现实世界中将模拟信号转为数字信号采集得到,这些数据天然就很难进行“分割”,这使得我们很难获取到包含输入序列和输出序列映射关系的大规模训练样本(人工标注成本巨高,且启发式挖掘方法存在很大局限性)。因此,在这种条件下,RNN无法直接进行端到端的训练和预测。

如下图,输入是“apple”对应的一段说话音频和手写字图片,从连续的音频信号和图像信号中逐一分割并标注出对应的输出序列非常费时费力,在大规模训练下这种数据要求是完全不切实际的。而如果输入序列和输出序列之间映射关系没有提前标注好,那传统的RNN训练方式就不能直接适用了,无法直接对音频数据和图像数据进行训练。

因此,在语音识别、图像识别等领域中,由于数据天然无法切割,且难以标注出输入和输出的序列映射关系,导致传统的RNN训练方法不能直接适用。那么,如何让RNN模型实现端到端的训练成为了关键问题。

Connectionist Temporal Classification(CTC)[1]是Alex Graves等人在ICML 2006上提出的一种端到端的RNN训练方法,它可以让RNN直接对序列数据进行学习,而无需事先标注好训练数据中输入序列和输入序列的映射关系,使得RNN模型在语音识别等序列学习任务中取得更好的效果,在语音识别和图像识别等领域CTC算法都有很比较广泛的应用。总的来说,CTC的核心思路主要分为以下几部分:

  • 它扩展了RNN的输出层,在输出序列和最终标签之间增加了多对一的空间映射,并在此基础上定义了CTC Loss函数
  • 它借鉴了HMM(Hidden Markov Model)的Forward-Backward算法思路,利用动态规划算法有效地计算CTC Loss函数及其导数,从而解决了RNN端到端训练的问题
  • 最后,结合CTC Decoding算法RNN可以有效地对序列数据进行端到端的预测

接下来,通过一个语音识别的实际例子来引出CTC的解决思路

2. 一个实际的例子–声学模型

语音识别的核心问题是把一段音频信号序列转化文字序列,传统的语音识别系统主要分为以下几部分,如下图。

其中,X表示音频信号,O是它的特征表示,一般基于LPC、MFCC等方法提取特征,也可以基于DNN的方式“学到”声学特征的表示。为了简化问题,我们暂且把O理解为是由实数数组组成的序列,它是音频信号的特征表示。Q是O对应的发音字符序列,即建模单元,一般可以是音素、音节、字、词等。W是音频信号X对应的文字序列,即我们最终的识别结果。

如图所示,核心问题是通过解码器找到令P(W|X)最大化的的W,通过贝叶斯公式可将其分解为P(O|Q)、P(Q|W)、P(W),分别对应声学模型、发音模型、语言模型。

其中,声学模型就是对P(O|Q)进行建模,通过训练可以“学到”音频信号和文字发音间的联系。为了简化问题,我们假定声学模型的建模单元Q选择的是音节,O选择的是MFCC特征(由39维数组组成的序列)。

如下图,输入序列是一段“我爱你中国”的音频,输出序列是音节序列“wo3 ai4 ni3 zhong1 guo2”,如果训练样本中已经“分割”好音频,并标注好它和音节的对应关系,则RNN模型如下:

然而,如前面所述,对音频“分割”并标注映射关系的数据依赖是不切实际的,实际情况是对音频按照时间窗口滑动来提取特征,比如按照每10毫秒音频提取特征得到一个N维数组。如下图所示:

由于人说话发音是连续的,且中间也会有“停顿”,所以输出序列中存在重复的元素,比如“wo3 wo3”,也存在表示间隔符号“_”。需从输出序列中去除掉重复的元素以及间隔符,才可得到最终的音节序列,比如,“wo3 wo3 ai4 _ ni3 _ zhong1 guo2 _” 归一处理后得到“wo3 ai4 ni3 zhong1 guo2”。因此,输出序列和最终的label之间存在多对一的映射关系,如下图:

RNN模型本质是对𝒑(𝒛│𝒙)建模,其中x表示输入序列,o表示输出序列,z表示最终的label,o和l存在多对一的映射关系,即:𝒑(𝒛│𝒙)=sum of all P(o|x),其中o是所有映射到z的输出序列。因此,只需要穷举出所有的o,累加一起即可得到𝒑(𝒛│𝒙),从而使得RNN模型对最终的label进行建模。

经过以上的映射转换,解决了端到端训练的问题,RNN模型实际上是对映射到最终label的输出序列的空间建模。然而,对每一个z都“穷举所有的o”,这个计算的复杂度太大,会使得训练速度变得非常慢,因此怎么更高效地进行端到端训练成为待解决的关键问题。

通过以上的实际例子,我们对问题的解决思路有了更加直观的了解,接下来就开始正式介绍CTC的理论原理。

3. 问题定义

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

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

接下来,在介绍如何计算Loss函数之前,我们需要对RNN输出层做一个简单的扩展。

4. RNN输出层扩展

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

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

5. CTC Loss函数定义

如下图,基于RNN条件独立假设,即可得到CTC Loss函数的定义:

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

6. CTC Loss函数计算

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

如下图,为了更形象表示问题的搜索空间,用X轴表示时间序列, Y轴表示输出序列,并把输出序列做标准化处理,输出序列中间和头尾都加上blank,用l表示最终标签,l’表示扩展后的形式,则由2|l| + 1 = 2|l’|,比如:l=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模型的端到端训练过程。

至此,CTC算法的模型训练过程与原理介绍完了,下一篇将介绍CTC算法的模型预测部分,Part2链接

References

  1. Graves et al., Connectionist Temporal Classification: Labelling Unsegmented Sequence Data with RNNs. In ICML, 2006. (Graves提出CTC算法的原始论文)
  2. Graves et al., A Novel Connectionist System for Unconstrained Handwriting Recognition. In IEEE Transactions on PAML, 2009.(CTC算法在手写字识别中的应用)
  3. Graves et al., Towards End-to-End Recognition with RNNs. In JMLR, 2014.(CTC算法在端到端声学模型中的应用)
  4. Alex Graves, Supervised Sequence Labelling with Recurrent Neural Networks. In Studies in Computational Intelligence, Springer, 2012.( Graves 的博士论文,关于sequence learning的研究,主要是CTC)

32 Replies to “CTC Algorithm Explained Part 1:Training the Network(CTC算法详解之训练篇)”

      1. 好文啊!先点赞为敬。然后就是,我想咨询一个问题,就是假设我现在手上有一套初始的音频信号,没有任何标签,那么如何给原始的音频信号加上标签呢(比如音素啥的)?然后才可以进行训练。传统的人工上标签确实如你文中所说的太要命了。

        1. 主要是声学模型的“数据增强”、“半监督训练”和“迁移学习”这几种流派,完全无监督的现在看还不切实际

  1. 谢谢!Section2里面𝒑(𝒛│𝒙)公式显示有点小问题。
    有个疑问望解释一下:前后向概率计算CTC Loss函数公式里的 l’ 指的是什么?好像没有定义过。

    1. 可以参考part2:https://xiaodu.io/ctc-explained-part2/,有具体的算法步骤,也可以直接看TensorFlow的ctc实现:https://github.com/tensorflow/tensorflow/blob/r1.13/tensorflow/python/ops/ctc_ops.py

  2. 作者写的太好了,网上没有比这个更清楚的了,希望作者多更新,学习学习,膜一下大佬。

  3. “所以,如果要计算𝒑(𝒛│𝒙),可以累加其对应的全部输出序列(也即映射到最终label的“路径”)的概率即可。”
    您好,在第四小节,概率为什么是累加的呢,它的全部的输出序列每个不是相互独立的吗

  4. 您好,ctc loss反向求导中,公式(1)对y_k'(t)求导时,结果是不是少了一个负号?(1/x)的导数不是(-1/square(x))吗?

  5. 作者您好,您的文章写的很棒,但是我有一个问题想问下就是,CTC求解了前向概率为什么还需要计算后向概率,是因为联系上下文吗?

    1. 好问题,原因是后面对ctc目标函数求导需要用到,可以仔细看下ctc目标求导的过程介绍那一节。

  6. 而无需事先标注好训练数据中输入序列和输出序列的映射关系?请问这句话怎么理解?不需要标注数据?还是说不需要输出是定长?

    1. 需要样本,但不需要标注出“输入和输出映射关系”,比如:输入“ABCDEF” => 输出“XYZ”,不需要再标注出映射关系:ABC=>X,DE=>Y,F=>Z

Leave a Reply

Your email address will not be published. Required fields are marked *