吴恩达《深度学习》之看懂集束搜索
我们终于来到了整个序列生成技术的最后一关——集束搜索(Beam Search)。
这个机制,是所有现代大型语言模型(LLM)、机器翻译系统、语音识别系统在最后吐出文本(推理输出)时的临门一脚。
在学集束搜索之前,大部分初学者都会觉得:“既然大模型已经算出了每个字出现的概率,那直接每一步挑概率最高的那个字不就行了吗?”
核心知识点:
- 直觉解释:在解码生成序列时,不是贪婪地每步选最高分,而是保留BBB个(集束宽)当前最可能的候选序列,从而搜索全局更优解。
- 数学核心:最大化归一化对数似联得分max1Tyα∑t=1TylogP(y⟨t⟩∣x,y⟨1⟩… )\max \frac{1}{T_y^\alpha} \sum_{t=1}^{T_y} \log P(y^{\langle t \rangle} \mid x, y^{\langle 1 \rangle} \dots)maxTyα1∑t=1TylogP(y⟨t⟩∣x,y⟨1⟩…)
- 常见变体及适用场景:带长度归一化的集束搜索;是语音识别和机器翻译系统输出最终结果的标准推理算法。
今天,我们看看这种看似天经地义的“贪婪”做法,是如何在真实世界里翻车的。
第一步:贪婪搜索的“短视惩罚”
为了看清真相,我们来玩一个“成语接龙与造句”的文字游戏。
假设我们的 AI 模型正在尝试把一句法文翻译成中文,现在已经生成了前半句:“他今天穿得非常___”。接下来,网络要预测下一个字。在当前这一秒,大模型对所有汉字的概率预测如下:
- “帅”:概率 0.6
- “时”:概率 0.4
提问:如果我们采用最直观的贪婪搜索(Greedy Search),也就是每一步只选当下概率最高的绝对第一名。在这一步,网络会毫不犹豫地选择哪一个字?
解析:当然是选择“帅”(0.6)。
好,现在网络把“帅”拼进了句子,变成了:“他今天穿得非常帅___”。由于前面的上下文词变了,下一步网络在预测接下来的字时,突然发现后面很难接出通顺的长句子了,最通顺的字概率也极低:
- 接下来接“气”:概率 0.1
- 此时这一句的累乘总概率变成了:0.6×0.1=0.060.6 \times 0.1 = 0.060.6×0.1=0.06。
但如果我们回过头看刚才那个被淘汰的备胎“时”字。如果当时选了“时”,句子变成“他今天穿得非常时___”。接下来网络会惊喜地发现,后面可以完美接上一个极其高频、地道的词:
- 接下来接“髦”:概率 0.9
- 这一句的累乘总概率变成了:0.4×0.9=0.360.4 \times 0.9 = 0.360.4×0.9=0.36。
终极追问:请盯着这两个句子的全局总概率:0.06vs0.36。
哪个句子才是人类听起来更地道、全局更优的解?为什么第一步选了最好的“帅”,最后却得到了一个更烂的结果?
致命缺陷暴露:贪婪搜索是“短视”的。它只看眼前这一步谁最红,却忽略了有些字虽然眼前不是第一名,但它的后劲十足,能带出后面更惊艳、概率更高的完美长句。
第二步:集束搜索——给备胎转正的机会
为了打破这种短视,科学家设计了集束搜索(Beam Search)。它的核心思想是:“老子不再在一棵树上吊死,我同时多留几条后路。”
这时候,我们需要引入一个核心参数:集束宽度(Beam Width,通常用BBB表示)。假设我们设定B=2B = 2B=2。我们重新回到刚才那一步:
- 第一步预测:“帅”(0.6),“时”(0.4),“酷”(0.05)。
因为B=2B=2B=2,网络不再只留第一名,而是把前两名(“帅”和“时”)作为候选种子同时保留。
提问:到了下一步,网络会以“帅”和“时”为各自的起点,分别繁衍后代:
- 从“帅”出发,去算后面接各种字的概率(比如“帅气”总分 0.06);
- 从“时”出发,去算后面接各种字的概率(比如“时髦”总分 0.36)。
在第二步结束时,网络手里握着多个两字组合,它再次开启雷达,只保留全局总得分最高的前 2 名。这时候发生了什么?
因果闭环:原本属于第二名的“时髦”(0.36)成功逆袭,把第一名的“帅气”(0.06)无情地踩在脚下!
通过这种在每一步都保留BBB个最可能序列的做法,集束搜索既没有像“穷举法”那样把全字典几万个字所有组合都算一遍(那会把显卡烧炸),又完美躲过了贪婪搜索的短视陷阱,用极低的计算代价捞到了全局更优解。
第三步:深入数学核心——为什么要“长度归一化”?
现在我们来看核心公式:
max1Tyα∑t=1TylogP(y⟨t⟩∣x,y⟨1⟩… )\max \frac{1}{T_y^\alpha} \sum_{t=1}^{T_y} \log P(y^{\langle t \rangle} \mid x, y^{\langle 1 \rangle} \dots)maxTyα1t=1∑TylogP(y⟨t⟩∣x,y⟨1⟩…)
我们来把这个公式肢解成最直观的两部分:
- 看后半部分的累加求和(∑logP\sum \log P∑logP):因为概率都是 0 到 1 之间的小数,小数连乘会越乘越小(比如0.1×0.1×0.10.1 \times 0.1 \times 0.10.1×0.1×0.1),在计算机里极易发生下溢。所以数学家通过取对数(log\loglog),把原本的“概率连乘”优雅地变成了“对数相加”。
- 看前半部分的分母1Tyα\frac{1}{T_y^\alpha}Tyα1(长度归一化):TyT_yTy代表生成句子的长度(字数),α\alphaα是一个 0 到 1 之间的调节系数。
提问:思考一个纯粹的数学问题。因为每个字算出来的对数概率都是负数。一句话越长,意味着需要加起来的负数就越多。
如果不除以这个分母,那么一个包含 5 个字的精简短句,和一个包含 50 个字的长句子相比,谁的总分在天然上更容易吃亏(变成一个绝对值极大的负数)?如果我们任由这种倾向发展,我们的 AI 最终会变成一个怎样的“说话习惯”?
推演直觉:没错!长句子由于加的负数太多,总分天然会暴跌。算法会严重偏袒短句子。
为了拯救长句子,公式强行除以了TyαT_y^\alphaTyα。这相当于对句子的字数做了一次功劳均摊(归一化),模型比的是“平均每个字的质量”,而不是“谁的字数少”。这就彻底解决了模型不爱说长句的顽疾。
第四步:PyTorch 里的“多线追踪”核心逻辑落地
在工业界,大模型的 Beam Search 逻辑可以用下面这段伪代码骨架来表达它的多线追踪思维:
# Beam Search 核心推理伪代码逻辑defbeam_search_decoding(model,src_input,beam_width=2,max_len=20):# 1. 初始状态:候选池里只有一句空话,总分为 0# 格式: (当前文本序列, 累积对数得分)candidates=[([start_token],0.0)]forstepinrange(max_len):all_successors=[]# 2. 遍历当前保留的 B 个种子,让它们分别向后繁衍预测forseq,scoreincandidates:ifseq[-1]==end_token:# 如果某条线已经生成完毕,保留all_successors.append((seq,score))continue# 让模型预测下一个字的概率分布next_word_probs=model.predict(src_input,seq)# 取出概率最高的前 B 个候选字top_b_words=get_top_b(next_word_probs,beam_width)forword,probintop_b_words:# 累加对数概率,生成新的长序列new_seq=seq+[word]new_score=score+log(prob)all_successors.append((new_seq,new_score))# 3. 核心:引入长度归一化,对所有繁衍出来的组合重新评分all_successors=[(seq,score/(len(seq)**0.7))forseq,scoreinall_successors]# 4. 只斩首留下最优秀的前 B 个组合,其余全部精简淘汰candidates=sort_by_score(all_successors)[:beam_width]returncandidates[0][0]# 最终返回全局最高分的那一句话总结报告
让我们用最后一行极客因果链,来为集束搜索封笔:
拒绝每步独苗 (贪婪) ⟹ 保留 B 个优质种子并列繁衍 ⟹ 引入长度归一化避免短句偏见 ⟹ 在计算量与全局最优间达成黑客平衡 ⟹ LLM 推理完美收尾\text{拒绝每步独苗 (贪婪)} \implies \text{保留 } B \text{ 个优质种子并列繁衍} \implies \text{引入长度归一化避免短句偏见} \implies \text{在计算量与全局最优间达成黑客平衡} \implies \text{LLM 推理完美收尾}拒绝每步独苗(贪婪)⟹保留B个优质种子并列繁衍⟹引入长度归一化避免短句偏见⟹在计算量与全局最优间达成黑客平衡⟹LLM推理完美收尾
贪婪搜索像是一个急功近利的赌徒,每一步都只押眼前赔率最低的那单注,最后往往输得血本无归;而集束搜索,则像是一个运筹帷幄的投资集团。它在每一步进化时,都同时下注和培养BBB个最具潜力的种子项目。它容许部分项目在前期低调潜伏,只为在时间的复利与长线的博弈中,精准地收割那全局最优的史诗大结局。
结语:恭喜你,全部通关了!
到今天为止,我们已经一起把特征归一化的噪声、深度残差的几何学、目标检测的张量矩阵、时序模型的记忆门阀,直到今天这临门一脚的集束搜索全部无损通电跑通了。
你从一开始看到公式和代码框架的迷茫,到现在能顺着逻辑看穿每一个数学符号背后的物理画面、代码层面的真实操作。这种“直觉与硬核并重”的硬核思维模型,才是你在人工智能和独立开发领域,敢于去啃任何开源源码、敢于去调任何硬核架构的最终免疫力。
把这十个硬核心智模型带到你的大模型开发实战中去吧。
欢迎在评论区留下你的终极思考:在如今的诸如 ChatGPT、Claude 等大模型的日常聊天推理中,为了让 AI 说话更具创造性、更拟人,我们往往会放弃严格的 Beam Search,转而使用设定了 Temperature(温度系数)或 Top-p 的采样(Sampling)策略。你认为集束搜索生成的文本,和大模型日常对话需要的文本之间,存在着怎样的风格和功能差异?
