概率与统计——分布、期望与贝叶斯,语言模型的建模基础

个人主页: > for_ever_love__ < (欢迎各位大佬莅临😊)
其他栏目: > 我想学python了 <

其他栏目: > iOS项目总结大全 <

其他栏目: > iOS UI <

文章目录

概率与统计------分布、期望与贝叶斯,语言模型的建模基础

承上:上一篇《线性代数入门》我们知道词是向量、注意力是点积。

本篇:本篇学概率分布、条件概率与链式法则------解释模型怎么「选出下一个词」。

启下:下一篇《微积分基础》解决「参数该往哪个方向调、调多少」。

学完这一节,你能动手做:

  1. 用交叉熵与困惑度衡量一个语言模型好不好
  2. 用 temperature、top-k、top-p 控制生成的严谨与发散
  3. 从零统计一个 bigram 语言模型,理解 GPT 在预测什么

上一篇我们搞定了线性代数,知道了"词是向量、注意力是点积"。这一篇回答一个更根本的问题:

大模型输出"下一个词",到底是怎么选出来的?

答案是:它给词表里每一个词算一个概率,然后按概率抽样 。整个大模型的本质,就是一台条件概率机器。

理解了概率,你就能理解:为什么 temperature 调高模型更"胡说八道"、为什么大模型会一本正经地编造事实(幻觉)、困惑度 perplexity 到底在衡量什么。

一、概率分布:模型眼里的世界

1.1 离散分布

词表有 N 个词,模型对每个词给一个概率,加起来 = 1:

python 复制代码
import numpy as np

vocab = ["猫", "狗", "跑", "吃", "鱼"]
probs = np.array([0.45, 0.25, 0.15, 0.10, 0.05])

print("和为:", probs.sum())     # 必须是 1.0

for w, p in zip(vocab, probs):
    print(f"{w}: {p:.0%}")

这就是一个概率分布(categorical distribution)。大模型最后一层 softmax 输出的,正是这样一个向量------只不过真实词表有 5 万~15 万个词。

1.2 采样:按概率抽一个

有了分布怎么"选词"?不是直接取最大的,而是抽样:

python 复制代码
np.random.seed(0)

# 按概率抽样 10000 次,看频率是否收敛到给定概率
samples = np.random.choice(len(vocab), size=10000, p=probs)
counts = np.bincount(samples, minlength=len(vocab)) / 10000

for w, p, c in zip(vocab, probs, counts):
    print(f"{w}: 理论 {p:.2%}  实测 {c:.2%}")

实测频率会非常接近理论概率------这就是大数定律。也解释了为什么同一个问题问两次,模型可能给出不同的答案:它在抽样,不是一个确定性函数。

二、条件概率与链式法则:语言模型的数学骨架

2.1 条件概率

P(鱼 | 猫) 读作"已知前面是'猫',下一个词是'鱼'的概率"。

语言模型做的就是这件事:

复制代码
P(整句话) = P(猫) × P(追|猫) × P(了|猫,追) × P(鱼|猫,追,了)

链式法则:联合概率 = 一串条件概率的乘积。这就是**自回归语言模型(GPT)**的数学定义。

2.2 从零统计一个 bigram 模型

不用神经网络,光靠数数也能做一个"语言模型":

python 复制代码
corpus = [
    "我 爱 吃 苹果",
    "我 爱 吃 香蕉",
    "我 不 爱 吃 鱼",
    "他 爱 吃 苹果",
    "小 猫 吃 鱼",
]

from collections import defaultdict

bigram = defaultdict(lambda: defaultdict(int))
unigram = defaultdict(int)

for line in corpus:
    words = line.split()
    for i in range(len(words) - 1):
        bigram[words[i]][words[i + 1]] += 1
        unigram[words[i]] += 1
    unigram[words[-1]] += 1

def p_next(prev, word):
    """P(word | prev)"""
    if unigram[prev] == 0:
        return 0.0
    return bigram[prev][word] / unigram[prev]

print("P(吃 | 爱) =", p_next("爱", "吃"))      # 爱后面全是吃 → 1.0
print("P(苹果 | 吃) =", p_next("吃", "苹果"))   # 吃后面苹果2次/共4次 → 0.5
print("P(鱼 | 吃) =", p_next("吃", "鱼"))

# 给定"吃",下一个词的完整分布
total = sum(bigram["吃"].values())
for w, c in bigram["吃"].items():
    print(f"  P({w}|吃) = {c/total:.2f}")

这就是语言模型最朴素的形态 :数语料 → 算频率 → 当概率。GPT 做的事完全一样,只不过它用一个 1750 亿参数的神经网络来预测这个分布,泛化能力强一万倍。

2.3 零概率问题与平滑

上面的模型有个致命伤:语料里没出现过的组合概率 = 0,一乘全完蛋。

python 复制代码
print("P(火箭 | 吃) =", p_next("吃", "火箭"))   # 0.0 → 整句话概率归零

解决办法是平滑(smoothing),给没见过的组合分一点点概率:

python 复制代码
def p_next_smooth(prev, word, alpha=0.5, V=1000):
    """加 α 平滑:分子 +α,分母 +α*V"""
    return (bigram[prev][word] + alpha) / (unigram[prev] + alpha * V)

print("平滑后 P(火箭|吃) =", p_next_smooth("吃", "火箭"))

神经网络模型天然避免了这个问题------softmax 的输出永远不为 0(除非下溢),这就是它比 n-gram 强的原因之一。

三、交叉熵与困惑度:模型好不好,用数字说话

3.1 信息量与熵

一个事件的概率越小,"发生了"带来的信息量越大:

复制代码
I(x) = -log P(x)
  • P=1(必然发生)→ 信息量 0(说了等于没说)
  • P=0.001(稀有)→ 信息量 ≈ 6.9(很"意外")

熵 = 信息量的期望,衡量"分布有多不确定":

python 复制代码
def entropy(p):
    p = np.array(p)
    p = p[p > 0]                 # 0*log0 约定为 0
    return -np.sum(p * np.log2(p))

print("均匀分布(5个词):", entropy([0.2]*5))          # ≈ 2.32 bit 最不确定
print("集中分布:", entropy([0.95, 0.02, 0.01, 0.01, 0.01]))  # ≈ 0.36 bit 很确定

3.2 交叉熵:这就是模型的损失函数

交叉熵衡量"用分布 Q 去编码真实分布 P,平均要花多少信息量":

python 复制代码
def cross_entropy(p_true, q_pred):
    """p_true: one-hot 真实标签;q_pred: 模型预测的概率分布"""
    p_true = np.array(p_true)
    q_pred = np.clip(np.array(q_pred), 1e-12, 1.0)   # 防 log(0)
    return -np.sum(p_true * np.log(q_pred))

# 真实下一个词是"猫"(index 0)
y_true = [1, 0, 0, 0, 0]

print("猜得很准:", cross_entropy(y_true, [0.90, 0.05, 0.03, 0.01, 0.01]))  # ≈ 0.105
print("猜得一般:", cross_entropy(y_true, [0.40, 0.30, 0.20, 0.05, 0.05]))  # ≈ 0.916
print("猜错了  :", cross_entropy(y_true, [0.05, 0.80, 0.10, 0.03, 0.02]))  # ≈ 2.996

结论 :预测越准,交叉熵越小。训练大模型,就是在最小化交叉熵------让模型给"正确答案"的概率尽可能接近 1。

3.3 困惑度 Perplexity

困惑度是交叉熵的指数形式,更直观:

python 复制代码
def perplexity(ce):
    return np.exp(ce)

print("ce=0.105 → ppl =", perplexity(0.105))   # ≈ 1.11
print("ce=2.996 → ppl =", perplexity(2.996))   # ≈ 20.0

困惑度的直观含义:模型在预测时"相当于在几个词之间犹豫"。ppl=20 意味着模型每次预测时,感觉像是在 20 个候选词里随机挑------越接近 1 越好。

评测大模型时经常看到 ppl 这个指标,现在你知道它在说什么了。

四、采样策略:temperature、top-k、top-p

同一个模型,为什么有时严谨有时放飞?答案是采样策略。

4.1 Temperature 温度

python 复制代码
logits = np.array([3.0, 2.0, 1.0, 0.5, 0.1])   # 模型原始输出(未归一化)

def softmax_with_temperature(logits, T=1.0):
    z = logits / T
    z = z - np.max(z)
    e = np.exp(z)
    return e / np.sum(e)

print("T=0.5 (保守):", softmax_with_temperature(logits, 0.5).round(3))
print("T=1.0 (默认):", softmax_with_temperature(logits, 1.0).round(3))
print("T=2.0 (放飞):", softmax_with_temperature(logits, 2.0).round(3))
Temperature 效果 适用
T < 1 分布更尖锐,倾向高概率词 代码生成、事实问答、抽取任务
T = 1 原始分布 通用
T > 1 分布更平缓,冷门词也有机会 创意写作、头脑风暴
T → 0 等价于贪婪解码(总选最大) 需要确定性输出

temperature 调太高,模型就开始胡说八道------因为低概率(往往是错的)的 token 被抽中的概率上升了。调太低则输出重复、呆板。

4.2 Top-k 与 Top-p(核采样)

更精细的控制:只在"靠谱的候选"里抽。

python 复制代码
def top_k_sample(probs, k=3):
    idx = np.argsort(probs)[-k:]          # 概率最高的 k 个
    sub = probs[idx] / probs[idx].sum()   # 重新归一化
    return np.random.choice(idx, p=sub)

def top_p_sample(probs, p=0.9):
    """核采样:按概率从高到低累加,只保留累计到 p 的最小集合"""
    order = np.argsort(probs)[::-1]
    cumsum = np.cumsum(probs[order])
    keep = order[cumsum <= p]
    if len(keep) == 0:
        keep = order[:1]
    sub = probs[keep] / probs[keep].sum()
    return np.random.choice(keep, p=sub)

probs = softmax_with_temperature(logits, 1.0)
print("分布:", probs.round(3))
print("top-k(2) 抽样 10 次:", [top_k_sample(probs, 2) for _ in range(10)])
print("top-p(0.9) 抽样 10 次:", [top_p_sample(probs, 0.9) for _ in range(10)])

top-p 比 top-k 聪明:候选数量自适应。分布很确定时只留 2~3 个词,分布很平(模型没把握)时留几十个------所以现在主流 API 默认推荐 top-p。

4.3 完整解码示例

python 复制代码
def generate(model_predict_fn, prompt_tokens, max_new=10, T=0.8, top_p=0.9):
    """自回归生成的通用骨架"""
    tokens = list(prompt_tokens)
    for _ in range(max_new):
        logits = model_predict_fn(tokens)         # 模型给下一步的 logits
        probs = softmax_with_temperature(logits[-1], T)
        next_id = top_p_sample(probs, top_p)
        tokens.append(int(next_id))
    return tokens

# 用"假模型"演示流程
vocab_size = 20
fake_model = lambda toks: np.tile(np.arange(vocab_size, 0, -1) / 20.0, (len(toks), 1))
print(generate(fake_model, [1, 2, 3], max_new=8))

真实 GPT 的生成就是这个循环:预测 → 采样 → 拼回去 → 再预测。你每次看到模型一个字一个字往外蹦,就是在跑这个循环。

五、贝叶斯:不确定性下的推理(RAG 的哲学基础)

贝叶斯定理:

复制代码
P(A|B) = P(B|A) · P(A) / P(B)

用人话讲:先验 P(A) 是你原本的信念,看到证据 B 之后,更新成 后验 P(A|B)。

python 复制代码
# 经典例子:疾病检测
# P(病) = 0.01(先验,发病率 1%)
# P(阳性|病) = 0.99(真阳性率)
# P(阳性|健康) = 0.05(假阳性率)

p_disease = 0.01
p_pos_given_disease = 0.99
p_pos_given_healthy = 0.05

p_pos = p_pos_given_disease * p_disease + p_pos_given_healthy * (1 - p_disease)
p_disease_given_pos = p_pos_given_disease * p_disease / p_pos

print(f"检出阳性后真得病的概率: {p_disease_given_pos:.1%}")

结果只有约 16.7% ------远低于直觉。这就是基础概率谬误:因为健康人基数太大,假阳性数量反超真阳性。

和大模型什么关系? RAG(检索增强生成)的哲学就是这个:

复制代码
P(答案 | 问题)                     ← 只靠模型参数记忆(先验)→ 容易幻觉
P(答案 | 问题, 检索到的文档)        ← 加入外部证据 → 更可信

RAG 做的事就是给模型注入证据,把后验分布"掰"到正确答案上。理解了贝叶斯,你就理解了为什么 RAG 能治幻觉。

六、常见坑与注意事项

坑 现象 解决
log(0) loss 变 -inf / nan np.clip(p, 1e-12, 1)
概率和不为 1 采样报错 用 softmax 保证归一化
直接 exp(logits) 大 logits 溢出成 inf 先减 max 再 exp
temperature 设 0 除零 T 最小设 0.01,或改贪婪解码
忽视先验 模型自信地胡说 用 RAG 补证据 / 要求给出引用
小样本统计不可靠 bigram 概率抖动 加平滑,或增大语料

七、本篇小结

  1. 大模型 = 条件概率机器 :P(下一个词 | 上文),链式法则把整句概率拆成一串条件概率。
  2. 交叉熵是损失函数 ,训练就是让正确答案的概率趋近 1;困惑度 ppl 直观表示"模型在几个词间犹豫"。
  3. Temperature 控制分布尖锐程度:低=严谨确定,高=创意发散,过高=胡说。
  4. Top-k / Top-p 限制候选集,top-p 自适应更主流;自回归生成就是"预测→采样→拼接"循环。
  5. 贝叶斯解释了 RAG 的价值:注入外部证据,用后验替代纯参数记忆的先验,从而抑制幻觉。

下一篇 微积分基础 :导数、梯度与链式法则。这是反向传播的数学钥匙------我们会手推一个两层网络的梯度,并对比"手算梯度"和"数值梯度"完全一致,让你彻底相信反向传播不是魔法。

本篇是《大模型开发从 0 到 1》专栏第 15 篇,阶段 2「数学基础(LLM 视角)」第 2 篇。专栏文章按「分类专栏」归类,顺序学习体验最佳。

相关推荐
ss2731 小时前
AI全栈实战 | 3.5-01 Python 全栈前端最小集:不会 React 也能做出能看的后台,关键就一条线
前端·python·react.js
郝学胜-神的一滴1 小时前
Numpy数据处理详解 01:NumPy 从环境搭建到入门上手
开发语言·人工智能·python·程序人生·数据分析·numpy
Omics Pro1 小时前
北理工李荣华:AI虚拟细胞证据多模态推理基准
数据库·人工智能·算法·机器学习·自然语言处理
m0_734172421 小时前
Python用zip配对前先检查长度
python·学习
FYKJ_20101 小时前
SpringSecurity教室智能预约系统73972-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·spring·django
外收内放2 小时前
Python基础语法练习题(49-50)
开发语言·python
Cc.Y2 小时前
Java零基础入门:集合框架:ArrayList 与 LinkedList —— 告别数组的“死板“,拥抱动态容器的“灵活
java·开发语言·python
小蒋观天下2 小时前
端侧大模型在安防摄像头部署实操(下篇)|模型量化、推理加速、视频接入与量产调优
大数据·人工智能·算法·安全·机器学习·计算机视觉·ai大模型
FYKJ_20102 小时前
springboot游泳馆管理系统79222-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·mysql·django·课程设计