3. 动态规划与策略迭代:从理论到代码
动态规划(Dynamic Programming,DP)在强化学习里是个什么角色?说白了,它是解决「已知环境模型」问题的黄金标准。我刚开始学RL时,总觉得DP太数学、太理想化——谁会给你完美的状态转移概率啊?但后来我发现,不理解DP,后面那些更高级的算法(比如Q-learning、Actor-Critic)你根本吃不透。它就像练武的马步,枯燥但必须扎实。
这一章,我们聚焦四个核心概念:策略评估、策略改进、策略迭代和值迭代。我会结合代码和流程图,把每一步掰开揉碎了讲。
3.1 策略评估(Policy Evaluation)
策略评估要解决什么问题?给定一个策略π,我们想知道它到底好不好。好坏的衡量标准就是状态值函数Vπ(s)。
回忆一下贝尔曼期望方程:
Vπ(s) = Σa π(a|s) Σs',r p(s',r|s,a) [r + γ Vπ(s')]
这个方程是自洽的——左边的V依赖于右边的V。怎么解?迭代!
核心思想:从一个初始值函数开始(比如全0),反复应用贝尔曼算子,直到收敛。
具体步骤:
- 初始化V(s)=0,对所有状态s
- 对每个状态s,计算:Vk+1(s) = Σa π(a|s) Σs',r p(s',r|s,a) [r + γ Vk(s')]
- 如果maxs |Vk+1(s) - Vk(s)| < θ,停止;否则回到第2步
这里θ是一个很小的阈值,比如1e-6。我习惯用θ=1e-4做快速原型,等调通了再收紧。
我的经验:在实际项目中,策略评估的收敛速度取决于γ。γ越接近1,收敛越慢。我曾经在一个机器人导航任务里,γ=0.99,跑了200轮才稳定。后来我把γ降到0.9,20轮就差不多了——当然,代价是策略变得短视。这是个trade-off,你得自己权衡。
来看一段Python伪代码:
def policy_evaluation(policy, env, gamma=0.9, theta=1e-4):
V = {s: 0.0 for s in env.states}
while True:
delta = 0.0
for s in env.states:
v = V[s]
new_v = 0.0
for a in env.actions:
action_prob = policy[s][a]
for s_next, reward, prob in env.transitions(s, a):
new_v += action_prob * prob * (reward + gamma * V[s_next])
V[s] = new_v
delta = max(delta, abs(v - new_v))
if delta < theta:
break
return V
这段代码里,env.transitions(s, a)返回的是(s', r, p(s',r|s,a))的列表。注意,这里假设环境模型已知——这正是DP的前提。
3.2 策略改进(Policy Improvement)
评估完策略,下一步就是改进它。怎么改?贪心!
对于每个状态s,我们看看在当前值函数V下,哪个动作能带来最大的期望回报:
π'(s) = argmaxa Σs',r p(s',r|s,a) [r + γ V(s')]
这个新策略π'一定比π好吗?是的,策略改进定理保证了这一点。只要新策略在至少一个状态上严格更好,它就是严格改进的。
注意:策略改进依赖的是当前的值函数V。如果V评估得不准,改进方向可能偏掉。我曾经犯过一个错误:在策略评估还没收敛时就做改进,结果策略来回震荡,就是不收敛。后来我老老实实等评估收敛再做改进,问题就解决了。
代码实现很简单:
def policy_improvement(V, env, gamma=0.9):
policy = {s: {} for s in env.states}
for s in env.states:
action_values = {}
for a in env.actions:
q_value = 0.0
for s_next, reward, prob in env.transitions(s, a):
q_value += prob * (reward + gamma * V[s_next])
action_values[a] = q_value
best_action = max(action_values, key=action_values.get)
policy[s] = {a: 1.0 if a == best_action else 0.0 for a in env.actions}
return policy
3.3 策略迭代算法(Policy Iteration)
把评估和改进串起来,就是策略迭代。流程如下:
- 初始化一个随机策略π
- 策略评估:计算Vπ
- 策略改进:基于Vπ得到π'
- 如果π' == π,停止;否则令π = π',回到第2步
这个算法保证在有限MDP中收敛到最优策略。为什么?因为每次迭代策略都严格改进,而策略数量是有限的(|A||S|),所以最多迭代|A||S|次就停了。
收敛性证明(直觉版):策略改进定理保证Vπ'(s) ≥ Vπ(s)对所有s成立,且至少一个s严格大于。值函数有上界(因为回报有界),所以序列{Vπ}单调递增且有上界,必然收敛。收敛时,策略不再改变,此时满足贝尔曼最优方程。
下面这张图展示了策略迭代的完整流程:
3.4 值迭代算法(Value Iteration)
策略迭代有个缺点:每次策略评估都要内嵌一个迭代循环,计算量大。值迭代把评估和改进合并成一个步骤,效率更高。
值迭代的核心公式:
Vk+1(s) = maxa Σs',r p(s',r|s,a) [r + γ Vk(s')]
你看,这里直接取max,不再依赖当前策略。每次迭代都在做「一步策略改进+一步策略评估」的融合操作。
我的习惯:值迭代的收敛速度通常比策略迭代快,尤其是在状态空间大的时候。但要注意,值迭代收敛的是值函数,策略可能提前稳定。我一般会同时监控值函数变化和策略变化,哪个先稳定就用哪个。
代码实现:
def value_iteration(env, gamma=0.9, theta=1e-4):
V = {s: 0.0 for s in env.states}
while True:
delta = 0.0
for s in env.states:
v = V[s]
action_values = []
for a in env.actions:
q_value = 0.0
for s_next, reward, prob in env.transitions(s, a):
q_value += prob * (reward + gamma * V[s_next])
action_values.append(q_value)
V[s] = max(action_values)
delta = max(delta, abs(v - V[s]))
if delta < theta:
break
# 从收敛的值函数中提取最优策略
policy = {}
for s in env.states:
action_values = {}
for a in env.actions:
q_value = 0.0
for s_next, reward, prob in env.transitions(s, a):
q_value += prob * (reward + gamma * V[s_next])
action_values[a] = q_value
best_action = max(action_values, key=action_values.get)
policy[s] = best_action
return policy, V
3.5 策略迭代 vs 值迭代:对比
这两个算法到底选哪个?我整理了一张对比表:
| 维度 | 策略迭代 | 值迭代 |
|---|---|---|
| 核心操作 | 评估→改进→评估→改进... | 直接更新值函数(取max) |
| 收敛速度 | 迭代次数少(策略快速改进) | 迭代次数多(但每次迭代快) |
| 每次迭代计算量 | 大(内嵌评估循环) | 小(一步到位) |
| 适用场景 | 状态空间小,或需要精确策略 | 状态空间大,或需要快速原型 |
| 收敛保证 | 有限步内收敛到最优 | 渐近收敛到最优 |
避坑指南:我曾经在一个网格世界任务里用策略迭代,状态数只有100,但动作空间是4,策略迭代跑了15轮就收敛了。后来换到连续状态空间(离散化后10000个状态),策略迭代每轮评估都要跑50次内循环,总共花了半小时。换成值迭代后,虽然迭代了200轮,但每轮只要一次扫描,5分钟就出结果了。所以,选哪个取决于你的问题规模。
3.6 收敛性证明(简要)
这两个算法为什么能收敛?我简单说下思路。
策略迭代:
- 策略改进定理保证Vπ' ≥ Vπ(逐点)
- 值函数有上界(回报有界,γ<1)
- 单调递增有上界序列必收敛
- 收敛时满足贝尔曼最优方程
值迭代:
- 贝尔曼最优算子T*是压缩映射(压缩因子γ)
- 根据巴拿赫不动点定理,反复应用T*会收敛到唯一不动点
- 这个不动点就是最优值函数V*
一句话总结:策略迭代靠「单调改进」收敛,值迭代靠「压缩映射」收敛。两种思路都漂亮,但值迭代的数学更优雅——你想想看,一个算子反复作用就能收敛到最优,多简洁。
嗯,到这里,动态规划的核心内容就讲完了。策略评估、策略改进、策略迭代、值迭代,这四个概念是强化学习的基石。后面的章节我们会看到,很多高级算法(比如DQN、A3C)都能追溯到这些基本思想。代码我已经给了,建议你找个简单环境(比如4x4网格世界)跑一跑,亲手感受一下收敛过程。