想象你是一位综艺选秀节目的总导演,要从一批练习生中选出一组人来主演年度大戏。最直接的想法:第一幕选唱功最好的,第二幕选舞蹈最好的,第三幕选颜值最高的——每个环节都挑单项冠军。可最后拼出来的戏,观众根本不买账,因为局部最优的组合并不等于整体最优。聪明的导演会怎么做?他会让几组“潜力组合”同时排练,每一轮淘汰表现差的小组,保留最有希望的几组继续打磨,直到彩排结束再从决赛组里选出冠军。这个“多组并跑、层层淘汰”的思路,正是自然语言生成中大名鼎鼎的Beam Search(束搜索)解码策略。它不像贪心解码那样孤注一掷,而是像导演同时培养多个种子选手一样,在每一步保留多个候选序列,最终选出全局更优的那一条。本文就用这场“选秀晋级赛”来带你彻底搞懂束搜索的每个环节。
对 AI 从业者而言,束搜索是机器翻译、文本摘要、语音识别等任务的标配解码器。掌握它,你才能真正理解生成模型“最后一公里”的质量瓶颈与调优方向,是深度学习工程师的必修内功。
可视化演示
展示Beam Search束搜索的候选序列扩展与剪枝过程,以及Diverse Beam Search的多样性惩罚机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Beam Search束搜索流程 候选扩展:每步从K个束各扩展V个候选Token 分数排序:按累计对数概率排序保留Top-K 剪枝:丢弃K*V-K个低分候选序列 score = Σ log P(yt|y<t, x) 束搜索在精度与效率间平衡
贪心解码困境
选秀导演的第一版方案是“单项冠军拼盘”:第一幕选练得最好的,第二幕选跳得最好的,第三幕选长得最上镜的。结果排出来的戏成了四不像。这就是贪心解码的典型困境——每一步只看当前这一步的最好,完全不管组合后的整体效果。
在序列生成里,贪心解码每一步都选择条件概率最大的词:
但整句话的质量应该用序列概率来衡量——每个词的概率连乘起来:
翻译任务中,一个词在局部概率最高,但连起来可能语法不通、语义跑偏。贪心就像那个只看单项的导演,每一步都挺满意,最后成片却翻车。要想选出整体最优的组合,必须让几个候选方案同时排练——这就引出了束搜索。
- 连乘符号$\prod_{t=1}^{T}$ 表示把从第 1 步到第 $T$ 步的概率全部相乘。
- 整体概率等式左边是整句话的生成概率,右边是每一步条件概率的乘积。
束搜索流程
导演改进了策略:每次彩排都同时保留两个“种子小组”(组数就是束宽 k),每组演完一幕,就派生出所有可能的下一幕人选,评委给所有新小组打分,只留下总分最高的两组继续排练。这就是束搜索的核心——每一步保留 k 个候选。
实现时,分数在对数空间累加,把概率乘法变成分数加法,避免多个小数连乘导致数值下溢:
完整流程是四步循环:扩展(每个候选生成所有可能的下一词)、打分(用对数概率累加)、排序(按分数从高到低)、剪枝(只留前 k 个)。当所有候选都以结束符 <EOS> 收尾,或者达到最大长度上限,解码终止,最终从 k 个候选里选分数最高的作为输出。
束搜索的本质,是导演在“海选全部组合”和“孤注一掷”之间取的折中:它不多不少,同时保留 k 条最有希望的戏路,层层淘汰,最终挑出彩排效果最好的那个剧本。
- 历史分数$s(y_{1:t-1})$ 是前 $t-1$ 个词累积的分数(对数概率)。
- 对数概率$\log P(y_t \mid y_{<t}, \mathbf{x})$ 是当前这一步生成词 $y_t$ 的对数条件概率。
- 累加更新新分数等于旧分数加上当前步的对数概率。
束宽的选择
晋级名额 k 是该设几个?名额太少,好苗子容易被早早在半途刷掉;名额太多,每轮要同时排练的小组翻倍,导演组忙不过来。束宽就是计算量与生成质量的秤。
束宽 k 直接决定计算成本。每生成一个词,都需要对 k 个候选各做一次完整的前向推理:
实际工程中,k 增大到一定程度后,翻译质量的提升迅速放缓,而推理耗时线性上升。经验上,机器翻译常用 k=4~10,对话生成常用 k=5~20,具体靠验证集调参。
这就好比选秀节目:海选阶段晋级名额设 10 个,足以网罗各路好手;设 100 个,导演组熬夜审片也审不完,而冠军质量并不会因此翻倍。束宽的妙处,正在于用小成本换大部分的收益。
- 序列长度$T$ 是生成序列的最大长度。
- 束宽$k$ 是同时维护的候选序列数量。
- 词表大小$|V|$ 是每一步扩展时的候选词个数。
- 总复杂度三者相乘表示整段解码的扩展次数量级。
长度惩罚
选秀还有个隐患:短剧本天然“占便宜”。因为序列分数是所有词对数概率之和,句子越短,累加的负数越少,分数反而越高。模型渐渐学会偷懒,输出“好的。再见。”这种敷衍短句。
束搜索需要给分数加上长度惩罚,让长句获得公平竞争的机会。经典做法是 Google NMT 提出的长度归一化:
当 $\alpha > 0$ 时,长句的负分数被“稀释”,短句不再自动占优;当 $\alpha$ 过大,模型又会走向另一个极端——啰嗦。翻译任务常用 $\alpha \approx 0.6$。
回到选秀舞台,这相当于评委对长剧目加“完成度加权分”——你排的场次多、台词多,只要整体不崩,就该得到鼓励。但如果分加得太狠,人人排超长烂剧,反而砸了招牌。α 就是那个帮评委把握分寸的旋钮。
- 对数总概率$\log P(y \mid \mathbf{x})$ 是整句的对数概率(负值)。
- 当前句子长度$|y|$ 是候选序列包含的词数。
- 长度因子分母$(5 + |y|)^\alpha$ 让分母随长度增长,$\alpha$ 控制惩罚强度($\alpha=0$ 时不惩罚)。
- 归一化分母$(5 + 1)^\alpha$ 是平滑基准,避免长度 1 时因子过小。
多样化解码
束搜索有个著名毛病:候选同质化。k 个晋级小组越到后面越像——因为淘汰逻辑只按分数排序,相似的组合分数也接近,最后霸榜的全是“差不多先生”。选秀变成了同门师兄弟内部比武,无法给观众多元选择。
改进方案是 Diverse Beam Search(多样束搜索):在打分时给每个候选减掉它与“已入选候选”的相似度,逼着不同小组走差异化路线:
相似度可以是词级别重合率、n-gram 重叠或隐状态余弦相似度。还有另一种实现思路——分组搜索:把 k 个名额分成若干组,第 1 组正常束搜索,第 2 组在打分时惩罚与第 1 组相似的候选,第 3 组惩罚与第 1、2 组相似的候选,依次迭代,保证各组差异明显。
晋级赛要想好看,导演就得防止“全是一个路子的选手”。让人人演独角戏当然不行,但让 k 个候选呈上风格各异的方案,决赛时的选择才真正有意义。
- 原始分数$\log P(y \mid \mathbf{x})$ 是模型对候选 $y$ 的原始对数概率。
- 已选候选集$\mathcal{G}$ 是目前已经留在束里的候选序列集合。
- 最相似惩罚$\max_{y' \in \mathcal{G}} \text{sim}(y, y')$ 找到与 $y$ 最像的已选候选,并计算相似度。
- 惩罚强度$\lambda$ 控制多样性的力度,$\lambda$ 越大,候选越刻意避开彼此。
与采样结合
选秀的终极矛盾在于:稳妥 vs 惊喜。束搜索总是挑概率最高的词,结果千篇一律;而完全随机采样又容易胡言乱语。业界做法是把两者揉在一起,用温度采样控制随机性:
为了让采样更安全,还要截断低概率尾部。Top-k 采样只从概率最高的 k 个词里抽;Top-p 核采样则动态截取累计概率刚好达到 $p$ 的最小集合:
工程上,束搜索也可以与采样结合:先用 top-p 或温度采样生成若干个候选分支,再用束搜索的排序机制筛选局部最优。就像选秀里先让选手自由发挥(采样制造惊喜),评委再从表现里挑出值得保留的苗子(束搜索兜底)。
- 候选词子集$\mathcal{V}'$ 是词表的某个子集。
- 累计概率$\sum_{y \in \mathcal{V}'} P(y)$ 是这个子集里所有词的概率之和。
- 最小集合$\min \{\cdot\}$ 找出满足累计概率恰大于等于 $p$ 的最小词子集。
应用场景
束搜索真正封神,是在机器翻译领域。翻译是“最优解”明确的任务——同一句话的译文质量和流畅度有客观高低,束搜索的“多路择优”能稳定产出比贪心更自然的译文。Google 神经机器翻译系统全面采用束搜索后,翻译质量大幅跃升。
衡量翻译质量的经典指标是 BLEU,它计算候选译文与参考译文的 n-gram 重合度:
文本生成领域,束搜索则需谨慎:新闻摘要希望稳定高质量,适合束搜索;故事创作希望文风多样,更适合加大温度或采样的比重。语音识别里,束搜索还能结合语言模型对多个候选句子重打分,纠正同音字歧义——比如“我爱机器学习”与“我爱鸡血学习”读音相似,束搜索凭借语言模型选出更通顺的那句。
这就像同一套选秀机制,在不同综艺里玩法各异:新闻联播选主持人要稳(小束宽),综艺选喜剧人要花活(采样+多样性),配音大赛要字正腔圆(束搜索+语言模型重打分)。工具是死的,用工具的眼光才是活的。
- n-gram 精确率$p_n$ 是 $n$ 元词组在参考译文中出现的比例。
- 几何平均$\sum_{n=1}^{4} \frac{1}{4} \log p_n$ 对 1-gram 到 4-gram 的精确率取对数平均。
- 长度惩罚因子$BP$ 在译文过短时打折,防止“漏译”刷分。
工程实现
真正的选秀后台,是一套流水线作业:多个小组同时排练,工作人员批量调度场地、灯光、摄像。束搜索的工程实现同样讲究批次并行——把 $k$ 个候选打包成一个 batch,一次前向推理同时计算所有候选的下一词概率:
分数累加要在对数空间完成,避免概率乘出浮点下溢;所有候选共享同一份词表索引,方便用张量操作批量排序和掩码剪枝。主流框架里,HuggingFace Transformers 的 generate() 方法、Fairseq 的 search 模块都已封装好束搜索,调一行参数即可使用。
工程实现的本质,是把导演的“多组并跑”变成一套高效的后台系统:批量调度、防溢出、剪枝排序全部流水线化。理解了束搜索的原理,再看框架源码,你就能读懂每一行都在做什么——这就是从“调参侠”走向“解码器专家”的关键一步。
- 隐状态矩阵$\mathbf{h}_t$ 是解码器在时刻 $t$ 的隐状态(可同时包含多个候选的表示)。
- 线性映射$W \mathbf{h}_t$ 把隐状态映射到词表维度的 logits。
- 批量 softmax对整个矩阵做归一化,一次得到所有候选在词表上的概率向量。
AI 应用场景
机器翻译系统中的在线解码
阶段:模型推理与解码策略选择
解决的问题:传统贪心解码在长句翻译中经常出现漏译、语序颠倒等问题;束搜索通过同时维护多个候选译文,显著提升翻译的流畅度和忠实度。
具体案例:Google Translate 的在线翻译服务,在解码阶段使用束搜索并配合长度惩罚参数 $\alpha$。用户输入一句中文“深度学习改变了人工智能的进程”,系统内部同时维护 8 条候选英文翻译,每生成一个词就排序剪枝,最终输出最通顺的译文,并拒绝"Deep learning changed AI process"这类短而糙的候选。
语音识别中的多候选重打分
阶段:声学模型解码 + 语言模型融合
解决的问题:同音字和多音字造成语音识别歧义,例如“我爱吃鸡”与“我爱吃鸡?”读音相同但语义不同;束搜索生成多个候选文本后,再由语言模型对整句打分,选出语义最合理的句子。
具体案例:智能音箱的语音助手在识别用户指令时,声学模型先用束搜索(k=16)产出 16 条候选文本,再用大语言模型对候选句子做重排序,识别“播放周杰伦的七里香”而不是“播放周杰伦的七里乡”,最终正确执行播放指令。
大语言模型的代码生成
阶段:生成策略调优阶段
解决的问题:代码生成任务中,束搜索能保证语法结构稳定,而采样带来创新;通过“束搜索+温度采样”结合,既能保证代码可编译,又能探索多种实现路径。
具体案例:GitHub Copilot 的代码补全功能在“保守模式”下使用小束宽束搜索,确保补全的代码符合最常见的 API 调用惯例;在“创意模式”下提高温度并结合 top-p 采样,生成风格跳脱但语法正确的备选实现。
Python 演示
import math def softmax(logits): """把 logits 转成概率分布""" mx = max(logits) exps = [math.exp(z - mx) for z in logits] total = sum(exps) return [e / total for e in exps] def decoder_logits(seq, vocab_size=5): """简易模拟解码器:根据已生成的序列返回 logits""" if not seq: return [1.2, 0.9, 0.6, 0.3, 0.1] last = seq[-1] logits = [0.2, 0.6, 0.7, 0.4, 0.9] if last == 1: logits[1] = 0.1 return logits def length_penalty(seq_len, alpha=0.6): """GNMT 风格长度惩罚:长句分数不被过度压低""" return ((5 + seq_len) / 6) ** alpha def beam_search(beam_size=3, max_len=5, alpha=0.6): """束搜索:每步保留 beam_size 个最优候选""" beams = [([], 0.0)] for _ in range(max_len): candidates = [] for seq, score in beams: probs = softmax(decoder_logits(seq)) top_idx = sorted( range(len(probs)), key=lambda j: probs[j], reverse=True )[:beam_size] for j in top_idx: new_seq = seq + [j] new_score = score + math.log(probs[j]) candidates.append((new_seq, new_score)) candidates.sort(key=lambda x: x[1], reverse=True) beams = candidates[:beam_size] best = max( beams, key=lambda x: x[1] / length_penalty(len(x[0]), alpha) ) return best def greedy(max_len=5): """贪心解码对照:每步只选概率最高的词""" seq, score = [], 0.0 for _ in range(max_len): probs = softmax(decoder_logits(seq)) j = max(range(len(probs)), key=lambda k: probs[k]) seq.append(j) score += math.log(probs[j]) return seq, score if __name__ == "__main__": vocab = ["我", "爱", "你", "呀", "。"] bs_seq, bs_score = beam_search(beam_size=3) gr_seq, gr_score = greedy() print("贪心解码:", "".join(vocab[i] for i in gr_seq)) print("束搜索k=3:", "".join(vocab[i] for i in bs_seq)) print("束搜索归一化得分:", round(bs_score / length_penalty(len(bs_seq)), 4))
probs = softmax(decoder_logits(seq))把解码器输出转成概率分布,模拟模型每步的预测。score + math.log(probs[j])在对数空间累加分数,等价于概率连乘,防止数值下溢。top_idx = sorted(...)[:beam_size]每个候选只扩展概率最高的 beam_size 个词,控制计算量。candidates.sort(key=lambda x: x[1], reverse=True)对所有扩展出的候选按累加分数降序排序。beams = candidates[:beam_size]剪枝:只保留总分最高的 beam_size 条序列进入下一轮。max(..., key=lambda x: x[1] / length_penalty(len(x[0]), alpha))用长度惩罚归一化后选出最终冠军序列。if __name__ == "__main__":程序入口,运行贪心与束搜索并打印结果对比。
常见误区
误区束搜索的 k 值越大,保留的候选越多,结果肯定越好,所以调到 50 甚至 100 准没错。
正解束宽越大,计算成本线性上升,但质量提升有显著天花板。当 k 过大时,候选集中在少数高概率路径附近,反而容易生成重复、冗余的句子,甚至比小束宽更差。实际中束宽 4~10 已经能拿到绝大部分收益,再大只会让训练好的模型输出“安全但平庸”的结果。
为何常见:初学者直觉认为“搜索空间越大越接近最优解”,忽略了生成任务里概率分布的尖峰特性——高概率区域其实很小,多搜几条路径并不能带来多少新信息。
误区束搜索的最终分数是对数概率之和,分数越高的候选一定语义更连贯、更符合人类表达习惯。
正解模型的对数概率衡量的是“模型觉得有多像训练数据”,而不是“人类觉得有多好”。模型可能偏爱高频但空洞的套话。而且由于分数是负数累加,短句天然分数更高,不加上长度惩罚,束搜索就会输出“好的。”这类敷衍短句。分数高低必须结合长度惩罚和人工评估才有意义。
为何常见:人们容易把“模型概率”等同于“真实质量”,忽略了语言模型只是对训练分布的近似,且存在长度偏置这一系统性偏差。
误区要么用束搜索做确定性解码,要么用采样做随机生成,二者不可能结合使用。
正解束搜索和采样是正交的,可以组合。实践中常用“温度采样 + top-k/top-p 截断”先生成一批随机候选,再用束排序的剪枝逻辑筛选;也有方法在束搜索的扩展阶段用采样替代 argmax,给候选引入多样性。多样束搜索(DBS)本质上就是在束搜索框架里加入惩罚项来制造差异。
为何常见:教程通常分开讲贪心/束搜索和采样,给人“非此即彼”的印象;实际上工程系统经常混合多种策略来平衡质量与多样。
误区长度惩罚很简单,直接用对数概率除以序列长度,写代码时 `score / len(seq)` 就行。
正解直接除以长度会过度补偿,让模型倾向输出冗长废话。经典做法是加入平滑项和幂指数:除以 $(5 + |y|)^\alpha / (5+1)^\alpha$,其中 α 通常在 0.6~1.0 之间。α 控制的是“多长才算太长”,而不是简单地平均每一步的分数。调 α 比调 k 对结果的影响更微妙。
为何常见:因为“归一化”这个词让人联想到平均,忽略了分母里的平滑项是工程上的关键细节,它避免了长度 1 的极端情况。
面试题
Q1. 贪心解码与束搜索的核心区别是什么?
- A.贪心解码每步只保留 1 个候选,束搜索保留 k 个候选← 正确答案
- B.贪心解码不用模型,束搜索用模型
- C.贪心解码速度更慢,束搜索更快
- D.贪心解码更随机,束搜索更确定性
答案:A。贪心解码每步只选概率最高的一个词,相当于束宽 k=1 的特殊情况;束搜索每步保留 k 个候选路径,综合全局后选最优。两者都依赖模型,速度上贪心更快而非更慢,且束搜索确定性更强而非更随机。
Q2. 束搜索中束宽 k=1 时,算法退化为哪种解码方式?
- A.随机采样
- B.贪心解码← 正确答案
- C.top-k 采样
- D.核采样
答案:B。束宽 k=1 意味着每步只保留分数最高的一个候选,每一步都沿着当前最优路径前进,不再有“多路择优”的过程,这正是贪心解码。随机采样、top-k 与核采样都涉及随机性,与 k=1 的确定性行为不符。
Q3. 束搜索的分数为什么在对数空间累加而非直接连乘概率?
- A.对数空间可以用加法代替乘法,防止浮点下溢← 正确答案
- B.对数空间可以让分数始终为正数
- C.对数空间能自动加入长度惩罚
- D.对数空间能提高模型预测准确率
答案:A。解码时序列概率是多个小于 1 的数连乘,句子稍长数值就会趋近于 0 导致浮点下溢,而取对数后连乘变累加,数值范围稳定。对数空间本身不改变单调性,也不影响长度惩罚和模型准确率——长度惩罚是额外的除法项,概率变对数也不改变正负性质。
Q4. 束搜索中为什么会出现“短句偏好”问题?
- A.因为短句的每个词概率更高
- B.因为序列分数是负数累加,句子越短分数越高← 正确答案
- C.因为束宽 k 太大会产生短句
- D.因为多样性惩罚让短句得分更高
答案:B。序列分数是每一步对数概率的累加,而对数概率恒为负数,所以句子越长累加的负数越多,总分越低。短句天然分数高,因此束搜索偏向输出短句。这需要用长度惩罚来纠正,而不是靠调整束宽或多样性惩罚。
Q5. 长度惩罚公式 $\frac{\log P(y|x)}{(5+|y|)^\alpha (5+1)^{-\alpha}}$ 中,$\alpha=0$ 意味着什么?
- A.完全不进行长度归一化← 正确答案
- B.惩罚强度达到最大
- C.只保留长度为 5 的句子
- D.分数变成正数
答案:A。当 α=0 时,任何长度的分母都是 $(5+|y|)^0 = 1$,长度因子完全不生效,分数退化为原始对数概率,即不施加长度惩罚。α 越大,长句的分数被“稀释”得越多;α 也不可能让负数变成正数,只是调整数值的尺度。
Q6. 束搜索的终止条件通常是什么?
- A.所有 k 个候选都已生成结束符 EOS← 正确答案
- B.只要有一个候选生成 EOS 就停止
- C.分数不再上升就停止
- D.生成到最大长度的 90% 就停止
答案:A。标准束搜索要求所有候选序列都以 EOS 结束(或达到最大长度上限),因为还需要从剩余候选中选出最终冠军。如果只等一个候选结束就停下,其他候选可能更优。也有工程实现会在达到最大长度时强制终止并选择当前分数最高的候选。
Q7. 关于多样束搜索(Diverse Beam Search),下列说法正确的是?
- A.在打分时减去与已选候选的相似度惩罚项← 正确答案
- B.通过分组迭代,让不同组尽量生成不同风格的候选← 正确答案
- C.把束宽 k 设得越大,多样性自动越强
- D.惩罚强度由超参数 λ 控制← 正确答案
答案:ABD。多样束搜索的核心是给候选打分时减去与已选候选的相似度(A 正确),λ 控制惩罚力度(D 正确);分组搜索让各组差异化(B 正确)。只增大束宽并不能保证多样性,反而可能让相似的高概率候选霸榜(C 错误)。
Q8. 温度采样中,温度 $T$ 趋近于 0 时,概率分布会怎样变化?
- A.分布趋于均匀分布
- B.分布趋于 one-hot,即最高概率词几乎被确定选中← 正确答案
- C.分布不变
- D.所有词的概率都变为 0
答案:B。温度 T 趋近于 0 时,softmax 的输入被放大,最大 logit 对应的概率趋近于 1,其他词趋近于 0,分布变成接近 one-hot 的确定性分布。T 趋近于无穷时分布才趋于均匀。T 不会让概率归零,而是极端地拉大差距。
Q9. top-p 核采样中,$p=0.9$ 的含义是什么?
- A.固定选取概率最高的 9 个词
- B.选取累计概率恰好达到 90% 的最小词集合,再从集合内采样← 正确答案
- C.只保留概率大于 0.9 的词
- D.以 90% 的概率选择最高词,10% 概率随机选
答案:B。top-p 是自适应截断:把词按概率降序排列,取累计概率刚好达到 p 的最小子集。分布越尖锐,集合越小;分布越平缓,集合越大。它不固定词的个数(排除 A),也不按单个概率阈值截断(排除 C),更不是两阶段随机选择(排除 D)。
Q10. 关于束搜索在工程实现中的注意事项,以下哪些是正确的?
- A.所有候选可以打包成一个 batch 做并行前向计算← 正确答案
- B.分数应在对数空间累加以避免浮点下溢← 正确答案
- C.长度惩罚的参数 α 越大,生成结果越倾向短句
- D.束搜索必然比采样生成的文本更多样
答案:AB。工程上把 k 个候选拼接成 batch 同步推理,显著提速(A 正确);对数累加防止下溢是标准做法(B 正确)。α 越大越鼓励长句而非短句(C 错误)。束搜索本质是确定性择优,多样性通常弱于采样(D 错误)。