策略梯度方法一直没有花单独一篇来写这次补上。前面那些文章主要围绕值函数展开也就是先估计Q(s,a)或者V(s)再用它们间接决定策略。而策略梯度直接对策略本身做参数化不去绕道值函数。这种思路上的转变解决了不少值函数方法很头疼的问题比如连续动作空间、随机策略的表达、以及策略退化时无法自救的情况。这一篇我会从最基础的推导开始把为什么要减基线、REINFORCE和Actor-Critic的区别、以及PPO里那个clip到底解决了什么讲清楚。最后附上自己实际操作中踩过的坑和排查经验。1. 为什么绕开值函数策略梯度方法的核心思路1.1 值函数方法在连续空间里的尴尬在之前写的DQN那几篇里整个训练流程是拿状态s输入网络输出每个动作的Q值然后选Q值最大的动作。这个逻辑在离散动作空间里很自然比如上下左右四个方向输出维度就是4比较大小非常方便。但一旦动作用连续变量表示比如机械臂关节角度、自动驾驶的方向盘转角事情就麻烦了。你可能想把连续动作离散化不就行了确实有人这么做但维度稍微一高就撑不住。假设一个机械臂有7个关节每个关节离散成100个档位动作空间就变成100的7次方这个数大到任何网络都输出不了。所以离散化只适合动作维度极低、精度要求不高的场景。另外一个更难察觉的问题是值函数方法本质上只能表达确定性策略。它通过贪婪选择或者ε-贪婪来选择动作这意味着在同一个状态下要么选那个最优动作要么随机乱试。那些需要随机策略的任务比如石头剪刀布、扑克牌游戏用值函数方法就很别扭因为你明明知道对手在观察你的行为模式纯随机才是最优解但DQN这类方法天生不擅长表达均匀随机地出拳这种策略。1.2 直接对策略求梯度绕开先估后选的弯路策略梯度方法换了一个角度定义一个参数为θ的策略网络π_θ(a|s)这个网络直接输出在状态s下选择动作a的概率然后优化目标就是让累积回报的期望最大化。整个过程不显式地估计Q值或V值至少最原始的版本如此而是对目标函数直接求梯度让参数沿着梯度方向更新。这样做有几个立竿见影的好处。动作空间连续时策略网络的输出可以是动作分布的均值或方差比如高斯分布的μ和σ采样得到实际动作。随机策略也是天然支持的因为网络的输出本身就是概率分布完美的随机策略就是各个动作概率相等。最重要的是策略梯度方法在策略变差的时候有自我修正的能力。值函数方法如果Q值估计偏了策略会被带偏而且很难自己绕回来而策略梯度是朝着回报更大的方向一步步挪哪怕步子偶尔迈歪了整体趋势是能纠正的。1.3 策略梯度 vs 值函数一张表看清各自优势维度值函数方法如DQN策略梯度方法动作空间适合离散、低维离散连续通吃随机策略很难自然表达天然支持策略单调性可能因Q值偏估而退化梯度方向保证局部改进方差相对较低偏高需要减基线等技巧收敛性可能不收敛收敛性更稳定实现复杂度相对简单需要处理采样与基线2. 从期望回报到梯度数学推导与直觉2.1 目标函数与用采样代替积分的关键一步策略梯度的起点很朴素定义一个目标函数J(θ)表示在策略π_θ下一条完整轨迹的累积回报的期望J(θ) E_{τ ~ π_θ}[R(τ)]其中τ表示一条完整的状态-动作序列R(τ)就是这条轨迹上所有奖励的总和。优化目标就是让J(θ)最大所以梯度方向是∇J(θ)参数更新就是θ ← θ α∇J(θ)。核心难题在于J(θ)是对轨迹分布的期望而这个分布取决于策略本身策略一变分布就变普通求导法则没法直接套。这里需要用到log-ratio trick也叫score function trick本质上就是利用恒等式∇π_θ(a|s) π_θ(a|s)∇log π_θ(a|s)把它代进期望的表达式经过几步代数操作就能得到∇J(θ) E_{τ ~ π_θ}[Σ_t ∇log π_θ(a_t|s_t) · R(τ)]这个式子的直观意义可以这样理解如果某条轨迹的回报R(τ)很高那么沿着这条轨迹上所有动作的log概率梯度方向去调整参数让这些动作再出现的概率增大如果回报很低就让这些动作的概率减小。最后把所有轨迹的贡献汇总就是策略的更新方向。这个公式的关键价值在于期望里不再包含策略的梯度只包含log概率的梯度这样就能用实际采样的轨迹来估计期望也就是蒙特卡洛采样。你不需要知道所有状态和动作的确切分布只需要不断地在环境里跑轨迹记录状态、动作、回报然后代入公式更新参数就行。2.2 为什么这个梯度方向是对的重力场类比用一个类比来帮助理解梯度方向为什么合理。想象你不是在做强化学习而是在一个滑雪坡上调整滑雪板的角度。坡上每个位置的高度就是累积回报滑雪板的角度就是策略参数。你没法直接看到整个山坡的全貌只能在自己脚边感受一下哪个方向往下倾斜得多一点然后朝那个方向微调。策略梯度做的事本质上就是这样通过采样几条轨迹估算出当前策略参数附近哪个方向的回报上升最快然后沿着那个方向挪一小步。它不需要知道整个回报曲面长什么样只需要局部的坡度信息。这也是为什么策略梯度方法就算在环境稍微有扰动的时候也还能继续工作因为它永远只是在探测局部梯度。但这里有个代价采样只是估计估计就有噪声。几条轨迹的回报可能差距巨大梯度方向的波动也就特别大。这就是后面要讲方差问题和基线技巧的出发点。2.3 核心公式的完整推导过程为了让你能真正搞懂而不只是记住结果我把关键的推导步骤列出来。里面有一步涉及期望和梯度的交换其他都是代数操作。目标函数的梯度展开如下两步。第一步把期望展开成积分形式利用概率分布的对数求导恒等式把梯度作用到log概率上∇J(θ) ∫ π_θ(τ)∇log π_θ(τ) R(τ) dτ第二步轨迹概率分解成每一步的乘积取对数后变成求和梯度也就随之分配到每个时间步π_θ(τ) Π_t π_θ(a_t|s_t)所以∇log π_θ(τ) Σ_t ∇log π_θ(a_t|s_t)代回原式就得到逐时间步的形式了。整个过程没有复杂的近似全部是精确的数学变形。这也是策略梯度方法和很多启发式方法不一样的地方它有个严格的优化目标每一步更新都是在朝目标函数梯度的无偏估计方向走。2.4 方差问题初现为什么这个朴素版本很难直接用理论很漂亮但直接照着这个公式写代码训练起来往往让人怀疑人生。原因就是方差。我举个例子某个状态s下策略以0.6的概率选动作a1回报是10以0.4的概率选动作a2回报是0。理论上期望回报是6梯度会温和地提升a1的概率。但你实际采样的时候如果这一轮只采到一条轨迹恰巧走了a2回报是0梯度就认为a1和a2都该降低概率这就完全反了。虽然大量的采样会让估计逐渐接近真实值但训练过程中梯度的大幅波动会导致训练曲线像心电图一样狂跳。更致命的是不同时间步的动作共享整条轨迹的总回报这意味着早期那个动作的更新会受到后面几十步中所有随机性的影响甚至包括其他动作的贡献。这种信用分配问题让梯度的噪声变得非常大。所以实际使用策略梯度方法时几乎没有人生般直接用最原始的版本。接下来要说的减基线、优化子等技术本质上都是在不改变梯度期望的前提下降低方差让训练能真正跑起来。3. 减基线让梯度估计不再噪声爆炸3.1 不用基线时的直观感受和数学后果我用一个非常简单的实验说明问题。在某仿真环境里我训练一个两层MLP的策略网络动作空间是2维连续reward在0到1之间波动。如果直接用朴素的REINFORCE算法学习率设到0.01训练到第2000步时loss曲线已经完全乱掉策略输出直接崩到边界值再也回不来。原因可以用一个简单的量级估算来看。假设一条轨迹有50步每步的reward标准差是0.5整条轨迹的总回报方差就是50×0.2512.5标准差约3.5。而梯度更新量里要乘上这个总回报所以更新方向的噪声相当可观。在真正的高维问题里reward的尺度可能更大噪声更离谱。数学上可以证明梯度的方差与回报的方差成正比。所以如果直接采样几次就更新每条轨迹之间差异那么大更新方向几乎被噪声主导完全不具备一致性。3.2 数学推导为什么减去一个只依赖于状态的函数不影响期望基线技巧在数学上非常简洁。假设我们给原始的梯度项减去一个函数b(s)这个函数只与状态s有关与动作a无关。那么对于单个时间步考虑期望E[∇log π_θ(a|s) · b(s)] Σ_a π_θ(a|s) · ∇log π_θ(a|s) · b(s)把∇log拆成∇π/π代入得到Σ_a b(s) · ∇π_θ(a|s) b(s) · ∇(Σ_a π_θ(a|s)) b(s) · ∇1 0这里用到Σ_a π_θ(a|s) 1这个概率归一化性质。所以不管b(s)取什么值期望上它对梯度没有任何影响。这就给了我们一个巨大的自由度可以选择一个适当的b(s)参与计算在不改变梯度期望的前提下降低方差。直觉上减去基线的效果是把绝对回报变成了相对回报。比如一条轨迹在某个状态下回报是1.0但如果该状态之前的平均回报就是1.5那这个1.0其实是偏低的应该降低该动作的概率。减去基线后梯度信号就变成了这个动作相比平均水平好多少而不是单纯的这个动作带来的回报数值是多少。3.3 状态值函数作为基线的理由与实现细节最常用的基线就是状态值函数V(s)。它表示从状态s出发按照当前策略能获得的期望累积回报。用它做基线梯度项变成∇J(θ) E[Σ_t ∇log π_θ(a_t|s_t) · (G_t - V(s_t))]其中G_t是t步以后的累积回报V(s_t)是基线。两者的差值在概念上就是这个动作的回报相对于平均水平好了多少这个量正是优势函数A(s,a)的定义。实现V(s)的方式有很多种。最简单的是用一个单独的神经网络结构通常和策略网络共享底层特征只是输出维度变成1。也可以用线性回归拟合但效果差一些。实际工程里我一般直接用Actor-Critic架构让评论员网络去估计V(s)然后用TD误差来替代真实的优势值这个后面细说。3.4 基线选不好时会发生什么一个实测记录我在一次实验里踩过一个很典型的坑。当时用的是最简单的REINFORCE加状态基线基线网络是一个单层线性层没有和策略网络共享特征提取层。训练到第3000步基线网络本身还没收敛输出的V(s)全是近似常数。这时候减去基线的效果约等于没减但多了额外一层梯度计算训练速度反而更慢。后来又换了一种做法让基线网络和策略网络共享前两层隐藏层只在输出层分开。效果立刻好了很多因为共享特征提取层意味着基线能更快地学到有价值的状态表示而不需要从零开始单独学习特征。从这个经验里得到的教训是基线网络必须足够强否则效果有限。减基线的核心是让V(s)尽量准确地预测当前策略下的平均回报V(s)越准方差降低越明显。如果你的基线网络一直在瞎猜那不如不要。4. 实战中的三个关键版本REINFORCE、Actor-Critic、PPO4.1 REINFORCE最原汁原味的蒙特卡洛策略梯度REINFORCE是策略梯度方法里最基础的一种特点是用整条轨迹的完整回报G_t来估计优势。算法流程非常清晰用当前策略跑一条轨迹收集每一步的状态、动作、回报到轨迹结束后计算每一个时间步的累积回报G_t然后对每个时间步计算∇log π(a_t|s_t)·(G_t - b(s_t))求和取平均作为梯度更新网络参数。这个算法在概念上有一种天然的事后诸葛亮意味先打完整场等结果明朗了再回头反思每一步该不该那么走。如果整场赢了所有被选中的动作都会被鼓励如果输了所有动作都会被惩罚。这种粗糙的信用分配方式效率不高但好在无偏——期望上不会误导策略。我在一个小型导航任务上跑过REINFORCE训练5000个episode才能达到比较稳定的水平而同样的任务用Actor-Critic只用1500个episode左右就能达到同样水平。所以REINFORCE现在更多是作为教学工具来理解策略梯度的基本原理实际工程里很少直接用它。4.2 Actor-Critic用偏差换方差Actor-Critic的核心改动是用TD误差来替代整条轨迹的总回报。TD误差定义为δ_t r_t γV(s_{t1}) - V(s_t)它衡量的是当前回报加下一状态的价值估计与当前状态的价值估计之间的差距这个差距本质上就是对优势函数的估计。策略网络Actor按δ_t的方向更新价值网络Critic则朝让δ_t变小的方向更新。这个改动把等整条轨迹跑完再回顾变成了每走一步就立刻反思因此方差大幅降低。代价是引入了偏差因为V(s_{t1})本身是估计值估计的误差会污染梯度。但在实际操作中这个权衡非常划算——方差降低带来的训练稳定性提升远大于偏差带来的影响。实现Actor-Critic架构时需要注意的是两个网络的学习率通常不同。策略网络的学习率一般设小一点价值网络可以大一些因为价值网络需要快速收敛才能提供准确的基线信号。我自己常用的配置是Actor学习率3e-4Critic学习率1e-3。4.3 PPO把采样优化变成了离线更新的安全区PPO要解决的问题是策略梯度每次更新只能用当前的采样数据数据用完就扔导致样本效率很低。理想情况下我们希望同一批数据能被反复利用几次但要保证更新步长不能太大否则策略跑偏了数据分布就不对劲了。PPO的核心思路是限制新旧策略之间的差距。假设旧策略是π_old新策略是π_θ定义两者的概率比值r_t(θ) π_θ(a_t|s_t) / π_old(a_t|s_t)如果r_t(θ)大于1说明新策略在当前位置的动作概率比旧策略大小于1说明概率变小了。PPO的目标函数在这个比值上做clipL(θ) E[min(r_t(θ) · Â_t, clip(r_t(θ), 1-ε, 1ε) · Â_t)]其中Â_t是优势函数估计ε通常取0.2。当r_t(θ)超出[1-ε, 1ε]这个区间时梯度被截断不再继续增大更新量。这个机制保证了策略每步只能稳妥地走一小步不会因为某次采样运气好就大幅改变策略。从我自己的实验经验看PPO在大部分连续控制任务上的表现都相当稳定尤其是和REINFORCE对比时训练曲线顺滑得多。它不需要精调每步的信任域参数也不需要像自然梯度方法那样计算费解的Fisher信息矩阵这让它成为实际使用中最受欢迎的策略梯度算法之一。4.4 三种算法的对比与选型建议特性REINFORCEActor-CriticPPO更新频率每条轨迹结束每个时间步每条轨迹后多次更新方差很高较低低偏差无偏有偏使用估计值有偏clip导致的偏移样本效率低中高实现难度简单中等中等偏复杂适用场景教学、小规模任务实时交互场景大多数实际工程任务选型时我的建议很简单刚开始学就用REINFORCE把公式和代码对应起来做真正的项目直接用PPO稳定性比另外两个好太多。Actor-Critic更适合那些每一步都能获得即时奖励、环境交互成本低的场景比如在线决策类的任务。5. 训练过程中那些防不胜防的坑5.1 reward尺度过大导致的梯度爆炸策略梯度方法对reward的尺度非常敏感。这是因为梯度更新量直接乘以回报值reward的绝对尺度会直接影响参数更新的幅度。我在一个任务里设置的reward范围是0到100但网络输出的动作范围只有-1到1。结果训练到第200步策略网络参数直接变成NaN。排查过程比较折磨一开始怀疑是学习率问题降低了10倍还是爆炸又怀疑是网络初始化问题换了初始化方法也不行。最后把reward的历史曲线打出来才发现回报值偶尔会出现特别大的峰值乘以梯度后直接把参数推到了数值溢出的边缘。解决办法有两个方向一是对reward做缩放比如除以一个常数十让它落在0到10的区间二是对梯度做归一化比如用梯度裁剪或归一化梯度的L2范数。两者的效果差别不大但我个人更倾向于先做reward缩放因为梯度裁剪只是防止数值爆炸并没有解决尺度对更新幅度的根本影响。5.2 学习率过大导致策略猝死策略梯度方法在训练初期最容易出现的一个现象是训练曲线原本在上升突然某个episode之后回报暴跌到最低值之后再也回不来。这个现象的根本原因是策略过度更新——某次采样恰好走到了回报特别高的轨迹梯度计算出来非常大参数更新这一步迈得太大策略直接变成确定性策略而且是无意中选了一条很差的路。我在实验里给学习率设到0.1测试过一次从训练曲线上看策略在第37个episode就完全崩掉了。崩掉前的几个episode回报还在快速上升说明策略确实在学习但学习率太大导致策略一步跨过头。有效的应对手段是降低学习率比如从1e-3开始调。另外可以用一个简单的检查手段在训练过程中记录策略网络输出概率分布的熵。如果熵在某一步骤突然掉到接近0说明策略变成了几乎确定性的这时大概率陷阱出现了需要回退到之前的模型检查点。5.3 梯度方差大的直观信号和系统的排查法训练曲线跳得像锯齿电图一样是策略梯度方法方差大的最直接表现。判断这种情况是否正常要看整体趋势如果曲线总体在上抬、只是波动幅度大属于正常现象如果曲线是水平振荡甚至长期不抬升那方差大的问题已经影响到收敛了。我常用的排查步骤是这样的先检查reward的scale是否合理把reward打印出来看最大值、最小值、均值再看基线网络是否已经收敛可以把V(s)的预测值和实际回报做相关性分析如果相关性很低说明基线没学到东西最后才看学习率。这三个步骤的次序不能乱。reward scale影响的是全局基线问题影响的是梯度质量学习率影响的是更新幅度。从全局到局部逐步排查能避免在错误的方向上浪费时间。5.4 熵正则项防止策略过早收敛到次优策略梯度方法还有一个容易被忽视的问题训练进行到中后期策略会逐渐收敛到一个确定的动作模式。如果这个模式恰好是局部最优策略就陷入其中无法跳脱了。我在某任务中遇到过策略收敛到原地转圈的行为回报稳定在一个比较低的值怎么调学习率都没用。解决办法经常是加熵正则项。在损失函数里加一个策略熵的惩罚项熵越大代表动作分布越均匀加这个项等于鼓励策略保持一定的随机性。实际公式是J(θ) E[Σ_t ∇log π(a_t|s_t) · A_t] β · H(π_θ)这里的β是熵正则系数通常在0.005到0.02之间调。β增大会让策略更随机探索增多减小则让策略更倾向确定性。从实验经验看加熵正则后策略中后期的收敛稳定性和最终回报都有提升虽然提升幅度不算大但避免了不少次陷入局部最优的尴尬。5.5 常见的训练失败模式速查表现象可能原因排查顺序梯度变成NaNreward尺度过大检查reward统计量训练曲线骤降后再不回升学习率过大/策略过度更新检查学习率与概率熵曲线长期震荡不收敛方差过大检查基线网络收敛度策略收敛到次优探索不足加熵正则项训练前期有效后期停滞基线网络过拟合降低Critic学习率6. 我是怎么调策略梯度算法的个人实操习惯顺手写一点个人经验给刚入门的朋友参考。我每跑新的任务最开始永远先用最小的配置验证逻辑通通一个非常小的网络、纯CPU训练、轨迹数量也设少。优先目的是确认梯度更新后回报均值确实在往上走然后再逐步放大规模。很多新手一上来就开大模型大batch size出了问题反而不知道是逻辑错还是资源不够。reward scale这件事我几乎每次都会踩。所以我现在的习惯是在环境封装层就把reward做一次归一化比如除以历史回报的滑动标准差避免每次跑新任务都要重新调这个参数。再就是日志。强烈建议每次训练都把reward均值、V(s)预测均值、策略熵、梯度L2范数这四样东西打出来。它们组合起来基本能定位90%的运行问题。比如梯度的L2范数如果从10^0突然跳到10^3那大概率是reward出现异常峰值了。关于PPO的参数ε我基本不动固定在0.2学习率如果训练不太稳定降到1e-4通常能救batch size小一点比如256比大batch更不容易过拟合到当前采样分布。这些经验在不同任务之间迁移性挺强的。如果只是做实验验证算法效果我会先试Actor-Critic因为它代码量少、调试直接。做真正的决策控制任务直接用PPO它能规避的坑比它引入的复杂度多得多。这两种方案覆盖了我遇到的绝大多数场景。