最近看 ctc 看了很多遍,因为一直看不懂,所以就死磕到底,最后发现越看越有感觉,就记录一下。
CTC Loss 要解决的问题就是当 label 长度小于模型输出长度时,如何做损失函数。
一般做分类时,已有的softmax loss 都是模型输出长度和 label 长度相同且严格对齐,而语音识别或者手写体识别中,无法预知一句话或者一张图应该输出多长的文字,这时做法有两种:seq2seq+attention 机制,不限制输出长度,在最后加一个结束符号,让模型自动和 gt label 对齐;另一种是给定一个模型输出的最大长度,但是这些输出并没有对齐的 label 怎么办呢,这时就需要 CTC loss 了。
在传统的语音识别的模型中,我们对语音模型进行训练之前,往往都要将文本与语音进行严格的对齐操作。这样就有两点不太好:
1. 严格对齐要花费人力、时间。
2. 严格对齐之后,模型预测出的 label 只是局部分类的结果,而无法给出整个序列的输出结果,往往要对预测出的 label 做一些后处理才可以得到我们最终想要的结果。
虽然现在已经有了一些比较成熟的开源对齐工具供大家使用,但是随着 deep learning 越来越火,有人就会想,能不能让我们的网络自己去学习对齐方式呢?因此 CTC(Connectionist temporal classification)就应运而生啦。
想一想,为什么 CTC 就不需要去对齐语音和文本呢?因为 CTC 它允许我们的神经网络在任意一个时间段预测 label,只有一个要求:就是输出的序列顺序只要是正确的就 ok 啦~ 这样我们就不在需要让文本和语音严格对齐了,而且 CTC 输出的是整个序列标签,因此也不需要我们再去做一些后处理操作。
对一段音频使用 CTC 和使用文本对齐的例子如下图所示:
如上图,传统的 Framewise 训练需要进行语音和音素发音的对齐,比如 “s” 对应的一整段语音的标注都是 s;而 CTC 引入了 blank(该帧没有预测值),“s”对应的一整段语音中只有一个 spike(尖峰)被认为是 s,其他的认为是 blank。对于一段语音,CTC 最后的输出是 spike 的序列,不关心每一个音素对应的时间长度。
与传统神经网络主要区别
训练流程和传统的神经网络类似,构建 loss function,然后根据 BP 算法进行训练,不同之处在于传统的神经网络的训练准则是针对每帧数据,即每帧数据的训练误差最小,而 CTC 的训练准则是基于序列(比如语音识别的一整句话)的,比如最大化 p(z|x)p(z|x) ,序列化的概率求解比较复杂,因为一个输出序列可以对应很多的路径,所有引入前后向算法来简化计算。
语音识别中的 DNN 训练,每一帧都有相应的状态标记,比如有 5 帧输入 x1,x2,x3,x4,x5,对应的标注分别是状态 a1,a1,a1,a2,a2。
CTC 的不同之处在于输出状态引入了一个 blank,输出和 label 满足如下的等价关系:
F(a−ab−)=F(−aa−−abb)=aab
多个输出序列可以映射到一个输出。
考虑到计算 p (l ∣ x) p(l|x) p(l∣x) 需要计算很多条路径的概率,随着输入长度呈指数化增加,可以引入类似于 HMM 的前后向算法来计算该概率值。
为了引入 blank 节点,在 label 首尾以及中间插入 blank 节点,如果 label 序列原来的长度为 U,那么现在变为 U’=2U+1。
为看清全貌,假设 T=7,则上图全貌如下:
前向变量 α t (s) \alphat(s) αt(s) ,表示 t t t 时刻在节点 s s s 的前向概率值,其中 s ∈ [ 1 , 2 ∣ l ∣ + 1 ] s\in[1, 2|l|+1] s∈[1,2∣l∣+1]。
初始化值如下:
α 1 (1) = y b 1 \alpha_1(1)=y_b^1 α1(1)\=yb1
α 1 (2) = y l 1 1 \alpha_1(2)=y{l_1}^1 α1(2)\=yl11
α 1 (s) = 0 , ∀ s > 2 \alpha_1(s)=0,\quad \forall s > 2 α1(s)\=0,∀s>2
递推关系:
α t (s) = { α ˉ t ( s ) y l s ′ t i f l s ′ = b o r l s − 2 ′ = l s ′ ( α ˉ t ( s ) + α t − 1 ( s − 2 ) ) y l s ′ t o t h e r w i s e \alpha_t(s)=\left{
α¯t(s)yls′tifls′\=borls−2′\=ls′(α¯t(s)+αt−1(s−2))yls′totherwise
\right. αt(s)\={αˉt(s)yls′tifls′\=borls−2′\=ls′(αˉt(s)+αt−1(s−2))yls′totherwise
其中:
α ˉ t (s) = d e f α t − 1 ( s ) + α t − 1 ( s − 1 ) \bar\alphat(s)\xlongequal{def}\alpha{t-1}(s)+\alpha_{t-1}(s-1) αˉt(s)def αt−1(s)+αt−1(s−1)
后向
初始化值:
β T (∣ l ′ ∣) = y b T \beta_T(|lT βT(∣l′∣)\=ybT
β T (∣ l ′ ∣ − 1) = y l ∣ l ∣ T \beta_T(|lT βT(∣l′∣−1)\=yl∣l∣T
ctc 代码学习参考ctc
图 1. 序列建模【src】
虽然并没为限定 Nw 具体形式,下面为假设其了某种神经网络(e.g. RNN)。
下面代码示例 toy Nw:
import numpy as np
np.random.seed(1111)
T, V = 12, 5
m, n = 6, V
x = np.random.random([T, m])
w = np.random.random([m, n])
def softmax(logits):
max_value = np.max(logits, axis=1, keepdims=True)
exp = np.exp(logits - max_value)
exp_sum = np.sum(exp, axis=1, keepdims=True)
dist = exp / exp_sum
return dist
def toy_nw(x):
y = np.matmul(x, w)
y = softmax(y)
return y
y = toy_nw(x)
print(y)
print(y.sum(1, keepdims=True))
参考:CTC loss 理解
参考:《Supervised Sequence Labelling with Recurrent Neural Networks》 chapter7
参考:【Learning Notes】CTC 原理及实现
https://blog.csdn.net/juluwangriyue/article/details/116944581?utm_medium=distribute.pc_relevant.none-task-blog-2baidujs_baidulandingword~default-0-116944581-blog-97136572.pc_relevant_multi_platform_whitelistv1&spm=1001.2101.3001.4242.1&utm_relevant_index=3