ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

深度学习 - 18 (1) CTC从基础到完全掌握

深度学习 - 18 (1) CTC从基础到完全掌握 CTC 底层数据流与动态规划0. 先给出整篇教程最核心的结论CTC 可以先不理解成一堆公式,而是理解成下面这条数据流:输入语音 ↓ Encoder ↓ 每个 frame 的分类概率 ↓ 模型概率表 [T, C] ↓ 真实 target ↓ 插入 blank,构造 extended target ↓ 得到 CTC state 1...S ↓ 根据 state transition 规则 决定当前 frame 哪些 state 可以到达 ↓ 当前 state 对应一个 label ↓ 把这个 label 映射到模型 vocab 的 class_id ↓ 从模型概率表取: P(frame_t = 这个 label | x) ↓ 与上一帧合法来源的累计概率结合 ↓ 同一个当前 state 的不同历史路径相加 ↓ 得到 α[t][s] ↓ 继续处理下一帧 ↓ 最终到达 target 的合法结束 state ↓ 得到 P(target | x) ↓ CTC Loss = -log P(target | x)CTC 最重要的不是“模型如何输出 target”。事实上模型根本没有直接输出 target。模型只做一件事:每个时间 frame 输出一个关于blank / token1 / token2 / ...的概率分布。CTC 再利用 target 构造出的状态空间,计算:这整个 target 有多大概率能够由这些 frame-level 概率解释出来。1. CTC 到底解决什么问题假设一段语音经过 Fbank 和 Encoder 后得到:T = 10即 10 个时间步。而真实文本只有:target = [c, a, t]长度:U = 3很明显:10 frames ≠ 3 tokens而训练数据通常只有:Audio → "cat"并没有:frame1 → ? frame2 → ? frame3 → ? ... frame10 → ?这样的 frame-level alignment。也就是说,我们知道:10 个 frame 最终应该对应 "cat"但不知道:c 在哪些 frame a 在哪些 frame t 在哪些 frameCTC 的做法就是:不人工指定 alignment,而是把所有满足 CTC 规则、最终能够得到 target 的 alignment path 都纳入概率计算。2. 第一张表:模型输出概率表这是整个 CTC 数据流的起点。假设 vocabulary 中有:a b c ... t另外增加一个:blank如果真正的 vocabulary 有V=10个 token,那么模型输出类别数:C=V+1 C = V + 1C=V+1所以如果有 10 个 token:C = 11而不是 10。2.1 模型输出是什么Encoder 输出:h1, h2, ..., hT每个时间步经过 Linear + Softmax,得到一个概率分布。所以整个模型概率表可以写成:P∈RT×C P \in \mathbb{R}^{T \times C}P∈RT×C其中:T:frame 数C:类别数,包括 blankP[t][k]:第t个 frame 输出第k个类别的概率例如:frameblankabc…10.300.100.050.40…20.200.700.100.10…30.600.200.200.00…………………这一张表是:模型真正输出的数据。3. 这里的概率到底是什么概率公式中的输入通常记为:x=[x1,x2,…,xT] x = [x_1,x_2,\ldots,x_T]x=[x1​,x2​,…,xT​]这里的x表示:整段输入序列。在 ASR 中可以理解为:x = 整条语音 / acoustic feature sequence例如:x1 = 第1个 Fbank frame x2 = 第2个 Fbank frame ... xT = 第T个 Fbank frame模型计算的不是:P(xt) P(x_t)P(xt​)CTC 也不关心:“这个 frame 本身出现的概率是多少?”因为 frame 已经是观察到的输入。真正计算的是:P(πt∣x) P(\pi_t \mid x)P(πt​∣x)也就是:给定整段输入x,第t个时间步输出某个 label 的概率。例如:Pt(c)=P(πt=c∣x) P_t(c)=P(\pi_t=c\mid x)Pt​(c)=P(πt​=c∣x)表示:给定整条输入语音,第t个时间步输出c的概率。4. 第二张表:CTC State Table现在进入 CTC 真正的核心。假设:target = [c, a, t]CTC 内部构造:[-, c, -, a, -, t, -]给它编号:state1234567labelblankcblankablanktblank这个序列叫:extended target而这些位置:1, 2, 3, 4, 5, 6, 7就是:CTC state这里非常重要:state_id 不是 class_id。例如:state 1 → blank state 2 → c state 3 → blank state 4 → a state 5 → blank state 6 → t state 7 → blank虽然:state 1 state 3 state 5 state 7都是 blank,但它们代表 target 的不同阶段,所以必须是不同 state。5. 为什么同一个 blank 要出现这么多次这不是为了产生多个 blank class。模型中仍然只有一个:blank class例如:class_id(blank) = 0但是 target 的不同位置需要不同 state:state1 = blank state3 = blank state5 = blank state7 = blank它们都映射到:class_id = 0所以:state1 → class 0 state3 → class 0 state5 → class 0 state7 → class 0因此同一个 frame 如果分别处于这些 state,使用的模型概率确实都是:Pt(blank) P_t(blank)Pt​(blank)但是:state 不同,意味着 target 已经进行到不同阶段,所以 DP 累计概率不同。这就是:同一个 label ≠ 同一个 state6. 第三个概念:模型表和 State Table 之间怎么连接现在有两套编号:模型类别编号class_id 0 = blank 1 = a 2 = b 3 = c ...CTC statestate 1 = blank 2 = c 3 = blank 4 = a 5 = blank 6 = t 7 = blank因此存在一个映射:state→label→class_id state \rightarrow label \rightarrow class\_idstate→label→class_id例如:state 4 ↓ label = a ↓ class_id = 1 ↓ model[t][1]所以如果当前 frame 是t,当前 DP 想进入state 4:Emission(t,4)=Pt(a) \text{Emission}(t,4)=P_t(a)Emission(t,4)=Pt​(a)实际就是:P[t][class_id(a)] P[t][class\_id(a)]P[t][class_id(a)]7. 可以把这个中间关系理解成一张“Emission 表”为了理解方便,可以在脑中构造一个:Emission[t][s]它不是必须真的存下来,但概念上非常好理解。定义:E[t,s]=Pt(ls′) E[t,s]=P_t(l'_s)E[t,s]=Pt​(ls′​)其中:t:当前 frames:extended target 中的 statel'_s:states对应的 label例如:extended target: state 1 2 3 4 5 6 7 label - c - a - t - class_id 0 3 0 1 0 4 0那么:E[t,1] = model[t,0] # blank E[t,2] = model[t,3] # c E[t,3] = model[t,0] # blank E[t,4] = model[t,1] # a E[t,5] = model[t,0] # blank E[t,6] = model[t,4] # t E[t,7] = model[t,0] # blank所以你之前提出的理解是完全成立的:可以先把[T,C]映射成一个[T,S]的 emission 表,再做 DP。实际高性能实现通常不需要额外完整复制这一张表,可以直接gather或索引取得概率。8. CTC 真正的 DP 表是什么现在有:T = frame 数 S = extended target 长度对于:target = [c,a,t]有:S = 2U+1 = 7所以 DP 表是:α∈RT×S \alpha \in \mathbb{R}^{T \times S}α∈RT×S例如:state 1state 2state 3state 4state 5state 6state 7frame 1α(1,1)α(1,2)α(1,3)…………frame 2α(2,1)α(2,2)α(2,3)…………frame 3α(3,1)α(3,2)α(3,3)………………………………这里:α(t,s) \alpha(t,s)α(t,s)表示:处理到第t个 frame,并且当前处于 states时,所有合法历史路径的概率总和。这一点必须牢记:α[t][s]不是一条 path 的概率。它是很多条 path 的概率汇总。9. 为什么需要 State现在我们真正开始走。假设:target = [c,a,t]那么:[-, c, -, a, -, t, -]从左到右代表 target 的进度:state 1: 还没有进入 c state 2: 正在表示 c state 3: c 已经结束,进入 c 后的 blank state 4: 已经进入 a state 5: a 已经结束,进入 a 后的 blank state 6: 已经进入 t state 7: t 已经结束因此:state 的作用不是表示“类别”,而是表示“target 已经走到哪里”。10. CTC 每个 frame 到底在做什么现在处理第t个 frame。问题不是:“这个 frame 应该是什么?”而是:“为了最终得到 target,这个 frame 可以落在哪些 state?”例如:extended target: 1 2 3 4 5 6 7 - c - a - t -如果当前上一帧在 state 2:state 2 = c下一帧可能:2 → 2 2 → 3 2 → 4分别表示:c → c c → blank c → a这些都是合法的。但是:2 → 1不允许,因为 CTC state 只能单调向前或停留,不能后退。11. 三种基本的 State Transition从上一帧到当前帧,当前 states一般可以从:s s-1 s-2过来。对应:1. 原地停留s → s表示:当前 token / blank 在连续多个 frame 中持续。例如:c → c → c → ccollapse 后仍然只有一个:c2. 前进一个 states-1 → s表示:从 target 的前一个阶段进入当前阶段。例如:c → blank blank → a a → blank3. 前进两个 states-2 → s这表示:跨过中间的 blank,直接从一个 token 进入下一个 token。例如:c → a可以。但是它有条件。12.s-2 → s为什么有条件只有当:ls′≠blank l'_s \neq blankls′​=blank并且:ls′≠ls−2′ l'_s \neq l'_{s-2}ls′​=ls−2′​才允许。简单理解:当前 state 必须是 token,而不是 blank;并且当前 token 不能和两步前是同一个 token。13. 为什么c → a可以直接跳target:[c,a,t]extended:[-,c,-,a,-,t,-]看:state 2 = c state 4 = a如果:state2 → state4就是:c → a检查:a != blank a != c所以可以直接跳。对应 path:c acollapse 后:c a没有问题。14. 为什么 target=[c,c]时不能直接跳target:[c,c]extended:[-,c,-,c,-]考虑:state2 → state4就是:c → c如果直接走:c cCTC collapse:c c ↓ c只得到一个 c。但 target 明明是:c c所以直接跳不行。必须:state2 → state3 → state4即:c → blank → c于是:c - c ↓ c c所以:blank 的一个关键作用,是让相邻的两个相同 target token 能够被区分。15. 一个很容易误解的地方:blank 可以连续例如:target = [c,a,t]路径:- - - c c - - a - t - -完全合法。这里:blank → blank是可以的。但是只允许:state1 → state1这样的原地停留。不能:state1 → state3因为 state1 和 state3 都是 blank。这属于:s → s+2而目的 state 是 blank,所以违反:ls′≠blank l'_s \neq blankls′​=blank因此:blank state1 → blank state3不允许。这恰好解决了两个问题:blank 可以持续很多 frame;但不能利用两个不同 blank state 重复表示同一个连续 blank 区间。16. 所以一个 State 到底能去哪里对于:[-,c,-,a,-,t,-]可以把规则理解成:state 1 (-) → 1 → 2 state 2 (c) → 2 → 3 → 4 state 3 (-) → 3 → 4 state 4 (a) → 4 → 5 → 6 state 5 (-) → 5 → 6 state 6 (t) → 6 → 7 state 7 (-) → 7这里没有:1 → 3 3 → 5 5 → 7因为这些都是:blank → blank的两格跳。17. 真正的 DP 路径在哪个表中?现在可以回答一个非常重要的问题:真正的“路径规划”到底在哪?不是直接在:[T,C]模型概率表里规划。真正的 DP 是在:[T,S] [T,S][T,S]这个:时间 × target-state空间里进行。例如:state 1 2 3 4 5 6 7 - c - a - t - frame1 ● ● ↘ ↓ frame2 ● ● ● ● ↘ ↓ ↘ frame3 ● ● ● ● ● ↘ ↓ ...所以:target 决定状态空间;state transition 决定图上的哪些边存在。18. 模型概率表在这里干什么模型输出表:[T,C]并不负责决定:能不能走它只负责:如果现在走到这个 state, 这个 state 对应的 label 有多大概率?例如:state 4 = a如果当前是 frame 7:model[7][class_id(a)]就是当前 emission probability。所以:State Transition ↓ 决定“能不能从这里到那里” Model Probability ↓ 决定“这一步的概率多大”两者职责完全不同。19. 现在进入最核心的“乘法和加法”这一部分是 CTC DP 真正的核心。记住两句话:沿时间向前走 → 概率相乘。不同历史路径汇合到同一个当前 state → 概率相加。20. 为什么跨 frame 要乘假设有一条 path:frame1 → frame2 → frame3具体:blank → c → c如果:P1(blank)=0.3 P2(c)=0.7 P3(c)=0.2那么这一整条 path 的概率:P(path)=0.3×0.7×0.2 P(path)=0.3 \times 0.7 \times 0.2P(path)=0.3×0.7×0.2因为它需要:第1帧是 blank 并且 第2帧是 c 并且 第3帧是 c这些事件联合起来,需要使用乘法。21. 为什么到同一个 state 要加假设最终:frame1 → frame2都可以到:state2 = c存在两条历史:path1: blank → c path2: c → c它们的概率可能分别是:0.21 0.49这两条路径最终都属于:frame2, state2所以:α(2,2)=0.21+0.49 \alpha(2,2)=0.21+0.49α(2,2)=0.21+0.49得到:α(2,2)=0.70 \alpha(2,2)=0.70α(2,2)=0.70这就是 DP 中的“汇合”。22. 这里特别容易误以为:当前 frame 不同 state 也要加不会。例如当前 frame 有:state2 = c state3 = blank state4 = a会分别计算:α(t,2) \alpha(t,2)α(t,2)α(t,3) \alpha(t,3)α(t,3)α(t,4) \alpha(t,4)α(t,4)它们三个:不相加。因为它们代表不同的 target 进度。例如:state2: 已经进入 c state3: c 已经结束,位于 c 后面的 blank state4: 已经进入 a它们是三个不同状态。23. 每个当前 State 是一个“概率桶”这是理解 DP 最好的方式。例如:多条历史路径 ↙ ↓ ↘ path1 path2 path3 ↓ state = c ↓ α[t][state]这个α[t][state]就像一个桶:所有合法到达这个 state 的路径概率,全部放在一起。所以:α[t][2] = 很多路径的总概率 α[t][3] = 另一批路径的总概率 α[t][4] = 又一批路径的总概率三个桶之间不混。24. CTC DP 的真正计算顺序现在计算:αt(s) \alpha_t(s)αt​(s)首先问:第一步:上一帧哪些 state 可以到当前 state?例如当前:state4 = a合法来源:state2 state3 state4所以历史概率:αt−1(2)+αt−1(3)+αt−1(4) \alpha_{t-1}(2)+\alpha_{t-1}(3)+\alpha_{t-1}(4)αt−1​(2)
返回列表