资讯详情

资讯详情

策略梯度从原理到实践:REINFORCE与Actor-Critic的演进路径

策略梯度Policy Gradient是强化学习里少数几条“直接优化策略”的路线也是从DQN式的间接决策走向连续控制的关键一步。在之前几讲我们把值函数方法翻来覆去讲透了从Q-learning到DQN走的都是同一条路线先估计“状态-动作”的价值再在决策时对动作空间做最大值操作。这套逻辑在离散动作空间里没问题可一旦进入机器人控制这类连续动作任务就会碰到两堵硬墙。这一讲要解决的就是这两堵墙核心内容是策略梯度的数学原理、REINFORCE落地实现以及从REINFORCE平滑过渡到Actor-Critic的完整路径。无论你是正在被高维连续控制折磨的实践者还是刚接触强化学习、想搞清楚“不学Q值怎么学策略”的新手这篇都值得读完。1. 值函数方法的两块天花板为什么“间接选动作”会撞墙1.1 离散动作里的“查表式”胜利值函数方法在离散动作空间里非常好用。一个离散动作空间通常就是有限个选项在某个状态下采用贪心策略时只需要遍历一遍所有动作逐个比较Q值就能选出最大的一项。经典教学任务“倒立摆”的动作通常只有左右两个遍历成本几乎为零于是很多入门教程都拿它演示DQN。也正因为如此初学者很难体会到隐藏在max操作背后的代价。Q-learning的核心是a* argmax_a Q(s,a)早期表格型版本需要遍历整个动作空间DQN虽然用网络逼近Q函数但决策时仍然要做同样的扫描。动作数量少的时候这没问题动作数量一旦上千或者是连续量这条路就开始吃力了。问题的本质在于值函数方法把“决策”拆成了两步——先评估价值再从价值中反推动作——这个两步流程把推理成本放到了决策阶段而策略梯度把决策本身变成了网络的前向传播动作直接由网络输出推理时不需要任何优化循环。这种差异在部署阶段尤其明显。实时控制系统每毫秒都在做决策如果每一步都要对连续动作空间做一次内部优化延迟会迅速累积到不可接受。策略网络则不同输入状态输出动作分布一次前向就结束。单凭这一点连续控制任务里策略梯度就有天然优势。1.2 连续控制的硬约束与随机策略的缺失连续控制是值函数方法真正的天花板。假设机械臂的关节力矩可以取任意实数动作空间就是一个连续向量空间。这时argmax_a Q(s,a)不再是一个数组遍历而是一个连续最优化问题。在高维连续空间里求解最优化本身就不便宜更别提每次决策都要实时求解这在部署时几乎不可行。值函数方法还有第二个很少被讨论的问题策略表达能力受限。值函数方法最终从Q函数导出一个确定性贪心策略——哪个动作的Q值高就选哪个。训练过程中固然可以用 ε-greedy 加探索但训练收敛后的策略本质上是确定性的。很多环境里这够用可一旦遇到“随机策略才是最优策略”的场景比如石头剪刀布这类需要随机出拳才能最大化的博弈确定性的值函数方法就很难表达正确的行为。策略梯度走的是另一条路网络直接输出动作概率分布 π_θ(a|s)随机性天然内嵌在输出里。探索不再是外挂的 ε 机制而是策略本身的一部分。网络通过调整分布的均值、方差或类别概率把“探索”和“利用”的权衡直接学出来。在部分可观测的场景里随机策略往往能带来更平滑、更鲁棒的行为。理解这两块天花板才能明白为什么我们非要“直接优化策略”。2. 策略梯度定理的直觉和数学为什么“轨迹梯度”只剩策略项2.1 目标函数与log导数小技巧策略梯度的目标是最大化期望回报目标函数写成J(θ) E_{τ ~ p_θ(τ)} [ R(τ) ]这里 τ 是一条完整轨迹也就是s0, a0, r0, s1, a1, r1, ...的组合R(τ) 是该轨迹的累积折扣回报。我们的目标是找到一组策略参数 θ使 J(θ) 最大。问题在于 R(τ) 由环境给出策略参数 θ 是通过影响轨迹出现的概率来影响期望的梯度无法直接穿透环境。解决这个问题的数学工具是 log 导数技巧。对任意一个关于 θ 的概率分布 p_θ(τ)都有恒等式∇_θ p_θ(τ) p_θ(τ) · ∇_θ log p_θ(τ)这个式子本身只是“先取对数再求导再乘回原函数”的逆运算但它把一个难以计算的梯度转化成了“概率乘以某个得分函数”的形式。代入 J(θ) 的期望表达式后原来对轨迹分布求梯度的困难变成了对策略函数取对数的梯度这一步是整个策略梯度方法的基石。2.2 轨迹概率拆开之后发生了什么把轨迹的概率 p_θ(τ) 完全展开会得到p_θ(τ) p(s0) · Π_t p(s_{t1} | s_t, a_t) · π_θ(a_t | s_t)这个乘积由三部分组成初始状态分布、环境状态转移概率、策略概率。关键就在这里——取对数之后再对 θ 求梯度初始状态分布和环境状态转移概率这两项都不含 θ梯度直接归零。剩下的只有策略项∇_θ log p_θ(τ) Σ_t ∇_θ log π_θ(a_t | s_t)所以策略梯度定理可以写成∇_θ J(θ) E_{τ ~ p_θ(τ)} [ (Σ_t ∇_θ log π_θ(a_t | s_t)) · R(τ) ]这个结果的意义怎么强调都不过分我们需要知道环境模型吗不需要。只需要自己策略的梯度和一条采出来的轨迹回报就能估计出目标函数的梯度。这也是“无模型强化学习”的核心来源。很多同学第一次看到这个式子会觉得太简单但它确实就是这么漂亮对数概率提供了“应该往哪个方向调整动作概率”回报提供了一个标量权重告诉算法“这条轨迹值不值得让概率更大”。2.3 一个容易误解的点回报凭什么乘在整个轨迹梯度上实现策略梯度时最常见的两种写法分别是把整条轨迹回报 R(τ) 乘在所有时间步的梯度上或者把“从当前时刻开始的未来回报”乘在当前时刻的梯度上。后者通常被称为 reward-to-go∇_θ J(θ) ≈ Σ_t ∇_θ log π_θ(a_t | s_t) · (Σ_{t≥t} γ^{t-t} r_{t})直觉上容易绕弯子既然回报是整条轨迹的为什么可以只取当前时刻之后的回报答案是因果性。当前时刻 t 的动作只影响 t 之后的回报不影响 t 之前的回报。从期望的角度看把前面的回报项去掉期望值不变但方差会明显降低。去掉的这部分相当于“已知的过去”不包含任何关于当前决策的信息只会引入噪声。实操中我建议从第一步就写成 reward-to-go 的形式而不是先写整条轨迹回报再“优化”。因为策略梯度的方差本身已经很大reward-to-go 几乎是零成本的白送减方差手段先用起来再谈其他优化。3. REINFORCE最直接的策略梯度落地算法3.1 标准REINFORCE执行流程REINFORCE 是策略梯度家族里最朴素的实现就是拿蒙特卡洛采样的方式去估计策略梯度。它的流程大致如下# 伪代码框架细节省略 for episode in range(max_episodes): trajectory [] state env.reset() while not done: action, log_prob policy.sample_with_log_prob(state) next_state, reward, done, _ env.step(action) trajectory.append((log_prob, reward)) state next_state # 从后往前计算折扣回报 returns [0] * len(trajectory) G 0 for t in reversed(range(len(trajectory))): log_prob, reward trajectory[t] G reward gamma * G returns[t] G # 策略梯度更新最大化 J框架里习惯做最小化所以取负号 loss 0 for log_prob, G in zip(log_probs, returns): loss loss - log_prob * (G - baseline) loss loss / len(trajectory) loss.backward() optimizer.step() baseline moving_average(returns)为什么 loss 是负号因为策略梯度原本是梯度上升而深度学习框架通常只提供梯度下降优化器于是我们把目标函数取负最大化期望回报变成最小化负的期望回报。这个符号问题看起来简单实际写代码时如果搞反策略会稳定地变差而且你很难第一时间察觉。3.2 Baseline为什么可行期望不变的数学依据REINFORCE 虽然实现简单方差却往往大到训练无法收敛。一个必要的改良是引入 baseline。算法不再直接使用回报 G而是使用G - b(s)其中 b(s) 是一个只与状态有关的函数最常用的就是状态价值函数的估计值。证明很简单对任意状态相关 baseline b(s)E_{a~π}[ ∇_θ log π_θ(a|s) · b(s) ] Σ_a π_θ(a|s) · b(s) · ∇_θ log π_θ(a|s) b(s) · Σ_a ∇_θ π_θ(a|s) b(s) · ∇_θ (Σ_a π_θ(a|s)) b(s) · ∇_θ 1 0这个推导说明了为什么 baseline 不会给梯度带来偏差它对梯度期望的贡献恒为零。但它的方差影响很大——G 和 b(s) 越相关相减后的剩余方差就越小。实操里最简单的 baseline 是维护一个回报的滑动平均进阶做法是训练一个 Critic 网络去估计 V(s)这正是从 REINFORCE 走向 Actor-Critic 的起点。3.3 REINFORCE的方差问题和缓解手段REINFORCE 用完整轨迹的回报作为梯度权重完整轨迹包含大量随机性策略采样随机性、环境转移随机性、未来决策随机性全部叠加在一起导致 G 的方差非常大。直观理解就是同样一个状态下同一条策略跑十次得到的累计回报可能从 -50 到 80 剧烈波动。缓解方差的手段可以排成一串按性价比从高到低排列第一用 reward-to-go 替代完整回报第二减去一个 baseline第三一次采样多条轨迹取平均相当于在多个蒙特卡洛样本之间平均第四对 advantage 做标准化也就是减均值再除以标准差。第四步严格来说会让梯度带有一定偏差因为它引入了批内统计量但在实践中通常能大幅提升稳定性属于工程上可接受的近似。我的建议是先做一到三确认收敛趋势后再考虑第四步否则容易掩盖算法本身的问题。4. 训练现场最容易翻车的三个细节学习率、奖励尺度与熵正则4.1 学习率随机梯度的脾气比想象中差策略梯度的梯度是一个蒙特卡洛估计方差天然比监督学习的梯度大。监督学习里梯度方向通常比较稳定学习率稍大或者稍小影响有限策略梯度里一个过大的学习率可能让策略在几十个 episode 内直接“变疯”从正常探索变成原地打转甚至彻底退化。我自己的经验是策略梯度的学习率通常要比同量级的监督任务小一个数量级起步。拿分类任务的手感去调策略梯度十有八九会翻车。调试时优先把学习率固定在一个保守值比如 3e-4 到 1e-3然后观察 return 曲线的走势。如果曲线只是噪声大但趋势向上说明学习率基本可用如果曲线一开始就剧烈下探先别急着加复杂度把学习率再降一半试试。与此同时打开梯度裁剪把梯度范数限制在一个合理范围内这一步能挡住大部分偶发的梯度爆炸。4.2 奖励尺度不一致同一套代码换环境就失效策略梯度的梯度大小受回报绝对尺度影响很大。同一个网络、同一个学习率在一个奖励动辄上百的环境里可能正常收敛换到另一个奖励始终在 ±0.1 之间的环境梯度就会小到更新几乎无效。这不是网络的问题而是 reward 的尺度没有对齐。常见的处理方法有三种一是对 advantage 做标准化把均值拉到 0、方差拉到 1二是维护一个可学习的 baseline让网络自己去适应奖励尺度三是对原始奖励做 clip 或 scale使奖励数值落在合理范围。第一个方法最直接但要注意批内统计量带来的偏差第二个方法更自然但需要额外维护一个 Critic 网络第三个方法最粗暴不过在这里经常也够用。我曾经在连续控制任务里被这个问题坑过很久算法在某一个仿真环境里跑得很稳换到另一个环境后完全不动。后来把每个 episode 的回报打印出来才意识到新环境奖励的整体量级比旧环境小了两个数量级。处理完奖励尺度之后同一个算法几乎没有再改任何超参数就恢复了正常。所以遇到“代码没变但结果崩了”的情况先去看奖励分布。4.3 熵正则防止策略过早“上头”策略梯度还有一个常见病策略熵在训练早期就急速下降很快变成接近确定性的分布。表面上看这似乎说明“策略已经很有信心了”但实际上很可能是过早收敛到了局部最优探索不足后续不管怎么更新都很难跳出去。对策是加入熵正则项。策略熵 H(π_θ(·|s)) 越大策略越随机越小越接近确定性分布。把负熵加入损失函数等于在“最大化回报”和“保持探索”之间做一个显式的折中loss -Σ_t log π_θ(a_t|s_t) · A_t - β · H(π_θ(·|s_t))β 是熵系数通常从 0.01 量级开始试。训练时应该持续监控策略熵的平均值如果熵在前几百个 episode 内就趋近于零大概率是 β 太小或学习率太大策略在真正探索到足够多区域之前就被“锁死”了。正常的训练里熵应该是缓慢下降的而不是雪崩式归零。记住这一点可以帮你省下大量的调试时间。5. 从REINFORCE到Actor-Critic用价值函数压缩方差5.1 蒙特卡洛回报的方差来源REINFORCE 里梯度权重是完整轨迹的累计回报这条轨迹从起点到终点经历了无数次随机决策。即使从同一个状态出发后续的探索路线也会完全不同因此累计回报的波动极大。REINFORCE 虽然是无偏的但高方差导致梯度信噪比很低需要大量采样才能得到稳定的更新方向。解决思路是找一个更稳定、方差更小的量来替代累计回报。一个自然的候选是状态价值函数 V(s)它表示“从这个状态出发期望能得到多少回报”。V(s) 本身就是一种期望天然比单次采样的累计回报稳定得多。我们不需要精确知道未来会发生什么只需要一个对未来的期望估计就能告诉策略“当前这个动作相对平均水平好还是差”这就是 advantage 的思想。5.2 Actor-Critic如何工作Actor-Critic 包含两个组件Actor 是策略网络输出动作分布Critic 是价值网络输出状态价值估计 V(s)。Critic 给出 advantageA_t r_t γ·V(s_{t1}) - V(s_t)这个式子是一个单步时序差分形式。如果用多步折现再加 GAE 之类的扩展能得到更平滑的 advantage 估计但核心思想不变用“实际奖励加上未来价值的估计”减去“当前状态的估计”差值就是对这个动作好坏的即时评价。Actor 的更新从“让整条轨迹的总回报变大”变成“让当前动作的 advantage 变大”两者期望一致但方差大幅下降。训练时 Critic 通过回归学习逼近真实价值函数回归目标就是 TD 目标r_t γ·V(s_{t1})。注意这个目标里包含 V(s_{t1})而 V(s_{t1}) 本身就是 Critic 的输出如果让梯度直接回传到 V(s_{t1})会出现自举导致的发散问题。所以训练 Critic 时目标里的 V(s_{t1}) 必须做 detach也就是只当常数、不回传梯度。5.3 容易被忽略的detach问题在 Actor-Critic 实现里梯度流管理是最容易出错的地方。advantage A_t 依赖 V(s_t)而 V(s_t) 又是 Critic 的输出。如果这个 advantage 直接进入 Actor 的损失函数同时又参与了 Critic 自身的回归更新那同一个梯度就会同时影响两个网络训练很容易不稳定。我的处理习惯是所有进入 Actor loss 的 advantage 都必须 detach让 Actor 只把它当做一个标量权重不去反向影响 CriticCritic 的 TD target 中的 V(s_{t1}) 也必须 detach防止价值网络对着自己的输出做自举。伪代码示意如下# 伪代码框架细节省略 for step in range(max_steps): action, log_prob actor.sample(state) next_state, reward, done, _ env.step(action) value critic(state) next_value critic(next_state) if not done else 0.0 advantage (reward gamma * next_value.detach() - value).detach() actor_loss -(log_prob * advantage).mean() critic_loss mse(value, (reward gamma * next_value.detach()).detach()) actor_loss.backward() actor_optimizer.step() critic_loss.backward() critic_optimizer.step()分开两个优化器是更稳妥的做法能避免两个网络共享梯度时互相干扰。如果你只有一个优化器也要确保 actor 和 critic 的梯度流互不串门。这个 detach 的细节写起来只差一行代码但它决定了整个训练过程是稳定收敛还是来回震荡。6. 调试策略梯度模型时我自己沉淀的土办法6.1 调试三板斧固定噪声、盯梯度范数、对比baseline调了几年策略梯度相关模型后我沉淀出一套简单但很有效的调试顺序。第一板斧在连续控制任务里先把探索噪声固定住只让策略均值参与学习。具体做法是暂时不更新策略分布的方差参数把它固定成单位阵的某个倍数先看均值能不能把轨迹推向高回报区域。均值学不动说明问题出在策略主体均值能学动、放开方差后反而崩溃问题就出在探索机制或者熵系数。这个切分方法能迅速缩小排查范围。第二板斧每次梯度更新前打印梯度范数。如果某次更新前梯度范数突然比平时高了几十倍这通常不是网络结构问题而是采样方差爆发。应对办法是第一时间打开梯度裁剪把范数限制在 1.0 到 5.0 之间同时降低学习率。第三板斧对比“减去 baseline 前后”的 return 曲线。如果加了 baseline 之后性能反而更差大概率不是 baseline 本身错了而是 Critic 网络学习率过高价值估计震荡太大反而往 Advantage 里注入了额外噪声。可以先给 Critic 单独设一个更小的学习率试试。6.2 几个可以立马上手的诊断指标我在训练策略梯度模型时最少会盯四个指标滑动平均 return、滑动平均策略熵、梯度范数、advantage 分布的统计量。return 看整体趋势熵看探索是否健康梯度范数看更新是否稳定advantage 分布看算法是否在有效学习。如果 return 持续上涨但熵掉得特别快说明策略正在快速确定化此时需要警惕它只是抓住了短期高回报后续可能卡在局部最优如果 return 几乎不动但 advantage 均值一直为负且绝对值很大提示策略在持续退化这时候先检查目标函数的符号是否写反再检查 advantage 的 detach 是否写对——这两个错误在策略梯度实现里出现的频率远比你想象的更高。最后照例分享一点个人体会我早期折腾策略梯度花时间最多的不是设计网络结构而是调那一堆看起来和算法无关的参数——学习率、熵系数、梯度裁剪阈值、baseline 的软更新系数。策略梯度理论本身并不复杂复杂的是它把概率分布、随机采样和优化器放在一起后产生的各种偶发不稳定。建议你从 REINFORCE 开始亲手写一遍把 baseline 加上去再切到 Actor-Critic走完这一遍之后再看 PPO、TRPO 这些进阶算法心里会踏实得多。下一讲进入信赖域策略优化时你会看到很多改良本质上都是为了解决这一讲反复提到的方差和更新步长问题。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →