第三章 动态规划与策略迭代:基于模型的强化学习方法

各位同学,今天我们来聊聊动态规划。说实话,这名字听起来挺吓人,但说白了,它就是一套「拆解问题」的数学工具。在强化学习里,动态规划是我们最早接触的、也是最有「确定性」的一类方法。

我个人习惯把这一章叫做「理想国里的算法」。为什么?因为动态规划要求我们完全知道环境的模型——也就是状态转移概率和奖励函数。这在真实世界里几乎不可能,但理解它,是后面所有算法的基石。

3.1 策略评估:算清楚当前策略到底有多好

先问一个问题:你手里有一个策略 π,它告诉你在每个状态该做什么动作。那这个策略到底好不好?

好,我们就需要量化它。策略评估做的就是这件事——计算每个状态的价值函数 Vπ(s)。

公式很简单,就是贝尔曼期望方程:

Vπ(s) = Σa π(a|s) Σs',r p(s',r|s,a) [r + γ Vπ(s')]

嗯,这里要注意:这个方程是自指的。Vπ(s) 依赖于 Vπ(s'),而 s' 又是下一个状态。怎么解?迭代!

我记得刚开始学的时候,总觉得这玩意儿要解到什么时候才算完。后来发现,只要迭代足够多次,V 会收敛到一个固定点。这就是「迭代策略评估」的核心思想。

核心要点: 策略评估就是反复应用贝尔曼期望方程,直到价值函数不再变化。

3.2 策略改进:让策略变得更好一点

评估完了,然后呢?我们得改进策略啊。

策略改进的思路很直接:在当前状态 s,我们看看有没有比 π(s) 更好的动作。怎么判断?用动作价值函数 Qπ(s,a)。

如果存在某个动作 a',使得 Qπ(s,a') > Vπ(s),那就把策略改成 a'。这就是贪心策略改进。

我在项目中遇到过一个问题:贪心改进会不会导致策略震荡?答案是:不会。策略改进定理保证了,每次改进后,新策略一定比旧策略好,或者至少一样好。

个人经验: 实际写代码时,策略改进这一步往往是最容易出bug的。我曾经因为索引写错,导致策略越改越差,排查了半天才发现是数组下标搞反了。

3.3 策略迭代:评估 + 改进,循环往复

把策略评估和策略改进串起来,就是策略迭代算法。流程如下:

  1. 初始化一个随机策略 π
  2. 策略评估:计算 Vπ
  3. 策略改进:基于 Vπ 生成新策略 π'
  4. 如果 π' == π,停止;否则回到第2步

你想想看,这个过程像不像一个「自我进化」的循环?每次评估完,改进一下;改进了,再评估。直到策略稳定下来。

我建议初学者先手算一个小例子,比如4×4的网格世界。手动迭代两轮,你就能深刻理解「评估」和「改进」是怎么交替进行的。

避坑指南: 我曾经在策略评估时设置了一个固定的迭代次数(比如100次),结果发现某些状态下价值还没收敛。后来改用阈值判断(比如变化小于1e-6),问题就解决了。

3.4 值迭代:换个思路,直接算最优价值

策略迭代需要反复做策略评估,每次评估又要迭代很多次。有没有更高效的方法?有,值迭代。

值迭代的核心思想是:不显式维护策略,而是直接更新价值函数。更新公式是:

Vk+1(s) = maxa Σs',r p(s',r|s,a) [r + γ Vk(s')]

注意看,这里用了 max,而不是策略 π 的期望。也就是说,每次更新时,我们假设自己会选最好的动作。

说白了,值迭代就是把策略改进「揉进」了价值更新里。一步到位,直接逼近最优价值函数。

算法 核心操作 收敛速度 适用场景
策略迭代 评估 + 改进交替 迭代次数少,但每次评估耗时 状态空间较小
值迭代 直接更新价值 迭代次数多,但每次更新快 状态空间较大

3.5 核心逻辑图:动态规划家族

下面我用一张SVG图来展示本章的知识体系。这张图我画了好几次才满意,希望能帮你理清思路。

动态规划与策略迭代知识体系 基于模型的强化学习 策略迭代 值迭代 策略评估 策略改进 循环直到收敛 直接更新价值函数 最终目标:找到最优策略 π* 和最优价值函数 V*

3.6 代码实现:手写一个策略迭代

光说不练假把式。下面是我写的一个简化版策略迭代,用于一个5×5的网格世界。代码不长,但包含了所有核心逻辑。

import numpy as np

# 环境参数
n_states = 25
n_actions = 4  # 上下左右
gamma = 0.9
theta = 1e-6  # 收敛阈值

# 初始化策略和价值
policy = np.zeros(n_states, dtype=int)
V = np.zeros(n_states)

# 策略评估
def policy_evaluation(policy, V, gamma, theta):
    while True:
        delta = 0
        for s in range(n_states):
            v = V[s]
            # 根据策略选择动作
            a = policy[s]
            # 假设确定性转移,到下一个状态
            s_next = get_next_state(s, a)
            reward = get_reward(s, a)
            V[s] = reward + gamma * V[s_next]
            delta = max(delta, abs(v - V[s]))
        if delta < theta:
            break
    return V

# 策略改进
def policy_improvement(policy, V, gamma):
    policy_stable = True
    for s in range(n_states):
        old_action = policy[s]
        # 贪心选择最优动作
        action_values = []
        for a in range(n_actions):
            s_next = get_next_state(s, a)
            reward = get_reward(s, a)
            action_values.append(reward + gamma * V[s_next])
        policy[s] = np.argmax(action_values)
        if old_action != policy[s]:
            policy_stable = False
    return policy, policy_stable

# 主循环
while True:
    V = policy_evaluation(policy, V, gamma, theta)
    policy, stable = policy_improvement(policy, V, gamma)
    if stable:
        break

print("最优策略已找到!")
print(policy.reshape(5, 5))
小提示: 实际项目中,get_next_state 和 get_reward 需要根据具体环境实现。我习惯把它们封装成一个类,方便复用。

3.7 策略迭代 vs 值迭代:怎么选?

这个问题我经常被问到。我的建议是:

  • 状态空间小(比如小于1000):用策略迭代。它收敛需要的迭代次数少,虽然每次评估耗时,但总体更快。
  • 状态空间大:用值迭代。每次更新只做一次扫描,内存占用也小。
  • 如果你不确定:先试值迭代。它实现简单,不容易出错。

我记得有一次做机器人路径规划,状态空间有上万个。策略迭代跑了半天没出结果,换成值迭代后,几分钟就收敛了。嗯,选对工具很重要。

注意: 两种算法都要求环境模型已知。如果不知道转移概率,就别用动态规划了,后面我们会讲蒙特卡洛和TD方法。

好了,这一章的内容就到这里。动态规划是强化学习的「第一性原理」,理解了它,后面的算法你就能触类旁通。代码多跑几遍,手算几个小例子,你会发现自己对「价值」和「策略」的理解会深很多。

可可精品资料库