📘 第6章 统计机器学习(概率分布和建模)
📄 一句话概括

上一节把参数当成一个固定的数,只是不知道。贝叶斯换了个立场: 参数自己也有分布——先是先验,看到数据之后变成后验。 这个转向带来的最实际的好处,是小样本时不再乱跳:抛一次出了正面,极大似然说「概率 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 秒实验

  1. 把第一段的先验从 Beta(2, 2) 改成 Beta(20, 20),重跑。 后验均值会从 0.6429 掉到0.5625 附近——先验的「虚拟抛掷」从四次变成四十次, 数据的话语权被压到六分之一。
  2. 把第一段的数据从「七正三反」改成「七十正三十反」,先验仍是 Beta(2, 2)。 后验均值会非常接近 0.7——这说明先验只管小样本,样本一多它自动退场。
  3. 把第六段的 alpha 从 1.0 改成 0.5 和 2.0,各跑一遍。 alpha 越大,两支的概率越接近(越保守);alpha 很小时又会往「绝不可能」那边靠。 这个旋钮没有标准答案,得看「没出现过的组合到底有多常见」。

常见误区

  1. 以为「先验」就是「拍脑袋」,所以不客观。先验是必须有的—— 没有先验也是一种先验(均匀先验)。真正要防的不是「有先验」,而是「先验强到数据说不动它」。
  2. 以为后验概率就是「真实概率」。后验是「给定数据和先验之后,你能说的最好的话」, 它的好坏完全取决于模型对不对。模型错了,后验再漂亮也只是「在错误的世界里最合理的答案」。
  3. 以为可信区间和置信区间可以互换着说。两者数值可能很接近, 但一句话说的是「p 在哪」,另一句说的是「方法靠不靠得住」。在正式报告里混用,是会被抓的。
  4. 以为朴素贝叶斯的「朴素」是缺点,所以它过时了。它确实很早, 可在文本分类、垃圾邮件过滤这些场景里,它到今天还是个强基线——快到几乎不花时间, 效果又常常接近复杂模型。「简单但管用」不是贬义词。
自测三问(点开对答案) 1. 为什么共轭这么重要? —— 因为**它把「算一个积分」换成了「加两个数」**。 没有共轭,每来一批数据都要重新做一次数值积分,慢且不稳; **有共轭,上一轮的后验可以直接当下一轮的先验,数据可以一批批流进来。** 这就是在线学习和增量更新的数学基础。 2. 为什么全是正面的时候,极大似然给 1.0,贝叶斯只给 0.8571(十次)? —— 极大似然只看数据,**十次全正,最合理的解释就是「正面概率 1」**, 它没有「样本可能太小」这个概念。 贝叶斯多加了一个先验 Beta(2, 2),相当于「先假设这硬币大概正常」, **于是把估计值往 0.5 那边拉了一把。** **样本越多,这一把拉得越轻**(n=100 时只到 0.9808), 这就是收缩——**它不是说贝叶斯更「聪明」,是它多带了一条信息。** 3. 朴素贝叶斯假设「特征互不影响」,这个假设明明不对,为什么还能用? —— 因为**分类只需要「谁的概率更大」这个排序对,不需要每个概率都准。** 特征之间的相关性往往给每一类都带来方向相同的偏差, **归一化之后互相抵消,排序还是对的。**反过来也要记住: **如果你要的是「这个概率到底是多少」(比如定价、风控),朴素贝叶斯的输出就不可信了**—— 它的概率值系统性偏极端,这跟「排序对」是两件事。

这一节一直在用「后验有公式」这个前提。 下一节把这个前提拿掉:绝大多数模型没有共轭关系,后验根本写不出来。那时候的办法不是硬算, 是去「抽」。

相关笔记