这两样东西看着不像,骨架一模一样:都是「一堆局部的小概率表,用图连起来, 然后问整张图的概率」。 区别只在图的形状: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 秒实验
- 第三段把 P(草地湿 | 洒水器关、不下雨) 从 0.00 改成 0.05,重跑。 「湿 + 洒水器关」那一行会从 1.0000 掉到 0.95 附近—— 这说明那个 1.0 完全是参数造成的,不是网络结构本身的必然。
- 第四段把观测从「3、1、3」改成「1、1、1」(连吃三天一个)。 最可能的天气序列会变成「雨→雨→雨」——因为雨的发射概率里「吃 1 个」最高。 这就是发射表在起作用:观测把隐藏状态往哪边拽。
- 第五段把天数从 50 改成 100,重跑那张表。 枚举的路径数到 3 的 100 次方,前向还是只乘 900 次。这个对比能让人彻底记住「为什么要递推」。
常见误区
- 以为贝叶斯网络能「表示任何相关」。它只能表示你画进箭头里的那些依赖。 两个变量之间如果真有关系而你没画边,网络就会当它们(在给定父节点后)独立。 结构错了,概率算得再准也是错的。
- 以为「边缘独立」和「条件独立」是一回事。Demo 里 P(雨 且 洒水器) = 0.09, 而 P(雨)P(洒水器) = 0.15,边缘上不独立;但给定云之后它们独立。 这两句话说的完全不是一件事,混起来会推出很荒唐的结论。
- 以为 HMM 的三张表「参数少所以简单」。参数确实少(21 个数), 但它能表示的东西一点都不简单:任意长的时间依赖,全被压进 21 个数里。 代价是它假设「给定当前状态,未来与过去无关」(马尔可夫性), 这个假设在真实数据里常常不成立。
- 以为 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 章到这儿结束。 下一章开始「从数据里学」:没有标签怎么分组(无监督)、有标签怎么分类(有监督)。 前面攒下的概率、距离、梯度,会在那里一次性用上。
- ☐ (本章完,下一节 7.1 无监督学习)