Beam Search算法示意图

1997年,IBM的研究人员在开发统计机器翻译系统时遇到了一个棘手的问题:如何在庞大的搜索空间中找到最优的翻译序列?穷举搜索计算量太大,贪婪搜索又太短视。他们最终选择了一个折中方案——Beam Search。二十多年过去了,这个算法不仅没有被淘汰,反而成为了Transformer、GPT等现代大模型的标准配置。一个「妥协」的产物为何能统治序列生成领域如此之久?

贪婪搜索的致命缺陷

在理解Beam Search之前,我们需要先看清楚它的对手——贪婪搜索(Greedy Search)到底出了什么问题。

序列到序列模型

假设我们正在用一个大语言模型生成文本。在每一步,模型都会输出一个词汇表上的概率分布。贪婪搜索的策略非常简单:永远选择概率最高的那个词。

$$y_t = \arg\max_{w \in V} P(w | y_{其中$V$是词汇表,$y_{

这个策略看起来很直观——既然模型认为这个词概率最高,选它不就对了吗?问题在于,贪婪搜索只看到了眼前的利益,完全忽略了全局最优的可能性。

让我们用一个具体的例子来说明。假设词汇表只有三个词$\{A, B, C\}$,我们要生成一个长度为3的序列。模型输出的概率如下:

flowchart TD
    subgraph 第一步["第一步:选择第一个词"]
        A1["P(A) = 0.6"] --> B1["P(B) = 0.3"]
        A1 --> C1["P(C) = 0.1"]
    end
    
    subgraph 第二步["第二步:假设选了A"]
        A2["P(A|A) = 0.2"]
        B2["P(B|A) = 0.3"]
        C2["P(C|A) = 0.5"]
    end
    
    subgraph 第三步["第三步:假设选了AC"]
        A3["P(A|AC) = 0.3"]
        B3["P(B|AC) = 0.4"]
        C3["P(C|AC) = 0.3"]
    end
    
    第一步 -->|"贪婪选A"| 第二步
    第二步 -->|"贪婪选C"| 第三步
    第三步 -->|"贪婪选B"| 结果["序列ACB<br/>概率=0.6×0.5×0.4=0.12"]

贪婪搜索会按照以下路径生成:

  1. 第一步选A(概率0.6,最高)
  2. 第二步选C(在A的条件下概率0.5,最高)
  3. 第三步选B(在AC的条件下概率0.4,最高)

最终得到的序列是"ACB",概率为$0.6 \times 0.5 \times 0.4 = 0.12$。

但是,如果我们查看所有可能的序列,可能会发现:

序列 计算过程 总概率
ACB 0.6 × 0.5 × 0.4 0.120
ABC 0.6 × 0.3 × 0.8 0.144
BAC 0.3 × 0.9 × 0.5 0.135

“ABC"序列的概率(0.144)实际上比"ACB”(0.120)更高!贪婪搜索因为在第二步选择了局部最优的C,错过了全局最优的序列。

这就是贪婪搜索的核心问题:局部最优不等于全局最优。一个看似次优的选择可能打开通往更好结果的大门。

搜索空间的残酷现实

注意力机制

你可能会问:为什么不直接遍历所有可能的序列,选择概率最高的那个?

理论上这是最优解。设词汇表大小为$|V|$,序列最大长度为$T'$,穷举搜索需要评估的序列数量为:

$$N_{exhaustive} = |V|^{T'}$$

对于一个典型的语言模型,$|V| \approx 50000$,$T' \approx 20-50$。这意味着搜索空间大小在$50000^{20}$到$50000^{50}$之间,这比可观测宇宙中的原子数量还要多得多。

相比之下,贪婪搜索的时间复杂度仅为:

$$O(|V| \times T')$$

它只需要$T'$步,每步遍历词汇表一次。但代价是可能错过最优解。

这就是序列生成面临的核心困境:我们既没有计算资源穷举,又不愿意接受贪婪搜索的短视

Beam Search:折中的艺术

Transformer架构

Beam Search的核心思想很简单:与其只保留一个候选(贪婪搜索),不如同时保留$k$个最有希望的候选。

这个$k$被称为Beam WidthBeam Size。所有被保留的候选序列组成的集合称为Beam

算法流程

flowchart TB
    Start["初始化:Beam = [空序列]"] --> Step1["时间步 t = 1"]
    
    Step1 --> Expand["扩展阶段:<br/>对Beam中每个序列<br/>尝试所有可能的下一个词"]
    
    Expand --> Candidates["生成 k × |V| 个候选序列"]
    
    Candidates --> Score["计算每个候选的累积对数概率"]
    
    Score --> Select["选择阶段:<br/>保留top-k个候选"]
    
    Select --> Check{"所有候选都<br/>生成了结束符?"}
    
    Check -->|"否"| Step2["时间步 t = t + 1"]
    Step2 --> Expand
    
    Check -->|"是"| Output["输出:返回概率最高的序列"]
    
    style Start fill:#e1f5fe
    style Output fill:#c8e6c9
    style Select fill:#fff3e0

具体来说,Beam Search的每一步包含两个阶段:

扩展阶段:对于Beam中的每个序列,尝试添加词汇表中的每一个词,生成新的候选序列。如果当前Beam有$k$个序列,词汇表大小为$|V|$,则生成$k \times |V|$个候选。

选择阶段:计算所有候选序列的累积对数概率,保留top-$k$个:

$$\text{score}(y_{1:t}) = \sum_{i=1}^{t} \log P(y_i | y_{使用对数概率是为了避免数值下溢——多个小于1的概率相乘会快速趋近于0。

时间复杂度分析

Beam Search的时间复杂度为:

$$O(k \times |V| \times T')$$

这比贪婪搜索多了$k$倍,但仍然远小于穷举搜索。当$k=1$时,Beam Search退化为贪婪搜索;当$k \to \infty$时,它趋近于穷举搜索(在实际实现中会有剪枝)。

一个完整的例子

让我们用一个具体例子来演示Beam Search的工作过程。设Beam Size $k=2$,词汇表$V=\{A, B, C, \text{}\}$,其中是结束符。

flowchart TD
    subgraph t0["t=0: 初始化"]
        S0["Beam: [空序列]"]
    end
    
    subgraph t1["t=1: 第一步扩展"]
        S1A["A (0.6)"]
        S1B["B (0.3)"]
        S1C["C (0.1)"]
    end
    
    subgraph t2["t=1: 选择top-2"]
        B1["Beam: [A, B]<br/>概率: 0.6, 0.3"]
    end
    
    subgraph t3["t=2: 第二步扩展"]
        E1["AA (0.6×0.2=0.12)"]
        E2["AB (0.6×0.3=0.18)"]
        E3["AC (0.6×0.5=0.30)"]
        E4["BA (0.3×0.4=0.12)"]
        E5["BB (0.3×0.1=0.03)"]
        E6["BC (0.3×0.5=0.15)"]
    end
    
    subgraph t4["t=2: 选择top-2"]
        B2["Beam: [AC, AB]<br/>概率: 0.30, 0.18"]
    end
    
    t0 --> t1 --> t2 --> t3 --> t4

在每一步,我们都保留了最有希望的$k$条路径。即使某条路径在某一时刻看起来不是最优的,只要它的累积概率够高,仍然有机会继续探索。

Beam Width:这道选择题没有标准答案

自注意力机制

Beam Size $k$的选择是Beam Search中最关键的决策之一,但它并没有一个普适的最优值。

k与质量的关系

直觉告诉我们,$k$越大,搜索越充分,生成质量应该越高。但实际情况远比这复杂。

Straker Labs的一项翻译质量研究给出了令人惊讶的数据:

Beam Size BLEU分数提升 VRAM增量 质量效率比
1 (贪婪) 基准 基准 基准
2 +6% +10% 0.60
5 +9% +33% 0.27
10 +9% +66% 0.14
20 +8% +133% 0.06

这个表格揭示了一个关键发现:Beam Size的边际收益递减极为明显

  • $k=2$时,只需要10%的额外显存,就能获得93%的质量提升潜力
  • $k=5$时,质量达到峰值
  • $k>5$后,质量不仅不再提升,甚至可能下降

Beam Search Curse:越努力越不幸

更令人困惑的是,当$k$继续增大时,生成质量反而会下降。这个现象被称为Beam Search Curse

graph LR
    A["k=1<br/>贪婪搜索<br/>质量较低"] --> B["k=2-5<br/>最佳区间<br/>质量峰值"]
    B --> C["k>5<br/>质量下降"]
    C --> D["k→∞<br/>趋近穷举<br/>但质量不如人意"]
    
    style B fill:#c8e6c9
    style C fill:#ffcdd2
    style D fill:#ffcdd2

为什么会这样?《Breaking the Beam Search Curse》论文提出了几个解释:

概率校准问题:神经网络输出的概率分布往往不够准确。高概率的序列不一定真正是高质量序列,因为模型的概率估计可能存在偏差。

长度偏差:Beam Search倾向于生成较短或较长的序列,取决于概率计算方式。这是因为更长的序列有更多的概率项相乘,累积概率自然更小。

重复生成:大$k$值增加了重复模式出现的概率。模型可能会陷入某个看似高概率的循环中。

让Beam Search更聪明的优化技术

为了解决上述问题,研究者们开发了多种优化技术。

长度惩罚(Length Penalty)

由于对数概率是负数累加,更长的序列自然会有更小的分数。为了平衡这一点,我们可以对分数进行归一化:

$$\text{score}(y) = \frac{\sum_{t=1}^{T} \log P(y_t | y_{其中$L$是序列长度,$\alpha$是惩罚系数,通常取$0.6-1.0$。

当$\alpha=0$时,不进行长度惩罚;当$\alpha=1$时,等价于对序列长度取平均。一个常用的变体是Google提出的长度惩罚:

$$\text{LP}(y) = \frac{(5 + L)^\alpha}{(5 + 1)^\alpha}$$

这个公式在长度为1时惩罚为1,随着长度增加,惩罚逐渐增大。

N-gram Blocking

Beam Search常见的另一个问题是重复生成相同的短语。N-gram Blocking通过禁止重复的n-gram来解决这个问题:

$$\text{如果 } (y_{t-n+1}, ..., y_t) \in \{(y_{i-n+1}, ..., y_i)\}_{i=1}^{t-n} \text{,则禁止 } y_t$$

简单来说,如果一个n-gram已经在序列中出现过,那么再次生成这个n-gram的概率被设为0。通常$n=3$或$n=4$效果较好。

Coverage Penalty

在机器翻译中,另一个问题是源语言中的某些词可能被忽略。Coverage Penalty通过惩罚"覆盖不足"的注意力分布来解决这个问题:

$$\text{CP}(y) = \beta \sum_{i=1}^{|x|} \log(\min(\sum_{t=1}^{T} a_{t,i}, 1))$$

其中$a_{t,i}$是在时间步$t$对源语言位置$i$的注意力权重。这个惩罚项鼓励模型均匀地关注源语言的每个位置。

flowchart LR
    subgraph 优化技术
        LP["长度惩罚<br/>平衡长短序列"]
        NB["N-gram Blocking<br/>防止重复"]
        CP["Coverage Penalty<br/>防止遗漏"]
    end
    
    subgraph 效果
        E1["翻译更完整"]
        E2["文本更流畅"]
        E3["生成更多样"]
    end
    
    LP --> E1
    NB --> E2
    CP --> E1

Beam Search的变种

基础Beam Search的成功催生了多种改进版本。

标准Beam Search的问题之一是$k$个候选序列往往非常相似——它们经常只在最后几个词上有差异。Diverse Beam Search通过引入多样性惩罚来解决这个问题:

$$\text{score}_g(y) = \text{score}(y) - \lambda \cdot \sum_{g' < g} \text{sim}(y, y_{g'})$$

其中$g$是当前组,$y_{g'}$是之前组中选出的序列,$\text{sim}$是相似度函数。

算法将$k$个候选分成$G$组,每组独立搜索,但后选择的组会受到与已选择序列相似度的惩罚。

有时我们需要强制生成的序列包含特定的词汇或短语。Constrained Beam Search通过在搜索过程中维护约束状态来实现这一点:

flowchart TD
    A["输入约束: ['iPhone', '苹果']"] --> B["初始化约束状态"]
    B --> C["正常Beam Search扩展"]
    C --> D{"约束词可被满足?"}
    D -->|"是"| E["更新约束状态<br/>优先扩展该候选"]
    D -->|"否"| F["继续正常扩展"]
    E --> G{"所有约束已满足?"}
    F --> G
    G -->|"否"| C
    G -->|"是"| H["输出包含所有约束的序列"]

这种技术在需要生成特定术语、名称或格式化内容时非常有用。

标准Beam Search按时间步顺序扩展,而Best-First Beam Search按候选分数顺序扩展。它维护一个优先队列,每次扩展分数最高的候选:

$$y^* = \arg\max_{y \in \text{candidates}} \text{score}(y)$$

这种方法可以更快地找到高质量序列,特别适用于搜索深度不均匀的任务。

Beam Search vs 采样方法:各有所长

在大模型时代,Beam Search并不是唯一的选择。各种采样方法如Top-K Sampling、Top-P (Nucleus) Sampling也越来越流行。

采样方法简介

Top-K Sampling:从概率最高的$K$个词中按概率随机采样。

$$y_t \sim P(y_t | y_{Top-P Sampling (Nucleus Sampling):从累积概率达到$p$的最小词集中采样。

$$\text{设 } V^{(p)} \text{ 为最小词集满足 } \sum_{w \in V^{(p)}} P(w) \geq p$$

$$y_t \sim P(y_t | y_{Holtzman的发现

PyTorch深度学习框架

2019年,Holtzman等人在论文《The Curious Case of Neural Text Degeneration》中发现了一个惊人的事实:人类写的文本并不是由高概率词组成的

graph TB
    subgraph 概率分布对比
        A["Beam Search<br/>偏好高概率词<br/>结果:重复、可预测"]
        B["人类文本<br/>包含中低概率词<br/>结果:多样、意外"]
        C["采样方法<br/>允许低概率词<br/>结果:更有创意"]
    end
    
    A -->|"问题"| D["文本退化<br/>重复模式"]
    B -->|"启发"| C
    C -->|"可能"| E["生成质量不稳定"]

他们发现,如果按照Beam Search的逻辑,人类写的文本反而是"低概率"的。人类经常使用意想不到的词汇和表达方式,这正是创造力的来源。

选择指南

基于以上分析,我们可以给出一个选择指南:

任务类型 推荐方法 原因
机器翻译 Beam Search (k=2-5) + 长度惩罚 翻译需要忠实、准确,长度可预测
文本摘要 Beam Search (k=4-6) 摘要有明确的目标长度
问答系统 Beam Search (k=1-3) 答案通常较短,确定性更重要
对话生成 Top-P (p=0.9) 对话需要多样性和意外性
故事创作 Top-P + Temperature 创意写作需要意外和多样性
代码生成 Beam Search 或 Greedy 代码需要精确、可预测

实战建议

机器翻译

# 典型配置
beam_size = 4
length_penalty = 0.6
coverage_penalty = 0.2
early_stopping = True  # 当所有候选都生成<eos>时停止

使用这些设置,现代翻译模型(如MarianMT、NLLB)通常能达到较好的BLEU分数。

文本摘要

# 典型配置
beam_size = 6
length_penalty = 2.0  # 更强的长度惩罚
max_length = 142
min_length = 56
no_repeat_ngram_size = 3

摘要任务需要更强的长度控制,因为模型容易生成过长或过短的摘要。

对话生成

# 典型配置(使用采样而非Beam Search)
do_sample = True
top_p = 0.92
temperature = 0.7
no_repeat_ngram_size = 3

对话需要多样性和自然感,采样方法通常表现更好。

为什么Beam Search仍然统治

回到我们最初的问题:为什么这个「折中」算法能够统治序列生成三十年?

答案在于它的实用性

  1. 可预测性:Beam Search的输出是确定的(给定相同的随机种子),这对于生产环境至关重要。

  2. 可控性:通过调整beam size和各种惩罚项,我们可以精细控制生成行为。

  3. 效率:相对于穷举搜索,Beam Search的计算开销是可接受的;相对于采样方法,它能更稳定地产生高质量输出。

  4. 可解释性:Beam Search的选择过程是透明的,我们可以清楚地知道每一步为什么选择某个词。

在大模型时代,Beam Search并没有过时。相反,它与现代技术(如采样方法)形成了互补:当任务需要确定性、准确性和可控性时,Beam Search仍然是首选;当任务需要创造性和多样性时,采样方法更合适。

真正的智慧不在于选择「最好」的算法,而在于根据任务特性选择最合适的工具。Beam Search之所以能统治三十年,正是因为它在效率和质量之间找到了一个恰到好处的平衡点——一个「折中」,但恰到好处的折中。

参考文献

  1. Sutskever, I., Vinyals, O., & Le, Q. V. (2014). Sequence to sequence learning with neural networks. NeurIPS.

  2. Bahdanau, D., Cho, K., & Bengio, Y. (2015). Neural machine translation by jointly learning to align and translate. ICLR.

  3. Holtzman, A., Buys, J., Du, L., Forbes, M., & Choi, Y. (2020). The curious case of neural text degeneration. ICLR.

  4. Vijayakumar, A. K., Cogswell, M., Selvaraju, R. R., Sun, Q., Lee, S., Crandall, D., & Batra, D. (2018). Diverse beam search. AAAI.

  5. Post, M., & Bergsma, S. (2014). The character of diversity. EMNLP.

  6. Wu, Y., et al. (2016). Google’s neural machine translation system. arXiv:1609.08144.

  7. D2L - 动手学深度学习:序列搜索与解码策略

  8. Hugging Face Blog: How to generate text