前言

本文是阅读论文《Connectionist Temporal Classification: Labelling UnsegmentedSequence Data with Recurrent Neural Networks1 的笔记,讨论Connectionist Temporal Classification(CTC)算法在语音识别(ASR)中的应用。

一、CTC简介

1.1 为什么学CTC?

Connectionist Temporal Classification(CTC)有道词典翻译成“联结主义时间分类”,这个概念乍一看让人迷糊,为简洁易懂,我们称其为连接时序分类,是 Alex Graves 等人在 2006 年提出的。
ASR语音对齐


图1 语音识别举例

在做 ASR 时,需要将语音序列识别成对应的文字,如图 1 所示,有一段 1.2 秒的语音,内容是“打开灯光”。ASR 模型输入是语音,输出是转写的文字。在 CTC 之前,训练 ASR 模型需要构造和训练声学模型、语言模型,每个都单独训练,最后将每个模型的输出组合起来建立语音信号到转录文本之间的映射关系,整个流程很复杂,学习门槛较高,这种算法基本上是基于 GMM-HMM 和 DNN-HMM,我们称之为传统 ASR 算法。CTC 就是拿语音信号和对应的转录文本,硬train一发,得到一个模型,来替代之前的复杂流程,我们称之为端到端(E2E)ASR。

CTC 除了训练简便外,近些年(约2015年之后),以 CTC 为代表的端到端模型,性能逐渐超过了基于 HMM 的传统 ASR 模型,其中的原因是多样的:深度神经网络强大的海量数据拟合能力,E2E 模型简单易部署(相近识别能力的传统 ASR 模型有声学模型、语言模型、词典,这些内存加起来要远大于一个 E2E 模型), NLP 等领域的成果更容易迁移过来等等。导致各家公司舍弃了传统 ASR 算法转向 E2E ASR,使得 E2E ASR 的研究更加丰富。CTC 算法是 E2E ASR 的里程碑式成果。

1.2 CTC要解决的问题

我们想用一个模型来解决语音识别。ASR 本身是一个分类问题,以 20ms 为一帧,常用汉字 4000 个的话,我们想知道第 1 帧应该转写为哪个汉字,便是 4000 分类问题,将每一帧都转写一个汉字,最后整合一下,问题就解决了。可否用图像分类的方式来解决,给每一帧打上标签,然后交叉熵损失直接训练就好。这种思路便是混合 DNN-HMM 模型的思路,先用 GMM-HMM 生成每一帧的标签,然后用神经网络进行分类训练。问题是这样就需要训练两个模型(GMM-HMM 和 NN模型),不用 GMM-HMM 的话,每一帧的标签如何得到。
有人会问,为啥你要 20ms 作为一帧,正常人说一个字大概 200ms,用 200ms 作为建模单元长度不行吗?因为语音信号是连续变量,你不清楚哪 200ms 刚好对应一个字,且每个人吐字时长都不同,有人讲话快,有人慢,这便是“连续时序分类”的“连续”难题。所以,只能尽可能让建模单元(帧长)短一些,最后将代表同一个字的单元拼接起来。
那么帧长足够小,每一帧也能识别出对应的字,比如有 5 帧,我们识别出 打 打 打 开 开,合并多余的字后,得到 打开,看似问题得到了解决。但如果这句话是哈哈哈,谢谢哥哥呢,按照上述方法合并多余的字后,得到哈谢哥,不知所云。所以说,这种方法在处理叠字时是有缺陷的。

1.3 CTC的思想

为了解决叠字的问题,CTC 引入了一个记号 “-”,读做 “空”,用其当做占位符。还是用哈哈哈,谢谢哥哥来举例,引入 “-” 后,语音经过解码后会得到如--哈---哈-哈哈----谢-谢--哥----哥哥---这样的序列,先将重复的字合并,然后将“-”去掉,就得到哈哈哈,谢谢哥哥
另一个就是训练问题,如何得到这样一个模型。我们先列出描述 CTC 的数学记号。
观测序列 x = ( x 1 , x 2 , ⋯   , x T ) ∈ X x = (x_1, x_2, \cdots, x_T)\in X x=(x1,x2,,xT)X 是每一帧音频信号, l = ( l 1 , l 2 , ⋯   , l U ) ∈ Z l= (l_1, l_2, \cdots, l_U )\in Z l=(l1,l2,,lU)Z 是其对应的标签,其中 U ≤ T U \le T UT,这里的 l i ∈ L l_i\in L liL L L L 代表不带 {-} 的常规标签集合,如果是中文就是汉字,英文就是 26 个英文字母(为简化,只讨论大写字母), L ′ = L ⋃ { − } L' = L \bigcup \{-\} L=L{}。这是多对一问题,多帧对应一个标签。我们的目标是训一个模型 h h h,使得 P ( l ∣ x ) \mathbb{P}(l|x) P(lx) 最大,本质还是极大似然估计。
回顾 HMM2 ,我们用前向后向算法算概率,用极大似然估计训模型,用 Viterbi 算法估计解码路径。CTC 和 HMM 算法是很相似的,我们依然按照这 3 个基本问题来说明 CTC。
CTC 的 3 个基本问题
(1)概率计算问题:在时刻 T T T,给定标签 l ∈ L ≤ T l\in L^{\le T} lLT和观测序列 x = ( x 1 , x 2 , ⋯   , x T ) x = (x_1, x_2, \cdots, x_T) x=(x1,x2,,xT),计算概率 P ( l ∣ x ) \mathbb{P}(l|x) P(lx)。不过这里不同于 HMM 计算 P ( O ∣ λ ) \mathbb{P}(O|\lambda) P(Oλ),CTC 计算 P ( l ∣ x ) \mathbb{P}(l|x) P(lx) 纯粹是为了训模型。
(2)学习问题:已知标签 l ∈ L ≤ T l\in L^{\le T} lLT和观测序列 x = ( x 1 , x 2 , ⋯   , x T ) x = (x_1, x_2, \cdots, x_T) x=(x1,x2,,xT),估计模型 h h h 的参数。
(3)预测问题:也称为解码问题,或是对齐问题。已知模型 h h h 和观测序列 x = ( x 1 , x 2 , ⋯   , x T ) x = (x_1, x_2, \cdots, x_T) x=(x1,x2,,xT),求使 P ( l ∣ x ) \mathbb{P}(l|x) P(lx) 最大的标签(路径) l ∈ L ≤ T l\in L^{\le T} lLT。即给定观测序列,求最有可能的对应的标签序列。

二、概率计算算法

训模型采用的是极大似然估计,要使得
h ( x ) = arg ⁡ max ⁡ l ∈ L ≤ T P ( l ∣ x ) (1) h(x) = \arg\max\limits_{l \in L^{\le T}}\mathbb{P}(l|x) \tag{1} h(x)=arglLTmaxP(lx)(1)
首先要算出 P ( l ∣ x ) \mathbb{P}(l|x) P(lx).
不同于 HMM 的学习问题用的是无监督的 Baum-Welch 算法,CTC 是要通过神经网络(反向传播算法)来训练模型参数。神经网络的 softmax 层输出 y k t y_k^t ykt t t t 时刻第 k k k 个节点输出的概率,即 t t t 时刻观测到标签 k k k 的概率 P ( l t = k ∣ x ) \mathbb{P}(l_t = k|x) P(lt=kx),这样将神经网络与概率建模联系起来。
神经网络的输入输出节点数是固定的,模型为
N : ( R m ) T ⟼ ( R n ) T x ⟶ y (2) \begin{aligned} N: (\mathbb{R}^m)^T &\longmapsto (\mathbb{R}^n)^T \\ x &\longrightarrow y \end{aligned} \tag{2} N:(Rm)Tx(Rn)Ty(2)
原文中,将网络输出的标签序列称为路径 π \pi π,给定观测序列 x x x,输出 π \pi π 的概率为
P ( π ∣ x ) = P ( π T ∣ π T − 1 , ⋯   , π 1 , x ) P ( π T − 1 ∣ π T − 2 , ⋯   , π 1 , x ) ⋯ P ( π 2 ∣ π 1 , x ) P ( π 1 ∣ x ) = P ( π T ∣ x ) P ( π T − 1 ∣ x ) ⋯ P ( π 1 ∣ x ) = ∏ t = 1 T y π t t ,         ∀ π ∈ L ′ T (3) \begin{aligned} \mathbb{P}(\pi|x) &= \mathbb{P}(\pi_T|\pi_{T-1}, \cdots,\pi_1, x) \mathbb{P}(\pi_{T-1}|\pi_{T-2}, \cdots,\pi_1, x)\cdots\mathbb{P}(\pi_2|\pi_1, x) \mathbb{P}(\pi_1|x) \\ &= \mathbb{P}(\pi_T|x) \mathbb{P}(\pi_{T-1}|x)\cdots\mathbb{P}(\pi_1|x) \\ &= \prod_{t=1}^T y_{\pi_t}^t, \ \ \ \ \ \ \ \forall \pi \in L'^T \end{aligned} \tag{3} P(πx)=P(πTπT1,,π1,x)P(πT1πT2,,π1,x)P(π2π1,x)P(π1x)=P(πTx)P(πT1x)P(π1x)=t=1Tyπtt,       πLT(3)
公式 ( 3 ) (3) (3) 有两个点需要注意:(1)第二个等号成立,是假设每个时刻输出的值是条件独立的,这其实是 CTC 算法被诟病的一个主要原因。因为这个假设不合理,语音相邻发音,连读、吞音等情况是很常见的,下一时刻的语音是与上一时刻相关的,比如哈哈嘿哈,前一个字的开口大小不同,发下一个音也会受到影响。这也催生了后来的 RNN-T、LAS 算法。(2)注意这里是 ∀ π ∈ L ′ T \forall \pi \in L'^T πLT L ′ = L ⋃ { − } L' = {L\bigcup \{-\}} L=L{},是带了空记号的。虽然加入了空记号可以解决叠字问题,但也引入了新的问题,生成 l l l 的路径会非常多。
比如 x = ( x 1 , x 2 , x 3 , x 4 ) x=(x_1, x_2, x_3, x_4) x=(x1,x2,x3,x4),标签为 l = l = l= 打开 ,则对应的可能得解码路径有 打打开--打开开打开开开打开--打-开-打--开-打开--打-开--打开……即存在多对一映射
B : L ′ T ⟼ L ≤ T π ⟶ l \begin{aligned} B: L'^T &\longmapsto L^{\le T} \\ \pi &\longrightarrow l \end{aligned} B:LTπLTl
将神经网络解码出的路径,映射到最终的标签上。根据公式 ( 3 ) (3) (3),我们能算出 P ( π ∣ x ) \mathbb{P}(\pi|x) P(πx),那么 P ( l ∣ x ) \mathbb{P}(l|x) P(lx) 便是将所有能产生 l l l 的路径 π \pi π 的概率全都加起来:
P ( l ∣ x ) = ∑ π ∈ B − 1 ( l ) P ( π ∣ x ) (4) \mathbb{P}(l|x) = \sum_{\pi \in B^{-1}(l)}\mathbb{P}(\pi|x) \tag{4} P(lx)=πB1(l)P(πx)(4)

2.1 前向算法

要利用 ( 4 ) (4) (4) 计算 P ( l ∣ x ) \mathbb{P}(l|x) P(lx),和 HMM 的概率计算相似,仍然是采用基于动态规划的前向后向算法,有这样4个步骤:(1)确定递推变量的物理意义,(2)初始化,(3)确定递推公式,(4)终止。
(1)前向变量
α t ( s ) = def ∑ π ∈ L ′ T : B ( π 1 : t ) = l 1 : s ∏ t ′ = 1 t y π t ′ t ′       ∀ 1 ≤ t ≤ T , 0 ≤ s ≤ T (5) \alpha_t(s) \overset{\text{def}}{=} \sum_{\pi \in L'^T:B(\pi_{1:t}) = l_{1:s}} \prod_{t'=1}^t y_{\pi_t'}^{t'} \ \ \ \ \ \forall 1\le t \le T,0 \le s \le T \tag{5} αt(s)=defπLT:B(π1:t)=l1:st=1tyπtt     ∀1tT0sT(5)
其中 π 1 : t \pi_{1:t} π1:t 表示 π \pi π 从下标 1 1 1 t t t 的子序列, l 1 : s l_{1:s} l1:s 同理。公式 ( 5 ) (5) (5) 是前向变量的定义,表示给定 x x x,能映射到 l 1 : s l_{1:s} l1:s π \pi π 的子路径的概率和。
这里引入 l ′ l' l, 如果 l = l = l=CAT,那么 l ′ = l' = l= -C-A-T- l l l 的长度为3, ∣ l ∣ = 3 |l| = 3 l=3 ∣ l ′ ∣ = 2 ∣ l ∣ + 1 = 7 |l'| = 2|l| + 1 = 7 l=2∣l+1=7. 即 l ′ l' l 也是一条标签序列,是在 l l l 的首尾以及每两个标签中间都插入 - 得到的。如图 2 2 2 所示,前向算法,都是从左上角出发,一直到右下角,白色的圈代表-,黑色圈是 l l l 中的标签。CTC前向算法


图2 前向算法

(2)初始化
如图 2 2 2 所示, t = 1 t=1 t=1初始化,要解码出CAT,第 1 1 1 行出发也行(如--C-A-T,以 ∣ l ′ ∣ = 7 |l'| = 7 l=7 举例),从第 2 2 2 行出发也行(如C--A-T-),但不能从第 3 3 3 行出发,那样就解不出C了。于是
α 1 ( 1 ) = y b 1 α 1 ( 2 ) = y l 1 1 α 1 ( s ) = 0 ,     ∀ s > 2 \begin{aligned} \alpha_1(1) &= y_b^1 \\ \alpha_1(2) &= y_{l1}^1 \\ \alpha_1(s) &= 0, \ \ \ \forall s>2 \end{aligned} α1(1)α1(2)α1(s)=yb1=yl11=0,   s>2
其中 y b 1 y_b^1 yb1 下标 b b b 表示-
(3)递推公式
我们接下来要推出 t > 1 t>1 t>1 时刻的 α t ( s ) \alpha_t(s) αt(s),观察图 2 2 2,除了刚出发的前两行,基本上黑色圈都有 3 个箭头指向它,白色圈是 2 个箭头指向它,比如当 l s ′ = b l_s' = b ls=b,代表此刻是白色圈,它来自左边的状态和左上的状态,于是有
α t ( s ) = α t − 1 ( s ) y l s ′ t + α t − 1 ( s − 1 ) y l s ′ t ,     i f l s ′ = b \alpha_t(s) = \alpha_{t-1}(s) y_{l'_s}^t + \alpha_{t-1}(s-1) y_{l'_s}^t, \ \ \ if l_s' = b αt(s)=αt1(s)ylst+αt1(s1)ylst,   ifls=b
同理若 l s ′ ! = b l_s' != b ls!=b,则有
α t ( s ) = α t − 1 ( s ) y l s ′ t + α t − 1 ( s − 1 ) y l s ′ t + α t − 1 ( s − 2 ) y l s ′ t ,     i f l s ′ ! = b    &    l s − 2 ′ ≠ l s ′ \alpha_t(s) = \alpha_{t-1}(s) y_{l'_s}^t + \alpha_{t-1}(s-1) y_{l'_s}^t+ \alpha_{t-1}(s-2) y_{l'_s}^t, \ \ \ if l_s' != b\ \ \& \ \ l'_{s-2}\neq l'_{s} αt(s)=αt1(s)ylst+αt1(s1)ylst+αt1(s2)ylst,   ifls!=b  &  ls2=ls
我们看到上式后面还有一个约束 l s − 2 ′ ≠ l s ′ l'_{s-2} \neq l'_{s} ls2=ls,这代表一种特殊情况,并不是所有的黑色圈都有三条箭头指向,比如SEE,如果解码图是---S-EE,对应的标签序列为SE,这是因为我们的标签中本身有叠字,我们希望解码路径是-S-E--E,这样得到标签序列为SEE,所以我们不允许两个相同标签直接跳转。
综合起来,就有
α t ( s ) = { ( α t − 1 ( s ) + α t − 1 ( s − 1 ) ) y l s ′ t         i f   l s ′ = b   o r   l s − 2 ′ = l s ′ ( α t − 1 ( s ) + α t − 1 ( s − 1 ) + α t − 1 ( s − 2 ) ) y l s ′ t     o t h e r w i s e (6) \alpha_t(s) =\begin{cases} (\alpha_{t-1}(s) + \alpha_{t-1}(s-1)) y_{l'_s}^t \ \ \ \ \ \ \ if\ l'_s=b\ or\ l'_{s-2}=l'_s \\ (\alpha_{t-1}(s) + \alpha_{t-1}(s-1) + \alpha_{t-1}(s-2)) y_{l'_s}^t \ \ \ otherwise \end{cases} \tag{6} αt(s)={(αt1(s)+αt1(s1))ylst       if ls=b or ls2=ls(αt1(s)+αt1(s1)+αt1(s2))ylst   otherwise(6)
注意到 α t ( s ) = 0    ∀ s < ∣ l ∣ − ( T − t ) \alpha_t(s) = 0 \ \ \forall s<|l|-(T-t) αt(s)=0  s<l(Tt),因为如果剩下的时间 T − t T-t Tt 太短,来不及解出剩下的标签 ∣ l ∣ − u |l|-u lu,即 T − t < ∣ l ∣ − s T-t < |l|-s Tt<ls,那概率就是 0 0 0 了,表示图 2 2 2 右上角的那部分没有箭头。 并且有 α t ( s ) = 0    ∀ s < 1 \alpha_t(s) = 0 \ \ \forall s<1 αt(s)=0  s<1.

(4)终止
结尾指向右下角的白色圈或黑色圈都可以,故有
P ( l ∣ x ) = α T ( ∣ l ′ ∣ ) + α T ( ∣ l ′ ∣ − 1 ) (7) \mathbb{P}(l|x) = \alpha_{T}(|l'|) + \alpha_{T}(|l'|-1) \tag{7} P(lx)=αT(l)+αT(l1)(7)

2.2 后向算法

只用前向算法已经可以算出 P ( l ∣ x ) \mathbb{P}(l|x) P(lx) 了,我们对称写出后向算法,在反向传播训练算偏导数时能用到。
(1)后向变量表示给定 x x x,能映射到 l s : ∣ l ∣ l_{s:|l|} ls:l π \pi π 的子路径的概率和。
β t ( s ) = def ∑ π ∈ L ′ T : B ( π t : T ) = l s : ∣ l ∣ ∏ t ′ = t T y π t ′ t ′       ∀ 1 ≤ t ≤ T , 0 ≤ s ≤ T (8) \beta_t(s) \overset{\text{def}}{=} \sum_{\pi \in L'^T:B(\pi_{t:T}) = l_{s:|l|}} \prod_{t'=t}^T y_{\pi_t'}^{t'} \ \ \ \ \ \forall 1\le t \le T,0 \le s \le T \tag{8} βt(s)=defπLT:B(πt:T)=ls:lt=tTyπtt     ∀1tT0sT(8)

(2)初始化
T T T 列初始化
β T ( ∣ l ′ ∣ ) = y b T β T ( ∣ l ′ ∣ − 1 ) = y l ∣ l ∣ T β T ( s ) = 0 ,     ∀ s < ∣ l ′ ∣ − 1 \begin{aligned} \beta_T(|l'|) &= y_b^T \\ \beta_T(|l'|-1) &= y_{l_|l|}^T \\ \beta_T(s) &= 0, \ \ \ \forall s<|l'|-1 \end{aligned} βT(l)βT(l1)βT(s)=ybT=yllT=0,   s<l1
(3)递推公式
β t ( s ) = { ( β t + 1 ( s ) + α t + 1 ( s + 1 ) ) y l s ′ t         i f   l s ′ = b   o r   l s + 2 ′ = l s ′ ( β t + 1 ( s ) + α t + 1 ( s + 1 ) + α t + 1 ( s + 2 ) ) y l s ′ t     o t h e r w i s e (9) \beta_t(s) =\begin{cases} (\beta_{t+1}(s) + \alpha_{t+1}(s+1)) y_{l'_s}^t \ \ \ \ \ \ \ if\ l'_s=b\ or\ l'_{s+2}=l'_s \\ (\beta_{t+1}(s) + \alpha_{t+1}(s+1) + \alpha_{t+1}(s+2)) y_{l'_s}^t \ \ \ otherwise \end{cases} \tag{9} βt(s)={(βt+1(s)+αt+1(s+1))ylst       if ls=b or ls+2=ls(βt+1(s)+αt+1(s+1)+αt+1(s+2))ylst   otherwise(9)
(4)终止
结尾指向左上角的白色圈或黑色圈都可以,故有
P ( l ∣ x ) = β 1 ( 1 ) + β 1 ( 2 ) (10) \mathbb{P}(l|x) = \beta_{1}(1) + \beta_{1}(2) \tag{10} P(lx)=β1(1)+β1(2)(10)

三、学习算法

已知训练集 S S S N w N_w Nw 代表权重为 w w w 的神经网络,整体误差是全体样本负对数似然的总和,即
O M L ( S , N w ) = − ∑ ( x , l ) ∈ S l n ( P ( l ∣ x ) ) (11) O^{ML}(S, N_w) = - \sum_{(x,l)\in S} ln(\mathbb{P}(l|x)) \tag{11} OML(S,Nw)=(x,l)Sln(P(lx))(11)
我们通过反向传播算法来更新参数 w w w,要求误差对输出变量 y k t y_k^t ykt 的偏导数
∂ O M L ( S , N w ) ∂ y k t = − ∂ l n ( P ( l ∣ x ) ) ∂ y k t (12) \frac{\partial O^{ML}(S, N_w)}{\partial y_k^t} = - \frac{\partial ln(\mathbb{P}(l|x))}{\partial y_k^t} \tag{12} yktOML(S,Nw)=yktln(P(lx))(12)
( 12 ) (12) (12) 中求和号没了是因为独立性假设。
下面就是学习算法中核心的表达式,根据前向变量和后向变量的定义,有
α t ( s ) β t ( s ) = ∑ π ∈ B − 1 ( l ) : π t = l s y l s t ∏ t = 1 T y π t t (13) \alpha_t(s) \beta_t(s) = \sum_{\pi \in B^{-1}(l): \pi_t = l_s} y_{l_s}^t \prod_{t=1}^T y_{\pi_t}^t \tag{13} αt(s)βt(s)=πB1(l):πt=lsylstt=1Tyπtt(13)
α t ( s ) \alpha_t(s) αt(s) 代表时刻 t t t 映射到 l 1 : s l_{1:s} l1:s 的概率,且 π t = l s \pi_t = l_s πt=ls; 同理, β t ( s ) \beta_t(s) βt(s) 代表时刻 t t t 映射到 l s : T l_{s:T} ls:T 的概率,且 π t = l s \pi_t = l_s πt=ls。两者相乘,右边项就会多出 y l s t y_{l_s}^t ylst. 将 y l s t y_{l_s}^t ylst 除到左边,两边遍历 t t t s s s 相加,利用公式 ( 3 ) (3) (3) 和公式 ( 4 ) (4) (4) 可以得到
P ( l ∣ x ) = ∑ t = 1 T ∑ s = 1 ∣ l ∣ α t ( s ) β t ( s ) y l s t (14) \mathbb{P}(l|x) = \sum_{t=1}^T \sum_{s=1}^{|l|} \frac{\alpha_t(s) \beta_t(s)}{y_{l_s}^t} \tag{14} P(lx)=t=1Ts=1lylstαt(s)βt(s)(14)
l a b ( l , k ) = { s : l s = k } lab(l,k) = \{s: l_s = k\} lab(l,k)={s:ls=k},利用条件独立性假设,有
∂ P ( l ∣ x ) ∂ y k t = − 1 y k t 2 ∑ s ∈ l a b ( l , k ) α t ( s ) β t ( s ) (15) \frac{\partial \mathbb{P}(l|x)}{\partial y_k^t} = -\frac{1}{{y_k^t}^2} \sum_{s\in lab(l,k)} \alpha_t(s) \beta_t(s) \tag{15} yktP(lx)=ykt21slab(l,k)αt(s)βt(s)(15)
我们的最终目标是求
∂ l n ( P ( l ∣ x ) ) ∂ y k t = 1 P ( l ∣ x ) ∂ P ( l ∣ x ) ∂ y k t (16) \frac{\partial ln(\mathbb{P}(l|x))}{\partial y_k^t} = \frac{1}{\mathbb{P}(l|x)} \frac{\partial \mathbb{P}(l|x)}{\partial y_k^t} \tag{16} yktln(P(lx))=P(lx)1yktP(lx)(16)
将公式 ( 7 ) (7) (7) 和 公式 ( 15 ) (15) (15) 带入 ( 12 ) (12) (12),可得误差对输出变量 y k t y_k^t ykt 的偏导数。
设 softmax 层之前,时刻 t t t 节点 k k k 的输出是 u k t u_k^t ukt,反向传播是对偏导数 ∂ l n ( P ( l ∣ x ) ) ∂ u k t \frac{\partial ln(\mathbb{P}(l|x))}{\partial u_k^t} uktln(P(lx)) 进行回传。
已知 softmax 层,
y k t = e u k t ∑ k ′ e u k ′ t y_k^t = \frac{e^{u_k^t}}{\sum_{k'} e^{u_{k'}^t}} ykt=keukteukt
其中 ∑ k ′ \sum_{k'} k 代表 k ′ k' k 求和遍历所有的输出层节点。
那么
∂ y k ′ t ∂ u k t = e u k ′ t δ k ′ k ∑ k ′ e u k ′ t − e u k ′ t e u k t ( ∑ k ′ e u k ′ t ) 2 = y k ′ t δ k ′ k − y k ′ t y k t (17) \frac{\partial y_{k'}^t}{\partial u_k^t} = \frac{e^{u_{k'}^t}\delta_{k'k} \sum_{k'}e^{u_{k'}^t}- e^{u_{k'}^t}e^{u_k^t}}{(\sum_{k'}e^{u_{k'}^t})^2} = y_{k'}^t\delta_{k'k} - y_{k'}^t y_k^t \tag{17} uktykt=(keukt)2euktδkkkeukteukteukt=yktδkkyktykt(17)
其中
δ k ′ k = { 1         i f   k ′ = k 0          o t h e r w i s e \delta_{k'k} = \begin{cases} 1 \ \ \ \ \ \ \ if\ k' = k \\ 0\ \ \ \ \ \ \ \ otherwise \end{cases} δkk={1       if k=k0        otherwise

根据链式法则
∂ l n ( P ( l ∣ x ) ) ∂ u k t = ∑ k ′ ∂ l n ( P ( l ∣ x ) ) ∂ y k ′ t ∂ y k ′ t ∂ u k t (18) \frac{\partial ln(\mathbb{P}(l|x))}{\partial u_k^t} = \sum_{k'} \frac{\partial ln(\mathbb{P}(l|x))}{\partial y_{k'}^t} \frac{\partial y_{k'}^t}{\partial u_k^t} \tag{18} uktln(P(lx))=kyktln(P(lx))uktykt(18)
( 16 ) (16) (16) ( 17 ) (17) (17) 带入 ( 18 ) (18) (18),可得
∂ l n ( P ( l ∣ x ) ) ∂ u k t = y k t − 1 y k t P ( l ∣ x ) ∑ s ∈ l a b ( l , k ) α t ( s ) β t ( s ) (19) \frac{\partial ln(\mathbb{P}(l|x))}{\partial u_k^t} = y_k^t - \frac{1}{y_k^t\mathbb{P}(l|x)} \sum_{s\in lab(l,k)} \alpha_t(s) \beta_t(s) \tag{19} uktln(P(lx))=yktyktP(lx)1slab(l,k)αt(s)βt(s)(19)
其中用到了 P ( l ∣ x ) = ∑ k ′ ∑ s ∈ l a b ( l , k ′ ) α t ( s ) β t ( s ) y k ′ t \mathbb{P}(l|x) = \sum_{k'}\sum_{s\in lab(l,k')} \frac{\alpha_t(s) \beta_t(s)}{y_{k'}^t} P(lx)=kslab(l,k)yktαt(s)βt(s).

CTC训练过程


图3 CTC训练过程

∂ l n ( P ( l ∣ x ) ) ∂ u k t \frac{\partial ln(\mathbb{P}(l|x))}{\partial u_k^t} uktln(P(lx)) 作为误差量(即图 3 3 3 中的error),如图 3 3 3 所示,左边是 CTC 训练过程中神经网络的概率输出,不同颜色代表不同的标签,虚线代表空记号的概率。可以看到 CTC 对各个标签的预测呈现尖峰形状,大部分时刻都预测为空记号-。右边是回传的倒数,开始时参数随机初始化,训练初期(如(a)所示),各个颜色曲线相对平缓,随着训练时间变长,偏导数往预测点附近集中,水平线之上的使得概率变大,水平线之下的让概率变小(如(b)所示)。训练快结束时(如(c)所示),左边各尖峰已经确定,右边偏导数也很小了,模型参数基本确定。

四、预测算法

模型训练好后,需要根据输入音频,解码得到对应的标签序列,即选择 l ∗ l^* l,使得
l ∗ = arg ⁡ max ⁡ l P ( l ∣ x ) l^* = \arg\max_l \mathbb{P}(l|x) l=arglmaxP(lx)
论文中给出了两种解码算法。

4.1 最佳路径解码(best path decoding)

和 HMM 中的近似算法一样,选择每个时刻 t t t 概率最大的标签,挨个组合起来即可。
请添加图片描述


图4 best path decoding3

时间步 P(-) P(A)
t=1 0.7 0.3
t=2 0.6 0.4

举个例子,如图 4 4 4 所示,当 t = 1 t=1 t=1 时,概率最大的是 P ( π 1 = b l a n k ) = 0.7 \mathbb{P}(\pi_1 = blank) = 0.7 P(π1=blank)=0.7,当 t = 2 t=2 t=2 时,概率最大的是 P ( π 2 = b l a n k ) = 0.6 \mathbb{P}(\pi_2 = blank) = 0.6 P(π2=blank)=0.6,所以根据最佳路径解码,最优路径为 -。其实如图 4 4 4 所示,最优路径是 l = A l = A l=A.
最佳路径解码是一种近似解法,优点是速度快,大部分情况下也能解码出不错的结果。缺点是,结果可能不一定是全局最优的,如图 4 4 4 所示,最佳路径解码与全局最优路径差别较大。

4.2 前缀搜索解码(prefix search decoding)

将每一步最可能的前缀路径保存下来,在此基础上继续解码。
举个例子4

时间步 P(-) P(A) P(B)
t=1 0.3 0.2 0.5
t=2 0.1 0.5 0.4
t=3 0.3 0.1 0.6
  1. 初始化:维护一个候选前缀记号,初始时仅包含空序列"",其累计概率为 1.0 1.0 1.0;每个时间步保留概率最高的那个,即束宽(Beam Width)设为 1。
  2. 时间步 t = 1 t=1 t=1
    • 对初始前缀"",生成可能得扩展:
      • 添加字符 A,概率为 0.2,路径为A
      • 添加字符 B,概率为 0.5,路径为B
      • 添加字符 -,概率为 0.3,路径为""
    • 合并相同前缀,不变。
    • 剪枝(保留概率最大的前缀),即B
  3. 时间步 t = 2 t=2 t=2
    • 对前缀B,生成可能得扩展:
      • 添加字符 A,概率为 0.25,路径为BA,合并路径后不变。
      • 添加字符 B,概率为 0.2,路径为BB,合并路径后得B
      • 添加字符 -,概率为 0.05,路径为B-,合并路径后得B
    • 合并相同前缀
      • 路径BA, 总概率 0.25。
      • 路径B, 总概率 0.25。
    • 剪枝
      • 由于BAB 概率一样,都保留。
  4. 时间步 t = 3 t=3 t=3
    • 前缀BA,生成可能扩展
      • 添加字符 A,概率为 0.025,路径为BAA,合并路径后得到BA
      • 添加字符 B,概率为 0.15,路径为BAB,合并路径后不变。
      • 添加字符 -,概率为 0.075,路径为BA-,合并路径后得到BA
    • 前缀B,生成可能扩展
      • 添加字符 A,概率为 0.025,路径为BA,合并路径后得到BA
      • 添加字符 B,概率为 0.15,路径为BB,合并路径后得到B
      • 添加字符 -,概率为 0.075,路径为B-,合并路径后得到B
    • 合并相同前缀
      • 路径BA, 总概率 = 0.025 + 0.075 + 0.025 = 0.125。
      • 路径BAB, 总概率 = 0.15。
      • 路径B, 总概率 = 0.15 + 0.075 = 0.225。

所以概率最高的路径是 B,这个结果是对的,但与全遍历的概率相比
P ( l = B ) = P ( B − − ) + P ( − B − ) + P ( − − B ) + P ( B B − ) + P ( − B B ) + P ( B B B ) = 0.5 × 0.1 × 0.3 + 0.3 × 0.4 × 0.3 + 0.3 × 0.1 × 0.6 + 0.5 × 0.4 × 0.3 + 0.3 × 0.4 × 0.6 + 0.5 × 0.4 × 0.6 = 0.321 \begin{aligned} \mathbb{P}(l = B) &= \mathbb{P}(B--)+\mathbb{P}(-B-)+\mathbb{P}(--B)+\mathbb{P}(BB-)+\mathbb{P}(-BB)+\mathbb{P}(BBB) \\ &= 0.5\times0.1\times0.3+0.3\times0.4\times0.3+0.3\times0.1\times0.6+0.5\times0.4\times0.3+0.3\times0.4\times0.6+0.5\times0.4\times0.6 \\ &= 0.321 \end{aligned} P(l=B)=P(B)+P(B)+P(B)+P(BB)+P(BB)+P(BBB)=0.5×0.1×0.3+0.3×0.4×0.3+0.3×0.1×0.6+0.5×0.4×0.3+0.3×0.4×0.6+0.5×0.4×0.6=0.321
还是有差距的,所以前缀搜索解码也是一种相似算法。

总结

本文介绍了 CTC 算法,包括 CTC 的 3 个基本问题。为了行文与 HMM2 对应,罗列了 3 个基本问题,问题 1 是利用前向后向算法算似然函数。问题 2 算出偏导数,利用反向传播算法更新模型参数。问题 3 利用训好的模型解码最优路径。Pytorch 官方已经实现了 CTCLoss5

参考文献

  1. Alex Graves, et al. Connectionist Temporal Classification: Labelling Unsegmented Sequence Data with Recurrent Neural Networks. 2006. ↩︎

  2. 隐马尔科夫模型(HMM), 2025. https://blog.csdn.net/weixin_45234741/article/details/147023021?spm=1001.2014.3001.5501 ↩︎ ↩︎

  3. Alex Graves, Supervised Sequence Labelling with Recurrent Neural Networks.(第七章), Springer, 2012 ↩︎

  4. 洪青阳,李琳, 《语音识别原理与应用》, 2020. ↩︎

  5. https://pytorch.org/docs/stable/generated/torch.nn.CTCLoss.html ↩︎

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐