主题
动态规划
动态规划(Dynamic Programming,DP)是在已知环境模型(转移概率与奖励函数)的前提下求解 MDP 的一类方法。它把 贝尔曼方程 变成迭代更新规则,通过策略评估与策略改进交替进行,最终收敛到最优策略。真实问题中模型往往未知、状态空间也太大,DP 很少直接使用;但它是理解后续所有方法的理论基础——蒙特卡洛、TD 学习乃至深度强化学习,都可以看作在“没有模型”或“状态太多”的条件下对 DP 的近似。
前提与思路
DP 需要两个条件:
- 完整的环境模型
; - 状态和动作空间足够小,可以逐个遍历。
核心思路:既然贝尔曼方程描述了价值函数的不动点,那就把它当作更新规则反复应用,直到收敛。
策略评估(Policy Evaluation)
迭代策略评估
给定策略
从任意
实践中常用原地更新(in-place):只保留一个数组,更新
收敛性
由于贝尔曼期望算子是
因此
扩展阅读:压缩映射与 DP 收敛性的证明
压缩映射。 在完备度量空间
贝尔曼期望算子是压缩映射。 记
对
贝尔曼最优算子也是压缩映射。 对
所以价值迭代以几何速度收敛到唯一的不动点
由此得到的实用结论。
- 若要误差小于
,大约需要 轮, 时约是 的 10 倍。 - 停止准则:若相邻两轮
,由三角不等式可得 ,可据此选择阈值。 - 策略迭代的收敛性:由策略改进定理,每轮策略单调不变差;有限 MDP 的确定性策略只有有限个,所以策略迭代在有限轮内终止于最优策略。
策略改进定理
有了
策略改进定理:若对所有
证明思路是反复展开:
如果贪心改进后策略不再变化,即
策略迭代(Policy Iteration)
交替进行“完整的策略评估”和“贪心策略改进”:
伪代码:
text
初始化 π(s) 为任意动作,V(s) = 0
重复:
# 1. 策略评估
重复直到 Δ < θ:
对每个 s:V(s) ← Σ_{s',r} p(s',r|s,π(s)) [r + γV(s')]
# 2. 策略改进
policy_stable ← True
对每个 s:
old ← π(s)
π(s) ← argmax_a Σ_{s',r} p(s',r|s,a) [r + γV(s')]
若 old ≠ π(s):policy_stable ← False
直到 policy_stable1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
由于有限 MDP 的确定性策略数量有限(
价值迭代(Value Iteration)
策略迭代每一轮都要把策略评估跑到收敛,代价较高。价值迭代把评估截断为只扫描一次,并把改进直接合并进更新式,相当于反复应用贝尔曼最优算子:
收敛后再一次性提取贪心策略:
广义策略迭代(GPI)
策略迭代和价值迭代都是广义策略迭代(Generalized Policy Iteration)的特例:价值函数不断向当前策略的真实价值靠拢(评估),策略不断相对当前价值函数变得贪心(改进)。两个过程既竞争又合作,最终同时收敛到最优。
flowchart LR
P[策略 π] -- 评估:V → v_π --> V[价值 V]
V -- 改进:π → greedy(V) --> P
后面要学的蒙特卡洛控制、SARSA、Q-learning、Actor-Critic,都遵循 GPI 的框架,只是评估和改进的方式不同。
代码:在 FrozenLake 上实现策略迭代与价值迭代
FrozenLake-v1 是一个 4×4 的冰面网格:从左上角 S 出发,到达右下角 G 得到奖励 1,掉进冰洞 H 则回合结束。地面打滑:执行某个方向后,只有 1/3 概率按预期移动,另外各有 1/3 概率滑向两侧的垂直方向。动作编号为 0 左、1 下、2 右、3 上。
py
"""动态规划:在 FrozenLake-v1(4x4,打滑)上实现策略迭代与价值迭代。"""
import gymnasium as gym
import numpy as np
env = gym.make("FrozenLake-v1", is_slippery=True)
P = env.unwrapped.P
nS, nA = env.observation_space.n, env.action_space.n
gamma, theta = 0.99, 1e-8
def q_from_v(v, s):
"""一步前瞻:q(s,a) = Σ p(s',r|s,a)[r + γ v(s')]"""
q = np.zeros(nA)
for a in range(nA):
for prob, s2, r, done in P[s][a]:
q[a] += prob * (r + gamma * v[s2] * (not done))
return q
def policy_evaluation(policy, v=None):
v = np.zeros(nS) if v is None else v.copy()
while True:
delta = 0.0
for s in range(nS):
new = q_from_v(v, s)[policy[s]] # 确定性策略
delta = max(delta, abs(new - v[s]))
v[s] = new # 原地更新(in-place)
if delta < theta:
return v
def policy_iteration():
policy = np.zeros(nS, dtype=int)
v = np.zeros(nS)
for i in range(100):
v = policy_evaluation(policy, v)
new_policy = np.array([np.argmax(q_from_v(v, s)) for s in range(nS)])
if np.array_equal(new_policy, policy):
return policy, v, i + 1
policy = new_policy
def value_iteration():
v = np.zeros(nS)
for i in range(10_000):
delta = 0.0
for s in range(nS):
best = np.max(q_from_v(v, s))
delta = max(delta, abs(best - v[s]))
v[s] = best
if delta < theta:
break
policy = np.array([np.argmax(q_from_v(v, s)) for s in range(nS)])
return policy, v, i + 1
def evaluate(policy, episodes=10_000, seed=0):
rng_env = gym.make("FrozenLake-v1", is_slippery=True)
wins = 0
for ep in range(episodes):
s, _ = rng_env.reset(seed=seed + ep)
done = False
while not done:
s, r, term, trunc, _ = rng_env.step(int(policy[s]))
done = term or trunc
wins += r
return wins / episodes
pi_pi, v_pi, n_pi = policy_iteration()
pi_vi, v_vi, n_vi = value_iteration()
arrows = np.array(["←", "↓", "→", "↑"])
print(f"策略迭代:{n_pi} 轮收敛;价值迭代:{n_vi} 次扫描收敛")
print("两种方法得到的策略是否一致:", np.array_equal(pi_pi, pi_vi))
print("最优策略:\n", arrows[pi_vi].reshape(4, 4))
print("v*(起点) =", round(v_vi[0], 4))
print(f"实际成功率(10000 回合):{evaluate(pi_vi):.3f}")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
实际运行输出:
text
策略迭代:7 轮收敛;价值迭代:324 次扫描收敛
两种方法得到的策略是否一致: True
最优策略:
[['←' '↑' '↑' '↑']
['←' '←' '←' '←']
['↑' '↓' '←' '←']
['←' '→' '↓' '←']]
v*(起点) = 0.542
实际成功率(10000 回合):0.7371
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
几点观察:
- 策略迭代只需 7 轮就收敛(但每轮内部有完整的策略评估),价值迭代需要 324 次扫描(
时收敛较慢)。 - 最优策略看起来“很奇怪”:起点处选择向左。这是因为地面打滑,向左(撞墙)不会掉进洞里,同时有 1/3 的概率滑向下方——智能体利用了打滑机制来规避风险。
- 起点的最优价值
是折扣后的期望回报;实际成功率约 73.7%(环境默认最多 100 步,DP 求解时忽略了这一时间限制)。
DP 的局限
- 需要完整模型:现实中通常拿不到
。→ 蒙特卡洛与 TD 方法直接从经验中学习。 - 维度灾难:每次扫描都要遍历所有状态,复杂度随状态数线性增长,而状态数随状态变量个数指数增长。→ 函数近似(如 DQN)。
- 全宽回溯代价高:每次更新要对所有后继状态求期望。→ 采样回溯。
尽管如此,DP 在中小规模的规划问题(库存管理、路径规划等)中依然实用,异步 DP(按任意顺序、只更新部分状态)和优先扫描(prioritized sweeping)也能显著提升效率。
常见误区
- 忘记处理终止状态:终止状态之后没有未来价值,计算时要把
乘以 。 - 以为策略迭代的评估必须精确收敛:其实只做有限几次扫描(修改的策略迭代)也能收敛到最优,价值迭代就是只扫描一次的极端情况。
- 用价值迭代的中间
当作某个策略的价值:价值迭代过程中的 一般不对应任何策略的真实价值。
小结
- 策略评估:反复应用贝尔曼期望算子,求
。 - 策略改进:对
贪心,得到不差于原策略的新策略。 - 策略迭代 = 完整评估 + 改进交替;价值迭代 = 反复应用贝尔曼最优算子。
- 广义策略迭代(GPI)是几乎所有强化学习控制算法的共同框架。
练习题
- 把例题中的
is_slippery改为False,最优策略和成功率会如何变化?先预测再运行验证。 - 把
从 0.99 改为 0.9,价值迭代需要的扫描次数和得到的策略有何变化?成功率呢? - 实现“修改的策略迭代”:每轮只做
次评估扫描( ),比较收敛所需的总扫描次数。 - 证明:如果策略改进后
,则 。 - 用
FrozenLake-v1的map_name="8x8"重新运行,记录两种方法的耗时。
延伸阅读
交互工具:网格世界:价值迭代 / 策略迭代可视化——在浏览器中编辑地图、调节
参考文献
- Bellman, R. (1957). Dynamic Programming. Princeton University Press.
- Howard, R. A. (1960). Dynamic Programming and Markov Processes. MIT Press.(策略迭代)
- Sutton, R. S., & Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.), 第 4 章.
下一步
继续阅读:蒙特卡洛方法——不需要环境模型,直接从完整回合的经验中估计价值。