其他栏目: > iOS项目总结大全 <
其他栏目: > iOS UI <
文章目录
- 概率与统计——分布、期望与贝叶斯,语言模型的建模基础
- 一、概率分布:模型眼里的世界
- 1.1 离散分布
- 1.2 采样:按概率抽一个
- 二、条件概率与链式法则:语言模型的数学骨架
- 2.1 条件概率
- 2.2 从零统计一个 bigram 模型
- 2.3 零概率问题与平滑
- 三、交叉熵与困惑度:模型好不好,用数字说话
- 3.1 信息量与熵
- 3.2 交叉熵:这就是模型的损失函数
- 3.3 困惑度 Perplexity
- 四、采样策略:temperature、top-k、top-p
- 4.1 Temperature 温度
- 4.2 Top-k 与 Top-p(核采样)
- 4.3 完整解码示例
- 五、贝叶斯:不确定性下的推理(RAG 的哲学基础)
- 六、常见坑与注意事项
- 七、本篇小结
概率与统计——分布、期望与贝叶斯,语言模型的建模基础
承上:上一篇《线性代数入门》我们知道词是向量、注意力是点积。
本篇:本篇学概率分布、条件概率与链式法则——解释模型怎么「选出下一个词」。
启下:下一篇《微积分基础》解决「参数该往哪个方向调、调多少」。
学完这一节,你能动手做:
- 用交叉熵与困惑度衡量一个语言模型好不好
- 用 temperature、top-k、top-p 控制生成的严谨与发散
- 从零统计一个 bigram 语言模型,理解 GPT 在预测什么
上一篇我们搞定了线性代数,知道了"词是向量、注意力是点积"。这一篇回答一个更根本的问题:
大模型输出"下一个词",到底是怎么选出来的?
答案是:它给词表里每一个词算一个概率,然后按概率抽样。整个大模型的本质,就是一台条件概率机器。
理解了概率,你就能理解:为什么 temperature 调高模型更"胡说八道"、为什么大模型会一本正经地编造事实(幻觉)、困惑度 perplexity 到底在衡量什么。
一、概率分布:模型眼里的世界
1.1 离散分布
词表有 N 个词,模型对每个词给一个概率,加起来 = 1:
importnumpyasnp vocab=["猫","狗","跑","吃","鱼"]probs=np.array([0.45,0.25,0.15,0.10,0.05])print("和为:",probs.sum())# 必须是 1.0forw,pinzip(vocab,probs):print(f"{w}:{p:.0%}")这就是一个概率分布(categorical distribution)。大模型最后一层 softmax 输出的,正是这样一个向量——只不过真实词表有 5 万~15 万个词。
1.2 采样:按概率抽一个
有了分布怎么"选词"?不是直接取最大的,而是抽样:
np.random.seed(0)# 按概率抽样 10000 次,看频率是否收敛到给定概率samples=np.random.choice(len(vocab),size=10000,p=probs)counts=np.bincount(samples,minlength=len(vocab))/10000forw,p,cinzip(vocab,probs,counts):print(f"{w}: 理论{p:.2%}实测{c:.2%}")实测频率会非常接近理论概率——这就是大数定律。也解释了为什么同一个问题问两次,模型可能给出不同的答案:它在抽样,不是一个确定性函数。
二、条件概率与链式法则:语言模型的数学骨架
2.1 条件概率
P(鱼 | 猫)读作"已知前面是’猫’,下一个词是’鱼’的概率"。
语言模型做的就是这件事:
P(整句话) = P(猫) × P(追|猫) × P(了|猫,追) × P(鱼|猫,追,了)链式法则:联合概率 = 一串条件概率的乘积。这就是**自回归语言模型(GPT)**的数学定义。
2.2 从零统计一个 bigram 模型
不用神经网络,光靠数数也能做一个"语言模型":
corpus=["我 爱 吃 苹果","我 爱 吃 香蕉","我 不 爱 吃 鱼","他 爱 吃 苹果","小 猫 吃 鱼",]fromcollectionsimportdefaultdict bigram=defaultdict(lambda:defaultdict(int))unigram=defaultdict(int)forlineincorpus:words=line.split()foriinrange(len(words)-1):bigram[words[i]][words[i+1]]+=1unigram[words[i]]+=1unigram[words[-1]]+=1defp_next(prev,word):"""P(word | prev)"""ifunigram[prev]==0:return0.0returnbigram[prev][word]/unigram[prev]print("P(吃 | 爱) =",p_next("爱","吃"))# 爱后面全是吃 → 1.0print("P(苹果 | 吃) =",p_next("吃","苹果"))# 吃后面苹果2次/共4次 → 0.5print("P(鱼 | 吃) =",p_next("吃","鱼"))# 给定"吃",下一个词的完整分布total=sum(bigram["吃"].values())forw,cinbigram["吃"].items():print(f" P({w}|吃) ={c/total:.2f}")这就是语言模型最朴素的形态:数语料 → 算频率 → 当概率。GPT 做的事完全一样,只不过它用一个 1750 亿参数的神经网络来预测这个分布,泛化能力强一万倍。
2.3 零概率问题与平滑
上面的模型有个致命伤:语料里没出现过的组合概率 = 0,一乘全完蛋。
print("P(火箭 | 吃) =",p_next("吃","火箭"))# 0.0 → 整句话概率归零解决办法是平滑(smoothing),给没见过的组合分一点点概率:
defp_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(很"意外")
熵= 信息量的期望,衡量"分布有多不确定":
defentropy(p):p=np.array(p)p=p[p>0]# 0*log0 约定为 0return-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,平均要花多少信息量":
defcross_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.105print("猜得一般:",cross_entropy(y_true,[0.40,0.30,0.20,0.05,0.05]))# ≈ 0.916print("猜错了 :",cross_entropy(y_true,[0.05,0.80,0.10,0.03,0.02]))# ≈ 2.996结论:预测越准,交叉熵越小。训练大模型,就是在最小化交叉熵——让模型给"正确答案"的概率尽可能接近 1。
3.3 困惑度 Perplexity
困惑度是交叉熵的指数形式,更直观:
defperplexity(ce):returnnp.exp(ce)print("ce=0.105 → ppl =",perplexity(0.105))# ≈ 1.11print("ce=2.996 → ppl =",perplexity(2.996))# ≈ 20.0困惑度的直观含义:模型在预测时"相当于在几个词之间犹豫"。ppl=20 意味着模型每次预测时,感觉像是在 20 个候选词里随机挑——越接近 1 越好。
评测大模型时经常看到ppl这个指标,现在你知道它在说什么了。
四、采样策略:temperature、top-k、top-p
同一个模型,为什么有时严谨有时放飞?答案是采样策略。
4.1 Temperature 温度
logits=np.array([3.0,2.0,1.0,0.5,0.1])# 模型原始输出(未归一化)defsoftmax_with_temperature(logits,T=1.0):z=logits/T z=z-np.max(z)e=np.exp(z)returne/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(核采样)
更精细的控制:只在"靠谱的候选"里抽。
deftop_k_sample(probs,k=3):idx=np.argsort(probs)[-k:]# 概率最高的 k 个sub=probs[idx]/probs[idx].sum()# 重新归一化returnnp.random.choice(idx,p=sub)deftop_p_sample(probs,p=0.9):"""核采样:按概率从高到低累加,只保留累计到 p 的最小集合"""order=np.argsort(probs)[::-1]cumsum=np.cumsum(probs[order])keep=order[cumsum<=p]iflen(keep)==0:keep=order[:1]sub=probs[keep]/probs[keep].sum()returnnp.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_inrange(10)])print("top-p(0.9) 抽样 10 次:",[top_p_sample(probs,0.9)for_inrange(10)])top-p 比 top-k 聪明:候选数量自适应。分布很确定时只留 2~3 个词,分布很平(模型没把握)时留几十个——所以现在主流 API 默认推荐 top-p。
4.3 完整解码示例
defgenerate(model_predict_fn,prompt_tokens,max_new=10,T=0.8,top_p=0.9):"""自回归生成的通用骨架"""tokens=list(prompt_tokens)for_inrange(max_new):logits=model_predict_fn(tokens)# 模型给下一步的 logitsprobs=softmax_with_temperature(logits[-1],T)next_id=top_p_sample(probs,top_p)tokens.append(int(next_id))returntokens# 用"假模型"演示流程vocab_size=20fake_model=lambdatoks: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)。
# 经典例子:疾病检测# P(病) = 0.01(先验,发病率 1%)# P(阳性|病) = 0.99(真阳性率)# P(阳性|健康) = 0.05(假阳性率)p_disease=0.01p_pos_given_disease=0.99p_pos_given_healthy=0.05p_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_posprint(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 概率抖动 | 加平滑,或增大语料 |
七、本篇小结
- 大模型 = 条件概率机器:
P(下一个词 | 上文),链式法则把整句概率拆成一串条件概率。 - 交叉熵是损失函数,训练就是让正确答案的概率趋近 1;困惑度 ppl直观表示"模型在几个词间犹豫"。
- Temperature控制分布尖锐程度:低=严谨确定,高=创意发散,过高=胡说。
- Top-k / Top-p限制候选集,top-p 自适应更主流;自回归生成就是"预测→采样→拼接"循环。
- 贝叶斯解释了 RAG 的价值:注入外部证据,用后验替代纯参数记忆的先验,从而抑制幻觉。
下一篇微积分基础:导数、梯度与链式法则。这是反向传播的数学钥匙——我们会手推一个两层网络的梯度,并对比"手算梯度"和"数值梯度"完全一致,让你彻底相信反向传播不是魔法。
本篇是《大模型开发从 0 到 1》专栏第 15 篇,阶段 2「数学基础(LLM 视角)」第 2 篇。专栏文章按「分类专栏」归类,顺序学习体验最佳。