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

这两样东西看着不像,骨架一模一样:都是「一堆局部的小概率表,用图连起来, 然后问整张图的概率」。 区别只在图的形状:HMM 的图是一条链(沿着时间),贝叶斯网络的图是一张网(沿着因果)。

📝 本节和前面几节的关系

这一节是第 6 章的收尾,也是「概率」这条线的汇合点。6.1 讲一个分布怎么描述, 6.2 讲怎么更新它,6.3 讲算不出来怎么办,这一节讲:把很多个分布用箭头连起来,会得到什么。 它跟第 5 章还连着: 贝叶斯网络就是 5.1 那个「图」,只是边上挂的是概率表。 所以这一节可以看成「图 + 概率」的合体。

一、贝叶斯网络:把「谁影响谁」画成箭头

四个变量:云、洒水器开、下雨、草地湿。箭头是:云指向洒水器和下雨,洒水器和下雨都指向草地湿。 网络只存四张条件概率表,每张只涉及「一个节点和它的父节点」:

节点 父节点 要存几个数
云 无 1 个
洒水器 云 2 个
下雨 云 2 个
草地湿 洒水器、下雨 4 个
四张表一共 9 个数。如果不用结构、直接存四个变量的联合分布,要 15 个。
变量越多,省得越狠——十个二值变量,联合要 1023 个,
一棵合理的网络可能只要几十个。这就叫「用结构换参数」。
这张网络还编码了一件很重要的事:条件独立。「给定云,洒水器和下雨互不影响」——因为除了云,
它们之间没有别的通路。
这也是为什么网络里不画「洒水器 → 下雨」这条边:它们之间真的没有直接影响。

二、一次枚举,回答任何问题

网络建好之后, 要什么概率都靠「把 16 种组合全枚举一遍、筛出你要的那些、加起来」。问「草地湿的概率是多少」:

云 洒水器 下雨 联合概率 占湿草地的比例
真 关 真 0.3240 50.1%
假 开 假 0.1800 27.8%
假 开 真 0.0495 7.6%
假 关 真 0.0450 7.0%
加起来是 0.6471。注意排在最前面的两种组合就已经占了一半多:
「云多、下雨、没开洒水器」 和 「没云、开了洒水器、没下雨」——
湿草地有两个来源,这张表把它们各自贡献了多少直接摊开了。
枚举的代价是 2 的 n 次方(n 是变量个数)。四个变量 16 种,还好;
三十个变量就是十亿种,枚举立刻废掉。所以实用的网络都要靠「变量消除」「信念传播」这些算法来省——
但它们做的事和枚举完全相同,只是不算白算的部分。

三、解释消除:看见结果,两个原因开始「抢功」

这是贝叶斯网络里最反直觉、也最漂亮的一个现象:

已知条件 P(下雨) 怎么理解
什么都不知道 0.5000 先验
看到草地湿了 0.7079 湿草是雨的证据,涨了
湿 + 洒水器开 0.3204 洒水器能解释湿,雨的必要性降
湿 + 洒水器关 1.0000 只剩雨能解释,顶到 1
注意第二行和第三行:多知道一件事,对雨的判断反而降了。本来 0.7079,
加上「洒水器开着」之后掉到 0.3204——这叫解释消除。
两个本来互不影响的因(雨和洒水器),一旦看到共同的结果(草地湿),它们就开始互相「抢功」。
这在直觉上完全站得住:草地湿了,要么天在下雨,要么洒水器开了,
现在知道洒水器开了,那下雨的必要性自然就小了。
第四行那个 1.0000 不是 bug。这套参数里「洒水器关着、草地还湿」只可能由下雨造成,所以概率是 1。
换个参数(比如草地可能自己潮)它就不到 1 了。
那它们「本来互不影响」这句话怎么量?直接乘一下:P(雨) × P(洒水器) = 0.5000 × 0.3000 = 0.1500,
而 P(雨 且 洒水器) = 0.0900。
两个数不一样,说明它们连边缘上都不独立——因为它们通过「云」连着:云多的时候又容易下雨、
又容易关洒水器。只有在「给定云」之后,这两件事才真的互不相干,这才是「条件独立」。

四、HMM:看不见的天气,看得见的冰激凌

场景:一个人住在没窗的房间,只能靠每天吃几个冰激凌来猜外面什么天气。天气有三种(晴、阴、雨), 它是隐藏的;冰激凌个数是观测到的。 这就是 HMM 的两张表: 转移表(今天什么天气影响明天)和发射表(天气影响观测)。再加一个初始分布。 一共 9 + 9 + 3 个数,就能描述任意长的一串天气。

今天→明天 晴 阴 雨
晴 0.70 0.20 0.10
阴 0.30 0.40 0.30
雨 0.20 0.30 0.50
观测到「3、1、3」这三天。
问「这串观测出现的概率是多少」:笨办法是把 3 的 3 次方 = 27 条天气路径全枚举一遍,
加起来 = 0.025860。
概率最大的一条是「晴→晴→晴」,概率 0.006125。注意它只占总概率的四分之一——
剩下四分之三散在另外 26 条路径上。这也是 HMM 跟「每天独立判断」最根本的不同:
它考虑的是一整串路径。

五、前向算法:为什么不能枚举

27 条还好。可天数一多,路径数是「乘以 3」地涨:

天数 枚举的路径数 前向要乘几次 倍数
3 27 27 1
10 5.9 万 90 660
20 35 亿 180 1900 万
50 7.2 乘 10 的 23 次方 450 1.6 乘 10 的 21 次方
五十天而已,枚举要 7 乘 10 的 23 次方条路径。前向算法只要乘 450 次——
这不是「快一点」,是「从不可能变成可能」。
它的诀窍只有一句话:把「枚举整条路径」换成「递推每个状态」。
第 t 天每个状态只存一个数(走到这里的总概率),
第二天用「昨天的三个数乘转移表」算今天的三个数,路径信息全被压进这三个数里了。
Demo 里两种算法算出同一个 0.025860,这就是验证。
这个「递推」的思路在整个领域里反复出现。第 9 章的循环神经网络、后面的动态规划、粒子滤波,
骨架都是「用一个固定大小的状态,代替一段越来越长的历史」。

六、解码和滤波:两个容易混的问题

讲到这里要说清两种问法,它们的答案可以不一样:

问法 要什么 算法 Demo 里
整条路径里哪条最可能 一整串天气 Viterbi 晴→晴→晴
第 t 天单独看哪个最可能 每一天各自 前向算法 晴→雨→晴
同三天数据,两种问法给出两条不同的路。这不是算错了,是两个问题的定义本来就不一样。
为什么「每天各自最可能」拼起来不是「最可能的整条路」?
因为每天的边缘概率是「把所有包含它的路径加起来」的结果,而整条路径的概率是「一条路乘到底」。
一个在求和,一个在求最大值,不能互换。
这个问题在实践里很要命。做语音识别要的是「最可能的整句话」(Viterbi),
做实时状态估计要的是「此刻最可能是哪个状态」(滤波)。
问错了,结果就错了,而且错得很难发现——因为两条路看起来都「挺合理」。
最后把整章收一下。一个分布怎么描述(6.1)、怎么随数据更新(6.2)、算不出来怎么办(6.3)、
很多个分布怎么连起来(6.4)——这四件事拼起来,就是「用概率做模型」的完整套路。
下一章开始,重心从「怎么描述不确定」转到「怎么从数据里学」。

七、动手 Demo:一张小网络,和一条会「记住昨天」的链

前三段是贝叶斯网络,后三段是 HMM。两边共用一套思路: 把局部的概率表乘起来,然后筛出你要的那些、加起来。 贝叶斯网络那边用的是「16 种组合全枚举」,简单粗暴但对; HMM 那边必须用前向算法,因为枚举在天数一多就崩了。两边各算一遍同一个量,正好互相验证。

def joint(c, s, r, w):                  # 四个变量的一种组合,乘四张表
    p = P_C if c else 1 - P_C
    p *= P_S[c] if s else 1 - P_S[c]
    p *= P_R[c] if r else 1 - P_R[c]
    q = P_W[(s, r)]
    p *= q if w else 1 - q
    return p

ALL = [(c, s, r, w) for c in (True, False) for s in (True, False)
       for r in (True, False) for w in (True, False)]

# 问什么就筛什么:边缘概率、条件概率都是同一套写法
p_w = sum(joint(*x) for x in ALL if x[3])
p_r_w = sum(joint(*x) for x in ALL if x[2] and x[3]) / p_w

# HMM 的前向算法:一次只推一天,把「路径数」换成「状态数」
alpha = PI * Bm[:, OBS[0] - 1]
for t in range(1, len(OBS)):
    alpha = (alpha.dot(A)) * Bm[:, OBS[t] - 1]
p_obs = alpha.sum()                     # 这串观测出现的总概率

# Viterbi:取对数之后换成「加」,回溯时要记住每个格子的来路
delta = np.log(PI) + np.log(Bm[:, OBS[0] - 1])
for t in range(1, len(OBS)):
    cand = delta[:, None] + np.log(A)
    psi.append(np.argmax(cand, axis=0))
    delta = cand.max(axis=0) + np.log(Bm[:, OBS[t] - 1])

实跑输出:

--------------------------------------------------------------------
一张小网络,和一条会「记住昨天」的链
--------------------------------------------------------------------
==============================================================

[1] 贝叶斯网络:把「谁影响谁」画成箭头

  四个变量:云(C)、洒水器开(S)、下雨(R)、草地湿(W)。
  箭头是:**C 指向 S 和 R,S 和 R 都指向 W。**

  网络只存「每个节点在父节点给定时的概率」,四张表:

    节点          父节点          概率
    --------------------------------------------------------
    云            (无)          P(云=真) = 0.50
    洒水器        云              P(开|云真) = 0.10  P(开|云假) = 0.50
    下雨          云              P(雨|云真) = 0.80  P(雨|云假) = 0.20
    草地湿        洒水器、下雨    四种组合 0.99/0.90/0.90/0.00

  **整张网络一共省下多少个数?**四个变量全联合要 15 个独立参数;
  这四张表只要 1 + 2 + 2 + 4 = 9 个。**变量越多,省得越狠——这就是「用结构换参数」。**

[2] 问一句「草地是湿的,概率多少」

  枚举全部 16 种组合,把「W 为真」的那些加起来:**P(草地湿) = 0.6471**

  下面这张表是把「W 为真」的 12 种组合按概率从大到小排的前几行:

    云    洒水器  下雨  联合概率    占比
    ----------------------------------------------------
    真    关      真    0.3240      50.1%
    假    开      假    0.1800      27.8%
    假    开      真    0.0495      7.6%
    假    关      真    0.0450      7.0%
    真    开      真    0.0396      6.1%
    真    开      假    0.0090      1.4%

  **一次枚举就把「边缘概率」算出来了**——这就是贝叶斯网络的用法:
  参数是局部的,**但只要你问,它能把整张图上的数乘起来给你。**

[3] 解释消除:看见湿草地,洒水器就「背锅」了

    已知条件              P(下雨)     怎么理解
    ------------------------------------------------------------
    什么都不知道          0.5000      先验,云多雨多
    看到草地湿了          0.7079      湿草是雨的证据,涨了
    湿 + 洒水器开         0.3204      洒水器能解释湿,雨的必要性降
    湿 + 洒水器关         1.0000      只剩雨能解释,顶到 1

  **第一行那个 1.0000 是真的**:这套参数里「洒水器关着、草地还湿」只可能由下雨造成。
  **注意第二行和第三行:多知道一件事,对雨的判断反而降了。**这叫解释消除——
  **两个本来互不影响的因(雨和洒水器),一旦看到共同的结果(草地湿),它们就互相「抢功」。**

  这个「互不影响」也可以直接量出来:
    P(雨) × P(洒水器) = 0.5000 × 0.3000 = 0.1500
    P(雨 且 洒水器)  = 0.0900
  **两个数差 0.0600,就是「边缘上也不独立」的痕迹。**
  **它们本来就通过「云」连着**——云多的时候又容易下雨又容易关洒水器(真人会关)。
  只有在**给定云**之后,这两件事才真的互不相干。**这就是「条件独立」。**

[4] HMM:看不见的天气,看得见的冰激凌

  场景:一个人住在没窗的房间,**只能靠每天吃几个冰激凌来猜外面什么天气。**
  天气有三种(晴、阴、雨),**它是隐藏的**;冰激凌个数(1、2、3)是观测到的。

    转移      明天晴      明天阴      明天雨
    ----------------------------------------------
    今天晴    0.70        0.20        0.10
    今天阴    0.30        0.40        0.30
    今天雨    0.20        0.30        0.50

    发射      吃 1 个     吃 2 个     吃 3 个
    ----------------------------------------------
    晴        0.10        0.40        0.50
    阴        0.30        0.40        0.30
    雨        0.60        0.30        0.10

  观测到「3、1、3」这三天。**问:这串观测出现的概率是多少?**

  笨办法:把 3 的 3 次方 = 27 条天气路径全枚举一遍,加起来 = **0.025860**
  概率最大的一条是 晴→晴→晴,它的概率是 0.006125。

[5] 前向算法:不用枚举也能算,而且能算到很长

  前向算法三行就完了,算出来 **0.025860**,跟枚举的 0.025860 一致。

  **为什么非要这么算?**因为枚举是「路径数」,前向是「状态数乘天数」:

    天数    枚举的路径数        前向要乘几次    倍数
    --------------------------------------------------------
    3       3^3 = 2.7e+01       27              1.0e+00
    10      3^10 = 5.9e+04      90              6.6e+02
    20      3^20 = 3.5e+09      180             1.9e+07
    50      3^50 = 7.2e+23      450             1.6e+21

  **五十天而已,枚举就是 7 乘 10 的 23 次方条路径。**
  这就是为什么 HMM 一定要用前向算法——**它把「枚举路径」换成了「递推状态」。**

[6] 解码:最可能的天气序列,不是「每天各自最可能」

  Viterbi 给出的整条路径:**晴→晴→晴**

  「每天各自挑最可能的天气」得到的是:**晴→雨→晴**

  **两种问法,答案可以不一样。**前者问「整条路径里哪条最可能」,
  后者问「第 t 天单独看,哪个天气最可能」。**前者要的是整条路,后者要的是每天。**
  HMM 里这两件事分别叫解码和滤波,用的算法也不同:**解码是 Viterbi,滤波是前向算法。**

  最后一句:**这两个东西(贝叶斯网络和 HMM)看着不像,骨架一模一样——**
  **都是「一堆局部的小概率表,用图连起来,然后问整张图的概率」。**
  区别只在:**HMM 的图是一条链(时间),贝叶斯网络的图是一张网(因果)。**

第一段那个「9 个数对 15 个数」值得停下来算一下。四个二值变量, 联合分布要 2⁴−1 = 15 个独立参数;这张网络只要 9 个。 十个变量呢?联合要 1023 个,而一个每节点最多两个父节点的网络只要几十个。 这就是为什么真实系统里几乎从不直接存联合分布。 第三段那张表是这一节最该记住的。0.5000 → 0.7079 → 0.3204 这个走向, 是「解释消除」最干净的演示:先看到结果(湿),再看到其中一个原因(洒水器), 另一个原因(下雨)的概率就掉了。这不是算法在耍花招,是它在老老实实地做条件概率。 第四段那个 0.006125 和 0.025860 的关系,很多人第一次看会以为是错。 最可能的单条路径只占总概率的四分之一——因为概率被摊到 27 条路上, 没有任何一条能占大头。这在长序列上会更极端: 最可能的路径概率趋近于 0,但它仍然是「最可能的那条」。 第六段那两条不同的路(晴晴晴 对 晴雨晴)是这一节最好的收尾。 它说明「最可能」这三个字必须说清是「哪一级的最可能」—— 是整条序列的联合概率最大,还是某一天的边缘概率最大。 这两种问法在实际系统里对应完全不同的算法,混用会出错。

30 秒实验

  1. 第三段把 P(草地湿 | 洒水器关、不下雨) 从 0.00 改成 0.05,重跑。 「湿 + 洒水器关」那一行会从 1.0000 掉到 0.95 附近—— 这说明那个 1.0 完全是参数造成的,不是网络结构本身的必然。
  2. 第四段把观测从「3、1、3」改成「1、1、1」(连吃三天一个)。 最可能的天气序列会变成「雨→雨→雨」——因为雨的发射概率里「吃 1 个」最高。 这就是发射表在起作用:观测把隐藏状态往哪边拽。
  3. 第五段把天数从 50 改成 100,重跑那张表。 枚举的路径数到 3 的 100 次方,前向还是只乘 900 次。这个对比能让人彻底记住「为什么要递推」。

常见误区

  1. 以为贝叶斯网络能「表示任何相关」。它只能表示你画进箭头里的那些依赖。 两个变量之间如果真有关系而你没画边,网络就会当它们(在给定父节点后)独立。 结构错了,概率算得再准也是错的。
  2. 以为「边缘独立」和「条件独立」是一回事。Demo 里 P(雨 且 洒水器) = 0.09, 而 P(雨)P(洒水器) = 0.15,边缘上不独立;但给定云之后它们独立。 这两句话说的完全不是一件事,混起来会推出很荒唐的结论。
  3. 以为 HMM 的三张表「参数少所以简单」。参数确实少(21 个数), 但它能表示的东西一点都不简单:任意长的时间依赖,全被压进 21 个数里。 代价是它假设「给定当前状态,未来与过去无关」(马尔可夫性), 这个假设在真实数据里常常不成立。
  4. 以为 Viterbi 和「每天取最大」差不多。Demo 里两者给出了不同的路。 而且在长序列上差别会更大:逐点取最大拼出来的那条路, 可能是一条概率极低、甚至根本不可能的路。
自测三问(点开对答案) 1. 为什么贝叶斯网络能省下那么多参数? —— 因为**它只存「每个节点在父节点给定时的概率」,不存整张联合分布。** 四个二值变量联合要 15 个独立参数,这张网络只要 9 个; **变量越多,差距越夸张**(十个变量:1023 对几十)。 代价是**你必须事先知道「谁影响谁」**——**结构对了才省,结构错了就全错。** 2. 「解释消除」到底在说什么? —— 说的是:**两个本来独立的因,在看到共同的结果之后,会变得互相竞争。**Demo 里 P(雨) = 0.5000, 看到草地湿涨到 0.7079, **再知道「洒水器开着」就掉回 0.3204**——因为湿草这个现象已经被洒水器解释掉了, 雨的必要性就小了。 **这是贝叶斯网络区别于「朴素贝叶斯」的关键能力**:朴素贝叶斯假设特征互不影响, **根本表达不了这种「抢功」。** 3. 为什么 HMM 一定要用前向算法,不能直接枚举? —— 因为**枚举的代价是「路径数」= 状态数的 T 次方**,T 是序列长度。 三天是 27 条, 五十天就是 7 乘 10 的 23 次方条—— **算到宇宙毁灭也算不完。**前向算法把「枚举路径」换成「递推状态」, **代价变成「状态数平方乘天数」**,五十天只要 450 次乘法。 **两者算的是同一个数(Demo 里都是 0.025860),但一个不可能,一个瞬间。**

第 6 章到这儿结束。 下一章开始「从数据里学」:没有标签怎么分组(无监督)、有标签怎么分类(有监督)。 前面攒下的概率、距离、梯度,会在那里一次性用上。

相关笔记