动态规划 本节摘要:动态规划(Dynamic Programming, DP)是「会作弊的强化学习」——你已经知道转移函数和奖励函数,只需反复套用贝尔曼方程,直到 或 不再变化。Bellman 在 1957 年设计了它,至今仍定义着「最优」:当人们说「这个 MDP 的最优策略」时,他们指的就是 DP 会返回的那个策略。本节讲透两大算法——策略迭代(Policy Iteration,评估 + 改进交替)与价值迭代(Value Iteration,一把扫把式合并二者)——以及它们的统一框架广义策略迭代(GPI)。你会在带「打滑」的 GridWorld 上看到两者各 46 次外循环就收敛,产出 。最后解释为什么 γ < 1 是收敛保证的关键(γ-压缩映射)。
本节摘要:动态规划(Dynamic Programming, DP)是「会作弊的强化学习」——你已经知道转移函数和奖励函数,只需反复套用贝尔曼方程,直到
V或π不再变化。Bellman 在 1957 年设计了它,至今仍定义着「最优」:当人们说「这个 MDP 的最优策略」时,他们指的就是 DP 会返回的那个策略。本节讲透两大算法——策略迭代(Policy Iteration,评估 + 改进交替)与价值迭代(Value Iteration,一把扫把式合并二者)——以及它们的统一框架广义策略迭代(GPI)。你会在带「打滑」的 GridWorld 上看到两者各 4~6 次外循环就收敛,产出V*(0,0) ≈ -6。最后解释为什么 γ < 1 是收敛保证的关键(γ-压缩映射)。
对应原课程:Phase 9 · Lesson 02 ·
dynamic-programming(原英文phases/09-reinforcement-learning/02-dynamic-programming/docs/en.md)。
阅读完本节,你应当能够:
V* 校验采样方法(Q 学习、PPO)的实现是否正确。你有一个模型已知的 MDP:对任意「状态-动作」对,都能查询 P(s' | s, a) 和 R(s, a, s')。库存经理知道需求分布;棋类游戏有确定性转移;GridWorld 是四行 Python——你拥有一个模型。
无模型的 RL(Q 学习、PPO、REINFORCE)是为「没有模型、只能采样」的情形发明的。但当你有模型时,有更快、更精确的方法:动态规划。
2026 年你仍然需要 DP,有三个理由。第一,RL 研究里每一个表格型环境(GridWorld、FrozenLake、CliffWalking)都用 DP 求出金标准策略。第二,精确值能用来 debug 采样方法:如果 Q 学习对 V*(s_0) 的估计与 DP 答案差 30%,你的 Q 学习有 bug。第三,现代离线 RL 与规划方法(MCTS、AlphaZero 的搜索、第 10 节的基于模型的 RL)本质上都是在学到的或给定的模型上迭代一次贝尔曼回溯。
策略迭代:交替两个步骤,直到策略不再变化。
π,反复套用 V(s) ← Σ_a π(a|s) Σ_{s',r} P(s',r|s,a) [r + γ V(s')] 直到收敛。V^π,把 π 改成对 V^π 的贪心策略:π(s) ← argmax_a Σ_{s',r} P(s',r|s,a) [r + γ V(s')]。收敛是有保证的:每次改进要么保持 π 不变,要么严格提升某些状态的 V^π,且确定性策略空间有限。即便大状态空间,通常 5~20 次外循环就收敛。
价值迭代:把评估与改进压成一次扫掠,直接套贝尔曼最优性方程:
V(s) ← max_a Σ_{s',r} P(s',r|s,a) [r + γ V(s')]
反复迭代到 max_s |V_new(s) - V(s)| < ε,最后取贪心动作即得策略。每次迭代严格更快(无内层评估循环),但通常需要更多次迭代才能收敛。
贝尔曼算子在上确界范数下是 γ-压缩:||T V - T V'||_∞ ≤ γ ||V - V'||_∞。压缩意味着唯一不动点 + 几何收敛。丢掉 γ < 1 就丢掉了这个保证——你需要有限视野或吸收型终点。
统一框架。价值函数与策略锁在一个双向改进循环里;任何把两者推向相互一致的方法——异步价值迭代、修正策略迭代、Q 学习、Actor-Critic、PPO——都是 GPI 的实例。
💡 GPI 是本章后半所有深度 RL 算法的「心智模型」。看到 Actor-Critic、PPO,你就该想到「评估 + 改进」这对舞步,只是采样代替了枚举。
沿用第 01 节的 4×4 GridWorld,并加一个随机变体:动作以概率 0.1 滑到垂直方向。
SLIP = 0.1 def transitions(state, action): if state == TERMINAL: return [(state, 0.0, 1.0)] outcomes = [] for direction, prob in action_probs(action): outcomes.append((apply_move(state, direction), -1.0, prob)) return outcomes
transitions(s, a) 返回一个 (s', r, p) 列表。这就是整个模型。
给定策略 π(s) = {动作: 概率},反复套贝尔曼方程直到 V 不再动:
def policy_evaluation(policy, gamma=0.99, tol=1e-6): V = {s: 0.0 for s in states()} while True: delta = 0.0 for s in states(): v = sum(pi_a * sum(p * (r + gamma * V[s_prime]) for s_prime, r, p in transitions(s, a)) for a, pi_a in policy(s).items()) delta = max(delta, abs(v - V[s])) V[s] = v if delta < tol: return V
把 π 替换成对 V 的贪心策略。若 π 没变,就到了最优:
def policy_improvement(V, gamma=0.99): new_policy = {} for s in states(): best_a = max( ACTIONS, key=lambda a: sum(p * (r + gamma * V[s_prime]) for s_prime, r, p in transitions(s, a)), ) new_policy[s] = best_a return new_policy
def policy_iteration(gamma=0.99): policy = {s: "up" for s in states()} # 任意起点 for _ in range(100): V = policy_evaluation(lambda s: {policy[s]: 1.0}, gamma) new_policy = policy_improvement(V, gamma) if new_policy == policy: return V, policy policy = new_policy
在 4×4 上典型收敛:4~6 次外迭代。输出 V*(0,0) ≈ -6,以及一个严格递减步数的策略。
def value_iteration(gamma=0.99, tol=1e-6): V = {s: 0.0 for s in states()} while True: delta = 0.0 for s in states(): v = max(sum(p * (r + gamma * V[s_prime]) for s_prime, r, p in transitions(s, a)) for a in ACTIONS) delta = max(delta, abs(v - V[s])) V[s] = v if delta < tol: break policy = policy_improvement(V, gamma) return V, policy
同一个不动点,代码更少。
💡 两种算法都收敛到
V*。规则:小状态空间用价值迭代(代码少、易实现),大状态空间、外循环代价高时用策略迭代(外迭代次数少)。
在 2026 年,DP 是正确性基线与各种规划器的内循环:
| 用例 | 方法 |
|---|---|
| 精确求解小型表格 MDP | 价值迭代(更简单)或策略迭代(外循环更少) |
| 验证 Q 学习 / PPO 实现 | 在玩具环境上对比 DP 的最优 V* |
| 基于模型的 RL(第 10 节) | 在学到的转移模型上做贝尔曼回溯 |
| AlphaZero / MuZero 的规划 | 蒙特卡洛树搜索 = 异步贝尔曼回溯 |
| 离线 RL(CQL、IQL) | 保守 Q 迭代——带 OOD 动作惩罚的 DP |
每次有人提到「最优价值函数」,他们指的就是「DP 不动点」。在论文里看到 V* 或 Q*,脑海里就该浮现这个循环。
stable-baselines3 不直接提供 DP(它是深度 RL 库),但科研里常用的 gymnasium + 自写 DP 是验证手段——frozenlake 这种小型 MDP 几十行 Python 就能跑出精确 V*,再去和 SB3 训出来的 DQN 对比。这种「先求精确解、再校验采样解」的工作流,是 RL 工程师的基本素养。
本节产出一个可复用 skill(位于原课程 outputs/skill-dp-solver.md)。骨架:
--- name: dp-solver description: 用策略迭代或价值迭代精确求解小型表格 MDP,并报告收敛行为。 version: 1.0.0 phase: 9 lesson: 2 tags: [rl, dynamic-programming, bellman] --- 给定一个模型已知的 MDP,输出: 1. 选择。策略迭代 vs 价值迭代。理由要挂到 |S|、|A|、γ。 2. 初始化。V_0、起始策略。对收敛的敏感度。 3. 停止。上确界范数容差 ε。预期扫把数。 4. 验证。精确算出 V*(s_0)。提取贪心策略。 5. 用途。这个基线将如何用于 debug/评估采样方法。 拒绝在状态空间 > 10⁷ 上跑 DP。 拒绝在没有上确界范数检查时声称收敛。 标记任何无限视野任务上 γ ≥ 1 为保证失效。
γ ∈ {0.9, 0.99}。多少次扫把能让 max |ΔV| < 1e-6?把 V* 打印成 4×4 网格。V*(0,0)。哪个在迭代数上更快?在墙钟上呢?k 次扫把而非到收敛。对 k ∈ {1, 2, 5, 10, 50},画出 V*(0,0) 误差曲线。这条曲线告诉你评估 / 改进的取舍是什么?if s == terminal: V[s] = 0 守住。max |V_new - V|,不要用平均。理论保证是在上确界范数上。V[s](高斯-赛德尔)比另开 V_new 字典(雅可比)收敛更快。生产代码用就地。argmax 每次可能不同地破平局,导致「策略稳定」检查震荡。用稳定的破平局(固定顺序中的第一个)。O(|S| · |A|)。到 ~10⁷ 状态还行,再大就要函数逼近(第 05 节起)。P 与 R 时,迭代贝尔曼方程到收敛即可得到最优。max 的扫把;两者收敛到同一个 V*。V 与 π 推向相互一致的方法(Q 学习、Actor-Critic、PPO)都是它的实例。||T V - T V'||_∞ ≤ γ ||V - V'||_∞,保证唯一不动点 + 几何收敛。V*、Q* 就是指 DP 不动点;用来 debug 采样方法。下一节,我们丢掉模型假设——只从完整回合中采样,用蒙特卡洛方法估计价值。这是从「精确」走向「可扩展」的第一步。