从概率分布中选择单个词元(采样、解码): AI 是怎么做选择题的?

#2025/12/29 #ai

大语言模型(LLM)写文章其实就像在玩一个“猜下一个词”的游戏

当它写到“我正在开一辆……”时,它不会直接告诉你答案,而是先给出一个“候选名单”,并给每个候选词打分(概率)。

目录

📊 第一步:模型给出的“候选名单”

假设模型计算完所有的数学公式后,给出了这样的概率分布(可以看作一个字典):

# 模型输出的概率分布  
next_token_probs = {  
    "车": 0.40,      # 可能性最高,40%  
    "卡车": 0.13,    # 可能性一般,13%  
    "自行车": 0.08,  # 可能性较低,8%  
    "大象": 0.02,    # 离谱但不是不可能,2%  
    # ... 其他几万个词概率几乎为0  
}  

现在问题来了:

  • 虽然“车”的分数最高,但我们一定要选它吗?

这就涉及到了解码策略(Decoding Strategy),也就是我们今天要讲的核心——怎么做决定。


🕹️ 策略一:贪心解码 (Greedy Decoding) —— “无聊的学霸”

规则:永远只选分数最高的那个词。
比喻:就像考试做选择题,你永远只选你觉得最对的那个选项,从来不猜。

代码逻辑:

def greedy_decoding(probs):  
    # max() 函数直接取最大值  
    return max(probs, key=probs.get)   

# 结果:每次运行都会返回 "车"  
print(greedy_decoding(next_token_probs))   
  • 优点:稳定、准确。
  • 缺点:
    • 太死板了!如果你让它讲笑话,它每次都会讲同一个笑话,因为每次概率最高的词都是固定的。这就解释了为什么把 Temperature(温度)设为 0 时,AI 的回答总是一模一样的。

🎲 策略二:随机采样 (Sampling) —— “有趣的灵魂”

规则:

  • 根据分数的比例,像转盘抽奖一样随机选。
    比喻:
  • 虽然“车”的中奖区最大(40%),但转盘转起来后,指针也有可能停在“卡车”(13%)甚至“大象”(2%)的区域。

代码逻辑:

import random  

def random_sampling(probs):  
    tokens = list(probs.keys())  
    weights = list(probs.values())  
    
    # choices 函数根据权重随机抽签  
    return random.choices(tokens, weights=weights, k=1)[0]  

# 结果:  
# 第1次运行 -> "车" (概率最大,最容易中)  
# 第2次运行 -> "卡车" (有点意外,但通顺)  
# 第3次运行 -> "大象" (非常有创意的故事走向!)  
  • 优点:有创造力!这是让 AI 写作、聊天变得生动、不重复的关键。
  • 缺点:有时候会“抽风”,选出完全不通顺的词(比如“我正在开一辆…大象”)。

🔧 两个控制“脑洞”的参数

为了不让 AI 太死板,也不让它太疯狂,程序员发明了两个参数来微调这个“转盘”:

1. Temperature (温度)

  • 作用:
    • 控制概率分布的“贫富差距”。
  • 低温 (接近0):
    • 放大差距。高的更高,低的更低。结果几乎等于贪心解码(保守)。
  • 高温 (比如1.0):
    • 缩小差距。让低概率的词也有更多机会被选中(更有创意,更疯狂)。

2. Top-P (核采样 / Nucleus Sampling)

  • 作用:
    • 把那些概率太低、太离谱的“垃圾选项”直接切掉,只在剩下的好词里抽奖。
  • 逻辑:
    • 比如设定 Top-P = 0.9,那就只把概率最高的几个词加起来,直到总和达到 90%,剩下的 10% 这种极其冷门的词(比如“大象”)根本不进入抽奖池。
  • 效果:
    • 既保留了变化的乐趣,又防止了完全胡说八道。

📝 总结

  • 模型输出 = 一堆带有概率的候选词。
  • 解码(采样) = 决定到底选哪个词的算法。
  • 贪心 = 永远选第一名(无聊但稳)。
  • 采样 = 按概率抽奖(有趣但偶尔翻车)。