上一节把参数当成一个固定的数,只是不知道。贝叶斯换了个立场: 参数自己也有分布——先是先验,看到数据之后变成后验。 这个转向带来的最实际的好处,是小样本时不再乱跳:抛一次出了正面,极大似然说「概率 100%」, 贝叶斯说「六成」。
这一节跟上一节是同一套零件,只多了一个东西:先验。分布还是那个分布,似然还是那个似然, 只是现在要把「看数据之前的态度」也写进去。 它后面还挂着一个能真干活的算法——朴素贝叶斯。这个分类器在文本分类上用了二十多年, 它的骨架就是这一节的三行公式,外加一个「假设各个特征互不影响」的简化。
一、一件事分三步看
场景:一枚硬币,正面概率 p 未知。抛之前你得有个态度——「大概正常,但不敢打包票」。 这个态度写成 Beta(2, 2):均值 0.5,形状是个宽胖的鼓包。 然后抛十次,七次正面。三样东西的关系就是一句话:后验 ∝ 先验 × 似然。
| p 的位置 | 先验的高低 | 后验的高低 |
|---|---|---|
| 0.2 | 中等 | 极低 |
| 0.5 | 最高 | 中 |
| 0.7 | 较高 | 最高 |
| 0.9 | 很低 | 低 |
| 后验被数据拽着往 0.7 那边挪了,但没挪到底。先验的均值 0.5000, | ||
| 后验的均值 0.6429——正好在 0.5 和 0.7 之间,偏 0.7 那一侧。 | ||
| 偏向哪一侧,取决于「先验有多重」。这里的 Beta(2, 2) 相当于四次虚拟抛掷, | ||
| 十次真实数据的话语权比它大,所以后验更靠近数据。下一节就把这个「多重」量出来。 |
二、共轭:为什么后验能直接加参数
上一节那个「先验乘似然再除以一个积分」听起来很吓人, 但有一种情况不用积分:当先验和似然是「一对」的时候,后验跟先验是同一个家族, 参数直接相加就行。
| 量 | 第一个参数 | 第二个参数 | 均值 |
|---|---|---|---|
| 先验 | 2 | 2 | 0.5000 |
| 数据(七正三反) | 7 | 3 | 0.7000 |
| 后验 | 9 | 5 | 0.6429 |
| 这张表就是共轭的全部内容:后验的 (9, 5) 等于先验的 (2, 2) 加上数据的 (7, 3)。 | |||
| Beta 配二项是这样,Dirichlet 配多项、正态配正态也都是这样,每对都有自己的「加法规则」。 | |||
| 这不是数学游戏,是工程上极重要的一件事。没共轭的时候,每来一批数据都要做一次数值积分; | |||
| 有共轭的时候,后验可以直接当「新的先验」继续用,数据可以一批一批地喂进来。 | |||
| 这就是在线学习最原始的形态。 | |||
| Demo 里还交叉验证了一遍:先验 Beta(1, 1) 加「三十次里二十次正面」, | |||
| 网格数值积分算出的后验均值 0.6562,公式给的 21/32 = 0.6562。 | |||
| 两条路一模一样——共轭的意思就是「有现成公式」。 |
三、先验有多「固执」
同样的数据(十次里七次正面),换三种先验,后验均值差得很远:
| 先验 | 相当于几次虚拟抛掷 | 后验均值 | 离 0.7 多远 |
|---|---|---|---|
| Beta(1, 1) | 2 次 | 0.6667 | 0.0333 |
| Beta(5, 5) | 10 次 | 0.6000 | 0.1000 |
| Beta(50, 50) | 100 次 | 0.5182 | 0.1818 |
| 先验越强,后验越被拉回 0.5。Beta(50, 50) 相当于一百次虚拟抛掷,十次真实数据在它面前只值一成, | |||
| 后验均值只挪到 0.5182——数据说了七成正,它还是坚持五成出头。 | |||
| 这就是先验的代价,也是它的用处。代价是「对明明发生了的事反应迟钝」; | |||
| 用处是「小样本时不至于乱跳」。 | |||
| 所以先验不能随便给强,该给多强取决于你对「这件事本来长什么样」有多确信。 | |||
| 还有一个底线要守住:先验不能把后验彻底堵死。如果先验在真值附近给了 0 概率, | |||
| 那么无论来多少数据, | |||
| 后验在那里都是 0——这叫「先验把答案排除掉了」,是贝叶斯方法最危险的一种误用。 |
四、收缩:小样本时它比极大似然靠谱
抛 n 次,全是正面。极大似然直接给 n/n = 1,贝叶斯给后验均值:
| n | 极大似然 | 后验均值(先验 Beta(2,2)) |
|---|---|---|
| 1 | 1.0000 | 0.6000 |
| 2 | 1.0000 | 0.6667 |
| 4 | 1.0000 | 0.7500 |
| 10 | 1.0000 | 0.8571 |
| 30 | 1.0000 | 0.9412 |
| 100 | 1.0000 | 0.9808 |
| 左边一列从头到尾都是 1.0000。极大似然的意思是「这硬币永远出正面」, | ||
| 而且再多抛几次它也不会改口——它没有「我可能看少了」这个概念。 | ||
| 右边一列从 0.6000 慢慢往上挪,样本越多越接近 1,但永远不到 1。 | ||
| 这叫收缩:样本少的时候估计值被拉向先验,样本多了先验就不管用了。 | ||
| 这个性质在真实工作里救过很多次场。一个新产品上线三天, | ||
| 一百个用户里三个点击——直接算 3% 太激进(样本太小),直接算 0 更荒唐(明明有人点了)。 | ||
| 收缩给的答案在两者之间,而且随着样本积累自动往真实值靠。 | ||
| 顺便说清它跟「加个平滑」的关系。上一节朴素贝叶斯里那个 +1, | ||
| 本质上就是给每个格子加了一个 Beta(1,1) 先验——同一个思想,换个名字。 |
五、可信区间和置信区间不是一回事
同一批数据(30 次里 20 次正面),两种区间各算一个:
| 说法 | 区间 | 它说的是什么 |
|---|---|---|
| 贝叶斯 95% 可信区间 | [0.4863, 0.8077] | p 落在这里的概率是 95% |
| 频率派 95% 置信区间 | [0.4980, 0.8354] | 这套方法 95% 的时候管用 |
| 两个数很接近,但说的不是一件事。前一句直接对 p 下判断; | ||
| 后一句说的是「这套流程靠不靠得住」——如果把这套流程重复无数次,95% 的区间会盖住真值。 | ||
| 为什么会有这种别扭的表述?因为在频率派的框架里,p 是个固定的数,不是随机变量, | ||
| 所以「p 落在区间里的概率」这句话本身就不合法(要么 0 要么 1)。只能说「区间盖住 p」的概率。 | ||
| 很多人把后者读成了前者,这是个流传很广的误读。实际影响是: | ||
| 置信区间不能回答「p 大概率是多少」这个问题,而绝大多数人想问的恰恰是这个。 | ||
| 这也正是贝叶斯那套在实际业务里更受欢迎的原因之一。 |
六、朴素贝叶斯:三行公式加一个大胆假设
把上面那套用到分类上,就是朴素贝叶斯: 要判断一个样本属于哪一类,就算「这一类出现的先验」乘「这一类的每个特征出现的条件概率」。 Demo 用十四天的天气记录判断「今天打不打球」。先验是 9 天打、5 天不打。今天阴、凉、高湿、强风:
| 类别 | 先验 | 四个条件概率 | 乘起来 |
|---|---|---|---|
| 打 | 0.6250 | 0.417 × 0.333 × 0.364 × 0.364 | 0.011478 |
| 不打 | 0.3750 | 0.125 × 0.250 × 0.714 × 0.571 | 0.004783 |
| 归一化之后是「打 0.7059、不打 0.2941」——判成打,但只敢说七成。 | |||
| 「只敢说七成」这件事本身是有价值的信息,很多分类器只给一个标签, | |||
| 而它给的是一个能拿去做决策的概率。 | |||
| 那个「假设」是什么?它假设「在给定类别的情况下,各个特征互不影响」。 | |||
| 这在真实数据里几乎一定不成立(高湿和下雨明明是相关的), | |||
| 可它偏偏很管用——这就是这个算法最反直觉的地方。 | |||
| 原因大概是: | |||
| 分类只需要「排序对」,不需要「概率准」。各特征的相关性往往给每一类都带来同样的偏差, | |||
| 归一化之后互相抵消。 | |||
| 最后那个 +1(拉普拉斯平滑)必须讲清楚。Demo 里去掉它, | |||
| 「不打」那一支因为十四天里从没出现过「不打球又阴天」,条件概率是 0, | |||
| 整个乘积塌成 0——模型说「绝不可能」,归一化后变成 1.0000 对 0.0000。 | |||
| 加上平滑之后是 0.7059 对 0.2941。注意它带来了什么、也带走了什么: | |||
| 带来的是「不会说出绝对的话」, | |||
| 带走的是「不打」那一支凭空多出三成概率——可这种组合一次都没出现。所以平滑不是「更准」, | |||
| 是用一点偏见换掉「零概率」这个硬伤。 |
七、动手 Demo:先验、后验,和一个会「拉一把」的估计
前五段都是同一枚硬币,只换先验。第六段换成一个真能干活的分类器,
它的核心就是那个 nb 函数——不到十行。
注意 nb 里的 alpha。传 1.0 就是拉普拉斯平滑,传 0.0 就是不平滑,
同一个函数跑两遍,输出会差得很远。
def beta_pdf(x, a, b): # Beta 密度
return np.exp((a - 1) * np.log(x) + (b - 1) * np.log(1 - x)
+ lgamma(a + b) - lgamma(a) - lgamma(b))
# 第一步:先验 Beta(2, 2),观测十次里七次正面
# 共轭的好处:后验参数直接相加 -> Beta(2+7, 2+3) = Beta(9, 5)
post_mu = (2 + 7) / float((2 + 7) + (2 + 3)) # 0.6429
# 收缩:全是正面时,极大似然一口咬定 1.0,后验均值慢慢挪
for n in (1, 2, 4, 6, 10, 30, 100):
mle = n / float(n) # 1.0000,一直不变
mu = (2 + n) / float((2 + n) + 2) # 越往后越接近 1
print(n, mle, mu)
# 朴素贝叶斯:先验 × 四个条件概率,再归一化
def nb(q, alpha): # alpha=1 就是拉普拉斯平滑
out = []
for L in (u'打', u'不打'):
v = (cnt[L] + alpha) / (len(ROWS) + alpha * 2.0)
cond = [(tab[i][1][L][q[i]] + alpha) /
(cnt[L] + alpha * len(tab[i][0])) for i in range(4)]
out.append(v * float(np.prod(cond)))
return out
实跑输出:
--------------------------------------------------------------------
先验、后验,和一个会「拉一把」的估计
--------------------------------------------------------------------
==============================================================
[1] 一件事分三步看:先验、似然、后验
场景:一枚硬币,正面概率 p 未知。**抛之前你得有个态度**——
「硬币大概正常,但不敢打包票」。这个态度写成 Beta(2, 2):
它的均值是 0.5,形状是个宽胖的鼓包。
然后抛十次,七次正面。**这三样东西的关系就是一句话:**
**后验 ∝ 先验 × 似然。**
p 先验 后验 往哪边挪
--------------------------------------------------------------
0.1 ## · ←
0.2 #### · ←
0.3 ###### · ←
0.4 ####### ## ←
0.5 ####### ####### →
0.6 ####### ############ →
0.7 ###### ############## →
0.8 #### ######## →
0.9 ## # ←
先验的均值 0.5000,后验的均值 0.6429。
**后验被数据拽着往 0.7 那边挪了,但没挪到底**——因为先验只相当于四次「虚拟抛掷」,
十次真实数据的话语权比它大。
[2] 共轭:后验还能套用同一个公式
Beta 先验配二项似然,后验还是 Beta——这叫共轭。
好处是**根本不用做积分**,参数直接相加:
量 第一个参数 第二个参数 均值
------------------------------------------------------
先验 2 2 0.5000
加上数据 7 3 0.7000
后验 9 5 0.6429
**后验就是 Beta(9, 5)**:先验的 (2, 2) 加上「七正三反」。均值 9/14 = 0.6429。
换个数验证一遍:先 Beta(1, 1) 加「三十次里二十次正面」,
网格算出来的后验均值 0.6562,公式给的是 21/32 = 0.6562。
**两条路一模一样——共轭的意思就是「有现成公式,不用数值积分」。**
[3] 先验有多「固执」
同样的数据(十次里七次正面),换三种先验:
先验 相当于几次虚拟抛掷 后验均值 离 0.7 多远
------------------------------------------------------------------
Beta(1, 1) 2 次 0.6667 0.0333
Beta(5, 5) 10 次 0.6000 0.1000
Beta(50, 50) 100 次 0.5182 0.1818
**先验越强,后验越被拉回 0.5。**Beta(50, 50) 相当于一百次虚拟抛掷,
十次真实数据在它面前只值一成,后验均值只挪到 0.5182。
**这就是「先验」的代价和好处**:它能防止小样本乱跳,
也会让你对明明发生了的事反应迟钝。**所以先验不能随便给强。**
[4] 小样本的时候,它比极大似然靠谱
抛 n 次,k 次正面。极大似然直接给 k/n,贝叶斯给后验均值:
n k 极大似然 k/n 后验均值 被拉回多少
----------------------------------------------------------
1 1 1.0000 0.6000 0.4000
2 2 1.0000 0.6667 0.3333
4 4 1.0000 0.7500 0.2500
6 6 1.0000 0.8000 0.2000
10 10 1.0000 0.8571 0.1429
30 30 1.0000 0.9412 0.0588
100 100 1.0000 0.9808 0.0192
全是正面的时候,极大似然一路给 1.0000——**它说「这硬币永远出正面」,而且再也不改口。**
贝叶斯从 0.7500 慢慢往上挪,n 到 100 才到 0.9808。
**这叫收缩:样本少的时候,估计值被拉向先验;样本多了,先验就不管用了。**
这也解释了一件事:**为什么「一次成功就断定 100% 成功」是个坏习惯。**
[5] 「95% 可信区间」和「95% 置信区间」
同一批数据(30 次里 20 次正面),两种区间各算一个:
贝叶斯 95% 可信区间:[0.4863, 0.8077]
它的意思:**给定数据和先验,p 落在这个区间里的概率是 95%。**
频率派 95% 置信区间:[0.4980, 0.8354]
它的意思:**如果把这个实验重复无数次,95% 的区间会盖住真值。**
**两个数很接近,但说的不是一件事。**前一句说的是「p 在哪」,
后一句说的是「这套方法靠不靠得住」。**很多人把后者当成了前者,这是个流传很广的误读。**
[6] 朴素贝叶斯:一个真能干活的版本
十四天的天气记录,判断「今天打不打球」:
一共 14 天:打 9 天,不打 5 天。
特征 取值 打的时候几天 不打的时候几天
----------------------------------------------------------
天气 晴 2 3
天气 阴 4 0
天气 雨 3 2
温度 凉 3 1
温度 温 4 2
温度 热 2 2
湿度 正常 6 1
湿度 高 3 4
风 弱 6 2
风 强 3 3
今天:天气 阴、温度 凉、湿度 高、风 强。打不打?
类别 先验 四个条件概率 乘起来
------------------------------------------------------------------------
打 0.6250 0.417 × 0.333 × 0.364 × 0.364 0.011478
不打 0.3750 0.125 × 0.250 × 0.714 × 0.571 0.004783
归一化成概率:打 0.7059,不打 0.2941——**判成「打」,但只敢说七成。**
**那一列里有个「+1」,叫拉普拉斯平滑。**去掉它再算一遍:
打 0.6429 0.444 × 0.333 × 0.333 × 0.333 0.010582
不打 0.3571 0.000 × 0.200 × 0.800 × 0.600 0.000000
归一化成概率:打 1.0000,不打 0.0000——**这回它说「绝不可能」。**
原因就一个:十四天里「不打球又阴天」一次都没出现过,于是条件概率是 0.000。
**一项是 0,整个乘积就塌成 0——不是「概率很低」,是「绝不可能发生」。**
平滑要治的正是这个:**加 1 相当于「每种组合先假设它至少出现过一次」。**
**注意它带来了什么、也带走了什么。**带来的是「不会说出绝对的话」;
带走的是「不打」那一支凭空多出 0.2941 的概率——**可这种组合一次都没出现。**
所以平滑不是「更准」,是**用一点偏见换掉「零概率」这个硬伤**。
第一段那两条柱状图要横着比。同一个 p 位置,先验高而后验低的地方,说明数据在那里「不支持」; 先验低而后验高的地方,说明数据把那里顶上去了。一高一低之间,就是数据说的话。 第二段那张表是全节最该记住的。先验 (2, 2) + 数据 (7, 3) = 后验 (9, 5), 加法。这三个字就是共轭的意思。 没有它,每来一批数据都要做一次数值积分,在线学习根本无从谈起。 第三段那张表的第三行最有教学价值。Beta(50, 50) 相当于一百次虚拟抛掷, 它让「十次里七次正面」这条信息几乎白给。 这不是「先验错了」,而是「先验说得太满」。真实项目里判断先验强度, 最实用的办法是问一句「我这份先验相当于多少条真实数据」。 第四段那张全正面的表,值得和上一节的极大似然对着看。 上一节我们夸极大似然「挑一个让已发生的事最不意外的参数」, 这一节就看见了它的软肋:样本一小,它就敢下绝对结论。 两节合起来才是一句完整的话:极大似然在样本充足时最好用,样本少时需要有人拉一把。 第六段那个「0.7059 对 0.2941」不是终点,是这一节真正的落点。 去掉平滑就变成「1.0000 对 0.0000」——这两个数看着更「干脆」,其实更危险: 一个从未见过的组合被判定为绝不可能,而现实里「从未见过」和「不可能」往往是两件事。 平滑的那一点偏见,买的就是这个教训。
30 秒实验
- 把第一段的先验从 Beta(2, 2) 改成 Beta(20, 20),重跑。 后验均值会从 0.6429 掉到0.5625 附近——先验的「虚拟抛掷」从四次变成四十次, 数据的话语权被压到六分之一。
- 把第一段的数据从「七正三反」改成「七十正三十反」,先验仍是 Beta(2, 2)。 后验均值会非常接近 0.7——这说明先验只管小样本,样本一多它自动退场。
- 把第六段的
alpha从 1.0 改成 0.5 和 2.0,各跑一遍。 alpha 越大,两支的概率越接近(越保守);alpha 很小时又会往「绝不可能」那边靠。 这个旋钮没有标准答案,得看「没出现过的组合到底有多常见」。
常见误区
- 以为「先验」就是「拍脑袋」,所以不客观。先验是必须有的—— 没有先验也是一种先验(均匀先验)。真正要防的不是「有先验」,而是「先验强到数据说不动它」。
- 以为后验概率就是「真实概率」。后验是「给定数据和先验之后,你能说的最好的话」, 它的好坏完全取决于模型对不对。模型错了,后验再漂亮也只是「在错误的世界里最合理的答案」。
- 以为可信区间和置信区间可以互换着说。两者数值可能很接近, 但一句话说的是「p 在哪」,另一句说的是「方法靠不靠得住」。在正式报告里混用,是会被抓的。
- 以为朴素贝叶斯的「朴素」是缺点,所以它过时了。它确实很早, 可在文本分类、垃圾邮件过滤这些场景里,它到今天还是个强基线——快到几乎不花时间, 效果又常常接近复杂模型。「简单但管用」不是贬义词。
自测三问(点开对答案)
1. 为什么共轭这么重要? —— 因为**它把「算一个积分」换成了「加两个数」**。 没有共轭,每来一批数据都要重新做一次数值积分,慢且不稳; **有共轭,上一轮的后验可以直接当下一轮的先验,数据可以一批批流进来。** 这就是在线学习和增量更新的数学基础。 2. 为什么全是正面的时候,极大似然给 1.0,贝叶斯只给 0.8571(十次)? —— 极大似然只看数据,**十次全正,最合理的解释就是「正面概率 1」**, 它没有「样本可能太小」这个概念。 贝叶斯多加了一个先验 Beta(2, 2),相当于「先假设这硬币大概正常」, **于是把估计值往 0.5 那边拉了一把。** **样本越多,这一把拉得越轻**(n=100 时只到 0.9808), 这就是收缩——**它不是说贝叶斯更「聪明」,是它多带了一条信息。** 3. 朴素贝叶斯假设「特征互不影响」,这个假设明明不对,为什么还能用? —— 因为**分类只需要「谁的概率更大」这个排序对,不需要每个概率都准。** 特征之间的相关性往往给每一类都带来方向相同的偏差, **归一化之后互相抵消,排序还是对的。**反过来也要记住: **如果你要的是「这个概率到底是多少」(比如定价、风控),朴素贝叶斯的输出就不可信了**—— 它的概率值系统性偏极端,这跟「排序对」是两件事。这一节一直在用「后验有公式」这个前提。 下一节把这个前提拿掉:绝大多数模型没有共轭关系,后验根本写不出来。那时候的办法不是硬算, 是去「抽」。
- ☐ (本节完,下一节 6.3 MCMC 方法)