工作台课程

CS221 · 人工智能:原理与技术

Lecture 13 · 贝叶斯网络 II:Gibbs 采样与条件独立

Lecture 13 · 贝叶斯网络 II:Gibbs 采样与条件独立

CS221: Artificial Intelligence — Principles and Techniques · Stanford · Autumn 2025
📅 Nov 3 · 💻 gibbs_sampling.py


承上启下:从拒绝采样到 Gibbs 采样

上一讲 Lecture 12 · 贝叶斯网络 I:建模与推断 完成了从「状态型模型」到「变量型模型」的转身:

  • 用一组随机变量 $X = (X_1, \dots, X_n)$ 描述世界;
  • 贝叶斯网络 = 有向无环图(DAG)+ 每个节点的局部条件分布 $p(x_i \mid x_{\mathrm{Parents}(i)})$,联合分布是它们的乘积:$P(X_1, \dots, X_n) = \prod_i p(x_i \mid x_{\mathrm{Parents}(i)})$;
  • 概率推断 = 在联合分布上「执行查询」(像 SQL):条件化证据、边缘化其余变量;
  • 近似推断的第一招是拒绝采样(rejection sampling):从概率程序前向采样,丢掉不匹配证据的样本。

本讲(贝叶斯网络 II) 沿着两条线推进:

  • 算法线:拒绝采样的痛点是「证据罕见时几乎所有样本都被丢弃」。本讲引入 Gibbs 采样(Gibbs sampling)——一种 MCMC 方法,固定证据、轮流重采样每个变量,样本永不被拒绝;再用马尔可夫毯(Markov blanket)让每步重采样只做局部计算。
  • 理论线:反过来审视图结构本身——条件独立(conditional independence):图如何编码「谁与谁无关」,为什么观测一个变量会「打开」或「堵住」信息通路。

提示

拒绝采样「每次从头生成、生成时不看证据、事后检查」;Gibbs 采样「从一个已经满足证据的样本出发,一次只改一个变量、证据永远焊死」。前者的样本彼此独立但大量浪费,后者的样本全部保留但前后相关。

下一讲 Lecture 14 · 贝叶斯网络 III:参数学习 会回答最后一个悬而未决的问题:这些局部条件概率本身从哪里来。


1. 回顾:联合分布与拒绝采样

1.1 警报网络(Alarm)

本讲反复用同一个玩具例子:盗窃 $B$(Burglary)、地震 $E$(Earthquake)、警报 $A$(Alarm)

graph TD
    B["B 盗窃"] --> A["A 警报"]
    E["E 地震"] --> A

    style B fill:#e1f5fe,stroke:#0277bd
    style E fill:#e1f5fe,stroke:#0277bd
    style A fill:#ffe0b2,stroke:#e65100

构造联合分布的四步与上一讲一致:定义变量 → 连边 → 每个节点一个局部条件分布 → 相乘:

$$P(B = b, E = e, A = a) = p(b)\, p(e)\, p(a \mid b, e)$$

from bayes import ProbTable, Bernoulli
from einops import einsum

# 3. 每个节点一个局部条件分布
p_b = ProbTable("B", [0.95, 0.05])                # p(b):盗窃先验,5% 概率发生
p_e = ProbTable("E", [0.95, 0.05])                # p(e):地震先验,5% 概率发生
p_a_given_be = ProbTable("A | B E",               # p(a | b, e):警报 = (盗窃 OR 地震)
                         lambda b, e, a: a == (b or e), shape=(2, 2, 2))

# 4. 联合分布 P(B, E, A) = p(b) p(e) p(a | b, e)
P_BEA = ProbTable("B E A",
                  einsum(p_b.p, p_e.p, p_a_given_be.p, "b, e, b e a -> b e a"))

精确推断回答 $P(B \mid A = 1)$:条件化证据取切片、边缘化其余变量、再归一化:

$$P(B = b \mid A = 1) = \frac{\sum_{e} P(B = b, E = e, A = 1)}{\sum_{b', e} P(B = b', E = e, A = 1)}$$

P_BEA1 = ProbTable("B E A=1", P_BEA.p[:, :, 1])        # 条件化 A=1:P(B, E, A=1)
P_BA1  = ProbTable("B A=1", einsum(P_BEA1.p, "b e -> b"))  # 边缘化 E:P(B, A=1)
P_A1   = ProbTable("A=1", einsum(P_BA1.p, "b ->"))    # 证据概率 P(A=1)(归一化常数)
P_B_given_A1 = ProbTable("B | A=1", P_BA1.p / P_A1.p) # 归一化:P(B | A=1)

上一讲手算过这个数:$P(B = 1 \mid A = 1) = 0.05 / 0.0975 \approx 0.51$。

注意

构造完整联合分布可能指数级慢:$n$ 个二值变量的联合表有 $2^n$ 项,先乘出整个张量再边缘化,空间和时间都随变量数指数增长。玩具网络无所谓,变量一多就必须转向采样这类近似方法——这正是本讲的动机。

1.2 概率程序 + 拒绝采样

一个概率程序(probabilistic program)返回联合分布的一个样本,从而定义了这个分布:

def alarm():
    B = Bernoulli(0.05)      # 采样盗窃
    E = Bernoulli(0.05)      # 采样地震
    A = B or E               # 警报是确定性函数
    return {"B": B, "E": E, "A": A}

拒绝采样:反复采样,只保留满足证据的样本,再把查询值计数归一化:

def rejection_sampling(program, query, evidence, num_samples):
    """program: 返回一个样本;query: sample→关注值;evidence: sample→是否保留"""
    counts = defaultdict(int)
    for _ in range(num_samples):
        sample = program()
        if evidence(sample):          # 不满足证据 → 直接丢弃
            counts[query(sample)] += 1
    total_count = sum(counts.values())
    probs = {q: counts[q] / total_count for q in counts}
    return probs

# P(B | A = 1)
query    = lambda sample: sample["B"]
evidence = lambda sample: sample["A"] == 1
result = rejection_sampling(alarm, query, evidence, num_samples=300)

注意

采 $N$ 个样本,期望只有 $N \cdot P(E = e)$ 个通过 if evidence(sample) 这道闸门。警报例子里 $P(A = 1) \approx 0.0975$,300 个样本平均只留下约 29 个;若证据概率是 $10^{-4}$ 量级,有效样本几乎为零——拒绝采样会慢到不可用。


2. Gibbs 采样:一种 MCMC

2.1 从「重来」到「续用」

对比两种采样的样本利用方式:

graph LR
    subgraph 拒绝采样["拒绝采样(大量被丢弃)"]
        R1["sample"] -."丢弃".-> RX1["✗"]
        R2["sample ✓"]
        R3["sample"] -."丢弃".-> RX3["✗"]
        R4["sample ✓"]
    end
    subgraph Gibbs["Gibbs 采样(永不丢弃)"]
        G1["sample"] --> G2["sample"] --> G3["sample"] --> G4["sample"]
    end

    style R2 fill:#c8e6c9,stroke:#2e7d32
    style R4 fill:#c8e6c9,stroke:#2e7d32
    style RX1 fill:#ffcdd2,stroke:#c62828
    style RX3 fill:#ffcdd2,stroke:#c62828
    style G1 fill:#e1f5fe,stroke:#0277bd
    style G2 fill:#e1f5fe,stroke:#0277bd
    style G3 fill:#e1f5fe,stroke:#0277bd
    style G4 fill:#e1f5fe,stroke:#0277bd
拒绝采样 Gibbs 采样
样本关系 每个样本相互独立 样本前后相关(马尔可夫链)
生成方式 每次从头生成整个样本 上一个样本上改一个变量
是否用证据 生成时不看证据 → 常被拒绝 固定证据,永远满足 → 不拒绝
代价 证据罕见时几乎全丢弃 样本相关,需要更多样本降方差

Gibbs 采样基本思想:从一个满足证据的任意完整赋值开始,一次改一个变量——对每个非证据变量 $X_i$,在「给定其余所有变量当前值」的条件分布下重采样:

$$X_i \sim P(X_i \mid X_{-i} = x_{-i})$$

其中 $X_{-i}$ 表示除 $X_i$ 外的全部变量(含被固定的证据变量),$x_{-i}$ 是它们的当前取值。它是马尔可夫链蒙特卡洛(Markov chain Monte Carlo, MCMC)的一个特例。

提示

每一步重采样都定义了状态空间上的一个随机转移,整个过程是一条马尔可夫链——与 Lecture 7 · 马尔可夫决策过程 里的链是同一个数学对象,只是这里没有动作和奖励。可以证明:这条链的平稳分布(stationary distribution)恰好是我们想要的后验 $P(X_{\text{非证据}} \mid E = e)$。只要链能充分「混合」(mix),走得足够久后,样本出现的频率就趋于后验概率——于是「数样本」就等于「算后验」。

2.2 例子:电话链 A → B → C

graph LR
    A["A"] -->|"p(b∣a)"| B["B"]
    B -->|"p(c∣b)"| C["C"]

    style A fill:#c8e6c9,stroke:#2e7d32
    style B fill:#e1f5fe,stroke:#0277bd
    style C fill:#ffe0b2,stroke:#e65100

直觉:A 发一个比特给 B,B 再转发给 C,每一步传话都有 20% 的概率被「噪声」翻转——就像传话游戏。

p_a         = ProbTable("A", [0.5, 0.5])                                      # p(a):均匀先验
p_b_given_a = ProbTable("B | A", lambda a, b: 0.8 if b == a else 0.2, shape=(2, 2))  # 80% 保真
p_c_given_b = ProbTable("C | B", lambda b, c: 0.8 if c == b else 0.2, shape=(2, 2))  # 80% 保真

推断目标:$P(A \mid C = 1)$。直觉:$C = 1$ 会推高 $A = 1$ 的概率,但证据要反向穿过两步「腐蚀」,所以关联被削弱——后验应该高于 0.5 但远低于 0.8。

先跑拒绝采样作对照:

def telephone():
    A = Bernoulli(0.5)
    B = Bernoulli(0.8 if A == 1 else 0.2)
    C = Bernoulli(0.8 if B == 1 else 0.2)
    return {"A": A, "B": B, "C": C}

query    = lambda sample: sample["A"]
evidence = lambda sample: sample["C"] == 1
rejection_probs = rejection_sampling(telephone, query, evidence, num_samples=100)

2.3 条件分布:用联合概率之比算出来

现在跑 Gibbs 采样。先给一个满足证据 $C = 1$ 的任意初始赋值:

x = {"A": 1, "B": 0, "C": 1}   # C = 1 是证据,固定不动;只重采样 A、B

关键一步:如何在「给定 $A = a$、$C = c$」下对 $B$ 重采样?把条件概率写成联合概率之比——分子是把 $B$ 设成某个值后的联合概率,分母对被采样变量 $B$ 求和(归一化常数):

$$P(B = b \mid A = a, C = c) = \frac{P(A = a, B = b, C = c)}{\sum_{b'} P(A = a, B = b', C = c)}$$

而每个联合概率就是局部条件概率的乘积,逐点可算:

$$P(A = a, B = b, C = c) = p(a)\, p(b \mid a)\, p(c \mid b)$$

def joint_prob(x, var, value):
    # x 是当前赋值,如 {A:0, B:0, C:1};(var: value) 是要尝试的改动,如 "B": 1
    y = x | {var: value}          # 更新后的赋值 {A:0, B:1, C:1}
    # 用局部条件概率之积算 P(y)
    return p_a.p[y["A"]] * p_b_given_a.p[y["A"], y["B"]] * p_c_given_b.p[y["B"], y["C"]]

提示

精确推断要把 $2^n$ 项的联合张量整个乘出来;Gibbs 只需要「对一个具体赋值求联合概率」——沿着网络把 $n$ 个局部因子逐点相乘,代价 $O(n)$。归一化也只对被采样那一个变量的取值域求和(二值变量只需算 2 个数)。指数运算被拆成了大量廉价的局部运算。

手动走一遍对 $B$ 的一次重采样($B$ 会被「拉向 1」,因为当前 $A = 1$ 且 $C = 1$,两头都偏好 $B = 1$):

p_ab0c = joint_prob(x, "B", 0)          # P(A=1, B=0, C=1)
p_ab1c = joint_prob(x, "B", 1)          # P(A=1, B=1, C=1)
p_ac   = p_ab0c + p_ab1c                # P(A=1, C=1):只对 B 求和
x["B"] = sample_dict({0: p_ab0c / p_ac, 1: p_ab1c / p_ac})   # 从条件分布采样

2.4 Gibbs 采样通用实现

def gibbs_sampling(init_x, vars, query, joint_prob, num_iterations):
    """从初始赋值 init_x 出发,在 vars(不含证据变量)上循环 num_iterations 次,
    返回 query 值的估计分布。"""
    x = dict(init_x)              # 拷贝一份(好习惯)
    counts = defaultdict(int)     # 记录 query 值的计数

    for _ in range(num_iterations):
        for var in vars:          # 轮流处理每个非证据变量
            # 对该变量的每个取值算未归一化概率 P(..., var=value)
            probs = {value: joint_prob(x, var, value) for value in [0, 1]}
            probs = normalize_dict(probs)         # 归一化成条件分布
            x[var] = sample_dict(probs)           # 从条件分布采样,就地更新
            counts[query(x)] += 1                 # 记录当前查询值

    return normalize_dict(counts)                 # 计数归一化 → 估计分布

跑起来:

# 一次迭代
gibbs_probs = gibbs_sampling(x, vars=["A", "B"], query=query,
                             joint_prob=joint_prob, num_iterations=1)
# 多次迭代收敛
gibbs_probs = gibbs_sampling(x, vars=["A", "B"], query=query,
                             joint_prob=joint_prob, num_iterations=100)
# 与拒绝采样结果对比:两者都在同一「量级」内

运行时间:$O(\text{迭代数} \times \text{变量数} \times |\text{取值域}| \times \text{变量数})$。最后那个「变量数」来自 joint_prob 每次都要乘上所有局部条件概率——下一节要把它优化掉。

说明

核心节奏:一次只改一个变量;用「联合概率之比」得到它的条件分布;只需对这个变量的取值域求和。
实践中常见的两个改进(讲义未展开,属常识补充):初始若干步样本受初始赋值影响大,通常丢弃(称 burn-in);相邻样本高度相关,可每隔 $k$ 步取一个样本(thinning)以降低相关性。


3. 马尔可夫毯:只算局部

3.1 动机:只有一个变量在变

考虑更长的链 $A \to B \to C \to D \to E$,联合概率是 $p(a)\, p(b \mid a)\, p(c \mid b)\, p(d \mid c)\, p(e \mid d)$。每次采样一个变量时都乘上全部因子太浪费——因为只有一个变量在变,大多数因子根本不含这个变量,对结果毫无影响。

回到电话链 $A \to B \to C$,设正在采样 $P(A \mid B = b, C = c)$:

$$\begin{aligned} P(A = 0, B = b, C = c) &= p(a{=}0) \cdot p(b \mid a{=}0) \cdot \color{gray}{p(c \mid b)} \\ P(A = 1, B = b, C = c) &= p(a{=}1) \cdot p(b \mid a{=}1) \cdot \color{gray}{p(c \mid b)} \end{aligned}$$

提示

因子 $p(c \mid b)$ 不含 $A$,在两种情形里取值完全相同,归一化相除时被约掉,对 $P(A \mid B{=}b, C{=}c)$ 毫无影响——直接忽略它!一般规律:采样 $X$ 时,只需保留公式里出现 $X$ 的那些局部因子($X$ 自己的 $p(x \mid \mathrm{parents})$,以及每个子节点的 $p(\text{child} \mid \dots, x, \dots)$)。

3.2 定义与性质

规则:采样某变量时,只需包含涉及该变量的局部条件概率。要评估这些因子,需要知道哪些其他变量的值?这组变量就叫该变量的马尔可夫毯(Markov blanket)

说明

一个节点 $X$ 的马尔可夫毯 $\mathrm{MB}(X)$ = 它的所有父节点、所有子节点,以及子节点的其它父节点(co-parents)。关键性质:给定马尔可夫毯,$X$ 与网络中其余所有变量条件独立:
$$P(X \mid X_{-X}) = P(X \mid \mathrm{MB}(X))$$
三类成员各有来历:父节点出现在 $X$ 自己的因子 $p(x \mid \mathrm{parents})$ 里;子节点与 co-parents 出现在每个子节点的因子 $p(\text{child} \mid \text{其全部父})$ 里。链结构里没有 co-parents,所以容易忘掉第三类——v-结构(两个父共享一个子)才用得上,这与 §5 的 explaining away 是同一件事。

在电话链 $A \to B \to C$ 中:

$$\mathrm{MB}(A) = \{B\}, \qquad \mathrm{MB}(B) = \{A, C\}, \qquad \mathrm{MB}(C) = \{B\}$$

graph TD
    P1["父节点"] --> V(("X"))
    P2["父节点"] --> V
    V --> C1["子节点"]
    V --> C2["子节点"]
    CoP["子节点的<br/>其它父节点"] --> C1

    subgraph MB["X 的马尔可夫毯"]
        P1
        P2
        C1
        C2
        CoP
    end

    style V fill:#ffe0b2,stroke:#e65100
    style P1 fill:#e1f5fe,stroke:#0277bd
    style P2 fill:#e1f5fe,stroke:#0277bd
    style C1 fill:#f3e5f5,stroke:#7b1fa2
    style C2 fill:#f3e5f5,stroke:#7b1fa2
    style CoP fill:#fce4ec,stroke:#c2185b

3.3 用马尔可夫毯加速

joint_prob 换成只含「涉及该变量的因子」的 markov_prob

def markov_prob(x, var, value):
    y = x | {var: value}
    if var == "A":
        return p_a.p[y["A"]] * p_b_given_a.p[y["A"], y["B"]]           # 不含 p(c | b)
    elif var == "B":
        return p_b_given_a.p[y["A"], y["B"]] * p_c_given_b.p[y["B"], y["C"]]   # 不含 p(a)
    else:
        raise ValueError(f"Unknown variable: {var}")

# 用同一个 gibbs_sampling,只是换了 joint_prob
x = {"A": 0, "B": 0, "C": 1}
query = lambda sample: sample["A"]
probs = gibbs_sampling(x, vars=["A", "B"], query=query,
                       joint_prob=markov_prob, num_iterations=100)

运行时间的改进——把最后一个「变量数」换成了「马尔可夫毯大小」:

版本 运行时间
joint_prob(乘全部因子) $O(\text{迭代数} \times \text{变量数} \times \lvert\text{取值域}\rvert \times \textbf{变量数})$
markov_prob(只乘相关因子) $O(\text{迭代数} \times \text{变量数} \times \lvert\text{取值域}\rvert \times \lvert\textbf{马尔可夫毯}\rvert)$

提示

若各节点的马尔可夫毯都很小(图稀疏),每步重采样就是常数代价,与网络总规模无关——这是 Gibbs 采样能在大型贝叶斯网络上实用的关键。反过来,稠密图(比如 Lecture 12 · 贝叶斯网络 I:建模与推断 里全连接的自回归语言模型)马尔可夫毯就是全体变量,局部化不省任何东西。

3.4 在警报网络上跑 Gibbs

p_b = ProbTable("B", [0.95, 0.05])
p_e = ProbTable("E", [0.95, 0.05])
p_a_given_be = ProbTable("A | B E", lambda b, e, a: a == (b or e), shape=(2, 2, 2))

x = {"B": 1, "E": 1, "A": 1}          # 满足证据 A = 1 的初始赋值

def compute_prob(x, var, value):
    y = x | {var: value}
    return p_b.p[y["B"]] * p_e.p[y["E"]] * p_a_given_be.p[y["B"], y["E"], y["A"]]

query = lambda sample: sample["B"]     # 关注 P(B | A = 1)
probs = gibbs_sampling(x, vars=["B", "E"], query=query,
                       joint_prob=compute_prob, num_iterations=200)
# 注意:这里的估计其实不太准 —— 引出下一节的坑

注意

讲义特意指出警报网络上的 Gibbs 估计不够准(精确值应约为 0.51)。原因在下一节:警报网络里 $B$、$E$、$A$ 之间存在确定性 OR 约束,条件在 $A = 1$ 上之后 $B$ 与 $E$ 高度耦合——「一次改一个变量」的链在这种地形上挪动得非常慢。


4. 何时用哪种采样

4.1 拒绝采样怕:罕见证据

# A → B,证据 B = 1 极其罕见
p_a         = ProbTable("A", [0.5, 0.5])
p_b_given_a = ProbTable("B | A", [[0.9999, 0.0001],
                                  [0.9998, 0.0002]])
# 目标 P(A | B = 1)

例子

证据概率 $P(B = 1) = 0.5 \times 0.0001 + 0.5 \times 0.0002 = 0.00015$——平均每 6667 个样本才留下 1 个。而精确后验很好算:$P(A = 1 \mid B = 1) = \frac{0.5 \times 0.0002}{0.00015} = 2/3$。拒绝采样想把这个 $2/3$ 估准,需要的总样本量是天文数字;Gibbs 采样直接把 $B = 1$ 焊死在赋值里,从不拒绝,每一步都是有效样本。

4.2 Gibbs 采样怕:高度相关

# A → B,B 完全复制 A(确定性)
p_a         = ProbTable("A", [0.5, 0.5])
p_b_given_a = ProbTable("B | A", lambda a, b: a == b, shape=(2, 2))
# 目标 P(A)(无证据)
x = {"A": 0, "B": 0}

def joint_prob(x, var, value):
    y = x | {var: value}
    return p_a.p[y["A"]] * p_b_given_a.p[y["A"], y["B"]]

query = lambda sample: sample["A"]
gibbs_probs = gibbs_sampling(x, vars=["A", "B"], query=query,
                             joint_prob=joint_prob, num_iterations=50)

因为 $B$ 恒等于 $A$:从 $(A{=}0, B{=}0)$ 出发,采 $A$ 时 $B = 0$ 逼得 $A = 0$,采 $B$ 时 $A = 0$ 逼得 $B = 0$……链永远卡在 $(0,0)$,探索不到 $A = 1$,50 次迭代给出严重偏差的答案(正确答案显然是均匀的 $[0.5, 0.5]$)。反倒是拒绝采样每次从头生成、无证据可拒,完美无偏。

graph LR
    S00["(A=0, B=0)"] -."卡住".-> S00
    S11["(A=1, B=1)"]
    S00 -. "无法到达" .-x S11

    style S00 fill:#ffcdd2,stroke:#c62828
    style S11 fill:#c8e6c9,stroke:#2e7d32

注意

「Gibbs 收敛到后验」的前提是马尔可夫链遍历(ergodic)——任意两个正概率状态之间可以互达。确定性(或零概率)的局部分布会把状态空间切成互不连通的岛,链困在初始岛里,收敛保证失效。即便不是严格确定、只是「强相关」,链也会混合(mixing)极慢:理论上用混合时间(mixing time)刻画需要多少步才能接近平稳分布。实践补救:多条链不同初始化、随机重启、或一次联合重采样一组强耦合变量(block Gibbs)。

4.3 对比总结

场景 拒绝采样 Gibbs 采样
一般精确性 无偏、独立样本 渐近正确、样本相关
罕见证据 ❌ 几乎全被拒绝,极慢 ✅ 固定证据,工作正常
高度相关变量 ✅ 从头生成,无偏 ❌ 卡住、混合慢
是否复用证据

说明

Gibbs 采样是 MCMC 家族的一个特例:每步的「提议」就是精确的单变量条件分布,因此接受率恒为 1。更一般的是 Metropolis-Hastings——从任意提议分布(proposal distribution)采候选样本,再按接受率纠偏。理论上用混合时间刻画有效运行时间(样本相关程度);实践中 Gibbs 简单有效,但在病态地形上可能非常慢。


5. 条件独立:图结构编码的独立性

到此为止,推断算法对图的具体结构基本无感(马尔可夫毯已经露出一点苗头)。现在正面回答:图结构本身告诉了我们哪些独立性

提示

独立性是贝叶斯网络的「负空间」:图里画的边说「谁可能影响谁」,图里没画的边说「谁与谁无关」。它一方面解释了参数为什么省(Lecture 12 · 贝叶斯网络 I:建模与推断 的紧凑性)、马尔可夫毯为什么成立(§3),另一方面直接指导建模——画错独立性比参数估错更致命。

5.1 独立与不独立

独立(independence):$A$ 与 $B$ 独立(记作 $A \perp B$),当且仅当对所有取值 $a, b$:

$$P(A = a, B = b) = P(A = a)\, P(B = b)$$

用四个最小网络看图结构与独立性的关系:

  • 例1($A \quad B$,无边):$P(a, b) = p(a)\, p(b)$,按定义独立 ✅。
  • 例2($A \to B$):$P(a, b) = p(a)\, p(b \mid a)$,一般不独立 ❌(除非 $p(b \mid a)$ 恰好不依赖 $a$)。
  • 例3($A \to C \leftarrow B$,v-结构/碰撞点 collider)
    $$P(a, b) = \sum_c p(a)\, p(b)\, p(c \mid a, b) = p(a)\, p(b) \underbrace{\sum_c p(c \mid a, b)}_{=1} = p(a)\, p(b)$$
    → $A$ 与 $B$ 独立 ✅(虽然它们通过 $C$ 相连)!
  • 例4($A \leftarrow C \to B$,公共原因 common cause)
    $$P(a, b) = \sum_c p(c)\, p(a \mid c)\, p(b \mid c) \neq p(a)\, p(b) \text{(一般)}$$
    → $A$ 与 $B$ 不独立 ❌(共同原因引起相关)。

5.2 条件独立

条件独立(conditional independence):给定 $C$,$A$ 与 $B$ 条件独立(记作 $A \perp B \mid C$),当且仅当对所有 $a, b, c$:

$$P(A = a, B = b \mid C = c) = P(A = a \mid C = c)\, P(B = b \mid C = c)$$

把例3、例4「条件在 $C$ 上」再看一遍,结论恰好反转

结构 含义 无条件 给定 C
例4 $A \leftarrow C \to B$ 公共原因(common cause) 不独立 条件独立
例3 $A \to C \leftarrow B$ 公共结果/碰撞点(v-结构) 独立 条件独立 ❌
  • 例4 给定 $C$:$P(a, b \mid c) = \dfrac{p(c)\, p(a \mid c)\, p(b \mid c)}{P(C = c)} = p(a \mid c)\, p(b \mid c)$($C$ 是根节点故 $P(C{=}c) = p(c)$,约掉)→ 条件独立。观测到公共原因,$A$、$B$ 就「解耦」了。
  • 例3 给定 $C$:$P(a, b \mid c) \propto p(a)\, p(b)\, p(c \mid a, b)$,最后那个因子把 $a, b$ 绑在一起,无法分解 → 条件独立。这就是 explaining away(解释消去):观测到公共结果,会让两个本来独立的原因之间产生依赖。

提示

$B \to A \leftarrow E$ 正是 v-结构:盗窃与地震无条件独立(先验上互不相干),但给定 $A = 1$(警报响了)就不再独立——此时若再得知是地震,就会降低对盗窃的怀疑。上一讲算过数值:$P(B{=}1 \mid A{=}1) \approx 0.51$ 但 $P(B{=}1 \mid A{=}1, E{=}1) = 0.05$。条件独立理论给这个现象补上了图结构层面的解释。

5.3 判定算法(d-分离)

问题:给定证据集 $C$,判断 $A$ 与 $B$ 是否条件独立?讲义给出的图算法(即 d-分离(d-separation) 的可操作版本):

flowchart TD
    S1["1. 把证据变量 C 涂灰(shade)"] --> S2["2. 递归删除所有未涂灰的叶节点"]
    S2 --> S3["3. 把每个节点的父节点两两相连(marry parents)"]
    S3 --> S4{"4. A 到 B 是否存在<br/>不经过任何涂灰节点的路径?"}
    S4 -->|存在路径| DEP["不独立"]
    S4 -->|无路径| IND["独立"]

    style S1 fill:#e1f5fe,stroke:#0277bd
    style S3 fill:#ffe0b2,stroke:#e65100
    style DEP fill:#ffcdd2,stroke:#c62828
    style IND fill:#c8e6c9,stroke:#2e7d32
  1. 涂灰证据变量 $C$;
  2. 递归删除未涂灰的叶节点——未被观测的叶子只会被边缘化掉,不影响独立性(这一步等价于只保留 $A$、$B$、$C$ 的祖先图 ancestral graph);
  3. 把每个节点的父节点两两相连(”marry parents”,术语叫道德化 moralization)——对应 v-结构里两个父在条件于共同子时产生的依赖;
  4. 看 $A$ 到 $B$ 是否存在一条不经过任何涂灰节点的路径(把边视为无向):有则不独立,无则独立。

说明

这一步专门捕捉 explaining away:当共同子节点(或其后代)被观测/保留时,两个父节点之间要视为直接相连——否则第 4 步的「路径被涂灰节点阻断」规则会漏掉「碰撞点被观测反而打开通路」这一反直觉情形。注意顺序:先删未观测的叶子,再结婚——若碰撞点是未观测的叶子,它会在第 2 步被删掉,其两个父就不会被连上,正确保持独立。

5.4 医疗诊断例子

graph TD
    C["C 感冒"] --> H["H 咳嗽"]
    A["A 过敏"] --> H
    A --> I["I 眼睛痒"]

    style C fill:#e1f5fe,stroke:#0277bd
    style A fill:#e1f5fe,stroke:#0277bd
    style H fill:#ffe0b2,stroke:#e65100
    style I fill:#f3e5f5,stroke:#7b1fa2

变量:感冒 $C$、过敏 $A$、咳嗽 $H$、眼睛痒 $I$(沿用 Lecture 12 · 贝叶斯网络 I:建模与推断 的网络)。用上面的算法逐一判定:

  • $C \perp A$ ✅:唯一通路 $C \to H \leftarrow A$ 是未观测的碰撞点,$H$ 作为未涂灰叶节点先被删除,通路断开;
  • $C \perp I$ ✅:$I$ 只由 $A$ 引起,而 $C$ 与 $A$ 之间无通路(同上),故 $C$ 影响不到 $I$;
  • $C \perp I \mid A$ ✅:$A$ 被涂灰后,路径 $C \cdots A \to I$ 在 $A$ 处被阻断;
  • $C \perp I \mid \{A, H\}$ ✅:观测 $H$ 会「结婚」$C$ 与 $A$(explaining away 打开 $C$–$A$ 依赖),但去往 $I$ 的必经点 $A$ 已被涂灰,通路仍被阻断。

注意

上面第四条若观测 $H$(不观测 $A$):$C \not\perp I \mid H$——碰撞点 $H$ 被观测打开了 $C$–$A$ 依赖,信息可以沿 $C \to H \leftarrow A \to I$ 流动。这正是上一讲「眼睛痒降低感冒概率」的图论解释。判独立性时务必把证据集写全,一个变量之差结论完全相反。

一句话记忆:链($X \to C \to Y$)与分叉($X \leftarrow C \to Y$)——观测中间/公共原因会「阻断」通路;碰撞点($X \to C \leftarrow Y$)相反——观测碰撞点(或其后代)反而「打开」两个父之间的依赖。


6. 总结

mindmap
  root((贝叶斯网络 II))
    精确推断
      构造联合分布
      条件化 + 边缘化(einops)
      指数级慢
    近似推断
      拒绝采样
        样本独立、无偏
        怕罕见证据
      Gibbs 采样(MCMC)
        固定证据、逐变量重采样
        条件分布 = 联合概率之比
        样本相关、怕高相关变量
    马尔可夫毯
      父 + 子 + 子的其它父
      只算局部条件概率
      稀疏图上大幅提速
    条件独立
      图结构编码独立性
      公共原因:给定则独立
      碰撞点:给定则不独立
      d-分离判定算法

关键要点

  • 精确推断(构造联合分布 + 边缘化/条件化)在变量多时指数级慢($2^n$ 项联合表),需要近似方法。
  • Gibbs 采样 = MCMC:固定证据变量,轮流对每个非证据变量按 $P(X_i \mid X_{-i})$ 重采样;链的平稳分布恰是后验,迭代足够久后样本频率趋于后验概率。
  • 条件分布用联合概率之比算:$P(X_i = v \mid X_{-i}) = \dfrac{P(X_i = v, X_{-i})}{\sum_{v'} P(X_i = v', X_{-i})}$——只需对被采样变量的取值域求和,无需实例化整个联合分布
  • 马尔可夫毯 = 父 + 子 + 子的其它父;给定马尔可夫毯,节点与其余变量条件独立,故采样只需毯内因子,复杂度里的「变量数」降为「毯大小」,稀疏图上大幅提速。
  • 拒绝采样 vs Gibbs:前者无偏但怕罕见证据(有效样本 $\propto P(E{=}e)$);后者复用证据但怕高度相关变量——确定性耦合会破坏遍历性、卡死在初始状态,强相关则混合极慢。
  • 条件独立由图结构决定:公共原因($A \leftarrow C \to B$)给定 $C$ 则独立;碰撞点($A \to C \leftarrow B$)无条件独立、给定 $C$ 反而不独立(explaining away)。判定用 d-分离:涂灰证据 → 删未涂灰叶(祖先图)→ 连父(道德化)→ 看是否有避开灰节点的通路。

下一讲预告:目前我们假设局部条件概率 $p(x_i \mid \mathrm{parents})$ 是已知的——如果只有数据、要学出这些参数怎么办?Lecture 14 · 贝叶斯网络 III:参数学习:最大似然与计数、拉普拉斯平滑(Laplace smoothing)、隐变量与 EM 算法。


复习自测

题目

它把证据变量直接固定在赋值里、只重采样其余变量,每个新赋值天然满足证据,所以没有「事后检查再丢弃」这一步。代价是样本来自一条马尔可夫链、前后高度相关:估计的有效样本量远小于迭代次数,需要更多步数降方差;且收敛速度受混合时间支配,强相关变量下可能极慢甚至(确定性耦合时)根本不收敛。

题目

$P(X_i = v \mid X_{-i}) = \dfrac{P(X_i = v, X_{-i})}{\sum_{v'} P(X_i = v', X_{-i})}$,其中联合概率是全部局部因子之积。不含 $X_i$ 的因子在分子分母中取值相同、被约掉,剩下的只有 $X_i$ 自己的因子 $p(x_i \mid \mathrm{parents})$ 和每个子节点的因子 $p(\text{child} \mid \text{其全部父})$;评估它们只需父、子、子的其它父的取值——即 $\mathrm{MB}(X_i)$。

题目

$\mathrm{MB}(A) = \{C, B\}$:$C$ 是子节点,$B$ 是子节点 $C$ 的另一个父($D$ 不在毯内——它是子的子)。必须包含 co-parent 的原因:采样 $A$ 时要评估因子 $p(c \mid a, b)$,其取值依赖 $B$;直觉上这正是 explaining away——$C$ 已知时 $A$ 与 $B$ 相互影响,忽略 $B$ 会算错条件分布。

题目

公共原因 $A \leftarrow C \to B$:无条件不独立(共同原因造成相关,如「冰淇淋销量与溺水人数」同受夏天驱动),给定 $C$ 后独立(知道了季节,两者不再互通信息)。v-结构 $A \to C \leftarrow B$:无条件独立(如盗窃与地震),给定 $C$ 后不独立(警报响了,得知地震会降低盗窃嫌疑——explaining away)。两者方向恰好相反,是 d-分离规则的核心。

题目

从 $(0,0)$ 出发,单变量翻转会产生联合概率为 0 的状态($A \neq B$),条件分布把所有质量都压在「不变」上,链永远停在初始岛——被破坏的是遍历性(不可约性):状态空间被零概率区域切成互不连通的部分,平稳分布论证失效。缓解办法:块采样(把强耦合变量作为整体联合重采样)、多链不同初始化/随机重启,或给确定性分布加微小噪声恢复连通性。


参考资料