想象这样一个晚上:你窝在沙发里,点开一部没有字幕的外语剧。演员说了一长串话,声音一帧接一帧地涌来,而你的任务是把它写成屏幕上短短几行字幕。最大的麻烦是——声音又长又连续,字幕又短又离散,到底哪一帧对应哪个字?如果一帧一帧地人工标注,怕是天亮也标不完一集。 CTC(Connectionist Temporal Classification)就像一位专治这个麻烦的“听写员”。它先给每一帧声音都记下几个候选字符,连成一条长长的工作草稿,再用一套折叠规则——把拖长的音合并、把含糊的空位跳过——把草稿整理成真正的字幕。更妙的是,它训练时根本不需要你标注每帧对应哪个字,只要给它一句完整的字幕,它自己就能学会对齐。接下来,我们会跟着这位听写员,看看 blank 空位如何工作、贪心解码如何快刀斩乱麻、Beam Search 如何稳扎稳打,以及它和另一位听写员 RNN-T 的区别。
理解 CTC 解码,是踏入语音识别工程的第一道门槛。无论你是在训练端到端 ASR、调试解码器延迟,还是上线语音产品,都绕不开这套“听音写字”的基本功。
可视化演示
交互式演示CTC解码的blank标签对齐、贪心解码、Beam Search搜索过程,通过滑块调节束宽观察解码路径变化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
CTC对齐模式 blank标签 字符标签 多对一映射实现无需对齐的端到端训练
认识CTC
你正在追一部外语剧,想给每一集配上字幕。演员语速飞快,声音像流水一样涌来,而你手里的纸只有几行空格。最难的不是“听懂”,而是“对位”:这么长一串声音,哪一段对应哪个字?如果逼你把每一帧画面都标上一个字,这活儿根本没法干。
CTC 就是来解决这个“长语音对短文本”问题的。它的全称是连接时序分类(Connectionist Temporal Classification),听起来很唬人,内核却是一位聪明的听写员。它不需要你提前告诉它“哪个音帧对应哪个字”,只需要最终的那句字幕,它自己就能把声音和文字的对齐关系学出来。
具体怎么做呢?对于一段输入语音 $X$(可以理解为一长串声学快照),模型在每一帧都会输出一个概率分布:可能是字母、也可能是 blank 占位符。把每帧挑一个标签连起来,就是一条“路径”$\pi$。一句字幕 $Y$ 对应的概率,等于所有能折叠成 $Y$ 的路径概率之和:
你看,听写员并不需要逐帧知道“这是哪个字”,它只需要在最后交出一句正确的字幕。所有草稿路径的概率加起来,就是这句字幕被“听对”的总把握。这就是 CTC 登堂入室的第一课:用路径求和,绕开逐帧对齐。
- 草稿路径模型在每一帧 $t$ 都会输出一个候选标签 $\pi_t$,把这些标签按时间连起来,就是一条听写草稿。
- 单条路径的打分每一帧的候选概率相乘,得到这条草稿路径整体的可信度。
- 折叠函数$\mathcal{B}$ 是听写员的整理规则,把草稿路径变成最终字幕。$Y$ 是目标文本,$\mathcal{B}(\pi) = Y$ 表示这条草稿能整理成目标字幕。
- 汇总所有可能$\mathcal{B}^{-1}(Y)$ 是所有能折叠成 $Y$ 的路径集合,把它们全部求和,才是字幕 $Y$ 的总概率。
对齐机制
听写员上岗第一天就碰到两个麻烦。第一个麻烦是“拖音”:演员把“好”字拖了整整一秒,这一秒里有几十帧,总不能写几十个“好”吧?第二个麻烦是“含糊音”:有些帧既像这个音又像那个音,硬选一个反而容易错。于是听写员发明了一个记号——blank,写作 _,专门表示“这一帧我先空着,不急着定”。
这里要特别注意:blank 不是静音,也不是句间停顿。它是“无标签”的占位符,作用就像草稿纸上的空格键。最妙的是,这个空格键还能解决“重复字”的问题。比如要表达字幕 aa,如果路径是 a a,折叠时两个相邻的 a 会被合并成一个 a,字幕就丢了。但如果写成 a _ a,中间的 blank 把两个 a 隔开,折叠后就能保留 aa。
CTC 的折叠规则 $\mathcal{B}$ 只有两步:先合并连续相同的标签,再删掉所有 blank。我们用一个小例子看,假设路径是 a _ a a _ b:
blank 就像字幕编辑里的“空格键”:它让相同字符可以连续出现而不被合并,也给了模型一个缓冲地带去处理那些含糊的音段。理解了折叠规则,你就掌握了 CTC 的核心玄机——声音再长,也能被这张折叠规则“压”成一句干净字幕。
- 原始草稿听写员逐帧记下的序列,
_表示这一帧先空着。 - 合并拖音相邻且相同的标签只保留一个,像拖长音的
a a只算一个a。 - 跳过空位把所有的 blank 标记删掉,剩下的就是正式字幕。
贪心解码
听写员是个急性子,他发明了一套“速记法”:每一帧画面,哪个候选字符看着最顺眼就写哪个,绝不留恋,绝不回头。就像考试时做一道选择题,不检查前后文,直接选当下概率最大的那个答案。
这套方法在 CTC 里就叫贪心解码。它把解码拆解成 $T$ 个独立的小问题:每一帧只做一次 $\arg\max$,挑出该帧概率最高的标签,连成一条路径,最后折叠成字幕。优点是速度飞快——每帧只比较几十个候选,延迟极低;缺点是容易犯“局部最优”的错误:单帧看着最合理的标签,连起来未必是整句最优。
急性子听写员的优点是快,缺点是“一着不慎,满盘皆输”——某一帧选错,可能让整句字幕跑偏。但在延迟敏感的实时场景里,比如直播字幕、语音助手,贪心解码依然是最常用的第一版方案。
- 每帧选最可能标签在词典 $\mathcal{L}$ 和 blank 中,挑出概率最大的那个标签 $\hat{\pi}_t$。
- 拼成完整草稿把所有帧选出的标签按时间顺序连起来,得到一条完整的贪心路径。
- 折叠成字幕用折叠规则 $\mathcal{B}$ 把草稿路径整理成最终字幕。
Beam Search
老练的听写员从不只带一个草稿本,而是同时摊开 $B$ 个草稿本。每听到新的一帧,他就在每个本子上分别记下候选字,然后把最不像真字幕的本子撕掉,始终保持 $B$ 本最有希望的草稿。这,就是 Beam Search,其中 $B$ 叫束宽。
束宽 $B$ 是个超参数。$B = 1$ 时,Beam Search 退化成贪心解码;$B$ 越大,越接近全局最优,但每一步要维护的候选路径也越多,计算成本线性上涨。每一步,算法会先从已有的 $B$ 条路径出发,给每条路径接上所有可能的下一标签,然后按对数概率排序,只保留前 $B$ 名:
多本草稿的好处是“东方不亮西方亮”:某一帧声学特征有歧义时,几条候选路径可以互相兜底。工业界的离线识别常常用 Beam Search 配合语言模型,把声学分数和语言分数合在一起排序,识别准确率比贪心解码高出一大截。
- 长出候选每步先把上一步保留的路径,分别接上词典里所有可能的下一标签,得到一个更大的候选集合。
- 计算得分对每条新路径计算对数概率,兼顾了从第 1 帧到第 $t$ 帧的所有声学证据。
- 保留前 B 条按得分从高到低排序,只留下前 $B$ 条路径进入下一轮,其余全部淘汰。
与RNN-T对比
很快,团队里来了另一位听写员 RNN-T,他的工作风格完全不同。CTC 是“先录音、后整理”——把整段草稿记完再统一折叠;而 RNN-T 是“边听边确认”——每写一个字,都要回头看一眼已经写出的内容,再决定下一个字是什么。他的手里多了一个“提示器”,叫预测网络,专门把已写出的字幕喂回来,辅助下一步预测。
结构上的核心差异在于输出节奏。CTC 的每一帧最多输出一个标签,所有字符必须靠 blank 隔开;RNN-T 则允许在一个输入帧内连续输出多个标签,直到输出一个特殊的 blank 才跳到下一帧。这使得 RNN-T 在流式识别中更灵活——它可以不等一句话说完,就能一个字一个字地“吐”出来。
RNN-T 的条件概率可以近似写成逐字生成的级联形式:
一句话总结:CTC 是把语音“压扁”再去匹配文本,RNN-T 是把文本“拉长”去贴合语音。CTC 设计简单、训练稳定,适合短句和离线场景;RNN-T 更流畅、更适合长句和实时交互。选谁,要看你的产品更在意延迟还是更在意长文本的连贯性。
- 逐字生成字幕被拆成 $U$ 个字符,一个一个地输出,整体概率是所有字符概率的乘积。
- 双重依赖生成第 $u$ 个字符时,既要看当前语音消费到的声学段 $X_{1:t_u}$,也要看已经写好的字幕前缀 $y_{<u}$。
端到端训练
传统的语音识别系统,训练前得先请人把每一帧声音都标上对应的音素,就像要求听写员给每一帧画面都写注释——费时费力,还容易标错。CTC 最大的贡献,就是把这套繁琐的逐帧标注彻底扔掉,实现真正的端到端训练。
端到端是什么意思?就是训练时只需要给模型一对数据:语音 $X$ 和它对应的字幕 $Y$。模型会自己穷举所有可能的对齐路径,把能折叠成 $Y$ 的路径概率全部加起来,然后让这个总概率尽量大。全程没有任何人告诉它“第 3 帧对应字母 a”,它自己就能学会怎么对齐。
训练目标的数学形式是负对数似然损失:
于是,这位听写员不再需要老师手把手地教每一帧怎么写。只要整句字幕对了,它就自己摸索出了声音和文字之间的对齐规律。这正是 CTC 能成为语音识别主流方案的原因:数据获取成本大幅降低,模型却能学到高质量的对齐能力,在输入法、字幕、会议转写等场景里遍地开花。
- 取负对数把“最大化概率”变成“最小化损失”,方便用梯度下降训练。
- 枚举所有路径求和符号遍历所有能折叠成目标字幕 $Y$ 的草稿路径,一部剧的同一句台词可能对应无数种“口音拖音”的路径。
- 逐帧连乘每条路径的概率,来自每一帧候选标签概率的连乘,任何一帧的低置信度都会拉低整条路径的分数。
AI 应用场景
语音输入法与语音搜索
阶段:模型训练与部署
解决的问题:传统语音识别需要逐帧音素标注,数据制作成本极高。CTC 端到端训练只需要“语音-文本”对,就能让输入法学会把声音转成文字,省掉了最贵的人工标注环节。
具体案例:某手机厂商训练中文语音输入法,收集了数万小时无标注的语音数据和对应文本。使用 Conformer + CTC 结构训练声学模型,配合贪心解码完成首版识别,上线后识别延迟低于 200ms,让用户“边说边出字”。
直播实时字幕生成
阶段:模型推理与延迟优化
解决的问题:直播场景要求低延迟,不可能等整句话说完了再开始识别。贪心解码每帧只做一次概率比较,速度快、无回溯,能够以极低延迟跟随说话人的语速。
具体案例:某视频平台的直播字幕功能,使用 CTC 声学模型 + 贪心解码做流式识别,每 100ms 刷新一次字幕。虽然偶尔有错别字,但观众能实时看到字幕,体验远胜无字幕。
会议纪要离线转写
阶段:推理精调与后处理
解决的问题:会议录音噪音多、说话人重叠、专业术语密集,单纯贪心解码容易出错。Beam Search 同时维护多条候选路径,再配合语言模型重新打分,能从全局视角选出最通顺的转写结果。
具体案例:某企业会议纪要系统,对 1 小时录音用 CTC + Beam Search(束宽 8)离线转写,并叠加领域语言模型纠错。相比贪心解码,字错误率降低了约 18%,生成了可直接存档的会议纪要。
Python 演示
import math # 模拟 8 帧、每帧 4 个标签(0:a 1:b 2:c 3:blank)的概率输出LABELS = ['a', 'b', 'c', '_']probs = [ [0.5, 0.2, 0.2, 0.1], [0.3, 0.4, 0.1, 0.2], [0.4, 0.3, 0.2, 0.1], [0.2, 0.5, 0.2, 0.1], [0.1, 0.3, 0.5, 0.1], [0.4, 0.3, 0.2, 0.1], [0.2, 0.4, 0.3, 0.1], [0.3, 0.2, 0.4, 0.1],] def collapse(path): """CTC 折叠:合并连续重复标签,再移除 blank。""" text = [] prev = None for token in path: if token == 3: # blank 打断连续字符 prev = None continue if token != prev: text.append(token) prev = token return text def greedy_decode(probs): path = [max(range(4), key=lambda t: probs[i][t]) for i in range(len(probs))] return [LABELS[x] for x in collapse(path)] def beam_search_decode(probs, beam_size=3): beams = [([], 0.0, None)] # (路径, 对数概率, 最近非blank标签) for frame in probs: candidates = [] for path, log_p, prev in beams: for token, p in enumerate(frame): if p == 0.0: continue new_path = list(path) new_prev = prev if token == 3: new_prev = None elif token != prev: new_path.append(token) new_prev = token candidates.append((new_path, log_p + math.log(p), new_prev)) candidates.sort(key=lambda x: x[1], reverse=True) beams = candidates[:beam_size] best = max(beams, key=lambda x: x[1]) return [LABELS[x] for x in best[0]] print('贪心解码结果:', ''.join(greedy_decode(probs)))print('Beam Search结果:', ''.join(beam_search_decode(probs, beam_size=3)))
def collapse(path):定义 CTC 折叠函数,先合并连续重复、再删除 blank,是解码的“最后一步整理”。if token == 3:判断当前标签是否为 blank(索引 3),blank 会把连续字符关系重置。if token != prev:只有当前标签与上一个输出不同时才追加,实现“合并拖音”。path = [max(range(4), key=lambda t: probs[i][t]) ...]贪心解码:每一帧直接取概率最大的标签索引。beams = [([], 0.0, None)]Beam Search 的初始状态:一条空路径、对数概率 0、无最近标签。for token, p in enumerate(frame):遍历当前帧每个标签及其概率,做路径扩展。candidates.sort(key=lambda x: x[1], reverse=True)按对数概率从大到小排序所有候选路径。beams = candidates[:beam_size]只保留前束宽条路径,其余全部淘汰。best = max(beams, key=lambda x: x[1])最终取整体概率最高的那条路径作为解码结果。
常见误区
误区我一直以为 blank 就是检测到没人说话、纯粹的静音时段,所以它的概率在安静时会变高。
正解blank 并不是声学上的“静音”,而是模型对“当前帧该输出什么还不确定”的占位符。它可以出现在任何帧上,哪怕是响亮的语音段。它的真正作用是消化多出来的时间帧,并隔开相邻的相同字符。静音是声学现象,blank 是序列标签,两者不是一回事。
为何常见:blank 的英文直译是“空白”,初学者很容易把它和无声、静音联系起来。
误区CTC 解码不就是每帧取最大概率的标签吗?训练完直接取 argmax 就是最终结果了。
正解贪心只是最快的一种近似解码,不是唯一方法。每帧的 argmax 组合起来未必是全局最优——某一帧的小错可能毁掉整句。Beam Search 从整句视角维护多条候选路径,通常准确率更高。就像考试时只顾眼下这题,不如同时检查几份草稿。
为何常见:很多开源工具默认提供 greedy 解码,初学者上手即用,误以为它就是 CTC 的标准答案。
误区P(Y|X) 是不是把目标文本 Y 里每个字符的概率直接相乘?比如"hello"就是五个字符概率连乘。
正解不对。目标文本 Y 的概率等于所有能折叠成 Y 的路径概率之和。同一个字幕“aa“可以由”a _ a”、"a a a"等多条路径产生,每条路径概率不同,全部相加才是 P(Y|X)。单一路径只是其中一个草稿,不能代表整句字幕。
为何常见:模型的输出天生定义在路径上,而我们的目标是文本,初学者容易忽略这两层之间的“折叠映射”。
误区折叠规则会把相邻重复字合并,那“hello“里的两个”l”是不是永远识别不出来?
正解完全可以识别。只要两条“l“中间隔着 blank,比如”l _ l“,折叠时 blank 会重置”相邻“关系,两个”l“就都能保留。折叠规则不是无脑去重,而是”相邻相同且中间无 blank”才合并。
为何常见:只记住了“合并重复”这半句话,忽略了 blank 的间隔作用,导致对重复字符产生误解。
误区束宽 B 是不是设得越大越好?比如 B=1000 肯定比 B=10 更准,反正算力够用。
正解B 越大,搜索越全面,但收益递减,计算量却线性增长。工程上束宽通常取 4~20,配合语言模型已经足够。B 过大时大量候选路径高度相似,白白消耗算力,甚至可能引入低概率噪声路径干扰判断。
为何常见:直觉上“搜得越多越准”,忽略了模型的表达能力上限和工程成本。
面试题
Q1. CTC 中的 blank 标签最核心的作用是?
- A.表示一段安静的静音帧
- B.标记语音段结束符号
- C.作为间隔符分隔重复字← 正确答案
- D.指示模型无输出字符
答案:C。blank 是“无标签”的占位符,最核心的作用是隔开相邻相同的字符,使“a _ a“能折叠输出”aa“。它不等同于静音,也不是结束符。D 只描述了表象”无输出“,没有点出它”分隔重复字符”的关键机制,所以选 C。
Q2. 路径 "a a a" 经 CTC 折叠后输出的文本是?
- A.保留完整的aaa三个字符
- B.只输出单个的字符a← 正确答案
- C.变成带下划线的标记
- D.输出空串不含有字符
答案:B。CTC 折叠规则第一步是合并相邻相同标签,三个连续的“a“会合并成一个”a“,且没有 blank 把它们隔开,所以输出是单个字符”a”。
Q3. 贪心解码每一帧采用的选择策略是?
- A.随机采样一个候选标签
- B.选取概率最大的标签← 正确答案
- C.选取概率最小的标签
- D.综合前后帧重新判断
答案:B。贪心解码的核心是“每帧独立做 argmax”,即选取当前帧概率最大的标签,不做前后文综合判断。这也是它快但不保证全局最优的原因。
Q4. 路径 "b _ b b a" 折叠后的输出结果是?
- A.输出结果为b b a字符← 正确答案
- B.输出结果为b a字符
- C.输出结果为b b b a
- D.输出结果为b _ b a
答案:A。先合并连续重复:路径中的“b b“合并为一个”b“,得到”b _ b a“;再删除 blank,得到”b b a“。所以选 A。B 漏掉了一个”b”,C 未合并重复,D 未删除 blank。
Q5. 下列关于贪心解码的说法,正确的有?
- A.每帧取最大概率标签← 正确答案
- B.中间不回溯不修改← 正确答案
- C.搜索过程是全局最优
- D.计算快延迟低适合实时← 正确答案
答案:ABD。贪心解码每帧独立取概率最大的标签,且不回溯、不修改,因此计算极快,适合实时场景。但它只是局部最优的近似,不是全局最优搜索,所以 C 错误。
Q6. CTC 训练的损失函数本质是?
- A.目标文本的负对数似然← 正确答案
- B.目标文本的交叉熵误差
- C.预测帧数的均方误差
- D.模型权重的L2正则项
答案:A。CTC 的损失是序列级的负对数似然,即对“能折叠成目标文本的所有路径概率之和”取负对数。它不是在帧级别做交叉熵,也不是回归误差或正则项,因此选 A。
Q7. Beam Search 中束宽 B 表示的是?
- A.每步保留的候选路径数← 正确答案
- B.输入语音帧的总数
- C.输出词典的大小规模
- D.模型的隐藏层维度
答案:A。束宽 B 是 Beam Search 每步保留的候选路径数量。B=1 退化为贪心解码;B 越大搜索越全面,但计算量也越大。其余选项都与束宽无关。
Q8. CTC 相比传统逐帧标注的优势有?
- A.不需要帧级对齐标注← 正确答案
- B.模型自动学到对齐关系← 正确答案
- C.必须人工标注每个音素
- D.只能使用单向循环网络
答案:AB。CTC 端到端训练只需要“语音-文本”对,不需要帧级对齐标注,对齐关系由模型自动学习。C 恰恰相反,正是不需要人工标每个音素。D 错误,CTC 不限定网络结构,可用 Transformer、Conformer 等。
Q9. 关于 CTC 与 RNN-T 对比,正确的说法是?
- A.RNN-T用预测网络辅助输出← 正确答案
- B.CTC每帧最多输出一个标签← 正确答案
- C.RNN-T可在一帧内输出多标签← 正确答案
- D.CTC的延迟一定低于RNN-T
答案:ABC。RNN-T 引入了预测网络,把已输出的文本前缀喂回模型;CTC 每帧最多输出一个标签;RNN-T 可以在一个输入帧内连续输出多个标签直到 blank。D 错误,延迟高低取决于具体实现和应用场景,不能一概而论。
Q10. 3 帧输入且每帧 p(a)=p(blank)=0.5,文本 "a" 的总概率是?
- A.0.500
- B.0.625← 正确答案
- C.0.750
- D.1.000
答案:B。共有 8 条等概率路径(每条 0.125)。折叠为"a"的路径有 5 条:_ _ a、_ a a、a _ a、a a _、a a a,总概率 5 × 0.125 = 0.625。A 是只数了 4 条路径的错误答案,C 和 D 都高估了。