做强化学习的朋友大概率都有过这种经历照着论文把策略梯度算法写好损失函数、梯度计算看着都没问题结果训练若干回合后奖励曲线纹丝不动甚至某一次更新后策略直接“失忆”原本会走路的智能体突然原地打转。这种体验很打击信心问题的根源往往不在某个张量运算而在于策略更新这一步本身不够稳定。TRPO与PPO就是为了解决“策略更新不稳定”出现的。TRPO用一条数学上很严谨的“信赖域”给策略更新套上安全绳让参数每次只能在一个小范围内移动PPO则把这种约束改成了更工程化、更好用的“近端裁剪”用一个极简的目标函数实现同样“别走太远”的效果。如今PPO几乎成了强化学习领域的默认选择从游戏策略到机器人控制都绕不开它。这篇内容我们就从底层原理讲到工程实现把两个算法的关系、数学细节、代码层面的坑一次讲清楚。无论你刚接触强化学习还是已经在用PPO调模型这篇文章都会让你对“稳定策略优化”有更完整的认识。我们会讨论为什么直接更新策略会崩KL散度约束到底约束了什么裁剪项为什么能限制步长以及实际训练中那些论文没写的调试经验。1. 策略梯度为什么需要“稳定优化”1.1 从策略梯度到惨痛教训先回顾最朴素的策略梯度目标。设策略为 π_θ我们希望最大化期望回报 J(θ) E_τ ~ π_θ [R(τ)]策略梯度写成∇_θ J(θ) E_{s, a ~ π_θ} [ ∇_θ log π_θ(a|s) · A(s, a) ]看起来很简单计算优势函数 A(s,a) 乘上对数概率的梯度更新参数 θ。但直接实现时会发现每次更新后得到的采样分布变了而那些样本是在旧的策略下采出来的。梯度只告诉你“在这个旧数据上应该往哪个方向调整”却完全没法告诉你“调整多少才安全”。我最初在某个模拟环境上试过朴素的REINFORCE增加基线版本学习率设为 1e-3 时前期还能看到奖励在涨但到了某个回合策略突然变得过于自信连续输出同一个动作环境反馈急转直下。表面上看是“探索不足”本质上是因为参数更新没有约束一步迈得太大策略分布被推到旧样本无法覆盖的区域下一轮更新的估计就会失真训练直接崩溃。这就是策略梯度方法最核心的矛盾数据分布依赖于当前策略而策略又根据旧数据更新。一旦更新步长过大目标函数中的重要性采样比率就会剧烈波动梯度的估计方差也跟着爆炸。所以“稳定优化”的本质是控制好新旧策略之间的差异让每一步更新都建立在足够可靠的样本基础上。1.2 步长敏感与信赖域的核心直觉相信很多人在调参时都有这种感受策略网络的学习率比普通神经网络“娇贵”得多。普通分类网络学习率调大一点最多是loss震荡但策略网络学习率调大后前期样本直接废弃智能体行为完全紊乱。原因在于策略梯度的目标函数在不同的θ取值下期望所对应的分布完全不同我们不能像监督学习那样用“同一份数据反复迭代”。一个直觉的解决思路是在每次更新时显式限制新旧策略的距离。如果用KL散度衡量新旧策略之间的差异并限制它不超过某个阈值δ就可以保证策略不会变化太剧烈。这个想法不算新但把它变成可解的目标函数正是TRPO的贡献。TRPO的全称是Trust Region Policy Optimization翻译过来就是“信赖域策略优化”。信赖域这个词来自优化理论意思是只相信当前点附近一个小区域内的近似是有效的超出这个区域模型的近似就不可靠了。把这个概念应用到策略空间就是限制新旧策略的KL散度使得对目标的近似在这个区域内保持可信。打个比方你要闭着眼睛往前迈步如果路子很平坦你可以迈大步如果脚下是悬崖峭壁你最好先伸脚探探。策略更新也一样旧样本只能告诉你“平坦区域”内的信息超出信赖域信息就失真了。TRPO和PPO都是在解决“怎么控制步伐大小”这件事只是手段不同。2. TRPO给策略更新划一条“安全边界”2.1 目标函数与KL约束的数学表达TRPO的目标函数可以从“重要性采样”的角度来理解。假设我们有旧策略 π_θ_old 采样得到的轨迹想要评估新策略 π_θ 的期望回报可以写成L(θ) E_{s, a ~ π_θ_old} [ ( π_θ(a|s) / π_θ_old(a|s) ) · A(s, a) ]其中比值 r_t(θ) π_θ(a|s) / π_θ_old(a|s) 就是重要性采样比率。直接用这个目标求最大化会出问题因为如果不加约束θ可以走得很远让 r_t(θ) 变得巨大目标函数值也能虚高但实际回报并不会增加。于是TRPO加了约束maximize L(θ) subject to E_{s ~ π_θ_old} [ KL(π_θ_old(·|s) || π_θ(·|s)) ] ≤ δ这里的KL散度是平均值不是每个状态都约束而是对所有访问状态的期望。δ通常取 0.01 或 0.001在连续动作空间可以稍大一点但整体上是个很小的数目的是让新旧策略在每个状态上的输出分布都足够接近。为什么要用KL散度而不是直接用参数差比如 ‖θ - θ_old‖ 来约束因为参数空间的距离没有考虑到参数变化对策略分布的实际影响。某些参数方向即使移动很大策略分布可能不变另一些方向移动很小却会引发剧烈变化。KL散度是直接作用在分布上的度量更符合我们的真实目的——控制行为变化而不是控制参数数值。2.2 共轭梯度解约束优化TRPO的原始优化问题是带约束的非线性优化不能直接用梯度下降。经典解法是把目标函数在 θ_old 附近做一阶近似把KL约束做二阶近似然后构造一个带约束的二次规划问题。具体来说令 Δθ θ - θ_old目标函数的泰勒展开为L(θ) ≈ L(θ_old) ∇_θ L(θ_old)^T ΔθKL约束的二阶展开为KL_avg ≈ (1/2) Δθ^T H Δθ其中 H 是KL散度对θ的Hessian矩阵也就是费雪信息矩阵。于是问题变成了maximize ∇_θ L^T Δθ subject to (1/2) Δθ^T H Δθ ≤ δ这个问题的解析解可以通过拉格朗日乘子得到Δθ ∝ H^{-1} ∇_θ L步长需要根据实际KL散度进行缩放。注意这里要用到 H^{-1}直接求Hessian逆矩阵在神经网络里不可行于是TRPO采用了一个经典的数值优化手法共轭梯度法。它不需要显式构造H只需要能够计算H与任意向量的乘积即可而H向量积可以通过反向传播自动求导高效得到。再配合线性搜索沿着共轭梯度方向逐步缩小步长直到满足KL散度约束。这在概念上很漂亮但工程实现时很痛苦。每次更新都要构建费雪信息矩阵并运行几十轮共轭梯度迭代计算量大约是正常反传的几十倍。我当时在CPU上跑了小环境还好换成图像输入的任务后单次更新慢得怀疑人生。2.3 TRPO落地难点与代价TRPO虽然在理论上给出了漂亮的稳定性保证但在实际使用中让人又爱又恨。以下几点我深有体会实现复杂度很高光是一个共轭梯度循环加上线性搜索再加上Hessian向量积的处理就劝退了很多初学者。费雪矩阵近似KL散度Hessian时忽略了策略映射的很多细节约束效果虽然是好的但计算耗时明显。标准TRPO不支持类似dropout或参数共享的重参数化技巧因为它严格依赖KL散度的二阶展开有些网络设计会破坏这个假设。虽然TRPO的样本效率比朴素策略梯度好很多但每次更新太慢整体训练时间依然相当可观。所以TRPO更像是一个“优雅的理论方案”证明了给策略更新划一条安全边界是可行的但在工程上我们还需要一个更轻量、更粗暴但有效的办法。PPO正是在这种需求下成为主流。3. PPO用裁剪式子把约束变成损失函数3.1 近端裁剪的损失设计PPO全称是Proximal Policy Optimization翻译为“近端策略优化”。名字里的“Proximal”来自优化算法中的近端算子意思是每一次更新时不要让参数离旧参数太远。它没有显式去解约束问题而是直接把“不能走太远”的惩罚塞进了目标函数。最经典的PPO-Clip目标函数长这样L_CLIP(θ) E_t [ min( r_t(θ) · A_t, clip(r_t(θ), 1-ε, 1ε) · A_t ) ]其中 r_t(θ) 还是新旧策略的概率比率A_t 是优势函数估计ε 是裁剪范围通常取 0.1 到 0.2。这个 min 操作看起来简单却包含了一个关键的机制。当 A_t 0 时说明这个动作比平均好我们希望鼓励它也就是让 r_t(θ) 变大。但 r_t(θ) 一旦超过 1ε就会被裁剪到 1ε意味着“给这个动作的鼓励到此为止”。当 A_t 0 时我们希望抑制这个动作也就是让 r_t(θ) 变小但 r_t(θ) 如果小于 1-ε也会被裁剪意味着“对它的惩罚也是有上限的”。这样设计的好处是新策略不会因为某些优势特别大的样本而疯狂提高相应动作的概率也不会因为一次不好的样本就把某个动作概率压到零。换句话说裁剪为 r_t 设置了一个「安全区间」 [1-ε, 1ε]在这个区间外梯度为0策略更新自动停止。3.2 裁剪机制为什么有效很多人第一次看到裁剪目标时会疑惑为什么用一个像“截断”这样的工程量手段就能达到类似TRPO的稳定效果关键在于TRPO的稳定来自全局KL约束而PPO的裁剪直接限制了每个样本点上的概率比率变化。当新旧策略差异很小时r_t 接近1裁剪不会激活PPO就是普通的策略梯度。当某一步更新让新旧策略差异变大时对应的 r_t 会偏离[1-ε, 1ε]区间梯度被截断更新就不会继续沿着这个方向走。由于单个样本的比率被限制整体KL散度通常也会被限制在合理范围。我在实践中观察到PPO-Clip的KL散度有时也会超出预期但相比朴素策略梯度稳定得多。它更像是一种软约束而不是硬约束。你可以把裁剪看作“每个样本的局部信赖域”TRPO则是在所有样本上求平均KL之后的“全局信赖域”。局部约束通常比全局约束更严格但也更灵活实现更简单。3.3 PPO-Clip与PPO-Penalty的联系PPO其实有两个版本论文里提到的PPO-Penalty和PPO-Clip。PPO-Penalty的思路更加直接把KL散度作为惩罚项加到目标函数中比如L L(θ) - β · KL_avgβ是一个系数在训练过程中根据KL散度的大小动态调整。如果KL太大β增大强惩罚如果KL太小β减小允许更大步长。这个方案思想更接近TRPO但把约束变成了软惩罚。PPO-Clip则完全丢弃KL计算只靠裁剪限制。两个版本我试下来PPO-Clip在大多数任务上更稳定调参也更容易。KL惩罚版本的问题在于β的调节节奏很难掌握有时候上一轮KL还是0.005下一轮突然到0.02β调整不够快就崩了。Clip版本不需要计算KL等于少了一个麻烦。但PPO-Penalty在某些需要精细控制策略变化的任务中也可能更好毕竟它提供了连续变化的惩罚而非硬截断但这需要比较细致的调参。4. 工程实现与调参全记录4.1 优势估计GAE的选型PPO和TRPO都依赖一个高质量的优势函数估计目前最常用的就是GAEGeneralized Advantage Estimation广义优势估计。GAE通过权衡偏差和方差用一条公式估计优势A_t Σ_{l0}^{∞} (γλ)^l δ_{tl}其中 δ_t r_t γ V(s_{t1}) - V(s_t)γ是折扣因子λ是GAE系数。λ越大优势估计利用的后续信息越多方差越大但偏差更小λ越小则更依赖当前奖励偏差大但方差小。在我做过的连续控制任务里λ通常设为0.95或0.97γ设为0.99或0.995。如果任务需要稀疏奖励λ可以适当提高让优势估计“看得更远”。但λ太接近1训练方差会增大需要配合更大的批大小来稳定。4.2 网络结构、批大小与训练循环策略网络和价值网络可以共享底层特征也可以完全分离。共享结构时梯度会互相干扰需要小心权衡分离结构通常更稳定但计算量更大。我的建议是对于中等规模环境先用两层全连接每层64或128个神经元策略输出层和值函数输出层分开。对于图像任务CNN的背骨部分可以共享但末端建议分两支。训练循环的基本流程如下使用当前策略 π_θ_old 与环境交互收集 T 个时间步的轨迹存入缓冲。计算GAE优势 A_t 和回报估计 R_t。将数据分成若干小批次使用PPO目标函数在 K 个epoch内进行梯度更新。每次更新时π_θ_old 保持为收集数据时的策略θ 不断更新。全部epoch完成后令 θ_old θ重新采集数据。这里有个容易被忽略的点重要性采样比率中的 π_θ_old 必须使用采样数据时的策略而不是上一轮更新后的策略。如果搞混了比率计算会失真目标函数的意义就没了。我在初学时就犯过这个错误导致训练时好时坏。4.3 关键超参与调参经验PPO最核心的超参数是裁剪范围ε一般取0.2收敛比较稳如果希望更激进可以降到0.1但风险更高。学习率上策略网络和值网络可以使用相同或不同的学习率。通常策略网络学习率比常数网络稍大一些但我在简单任务上用过相同学习率也没问题。每次更新时的epoch数论文中常用3到4。如果KL散度在更新后增长过大应该减少epoch或减小学习率。批大小方面小批大数据可以更快更新但稳定性稍差大批小数据则更稳定但梯度更新次数少。我常用的组合是每轮采2048个时间步分32个小批次更新3个epoch。在简单环境上收敛良好。神经网络层数、激活函数也会影响稳定性。隐藏层激活通常用tanh在连续控制上效果更稳ReLU容易输出较大的动作分布参数导致数值问题。策略输出层的方差通常是全局共享的log_std有些任务可以将其作为可训练参数学习这样比固定方差更灵活。4.4 两个算法在小环境上的对比观察为了更直观地理解TRPO和PPO的区别我在一个简单的2D连续控制任务上做过对照实验。采用同样的网络结构和优势估计TRPO每次更新需要运行约50轮共轭梯度迭代PPO则直接做3轮Adam更新。结果很有意思两者最终都能收敛到接近最优的奖励但TRPO每轮更新耗时大约是PPO的3到5倍。PPO在前期奖励增长速度略慢于TRPO但后期稳定性更好。TRPO的KL散度曲线几乎一直保持在设定的阈值附近说明约束有效PPO的KL散度偶尔会冲到阈值以上但很快回落。调参难度方面PPO明显更友好TRPO对神经网络结构更敏感。这个对比让我更加确信在中小规模问题上PPO的性价比远高于TRPO。TRPO的算法思想“保证稳定更新”依然重要但PPO用更工程化的方式实现了同样的目标。5. 训练中的常见问题与排查手册5.1 KL散度爆炸和策略熵趋零训练PPO时如果奖励长期不动去观察KL散度和策略熵往往会发现两种情况。第一种是KL散度突然超过0.1甚至1.0说明策略更新步长过大重要性采样比率失控。解决方式是降低学习率减小裁剪范围ε或者增加一个小量级的KL惩罚项作为辅助。第二种是策略熵趋近0说明策略已经坍缩成一个几乎确定的映射失去了探索能力。常见原因是优势估计的噪声过大或者数据重复更新次数太多。我会在每次日志里写入平均KL散度和策略熵作为健康指标。如果熵在持续下降就算奖励还没崩也要警惕考虑提高熵奖励系数。标准PPO虽然没有熵项但很多实现都会在目标函数里加一项 β_ent · H(π) 来鼓励探索β_ent 一般取0.0到0.01。5.2 奖励曲线震荡奖励曲线剧烈震荡通常有几个来源。一是学习率太高更新步长过大跨越了策略的“安全区域”。二是batch内的数据分布不均匀某些轨迹特别好或特别差导致梯度方向不稳定。三是GAE的λ取得太大优势估计方差过高。排查思路很简单先固定种子跑几次实验区分是环境随机性还是算法本身不稳定。如果同一随机种子下奖励曲线每次差异很大说明梯度噪声或更新策略有问题。然后把学习率降为原来的1/3观察震荡是否缓解。如果缓解基本就是步长问题还可以顺手加一个梯度裁剪比如最大范数0.5。5.3 损失函数出现NaN或数值异常NaN往往出现在计算重要性比率 r_t 时。如果策略输出概率非常小概率比值可能接近0或无穷大。解决方法是给输出分布的下限加一个保护比如 log_std 限制在 [-20, 2] 之间概率密度计算时加上微小常数。另一个常见原因是价值网络的梯度爆炸可以给价值损失做裁剪或者在价值网络更新时使用更小的学习率。我通常会在损失计算前对优势值做一次标准化也就是减去均值除以标准差。这不会影响真实梯度方向但能避免优势值范围过大导致数值不稳定。这个技巧在大多数实现里都会内置。5.4 实操里容易忽略的细节这里分享几个踩过坑的细节如果使用共享网络价值函数的预测值最好除以一个较大倍率或者做layer norm不然价值损失的量级可能压制策略损失。收集数据时必须在同一个策略下跑完一整轮再进入更新阶段不能中途切换策略。对于多线程环境注意每个环境的状态不要共享否则样本相关性很高训练会不稳。每次更新完记得清空缓冲区并且把策略网络放回训练模式。保存模型时连同优化器的状态一起保存否则中断恢复后学习率等状态会丢失。还有一个细节是PPO的裁剪目标是逐元素取min但如果有多个动作维度r_t 的计算要基于联合概率密度。通常连续动作空间的 r_t exp( log_prob_new - log_prob_old )也就是对数概率之差做指数这样更稳定。不要直接对新旧概率密度做除法容易数值溢出。6. 我的一点经验之谈最后分享几个我觉得值得记住的体会。首先TRPO和PPO都不是万能药。如果你在一个极其稀疏奖励的任务上训练无论用什么稳定优化算法单靠策略梯度都可能解决不了探索问题。稳定优化只是确保策略不崩不代表它有本事找到好的探索路径。其次在调试PPO时我习惯把超参数的变化控制在最小范围。每次只改一个变量并且用一组固定的随机种子跑多个trial然后看中位数而不是单次结果。这能帮你把“算法问题”和“环境随机性”分开。最后如果任务允许优先从PPO-Clip开始。它足够稳实现简单社区也有大量成熟写法。等你把基础流程走通之后再回头研究TRPO的约束理论那种“哦原来PPO是这样的简化”的感觉会让整个图景变得特别清晰。STRPO和PPO让我最着迷的一点是它们告诉我们在优化一个会改变自身数据分布的对象时不能只顾着朝梯度方向猛冲还要随时回头看看自己是不是离出发点太远了。这个道理不仅在算法里成立放在很多流水线工程里也一样适用。希望这份梳理能帮你少走弯路。
阅读完成 · 觉得有帮助?