# Lecture 7 · 马尔可夫决策过程

> **CS221: Artificial Intelligence — Principles and Techniques** · Stanford · Autumn 2025
> 📅 Oct 13 · 💻 [`mdp.py`](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/mdp.py)

---

## 承上启下：从确定性到随机性

前两讲（[[Lecture 5 · 搜索算法 I]]、[[Lecture 6 · UCS 与 A* 搜索]]）研究的是**搜索问题**：给定起始状态、后继函数（动作、代价、下一状态）和终止判断，找一条总代价最小的动作序列。这套框架有一个隐含的关键假设——**动作的结果是确定性的（deterministic）**：在状态 $s$ 执行动作 $a$，永远到达同一个状态 $\text{Succ}(s,a)$。所以 UCS/A* 才能放心地输出一条固定的路径作为解。

本讲把这个假设去掉：**动作的结果是随机的（stochastic）**。执行同一个动作，可能以不同概率到达不同状态——就像掷骰子，或者出门时无法预知的交通状况。这一泛化得到的模型就是**马尔可夫决策过程（Markov Decision Process, MDP）**。

**动机例子**：你要去超市，选步行、骑车还是开车？开车平均最快，但可能堵车、可能找不到停车位；步行最慢但时间确定。理性的决策不能只看「最好情况」或「最坏情况」，而要在**不确定性下最大化期望收益**——这正是 MDP 的用武之地。

本讲假设我们**完全知道**环境的运作规律（转移概率与奖励）；下一讲 [[Lecture 8 · 强化学习]] 将进一步去掉这个假设，讨论环境是黑盒时如何从交互中学习。

---

## 1. 马尔可夫决策过程（MDP）

### 1.1 历史与命名

MDP 起源于 1950 年代的运筹学（Operations Research），奠基性工作是 Bellman 1957 年的《Dynamic Programming》。这个名字的三个词各有含义：

- **马尔可夫（Markov）**：来自马尔可夫链，指**马尔可夫性质**——给定当前状态，未来与过去条件独立：

$$\mathbb{P}(s_{t+1} \mid s_t, a_t, s_{t-1}, a_{t-1}, \dots, s_0) = \mathbb{P}(s_{t+1} \mid s_t, a_t)$$

  即当前状态 $s_t$ 已经**编码了预测未来所需的全部历史信息**。这不是对世界的断言，而是对「状态怎么定义」的要求：如果历史信息影响未来，就应该把它塞进状态里。
- **决策（Decision）**：与马尔可夫链不同，这里有一个智能体在每一步**主动选择动作**，而不是被动地看链条随机演化。
- **过程（Process）**：事情随时间一步一步顺序发生。

> [!tip] 直觉
> MDP = 马尔可夫链 + 决策。马尔可夫链回答「世界会怎样随机演化」，MDP 额外回答「我该怎么行动才能把这个随机演化引向对我有利的方向」。搜索问题则是 MDP 在「转移概率全为 1」时的退化特例。

### 1.2 形式化定义

一个 MDP 由以下部分组成（沿用课程代码接口）：

```python
class MDP:
    def start_state(self) -> Any:
        """起始状态"""
        raise NotImplementedError
    
    def successors(self, state: Any) -> list[Step]:
        """从 state 出发的后继：(动作, 概率, 奖励, 新状态)"""
        raise NotImplementedError
    
    def is_end(self, state: Any) -> bool:
        """是否为终止状态"""
        raise NotImplementedError
    
    def discount(self) -> float:
        """折扣因子 γ ∈ [0,1]"""
        raise NotImplementedError

@dataclass(frozen=True)
class Step:
    action: Any       # 动作
    prob: float       # 到达该状态的概率
    reward: float     # 获得的奖励
    state: Any        # 到达的状态
```

**数学记号**：

- $\text{Actions}(s)$：状态 $s$ 下所有可用动作的集合；
- $T(s, a, s')$：**转移概率（transition probability）**，在状态 $s$ 执行动作 $a$ 后到达 $s'$ 的概率；
- $\text{Reward}(s, a, s')$：这次转移获得的**奖励（reward）**（可以为负，表示代价）；
- $\gamma \in [0, 1]$：**折扣因子（discount factor）**，控制未来奖励相对当下的重要性；
- 概率归一化约束：对每个合法的 $(s, a)$，

$$\sum_{s'} T(s, a, s') = 1$$

  即执行动作后**必然**落到某个后继状态，$T(s,a,\cdot)$ 是一个合法的概率分布。

> [!warning] 易错点
> 搜索问题里我们**最小化代价（cost）**，MDP 里约定**最大化奖励（reward）**。两者只是符号翻转：把「耗时 1 分钟」写成奖励 $-1$ 即可。写公式或代码时务必确认方向，否则会把「最优」算成「最差」。

### 1.3 例子 1：不可靠的电车

把 [[Lecture 6 · UCS 与 A* 搜索]] 里的电车问题加上随机性：

- 位置编号 $1$ 到 $n$（与搜索版相同），从 $1$ 出发，目标是到达 $n$；
- **步行**：从 $i$ 到 $i+1$，耗时 1 分钟，结果确定；
- **电车**：从 $i$ 到 $2i$，耗时 2 分钟，但有概率 $p$ **故障**——花了 2 分钟却停在原地（官方讲义演示用 $n=10$、$p=0.4$）；
- 目标：**期望**总耗时最短，即最大化期望奖励（奖励 = 负的耗时）。

```mermaid
graph LR
    1 -->|"walk, p=1, r=-1"| 2
    1 -->|"tram 成功, p=0.6, r=-2"| 2B["2 (=2×1)"]
    1 -->|"tram 失败, p=0.4, r=-2"| 1
    
    style 1 fill:#c8e6c9,stroke:#2e7d32
    style 2B fill:#ffe0b2,stroke:#e65100
```

注意同一个动作 `tram` 对应**两条**出边（成功/失败），概率和为 $0.6 + 0.4 = 1$——这正是「动作的结果是分布」的直观体现。

```python
class FlakyTramMDP(MDP):
    def __init__(self, num_locs: int, failure_prob: float):
        self.num_locs = num_locs
        self.failure_prob = failure_prob
    
    def start_state(self) -> int:
        return 1
    
    def successors(self, state: int) -> list[Step]:
        successors = []
        
        # 步行（确定性）
        if state + 1 <= self.num_locs:
            successors.append(Step(
                action="walk", prob=1.0, reward=-1, state=state + 1
            ))
        
        # 电车（随机性）
        if 2 * state <= self.num_locs:
            # 成功：到达目标
            successors.append(Step(
                action="tram", prob=1 - self.failure_prob,
                reward=-2, state=2 * state
            ))
            # 失败：停在原地
            successors.append(Step(
                action="tram", prob=self.failure_prob,
                reward=-2, state=state
            ))
        
        return successors
    
    def is_end(self, state: int) -> bool:
        return state == self.num_locs
    
    def discount(self) -> float:
        return 1.0
```

> [!example] 电车该不该坐？算一笔期望账
> 从状态 $i$ 反复尝试坐电车直到成功，成功前平均需要 $\frac{1}{1-p}$ 次尝试（几何分布的期望），每次花 2 分钟，所以期望耗时 $\frac{2}{1-p}$。当 $p=0.4$ 时约 $3.3$ 分钟就能从 $i$ 跳到 $2i$；而步行需要 $i$ 分钟。$i$ 越大电车越划算——最优策略自然是「位置靠后时坐电车、靠前时步行」，这个权衡会由后面的价值迭代**自动**算出来，无需手工分析。

### 1.4 例子 2：骰子游戏

每一轮你二选一：

- 选 **quit**（退出）：立刻获得 \$10，游戏结束；
- 选 **stay**（继续）：先获得 \$4，然后对方掷 6 面骰子：
  - 掷出 1 或 2（概率 $\tfrac{1}{3}$）：游戏被强制结束；
  - 掷出 3–6（概率 $\tfrac{2}{3}$）：进入下一轮，可以再次选择。

```mermaid
graph LR
    In -->|"quit, p=1, r=10"| End
    In -->|"stay 继续, p=2/3, r=4"| In
    In -->|"stay 终止, p=1/3, r=4"| End
    
    style In fill:#e1f5fe,stroke:#0277bd
    style End fill:#ffe0b2,stroke:#e65100
```

```python
class DiceGameMDP(MDP):
    def start_state(self) -> str:
        return "in"
    
    def successors(self, state: str) -> list[Step]:
        return [
            Step(action="quit", prob=1, reward=10, state="end"),
            Step(action="stay", prob=1/3, reward=4, state="end"),
            Step(action="stay", prob=2/3, reward=4, state="in"),
        ]
    
    def is_end(self, state: str) -> bool:
        return state == "end"
    
    def discount(self) -> float:
        return 1.0
```

这个例子状态极少（只有 `in` 和 `end`），却完整体现了 MDP 的所有要素，后面会用它手算最优策略。注意 stay 的 \$4 是**先拿到手**的——无论骰子结果如何，这一步的奖励都是 4。

### 1.5 MDP vs 搜索问题

| 维度 | 搜索问题 | MDP |
|:---|:---|:---|
| 起始状态 | ✓ start_state | ✓ start_state |
| 终止判断 | ✓ is_end | ✓ is_end |
| 后继函数 | ✓ successors | ✓ successors |
| 优化目标 | 代价（cost），最小化 | 奖励（reward），最大化期望 |
| **核心区别** | 每个动作 → **一个**确定的下一状态 | 每个动作 → 下一状态上的**概率分布** |

---

## 2. 策略（Policy）

### 2.1 定义：为什么解不再是动作序列

**策略（policy）** $\pi$ 是一个从状态到动作的映射函数：

$$\pi : s \mapsto a, \quad a \in \text{Actions}(s)$$

```python
Policy = Callable[[State], Action]
```

- 搜索问题的解是一条**动作序列**（如 `[walk, tram, walk, tram]`）：因为结果确定，未来会经过哪些状态在出发前就已知，提前排好一串动作即可。
- MDP 的解必须是**策略函数** $\pi(s)$：因为结果随机，你无法预知自己会落到哪个状态，所以必须**对每个可能到达的状态都预先备好决策**。

> [!tip] 直觉
> 策略更像「行为准则」而非「行程单」。行程单（动作序列）在第一个意外发生时就作废了；行为准则（策略）则规定「无论我身处何地，都知道下一步做什么」，天然对随机性免疫。这也是「解的形式」随问题假设变化的一个典型例子：假设变弱（确定 → 随机），解就得变强（序列 → 函数）。

### 2.2 示例策略

```python
# 骰子游戏的策略
def always_stay_policy(state: str) -> str:
    return "stay"

def always_quit_policy(state: str) -> str:
    return "quit"

# 电车问题的策略
def always_walk_policy(state: int) -> str:
    return "walk"

def tram_if_possible_policy(mdp: MDP, state: int) -> str:
    """能坐电车就坐，否则步行"""
    if state * 2 <= mdp.num_locs:
        return "tram"
    else:
        return "walk"
```

### 2.3 策略执行（Rollout）

固定一个策略，在 MDP 中实际运行一遍，产生一条随机轨迹，称为一次 **rollout**：智能体查询策略得到动作，环境按 $T(s,a,\cdot)$ 采样下一状态并给出奖励，如此往复直到终止。

```python
def generate_rollout(mdp: MDP, policy: Policy) -> Rollout:
    """执行策略并返回轨迹"""
    steps = []
    state = mdp.start_state()
    
    while not mdp.is_end(state):
        # 策略决定动作
        action = policy(state)
        
        # MDP 根据概率分布采样结果
        successors = [s for s in mdp.successors(state) if s.action == action]
        probs = [s.prob for s in successors]
        choice = np.random.choice(len(successors), p=probs)
        step = successors[choice]
        steps.append(step)
        
        # 进入下一状态
        state = step.state
    
    return Rollout(steps=steps, discount=mdp.discount())
```

注意分工：**策略负责选动作，环境负责掷骰子**。同一个策略跑两次 rollout，得到的轨迹一般不同——这就是为什么评价策略要看「期望」。

### 2.4 效用（Utility）与折扣

一条轨迹的**效用（utility）**定义为**折扣奖励之和**。设轨迹依次获得奖励 $r_1, r_2, r_3, \dots$，则

$$u = r_1 + \gamma r_2 + \gamma^2 r_3 + \cdots = \sum_{t \ge 1} \gamma^{t-1} r_t$$

其中 $\gamma$ 是折扣因子：每晚一步到手的奖励，价值就要打一次 $\gamma$ 的折扣。

```python
def compute_utility(steps: list[Step], discount: float) -> float:
    """计算效用（折扣奖励和）"""
    rewards = [step.reward * discount ** i for i, step in enumerate(steps)]
    return sum(rewards)
```

**折扣因子 $\gamma$ 的两种取值**：

- $\gamma = 1$：所有奖励等权重，适合**保证有限步终止**的问题（如骰子游戏、电车问题）；
- $\gamma < 1$：更看重近期奖励。除了体现「现钱比期货值钱」的偏好，它还有关键的数学作用——对可能无限长的轨迹，只要单步奖励有界 $|r_t| \le R_{\max}$，效用就被几何级数控制住：

$$|u| \le \sum_{t\ge 1} \gamma^{t-1} R_{\max} = \frac{R_{\max}}{1-\gamma} < \infty$$

  从而期望效用是良定义的，后面的迭代算法也因此保证收敛。

> [!example] 折扣的数值效果
> 轨迹奖励为 $[4, 4, 4, 10]$：
> - $\gamma = 1$：$u = 4 + 4 + 4 + 10 = 22$
> - $\gamma = 0.9$：$u = 4 + 4(0.9) + 4(0.81) + 10(0.729) = 18.13$
>
> 越靠后的奖励缩水越狠——最后那笔 \$10 实际只值 \$7.29。

> [!warning] 易错点
> **效用是随机变量，价值是它的期望。**单次 rollout 的效用忽高忽低，不能用来断言策略好坏；策略的「好坏」由下一节定义的期望效用 $V_\pi$ 刻画。混淆这两者是初学最常见的错误。

---

## 3. 策略评估（Policy Evaluation）

### 3.1 问题：给定策略，它有多好？

**策略的价值（value）** $V_\pi(s)$：从状态 $s$ 出发、始终按策略 $\pi$ 行动所得效用的**期望**：

$$V_\pi(s) = \mathbb{E}\left[\sum_{t \ge 1} \gamma^{t-1} r_t \;\middle|\; s_0 = s,\ a_t = \pi(s_t)\right]$$

期望是对所有随机转移取的。策略评估（policy evaluation）就是计算 $V_\pi$。

### 3.2 方法一：蒙特卡洛估计

最直接的办法：跑很多次 rollout，效用取平均。

```python
def monte_carlo_policy_evaluation(mdp: MDP, policy: Policy, num_rollouts: int) -> float:
    """通过采样估计策略价值"""
    utilities = [generate_rollout(mdp, policy).utility for _ in range(num_rollouts)]
    return np.mean(utilities)
```

由大数定律，样本均值收敛到真实期望。但收敛速度是 $O(1/\sqrt{n})$——要把估计精度提高 10 倍，样本量得增加 100 倍，又慢方差又大。既然我们**已知** $T$ 和 $\text{Reward}$，就应该有比「反复试」更聪明的精确算法。

### 3.3 Q 值（Q-value）

先定义一个核心的中间量。给定任意价值函数 $V$，**Q 值**表示「在状态 $s$ 执行动作 $a$，之后的前景按 $V$ 来估价」的期望回报：

$$Q(s, a, V) = \sum_{s'} T(s, a, s') \left[ \text{Reward}(s, a, s') + \gamma \, V(s') \right]$$

逐项读：对每个可能的落点 $s'$，按转移概率 $T$ 加权，回报由两部分组成——这一步的即时奖励 $\text{Reward}(s,a,s')$，加上打过折扣的后续价值 $\gamma V(s')$。

```python
def compute_q_value(successors: list[Step], discount: float, values: dict) -> float:
    """计算 Q(s, a, V)"""
    return sum(
        step.prob * (step.reward + discount * values[step.state])
        for step in successors
    )
```

> [!tip] 直觉
> Q 值是「向前看一步」的算子：它把「未来所有状态的估价 $V$」压缩成「现在这个动作的估价」。策略评估和价值迭代都只是围绕这一个算子做文章——前者把 $V$ 递归地定义成自己的一步展开，后者在展开时额外做一次 $\max$。

### 3.4 贝尔曼方程（策略评估版）

按策略 $\pi$ 行动时，状态 $s$ 的价值满足递归关系（Bellman equation for a fixed policy）：

$$V_\pi(s) = Q(s, \pi(s), V_\pi) = \sum_{s'} T(s, \pi(s), s') \left[ \text{Reward}(s, \pi(s), s') + \gamma \, V_\pi(s') \right]$$

含义：**现在的价值 = 执行 $\pi(s)$ 的期望即时奖励 + 折扣后的期望未来价值**。终止状态的价值恒为 $0$（没有未来了）。这不是一个可以直接代入求值的公式——$V_\pi$ 出现在等号两边——而是一个方程组，解法见下。

### 3.5 策略评估算法：不动点迭代

把贝尔曼方程当作更新规则反复套用，即**自举（bootstrapping）**——用旧的估计值算新的估计值：

$$V_\pi^{(t)}(s) \leftarrow Q\big(s, \pi(s), V_\pi^{(t-1)}\big)$$

```python
def policy_evaluation(mdp: MDP, policy: Policy, max_iters: int = 100, tolerance: float = 1e-5):
    """计算策略 π 的价值"""
    # 初始化：终止状态价值为 0，其余为 -100（悲观的初始猜测，具体数值不影响收敛结果）
    values = {state: 0 if mdp.is_end(state) else -100 for state in get_states(mdp)}
    
    distances = []
    for iter in range(max_iters):
        new_values = {}
        
        for state in values:
            if mdp.is_end(state):
                new_values[state] = 0
                continue
            
            # 只考虑策略选择的动作
            action = policy(state)
            successors = get_action_successors(mdp, state)[action]
            new_values[state] = compute_q_value(successors, mdp.discount(), values)
        
        # 检查收敛
        distance = max(abs(new_values[s] - values[s]) for s in values)
        distances.append(distance)
        if distance < tolerance:
            break
        
        values = new_values
    
    return PolicyEvaluationResult(values=values, distances=distances)
```

**自举过程的语义**：$V^{(t)}$ 恰好等于「按策略执行 $t$ 步然后强制终止」的期望效用——

- 第 0 步：立即终止的价值；
- 第 1 步：执行策略 1 步再终止的价值；
- 第 2 步：执行策略 2 步再终止的价值；
- ……
- 收敛极限：执行策略到底的真实价值 $V_\pi$。

```mermaid
graph LR
    V0["V^(0)<br/>终止"] --> V1["V^(1)<br/>1步+终止"]
    V1 --> V2["V^(2)<br/>2步+终止"]
    V2 --> V3["V^(3)<br/>3步+终止"]
    V3 --> Vn["V^(∞)<br/>真实价值"]
    
    style V0 fill:#ffcdd2,stroke:#c62828
    style Vn fill:#c8e6c9,stroke:#2e7d32
```

> [!note] 收敛的判据与代价
> 每轮迭代用 $\max_s |V^{(t)}(s) - V^{(t-1)}(s)|$ 度量变化，小于容差（如 $10^{-5}$）即停。单轮复杂度为 $O(|S| \cdot |S'|)$（每个状态展开其后继），总代价乘以迭代轮数 $T$。相比蒙特卡洛，这是**确定性的精确计算**，没有采样方差——代价是必须知道完整的 $T$ 和 $\text{Reward}$。另外，求解 $V_\pi$ 本质上是解 $|S|$ 元线性方程组，也可以直接用线性代数求精确解，迭代法胜在实现简单、可随时截断。

---

## 4. 价值迭代（Value Iteration）

### 4.1 从评估到优化

- **策略评估**：计算**给定策略**的价值 $V_\pi$；
- **价值迭代**：计算**最优策略**的价值 $V^*$，并顺带构造最优策略 $\pi^*$。

后者看起来难得多（类比：计算 $f(x)$ 在某点的值 vs 求 $\min_x f(x)$），但在 MDP 里两者的算法几乎一模一样——只多一个 $\max$。

### 4.2 贝尔曼最优方程

**策略评估递归**（固定 $\pi$）：

$$V_\pi(s) = \sum_{s'} T(s, \pi(s), s') \left[ \text{Reward}(s, \pi(s), s') + \gamma \, V_\pi(s') \right]$$

**贝尔曼最优方程**（Bellman optimality equation, Bellman 1957）：

$$V^*(s) = \max_{a \in \text{Actions}(s)} \sum_{s'} T(s, a, s') \left[ \text{Reward}(s, a, s') + \gamma \, V^*(s') \right] = \max_{a} Q(s, a, V^*)$$

**唯一区别**：不再照抄固定策略给的动作 $\pi(s)$，而是对所有动作取 **max**。最优策略随之而来——在每个状态选达到最大 Q 值的那个动作：

$$\pi^*(s) = \arg\max_{a \in \text{Actions}(s)} Q(s, a, V^*)$$

> [!tip] 直觉
> 为什么「逐状态各自贪心」就能得到全局最优策略？因为 $V^*(s')$ 已经**概括了从 $s'$ 出发之后所有未来的最优结果**——马尔可夫性质保证未来只依赖于当前状态。所以在 $s$ 处做决策时，不需要展开整棵未来树，只需比较「一步奖励 + 落点的 $V^*$」。这正是动态规划「最优子结构」思想在随机环境下的版本，与 [[Lecture 6 · UCS 与 A* 搜索]] 中「最短路的子路径也是最短路」一脉相承。

### 4.3 价值迭代算法

```python
def value_iteration(mdp: MDP, max_iters: int = 100, tolerance: float = 1e-5):
    """计算最优价值函数和最优策略"""
    # 初始化
    values = {state: 0 if mdp.is_end(state) else -100 for state in get_states(mdp)}
    pi = {state: None for state in values}  # 最优策略
    
    distances = []
    for iter in range(max_iters):
        new_values = {}
        
        for state in values:
            if mdp.is_end(state):
                new_values[state] = 0
                continue
            
            # 对所有动作计算 Q 值，取最大
            new_values[state], pi[state] = value_iteration_for_state(mdp, state, values)
        
        # 检查收敛
        distance = max(abs(new_values[s] - values[s]) for s in values)
        distances.append(distance)
        if distance < tolerance:
            break
        
        values = new_values
    
    return ValueIterationResult(values=values, pi=pi, distances=distances)

def value_iteration_for_state(mdp: MDP, state: Any, values: dict) -> tuple[float, Any]:
    """计算 V*(state) 和 π*(state)"""
    # V*(s) = max_a Q(s, a, V*)
    actions = []
    q_values = []
    
    for action, successors in get_action_successors(mdp, state).items():
        actions.append(action)
        q_values.append(compute_q_value(successors, mdp.discount(), values))
    
    # 选择最佳动作
    best_value = np.max(q_values)
    best_action = actions[np.argmax(q_values)]
    
    return best_value, best_action
```

### 4.4 例子：手算骰子游戏

> [!example] 求解 $V^*(\text{in})$
> 贝尔曼最优方程（$\gamma = 1$）：
> $$V^*(\text{in}) = \max\Big\{\underbrace{10}_{\text{quit}},\ \underbrace{4 + \tfrac{1}{3}\cdot 0 + \tfrac{2}{3}\, V^*(\text{in})}_{\text{stay}}\Big\}$$
> 假设 stay 是最优的，解方程 $V = 4 + \tfrac{2}{3} V$，得 $V = 12$。验证：$12 > 10$，假设成立。所以
> $$V^*(\text{in}) = 12, \qquad \pi^*(\text{in}) = \text{stay}$$
> **直觉**：一直玩下去平均能拿 \$12（平均玩 3 轮 × 每轮 \$4），高于立刻退出的 \$10，所以最优策略是 stay。注意期望值 12 并不出现在任何单次游戏里——单次可能只拿 \$4，也可能拿 \$20+，12 是长期平均。

### 4.5 收敛性

> [!note] 为什么价值迭代一定收敛？
> 当 $\gamma < 1$ 时，贝尔曼更新是一个**压缩映射（contraction mapping）**：对任意两个价值函数 $U, V$，一轮更新后 $\max_s |U'(s) - V'(s)| \le \gamma \max_s |U(s) - V(s)|$——误差每轮至少缩小到 $\gamma$ 倍。由 Banach 不动点定理，迭代从**任意初始值**出发都指数级收敛到唯一不动点 $V^*$（初始化成 $-100$ 还是 $0$ 只影响速度，不影响结果）。
> 当 $\gamma = 1$ 时没有这个保证，需要额外条件：所有策略都以概率 1 在有限期望步数内终止（如骰子游戏、电车问题）。若存在能无限循环攒奖励的回路且 $\gamma=1$，价值会发散。

---

## 5. 对比与总结

### 5.1 算法对比

| 算法 | 目标 | 递归关系 | 输出 |
|:---|:---|:---|:---|
| **策略评估** | 评估给定策略 $\pi$ | $V_\pi(s) = Q(s, \pi(s), V_\pi)$ | $V_\pi$（无策略） |
| **价值迭代** | 找最优策略 | $V^*(s) = \max_a Q(s, a, V^*)$ | $V^*$ 与 $\pi^*$ |

**共同点**：

- 都基于贝尔曼式递归做**自举**——用当前估计值更新估计值；
- 都迭代至变化量小于容差；
- 每轮复杂度 $O(|S| \cdot |A| \cdot |S'|)$（每个状态、每个动作、枚举每个后继），总复杂度再乘迭代轮数 $T$。

### 5.2 收敛的两个阶段

```mermaid
graph LR
    A["传播阶段<br/>信息从终止状态扩散"] --> B["精化阶段<br/>误差指数衰减 → 0"]
    
    style A fill:#e1f5fe,stroke:#0277bd
    style B fill:#c8e6c9,stroke:#2e7d32
```

观察每轮的 distance 曲线，会看到两个阶段：

1. **传播阶段**：初始时只有终止状态的价值是准的，「终点在哪、值多少」的信息每轮向外扩散一步，需要约等于图的直径的轮数才能触达所有状态；
2. **精化阶段**：信息触达后，误差按压缩映射以 $\gamma$ 的比率指数衰减，曲线在对数坐标下呈直线下降。

### 5.3 MDP 求解流程

```mermaid
flowchart TD
    A["定义 MDP"] --> B{"有给定策略吗?"}
    B -->|有| C["策略评估<br/>Policy Evaluation"]
    B -->|无| D["价值迭代<br/>Value Iteration"]
    C --> E["V_π(s)"]
    D --> F["V*(s) + π*(s)"]
    
    style C fill:#e1f5fe,stroke:#0277bd
    style D fill:#ffe0b2,stroke:#e65100
```

---

## 6. 总结

```mermaid
mindmap
  root((MDP))
    定义
      状态 / 动作 / 转移概率
      奖励 / 折扣因子
      随机结果
    策略
      状态 → 动作
      Rollout → 效用
      价值 V_π = 期望效用
    策略评估
      计算给定策略的价值
      贝尔曼方程（固定 π）
      自举迭代
    价值迭代
      计算最优策略
      贝尔曼最优方程（max over a）
      同时得到 V* 和 π*
```

**关键要点**：

- **MDP** 是搜索问题的随机化推广——动作结果从「一个确定状态」变成「状态上的概率分布」，优化目标从最小化代价变成最大化**期望**效用；
- **策略** $\pi(s)$ 是 MDP 的解的形式（类比搜索的动作序列）：因为无法预知会落到哪个状态，必须对每个状态备好决策；
- **效用**是单条轨迹的折扣奖励和（随机变量），**价值** $V_\pi(s)$ 是它的期望；折扣 $\gamma<1$ 同时表达时间偏好并保证无限期问题良定义；
- **Q 值** $Q(s, a, V)$ 是「向前看一步」的算子：即时奖励 + 折扣后继价值的期望；
- **策略评估**：以 $V_\pi(s) = Q(s, \pi(s), V_\pi)$ 为更新规则自举迭代；
- **价值迭代**：以 $V^*(s) = \max_a Q(s, a, V^*)$ 自举迭代，同时得到 $V^*$ 和 $\pi^*$；
- 两个算法的**唯一区别**：评估用固定的 $\pi(s)$，迭代对动作取 $\max$；$\gamma<1$ 时两者都由压缩映射保证指数收敛。

**下一讲预告**：本讲的一切都建立在「$T$ 和 $\text{Reward}$ 已知」之上。如果环境是黑盒——只能靠试错与它交互、从反馈中学习——怎么办？→ [[Lecture 8 · 强化学习]]。

---

## 复习自测

> [!question]- Q1：为什么 MDP 的解是策略 $\pi(s)$ 而不是像搜索问题那样的动作序列？
> 因为转移是随机的，执行动作后落到哪个状态事先未知，一条固定的动作序列在第一次「意外转移」时就失效了。策略对**每个**可能到达的状态都规定了动作，无论随机性把智能体带到哪里都有决策可用。搜索问题结果确定、路径可预知，所以序列就够了。

> [!question]- Q2：策略评估与价值迭代的递归式只差在哪里？为什么这一点差别就把「评估」变成了「优化」？
> 唯一差别：评估用固定动作 $Q(s,\pi(s),V)$，迭代取 $\max_a Q(s,a,V)$。取 max 意味着每轮更新都在每个状态**局部改进**动作选择；由马尔可夫性质，$V^*(s')$ 已概括后续所有最优行为，所以逐状态贪心即可拼出全局最优（最优子结构），不需要枚举所有策略。

> [!question]- Q3：算一算——骰子游戏中若 stay 的即时奖励从 \$4 降为 \$2，最优策略和 $V^*(\text{in})$ 是多少？
> 设 stay 最优：$V = 2 + \tfrac{2}{3}V \Rightarrow V = 6$，但 $6 < 10$，假设矛盾。故最优是 quit，$V^*(\text{in}) = \max(10,\ 2 + \tfrac{2}{3}\cdot 10) \approx \max(10, 8.67) = 10$，$\pi^*(\text{in}) = \text{quit}$。可见最优策略对参数敏感，贝尔曼方程会自动完成这类权衡。

> [!question]- Q4：$\gamma < 1$ 时价值迭代为什么保证收敛？$\gamma = 1$ 时需要什么额外条件？
> $\gamma<1$ 时贝尔曼更新是压缩映射：每轮迭代把任意两个价值函数间的最大差距至少缩小到原来的 $\gamma$ 倍，故从任意初始化出发都指数收敛到唯一不动点 $V^*$。$\gamma=1$ 时压缩性消失，需要所有策略都以概率 1 有限步终止；若存在可无限逗留且累积奖励的回路，价值会发散。

> [!question]- Q5：单次 rollout 的效用和 $V_\pi(s)$ 是什么关系？为什么不能用一次 rollout 判断策略好坏？
> 效用是随机变量——同一策略每次 rollout 因随机转移得到不同效用；$V_\pi(s)$ 是这个随机变量的期望。单次样本方差可能很大（骰子游戏 always-stay 单次可得 \$4 也可得 \$20+，期望是 \$12），用一次结果比较两个策略相当于用一次抛硬币判断硬币是否公平。蒙特卡洛评估需要大量样本，精度按 $O(1/\sqrt{n})$ 缓慢提升，这正是已知模型时改用贝尔曼迭代的动机。

---

## 参考资料

- 💻 [mdp.py](https://github.com/stanford-cs221/autumn2025-lectures/blob/main/mdp.py)
- 📄 [Dynamic Programming (Bellman, 1957)](https://gwern.net/doc/statistics/decision/1957-bellman-dynamicprogramming.pdf) — 价值迭代的起源
- 📖 [Sutton & Barto: Reinforcement Learning (2nd ed.)](http://incompleteideas.net/book/the-book-2nd.html) — 第 3 章：有限 MDP；第 4 章：动态规划
