CTC算法详解之训练篇

原文地址:https://xiaodu.io/ctc-explained

介绍CTC理论原理,包括问题定义、公式推导、算法过程等。

现实应用中许多问题可以抽象为序列学习(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模型在序列学习任务中取得更好的应用效果。

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模型的端到端训练过程。

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)
### CTC算法的原理与实现 #### 原理概述 CTC(Connectionist Temporal Classification)是一种用于处理序列标注问题的算法,特别适用于输入和输出长度不同且未对齐的情况。它广泛应用于语音识别(ASR)、手写文字识别等领域[^1]。 CTC的核心思想在于引入了一个特殊的空白符 $\text{blank}$ 来解决输入序列 $X$ 和目标序列 $Y$ 之间的时间步不对齐问题。具体来说,对于任意给定的目标序列 $Y$,CTC允许存在多个可能的中间对齐路径,并通过对这些路径的概率进行加总得到最终的条件概率 $P(Y|X)$[^2]。 #### 对齐机制 为了描述输入序列 $X$ 到输出序列 $Y$ 的映射关系,CTC定义了一种扩展形式的标签集合,其中包含了原始标签集以及额外的一个空白字符 $\text{blank}$。通过这种方式,可以将任何有效的输出序列表示成一系列带有重复项或者插入空白符的形式。例如,如果目标序列为 `AB`,那么其对应的合法对齐可能是: $$ A-\ B-\quad \text{(这里 `-` 表示 blank)} $$ 接着利用动态规划技术有效地计算所有符合条件的路径组合所贡献的整体似然度值[^3]。 #### 动态规划求解 由于实际应用中可能存在指数级数量的不同排列组合情况,因此直接枚举每一条单独轨迹并累加它们各自对应的可能性显然不可行。为此,CTC提出了基于前向-后向变量的方法来高效完成这一任务。 向前传递过程中记录下截止当前时刻为止到达某个特定状态的所有可行方案累积权重;而反方向操作则相反——从末端回溯至起点位置统计相关信息。两者相结合即可快速得出全局最优解表达式: $$ \alpha_t(s)\cdot\beta_t(s), $$ 此处分别代表截至$t$时间点处于$s$状态下已知未来部分的信息量乘积结果[^4]。 #### 损失函数构造 最后一步便是依据上述理论框架搭建起适合神经网络优化流程使用的交叉熵损失函数形式。假设训练样本集中共有N组配对数据$(x^n,y^n),n=1,...,N$,于是整体目标即最小化平均负对数可能性指标: $$ L=\frac{-1}{N}\sum_{n=1}^{N}\log[P(y^n|x^n)]. $$ 此公式的直观意义非常清晰明了:最大化正确答案出现几率的同时降低错误选项得分,从而促使整个系统逐步逼近理想状况下的表现水平。 --- ### Python代码实现示例 以下是使用TensorFlow/Keras库实现简单版CTC模型的例子: ```python import tensorflow as tf from tensorflow.keras import layers, models def build_ctc_model(input_dim, output_dim): inputs = layers.Input(shape=(None, input_dim)) # 构建基础特征提取层 x = layers.LSTM(128, return_sequences=True)(inputs) x = layers.Dense(output_dim + 1, activation='softmax')(x) labels = layers.Input(name="labels", shape=[None], dtype=tf.int32) input_length = layers.Input(name="input_length", shape=[1], dtype=tf.int32) label_length = layers.Input(name="label_length", shape=[1], dtype=tf.int32) loss_out = layers.Lambda( lambda args: tf.keras.backend.ctc_batch_cost(*args), name="ctc_loss" )([labels, x, input_length, label_length]) model = models.Model(inputs=[inputs, labels, input_length, label_length], outputs=loss_out) prediction_model = models.Model(inputs=inputs, outputs=x) return model, prediction_model model, pred_model = build_ctc_model(input_dim=64, output_dim=27) model.compile(optimizer='adam', loss={'ctc_loss': lambda y_true, y_pred: y_pred}) ``` 以上片段展示了如何创建一个基本的CTC架构并通过Keras API编译好准备投入后续学习阶段的工作当中去。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值