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

> **CS221: Artificial Intelligence — Principles and Techniques** · Stanford · Autumn 2025
> 📅 Nov 3 · 💻 [`gibbs_sampling.py`](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/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）**：图如何编码「谁与谁无关」，为什么观测一个变量会「打开」或「堵住」信息通路。

> [!tip] 直觉：两种采样的根本差别
> 拒绝采样「每次从头生成、生成时不看证据、事后检查」；Gibbs 采样「从一个已经满足证据的样本出发，一次只改一个变量、证据永远焊死」。前者的样本彼此独立但大量浪费，后者的样本全部保留但前后相关。
>
> 下一讲 [[Lecture 14 · 贝叶斯网络 III：参数学习]] 会回答最后一个悬而未决的问题：这些局部条件概率本身从哪里来。

---

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

### 1.1 警报网络（Alarm）

本讲反复用同一个玩具例子：**盗窃 $B$（Burglary）、地震 $E$（Earthquake）、警报 $A$（Alarm）**。

```mermaid
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)$$

```python
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)}$$

```python
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$。

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

### 1.2 概率程序 + 拒绝采样

一个**概率程序（probabilistic program）**返回联合分布的一个样本，从而定义了这个分布：

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

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

```python
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)
```

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

---

## 2. Gibbs 采样：一种 MCMC

### 2.1 从「重来」到「续用」

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

```mermaid
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）**的一个特例。

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

### 2.2 例子：电话链 A → B → C

```mermaid
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% 的概率被「噪声」翻转——就像传话游戏。

```python
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。

先跑拒绝采样作对照：

```python
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$** 的任意初始赋值：

```python
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)$$

```python
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"]]
```

> [!tip] 直觉：永远不用实例化联合分布
> 精确推断要把 $2^n$ 项的联合张量整个乘出来；Gibbs 只需要「对**一个具体赋值**求联合概率」——沿着网络把 $n$ 个局部因子逐点相乘，代价 $O(n)$。归一化也只对**被采样那一个变量**的取值域求和（二值变量只需算 2 个数）。指数运算被拆成了大量廉价的局部运算。

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

```python
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 采样通用实现

```python
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)                 # 计数归一化 → 估计分布
```

跑起来：

```python
# 一次迭代
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` 每次都要乘上**所有**局部条件概率——下一节要把它优化掉。

> [!note] 核心节奏 + 实践细节
> 核心节奏：一次只改一个变量；用「联合概率之比」得到它的条件分布；只需对**这个变量**的取值域求和。
> 实践中常见的两个改进（讲义未展开，属常识补充）：初始若干步样本受初始赋值影响大，通常丢弃（称 **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}$$

> [!tip] 直觉：会被约掉的因子不必算
> 因子 $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）**。

> [!note] 定义与关键性质
> 一个节点 $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\}$$

```mermaid
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`：

```python
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)$ |

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

### 3.4 在警报网络上跑 Gibbs

```python
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)
# 注意：这里的估计其实不太准 —— 引出下一节的坑
```

> [!warning] 易错点：Gibbs 不是万灵药
> 讲义特意指出警报网络上的 Gibbs 估计**不够准**（精确值应约为 0.51）。原因在下一节：警报网络里 $B$、$E$、$A$ 之间存在确定性 OR 约束，条件在 $A = 1$ 上之后 $B$ 与 $E$ 高度耦合——「一次改一个变量」的链在这种地形上挪动得非常慢。

---

## 4. 何时用哪种采样

### 4.1 拒绝采样怕：罕见证据

```python
# 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)
```

> [!example] 算一算：拒绝采样浪费到什么程度
> 证据概率 $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 采样怕：高度相关

```python
# 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]$）。反倒是拒绝采样每次从头生成、无证据可拒，完美无偏。

```mermaid
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
```

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

### 4.3 对比总结

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

> [!note] 拓展阅读（讲义给的线索）
> Gibbs 采样是 MCMC 家族的一个特例：每步的「提议」就是精确的单变量条件分布，因此接受率恒为 1。更一般的是 **Metropolis-Hastings**——从任意**提议分布（proposal distribution）**采候选样本，再按接受率纠偏。理论上用**混合时间**刻画有效运行时间（样本相关程度）；实践中 Gibbs 简单有效，但在病态地形上可能非常慢。

---

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

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

> [!tip] 直觉：为什么关心独立性
> 独立性是贝叶斯网络的「负空间」：图里画的边说「谁可能影响谁」，图里**没画**的边说「谁与谁无关」。它一方面解释了参数为什么省（[[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（解释消去）**：观测到公共结果，会让两个本来独立的原因之间产生依赖。

> [!tip] 直觉：回到警报网络
> $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）** 的可操作版本）：

```mermaid
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$ 是否存在一条**不经过任何涂灰节点**的路径（把边视为无向）：有则不独立，无则独立。

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

### 5.4 医疗诊断例子

```mermaid
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$ 已被涂灰，通路仍被阻断。

> [!warning] 易错点：差一个条件结论就反转
> 上面第四条若**只**观测 $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. 总结

```mermaid
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 算法。

---

## 复习自测

> [!question]- Q1：Gibbs 采样凭什么「永不拒绝样本」？它为此付出了什么代价？
> 它把证据变量直接固定在赋值里、只重采样其余变量，每个新赋值天然满足证据，所以没有「事后检查再丢弃」这一步。代价是样本来自一条马尔可夫链、前后高度相关：估计的有效样本量远小于迭代次数，需要更多步数降方差；且收敛速度受混合时间支配，强相关变量下可能极慢甚至（确定性耦合时）根本不收敛。

> [!question]- Q2：写出 Gibbs 对 $X_i$ 重采样的条件分布，并解释为什么只需马尔可夫毯内的因子。
> $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)$。

> [!question]- Q3：算一算：网络 $A \to C \leftarrow B$、$C \to D$ 中，$\mathrm{MB}(A)$ 是什么？为什么定义里必须包含「子节点的其它父节点」？
> $\mathrm{MB}(A) = \{C, B\}$：$C$ 是子节点，$B$ 是子节点 $C$ 的另一个父（$D$ 不在毯内——它是子的子）。必须包含 co-parent 的原因：采样 $A$ 时要评估因子 $p(c \mid a, b)$，其取值依赖 $B$；直觉上这正是 explaining away——$C$ 已知时 $A$ 与 $B$ 相互影响，忽略 $B$ 会算错条件分布。

> [!question]- Q4：公共原因结构与 v-结构在「给定中间节点前后」的独立性如何反转？各举一例。
> 公共原因 $A \leftarrow C \to B$：无条件**不独立**（共同原因造成相关，如「冰淇淋销量与溺水人数」同受夏天驱动），给定 $C$ 后**独立**（知道了季节，两者不再互通信息）。v-结构 $A \to C \leftarrow B$：无条件**独立**（如盗窃与地震），给定 $C$ 后**不独立**（警报响了，得知地震会降低盗窃嫌疑——explaining away）。两者方向恰好相反，是 d-分离规则的核心。

> [!question]- Q5：为什么确定性关系（$B$ 恒等于 $A$）会让 Gibbs 卡死？马尔可夫链的哪个性质被破坏了？用什么办法缓解？
> 从 $(0,0)$ 出发，单变量翻转会产生联合概率为 0 的状态（$A \neq B$），条件分布把所有质量都压在「不变」上，链永远停在初始岛——被破坏的是**遍历性（不可约性）**：状态空间被零概率区域切成互不连通的部分，平稳分布论证失效。缓解办法：块采样（把强耦合变量作为整体联合重采样）、多链不同初始化/随机重启，或给确定性分布加微小噪声恢复连通性。

---

## 参考资料

- 💻 [gibbs_sampling.py](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/gibbs_sampling.py) — 本讲源码
- 💻 [bayes.py](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/bayes.py) — `ProbTable`、`Bernoulli`、拒绝采样（上一讲）
- 📄 [Stochastic Relaxation, Gibbs Distributions, and the Bayesian Restoration of Images (Geman & Geman, 1984)](https://ieeexplore.ieee.org/document/4767596) — Gibbs 采样的奠基论文
- 📄 [Equation of State Calculations by Fast Computing Machines (Metropolis et al., 1953)](https://doi.org/10.1063/1.1699114) — MCMC / Metropolis 算法源头
- 📄 [Monte Carlo Sampling Methods Using Markov Chains (Hastings, 1970)](https://doi.org/10.1093/biomet/57.1.97) — Metropolis-Hastings 推广
- 📖 [Koller & Friedman: Probabilistic Graphical Models (2009)](https://mitpress.mit.edu/9780262013192/probabilistic-graphical-models/) — 第 12 章（Gibbs / MCMC）、d-分离
- 📖 [Bishop: Pattern Recognition and Machine Learning (2006)](https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/) — 第 8.3 节马尔可夫毯、第 11.3 节 Gibbs 采样
- 🌐 [CS221 课程主页](https://stanford-cs221.github.io/) — 讲义与作业
