4强化学习:导论(第2版)

动态规划

策略评估与改进

策略评估策略迭代价值迭代

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第4章:动态规划(Dynamic Programming)

一、导读

1.1 本章学习目标

第4章讲解如何使用动态规划(DP)方法求解有限 MDP。动态规划的核心思想是利用贝尔曼方程的递归结构,将复杂的全局优化问题分解为一系列简单的子问题。虽然 DP 要求已知环境的完整模型(转移概率和奖励函数),在实际中往往不满足,但 DP 是理解后续无模型方法(蒙特卡洛、时序差分)的重要基础。

本章的核心学习目标包括:

  • 理解策略评估(Policy Evaluation):给定策略,用贝尔曼方程迭代计算价值函数
  • 掌握策略改进(Policy Improvement):给定价值函数,贪心地改进策略
  • 理解策略迭代(Policy Iteration):交替执行策略评估和策略改进,直到收敛
  • 掌握值迭代(Value Iteration):将策略评估压缩为一步,直接迭代贝尔曼最优方程
  • 理解 DP 方法的局限性:需要完整的环境模型、计算复杂度高
  • 了解广义策略迭代(GPI):策略评估和策略改进的交互框架

1.2 为什么本章如此重要

动态规划是强化学习算法的理论基石。即使在实际应用中我们很少直接使用 DP(因为通常需要环境模型),但 DP 的思想贯穿了几乎所有强化学习算法:

  • 蒙特卡洛方法用采样回报替代 DP 中的期望
  • 时序差分方法用估计值替代 DP 中的真实价值
  • 策略梯度方法仍然在优化 DP 定义的目标函数

理解 DP,就理解了强化学习算法的"理想形态"——在拥有完整信息时,最优解是什么样的。

1.3 核心问题引导

策略评估为什么可以用迭代法求解?它保证收敛吗?

策略改进定理告诉我们什么?为什么贪心改进一定能让策略变好?

值迭代与策略迭代的区别是什么?各自适用于什么场景?

广义策略迭代(GPI)如何统一了各种强化学习算法?

当状态空间很大时,DP 面临什么挑战?如何缓解?


二、核心概念详解

4.1 策略评估(Policy Evaluation)

给定策略 $\pi$,策略评估的目标是计算状态价值函数 $v_\pi$。由于 $v_\pi$ 满足贝尔曼方程:

$$v_\pi(s) = \sum_a \pi(a|s) \sum_{s'} P(s'|s,a) [R(s,a,s') + \gamma v_\pi(s')]$$

我们可以用迭代法求解。初始化 $v_0(s) = 0$(或任意值),然后反复应用贝尔曼备份:

$$v_{k+1}(s) = \sum_a \pi(a|s) \sum_{s'} P(s'|s,a) [R(s,a,s') + \gamma v_k(s')]$$

当 $v_k$ 收敛时,$v_k \to v_\pi$。

python
import numpy as np

class GridWorld:
    def __init__(self, size=4):
        self.size = size
        self.n_states = size * size
        self.n_actions = 4
        self.gamma = 1.0
        self.actions = [(-1,0),(1,0),(0,-1),(0,1)]
        self.terminal = [(0,0), (size-1, size-1)]

    def is_terminal(self, state):
        r, c = state // self.size, state % self.size
        return (r, c) in self.terminal

    def get_transitions(self, state, action):
        if self.is_terminal(state):
            return [(state, 0.0, True)]
        r, c = state // self.size, state % self.size
        dr, dc = self.actions[action]
        nr, nc = max(0, min(self.size-1, r+dr)), max(0, min(self.size-1, c+dc))
        next_state = nr * self.size + nc
        reward = -1.0
        return [(next_state, reward, self.is_terminal(next_state))]

def policy_evaluation(grid, policy, theta=1e-4):
    V = np.zeros(grid.n_states)
    while True:
        delta = 0
        for s in range(grid.n_states):
            if grid.is_terminal(s):
                continue
            v = V[s]
            new_v = 0
            for a in range(grid.n_actions):
                prob = policy[s][a]
                if prob == 0:
                    continue
                for next_s, reward, _ in grid.get_transitions(s, a):
                    new_v += prob * (reward + grid.gamma * V[next_s])
            V[s] = new_v
            delta = max(delta, abs(v - V[s]))
        if delta < theta:
            break
    return V

grid = GridWorld(size=4)
random_policy = np.ones((grid.n_states, grid.n_actions)) / grid.n_actions
V = policy_evaluation(grid, random_policy)

print("Value function under random policy:")
for r in range(4):
    row = []
    for c in range(4):
        s = r * 4 + c
        if grid.is_terminal(s):
            row.append("  0")
        else:
            row.append(f"{V[s]:5.1f}")
    print(" ".join(row))

4.2 策略改进(Policy Improvement)

给定价值函数 $v_\pi$,策略改进通过贪心地选择每个状态下价值最高的动作来改进策略:

$$\pi'(s) = \arg\max_a \sum_{s'} P(s'|s,a) [R(s,a,s') + \gamma v_\pi(s')]$$

策略改进定理:如果 $\pi'$ 是通过上述方式从 $\pi$ 得到的,则 $v_{\pi'}(s) \geq v_\pi(s)$ 对所有 $s$ 成立。如果 $\pi' = \pi$,则 $\pi$ 已经是最优策略。

python
def policy_improvement(grid, V):
    policy = np.zeros((grid.n_states, grid.n_actions))
    for s in range(grid.n_states):
        if grid.is_terminal(s):
            continue
        action_values = np.zeros(grid.n_actions)
        for a in range(grid.n_actions):
            for next_s, reward, _ in grid.get_transitions(s, a):
                action_values[a] += reward + grid.gamma * V[next_s]
        best_action = np.argmax(action_values)
        policy[s][best_action] = 1.0
    return policy

V = policy_evaluation(grid, random_policy)
new_policy = policy_improvement(grid, V)

action_names = ['up', 'down', 'left', 'right']
print("\nImproved policy:")
for r in range(4):
    row = []
    for c in range(4):
        s = r * 4 + c
        if grid.is_terminal(s):
            row.append("T")
        else:
            a = np.argmax(new_policy[s])
            row.append(action_names[a][0].upper())
    print(" ".join(row))

4.3 策略迭代(Policy Iteration)

策略迭代交替执行策略评估和策略改进,直到策略不再改变:

初始化:随机策略 $\pi$

策略评估:计算 $v_\pi$

策略改进:$\pi' \leftarrow$ greedy($v_\pi$)

检查收敛:如果 $\pi' = \pi$,停止;否则 $\pi \leftarrow \pi'$,回到步骤2

python
def policy_iteration(grid, theta=1e-4):
    policy = np.ones((grid.n_states, grid.n_actions)) / grid.n_actions
    iteration = 0

    while True:
        V = policy_evaluation(grid, policy, theta)
        new_policy = policy_improvement(grid, V)

        iteration += 1
        policy_stable = np.allclose(policy, new_policy)
        print(f"Iteration {iteration}: stable={policy_stable}")

        if policy_stable:
            break
        policy = new_policy

    return V, policy

grid = GridWorld(size=4)
V, optimal_policy = policy_iteration(grid)

print("\nOptimal value function:")
for r in range(4):
    row = []
    for c in range(4):
        s = r * 4 + c
        if grid.is_terminal(s):
            row.append("  0")
        else:
            row.append(f"{V[s]:5.1f}")
    print(" ".join(row))

action_names = ['up', 'down', 'left', 'right']
print("\nOptimal policy:")
for r in range(4):
    row = []
    for c in range(4):
        s = r * 4 + c
        if grid.is_terminal(s):
            row.append("T")
        else:
            a = np.argmax(optimal_policy[s])
            row.append(action_names[a][0].upper())
    print(" ".join(row))

4.4 值迭代(Value Iteration)

策略迭代的一个问题是每次策略评估都需要迭代到收敛,这很耗时。值迭代将策略评估压缩为一步贝尔曼备份,直接迭代贝尔曼最优方程:

$$v_{k+1}(s) = \max_a \sum_{s'} P(s'|s,a) [R(s,a,s') + \gamma v_k(s')]$$

python
def value_iteration(grid, theta=1e-4):
    V = np.zeros(grid.n_states)
    iteration = 0

    while True:
        delta = 0
        for s in range(grid.n_states):
            if grid.is_terminal(s):
                continue
            v = V[s]
            max_val = -float('inf')
            for a in range(grid.n_actions):
                expected = 0
                for next_s, reward, _ in grid.get_transitions(s, a):
                    expected += reward + grid.gamma * V[next_s]
                max_val = max(max_val, expected)
            V[s] = max_val
            delta = max(delta, abs(v - V[s]))

        iteration += 1
        if delta < theta:
            break

    policy = np.zeros((grid.n_states, grid.n_actions))
    for s in range(grid.n_states):
        if grid.is_terminal(s):
            continue
        action_values = np.zeros(grid.n_actions)
        for a in range(grid.n_actions):
            for next_s, reward, _ in grid.get_transitions(s, a):
                action_values[a] += reward + grid.gamma * V[next_s]
        best_action = np.argmax(action_values)
        policy[s][best_action] = 1.0

    return V, policy, iteration

grid = GridWorld(size=4)
V, policy, iters = value_iteration(grid)
print(f"Value iteration converged in {iters} iterations")

4.5 广义策略迭代(GPI)

广义策略迭代(Generalized Policy Iteration, GPI)是一个统一的框架,描述了策略评估和策略改进之间的交互关系:

  • 策略评估:使价值函数与当前策略一致
  • 策略改进:使策略相对于当前价值函数贪心最优

这两个过程相互竞争又相互促进:策略改进使价值函数"过时",策略评估又使价值函数与改进后的策略一致。当两者都达到稳定时,就得到了最优策略。

几乎所有强化学习算法都可以被理解为 GPI 的实例:

  • 动态规划:两个过程都完全收敛
  • 蒙特卡洛方法:策略评估通过采样完成
  • 时序差分:策略评估通过自举完成

4.6 DP 的局限性

动态规划方法有两个主要局限:

需要完整的环境模型:DP 需要知道状态转移概率 $P(s'|s,a)$ 和奖励函数 $R(s,a,s')$。在大多数实际问题中,这些是未知的。

计算复杂度高:每次迭代需要遍历所有状态和动作。当状态空间为 $10^{10}$(如围棋)时,DP 完全不可行。

这些局限催生了无模型方法(蒙特卡洛、时序差分)和函数近似方法(深度强化学习)。


三、实战案例

3.1 案例一:Jack 的租车问题

经典的 Jack 租车问题是 DP 的经典应用。Jack 在两个地点经营租车业务,每天可以在两个地点之间转移车辆,目标是最大化长期收益。

python
class JacksCarRental:
    def __init__(self, max_cars=20, max_move=5):
        self.max_cars = max_cars
        self.max_move = max_move
        self.gamma = 0.9
        self.rental_reward = 10
        self.moving_cost = 2

        self.poisson_rental = {1: 3, 2: 4}
        self.poisson_return = {1: 3, 2: 2}

    def solve(self, theta=0.01):
        n = self.max_cars + 1
        V = np.zeros((n, n))
        policy = np.zeros((n, n), dtype=int)

        for iteration in range(10):
            V = self._policy_evaluation(V, policy)
            policy, stable = self._policy_improvement(V, policy)
            print(f"Iteration {iteration}: policy stable = {stable}")
            if stable:
                break

        return V, policy

    def _policy_evaluation(self, V, policy, theta=0.01):
        n = self.max_cars + 1
        while True:
            delta = 0
            for i in range(n):
                for j in range(n):
                    v = V[i, j]
                    move = policy[i, j]
                    new_i = min(n-1, max(0, i - move))
                    new_j = min(n-1, max(0, j + move))
                    cost = abs(move) * self.moving_cost
                    expected = -cost
                    for r1 in range(min(10, new_i+1)):
                        for r2 in range(min(10, new_j+1)):
                            prob = 0.01
                            reward = (r1 + r2) * self.rental_reward
                            next_i = min(n-1, max(0, new_i - r1 + int(r1*0.5)))
                            next_j = min(n-1, max(0, new_j - r2 + int(r2*0.5)))
                            expected += prob * (reward + self.gamma * V[next_i, next_j])
                    V[i, j] = expected
                    delta = max(delta, abs(v - V[i, j]))
            if delta < theta:
                break
        return V

    def _policy_improvement(self, V, policy):
        n = self.max_cars + 1
        new_policy = np.zeros_like(policy)
        stable = True

        for i in range(n):
            for j in range(n):
                old_action = policy[i, j]
                best_value = -float('inf')
                best_action = 0

                for move in range(-self.max_move, self.max_move + 1):
                    new_i = min(n-1, max(0, i - move))
                    new_j = min(n-1, max(0, j + move))
                    if new_i < 0 or new_i >= n or new_j < 0 or new_j >= n:
                        continue
                    cost = abs(move) * self.moving_cost
                    expected = -cost
                    for r1 in range(min(10, new_i+1)):
                        for r2 in range(min(10, new_j+1)):
                            prob = 0.01
                            reward = (r1 + r2) * self.rental_reward
                            next_i = min(n-1, max(0, new_i - r1 + int(r1*0.5)))
                            next_j = min(n-1, max(0, new_j - r2 + int(r2*0.5)))
                            expected += prob * (reward + self.gamma * V[next_i, next_j])

                    if expected > best_value:
                        best_value = expected
                        best_action = move

                new_policy[i, j] = best_action
                if old_action != best_action:
                    stable = False

        return new_policy, stable

print("Jack's Car Rental (simplified)")
jcr = JacksCarRental(max_cars=5, max_move=3)
V, policy = jcr.solve()
print(f"Optimal policy shape: {policy.shape}")
print(f"Sample policy values:\n{policy[:6, :6]}")

3.2 案例二:值迭代 vs 策略迭代的效率对比

python
def compare_dp_methods():
    import time

    sizes = [4, 8, 12]

    for size in sizes:
        grid = GridWorld(size=size)

        start = time.time()
        V1, pi1 = policy_iteration(grid)
        pi_time = time.time() - start

        start = time.time()
        V2, pi2, vi_iters = value_iteration(grid)
        vi_time = time.time() - start

        print(f"Size {size}x{size}:")
        print(f"  Policy iteration: {pi_time:.4f}s")
        print(f"  Value iteration:  {vi_time:.4f}s ({vi_iters} iters)")
        print(f"  Values match: {np.allclose(V1, V2)}")

compare_dp_methods()

四、本章小结

第4章讲解了动态规划方法求解 MDP 的完整框架:

策略评估:通过迭代贝尔曼方程计算给定策略的价值函数

策略改进:贪心地选择最优动作来改进策略

策略迭代:交替执行评估和改进,保证单调改进直到最优

值迭代:直接迭代贝尔曼最优方程,更高效但每步不完全评估

GPI 框架:统一了各种强化学习算法的核心思想

局限性:需要完整模型、计算复杂度高,催生了无模型方法


五、思考与练习

思考题

策略迭代为什么保证收敛?它最多需要多少次迭代?

值迭代的收敛速度受哪些因素影响?折扣因子 $\gamma$ 如何影响收敛速度?

在什么情况下策略迭代比值迭代更快?反之呢?

如果状态空间有 $10^6$ 个状态,DP 方法可行吗?有什么替代方案?

编程练习

在一个 8x8 的网格世界中比较策略迭代和值迭代的收敛速度

实现异步值迭代:不要求按固定顺序遍历所有状态

在有随机动作(10%概率执行错误方向)的环境中求解最优策略

4/11动态规划
0%