真实面经题目 · 原创解析
如何手写 Beam Search,并处理候选扩展、剪枝和停止条件?
这题考如何手写 Beam Search,回答重点是维护 beam 候选、逐步扩展、按累计分数 top-k 剪枝、处理 EOS 停止并返回最优序列。
真实面经题目 · 原创解析
这题考如何手写 Beam Search,回答重点是维护 beam 候选、逐步扩展、按累计分数 top-k 剪枝、处理 EOS 停止并返回最优序列。
Beam Search 是一种在生成任务里保留多个候选路径的近似搜索。它不会像 greedy 每步只选一个最高概率 token,也不会像穷举保留所有路径,而是维护 beam_size 个当前最优序列。实现时先用起始 token 初始化 beams,每个 beam 记录序列、累计 log probability 和是否结束。每一轮对未结束 beam 调模型拿到下一 token 的 log probabilities,扩展 top-k 个 token,生成新的候选;对已经遇到 EOS 的 beam 不再扩展,直接保留。然后把所有候选按累计分数排序,取前 beam_size 个作为下一轮 beams。循环到达到最大长度,或所有 beam 都结束。最后返回分数最高的完整序列,实际系统还常加长度惩罚、重复惩罚和 batch 化。手写题的关键是三个动作:扩展候选、top-k 剪枝、EOS 停止。
Beam Search 是介于 greedy 和穷举之间的解码策略。greedy 每步只保留一个当前最优选择,容易早期选错;穷举搜索空间指数爆炸。Beam Search 每步保留 beam_size 个高分候选,用有限计算提高找到好序列的概率。
每个 beam 至少包含 token 序列、累计 log probability 和 ended 标记。使用 log probability 是为了把每步概率相乘变成相加,避免数值下溢,也方便排序。ended 标记用于处理 EOS 后不再继续扩展。
对每个未结束 beam,把当前序列喂给模型,得到下一 token 的 log probabilities。通常只取每个 beam 的 top-k token 参与扩展,避免一次性展开整个词表造成计算和内存浪费。
扩展后会得到 beam_size*k 个候选,再加上已经结束的候选。要按累计分数做全局排序,保留前 beam_size 个,而不是每个旧 beam 各自保留一个。这样才能让高质量路径占据更多 beam 名额。
当某条序列生成 EOS 后,应标记 ended 并停止扩展;当所有 beams 都 ended,或生成长度达到 max_len,循环结束。最后可以从 ended 候选中选最好结果,如果没有 ended,就从当前 beams 中选最高分。
基础 Beam Search 偏好短句,因为 log probability 累加越长通常越小。实际生成常加入 length penalty、重复惩罚、最小长度、禁止 n-gram 重复和 batch 化缓存。手写题先写清基础流程,再说明这些工程增强即可。
def beam_search(next_log_probs, start_id, eos_id, beam_size=3, max_len=20):
# next_log_probs(tokens) -> list of (token_id, log_prob) for the next step
beams = [([start_id], 0.0, False)]
for _ in range(max_len):
candidates = []
for tokens, score, ended in beams:
if ended:
candidates.append((tokens, score, True))
continue
for token_id, log_p in sorted(
next_log_probs(tokens),
key=lambda item: item[1],
reverse=True,
)[:beam_size]:
new_tokens = tokens + [token_id]
candidates.append((
new_tokens,
score + log_p,
token_id == eos_id,
))
candidates.sort(key=lambda item: item[1], reverse=True)
beams = candidates[:beam_size]
if all(ended for _, _, ended in beams):
break
best_tokens, best_score, _ = max(beams, key=lambda item: item[1])
return best_tokens, best_score greedy 每步只保留一个最高分 token,Beam Search 保留多个候选路径,能纠正部分早期局部最优,但计算量更高。
序列概率是各步概率相乘,直接相乘容易下溢;取 log 后变成加法,排序等价且更稳定。
累计 log probability 通常会偏向短序列。长度惩罚可以让较长但合理的序列不被天然压低。
要参与。EOS 候选不再扩展,但它可能已经是完整高分答案,应该保留并和其他候选一起比较。