首页 / 资讯中心 / 文章详情

PPO论文精读:从策略梯度到近端策略优化的工程实践

PPO论文精读:从策略梯度到近端策略优化的工程实践 ★ FEATURED ARTICLE
1. 这篇论文到底在解决什么问题如果你最近在折腾大模型的对齐训练或者在做强化学习相关的项目大概率会频繁撞见一个缩写PPO。全称是 Proximal Policy Optimization中文一般叫“近端策略优化”。这篇论文由 OpenAI 的几位研究员在 2017 年挂到 arXiv 上编号是 1707.06347到今天已经被引用了好几万次几乎是强化学习落地时绕不开的一块基石。我第一次认真读这篇论文是因为手头一个机器人控制的小项目用普通的策略梯度方法训练方差大得离谱调参调到怀疑人生。后来换成 PPO同样的环境训练曲线肉眼可见地稳了下来。从那以后我就养成了一个习惯凡是涉及“让模型根据反馈自己调整行为”的场景先想想 PPO 能不能用。这篇论文的核心贡献其实就一句话它提出了一种新的策略梯度方法在“每次更新幅度别太大”和“实现起来别太复杂”之间找到了一个很实用的平衡点。它既不像 TRPOTrust Region Policy Optimization那样需要计算二阶导数、实现起来一堆约束也不像普通策略梯度那样一步走歪就全盘崩溃。用论文里的话说它只用了“一阶”的信息却达到了接近 TRPO 的可靠性。适合谁来读这篇论文我的判断是三类人。第一类是做强化学习工程落地的你需要知道 PPO 的损失函数到底长什么样、每个符号代表什么才能在实际代码里改对地方。第二类是做模型对齐的现在很多对齐流程里 PPO 仍然是主力算法之一理解它的裁剪机制能帮你判断训练为什么不稳。第三类是想搞懂“为什么 PPO 比别的策略梯度好用”的算法爱好者这篇论文的推导并不长但每一步都值得琢磨。接下来我会按照“整体思路拆解、核心细节解析、实操落地、常见问题排查”这个顺序把这篇论文从头到尾捋一遍。不是翻译而是把我自己读论文、写代码、踩坑之后的理解讲出来尽量让刚入门的人也能跟上。2. 整体设计与思路拆解2.1 从策略梯度到“别走太远”的直觉要理解 PPO得先理解它想解决的前置问题。策略梯度的基本思想很朴素你有一个策略网络输入状态输出动作的概率分布。你让它去环境里跑收集一批轨迹然后根据回报的高低来调整网络参数——回报高的动作下次出现的概率调大一点回报低的调小一点。这个思路本身没问题问题出在“调多少”上。如果每次更新的步长太大新策略和旧策略差别太大那么你用旧策略收集的数据来估计新策略的好坏就不准了训练会剧烈震荡甚至崩溃。反过来步长太小训练又慢得让人着急。TRPO 的思路是我显式地加一个约束要求新旧策略之间的 KL 散度不能超过某个阈值。这个想法很优雅但实现起来麻烦需要算 Fisher 信息矩阵、做共轭梯度代码复杂计算量也大。PPO 的思路则是我不搞那么复杂的约束我直接在目标函数里动手脚让“新旧策略差别太大”这件事在数值上得不到好处。具体怎么做就是后面要讲的裁剪机制。这个思路的好处是你只需要算一阶梯度用普通的随机梯度下降就能训练代码量可能只有 TRPO 的几分之一。2.2 论文的整体结构安排这篇论文的正文其实不长我数了一下核心内容大概十来页。它的组织方式很清晰先回顾策略梯度和 TRPO 的背景然后提出 PPO 的两种形式——一种是裁剪的替代目标clipped surrogate objective另一种是自适应 KL 惩罚adaptive KL penalty接着给出算法伪代码最后用一系列实验对比 PPO 和之前的方法。我读的时候建议重点关注三个地方。第一个是公式 (7)也就是裁剪后的目标函数这是整篇论文的心脏。第二个是算法 1 的伪代码它告诉你实际训练时数据怎么收集、怎么更新、循环多少次。第三个是实验部分里关于“不同超参数下 PPO 的表现”那几张图它能帮你建立对超参数敏感度的直观感受。论文里还对比了 PPO 和几种基线方法包括 TRPO、普通策略梯度、A2C 等。结论是 PPO 在连续控制任务上表现更好样本效率更高而且对超参数不那么挑。这个结论在我自己的项目里基本得到了验证但也有一些前提条件后面会细说。2.3 为什么是“近端”而不是“信任域”这里插一句关于命名的理解。“Proximal”这个词在数学优化里指的是“近端”意思是每次更新都停留在当前点附近不要跑太远。TRPO 用的是“Trust Region”信任域意思是“我信任当前策略附近这个区域内的估计是准的超出这个区域我就不信了”。两者背后的直觉是一样的都是控制更新幅度但实现哲学不同。PPO 选择“近端”这个说法我理解是在强调它的实现更轻量我不需要显式地定义和维护一个信任域我只需要在目标函数里让“跑太远”的收益被削掉。这个选择带来的直接好处就是工程上好落地这也是为什么 PPO 在工业界比 TRPO 流行得多。3. 核心细节解析与实操要点3.1 裁剪目标函数到底在裁剪什么论文里最关键的公式是那个裁剪后的替代目标。我把它拆开讲。首先定义概率比r_t(θ) π_θ(a_t | s_t) / π_θ_old(a_t | s_t)这个比值衡量的是在新参数 θ 下某个动作被选中的概率相对于旧参数下被选中的概率放大了多少倍。如果 r_t 等于 1说明新旧策略对这个动作的偏好完全一样如果 r_t 大于 1说明新策略更倾向于选这个动作小于 1 则相反。普通策略梯度的目标函数里这个比值是直接乘上优势函数 A_t 的。优势函数告诉你这个动作比平均水平好多少。如果 A_t 是正的说明这个动作值得鼓励那么 r_t 越大目标函数越大梯度就会推着参数让这个动作概率更高。问题在于如果 r_t 变得非常大比如 10 倍、100 倍那么目标函数就被这一项主导了更新会非常激进。PPO 的做法是把 r_t 裁剪到一个区间里通常是 [1-ε, 1ε]ε 取 0.1 或 0.2。然后取“裁剪后的目标”和“未裁剪目标”的较小值。这个“取较小值”很关键它意味着如果某个更新会让目标函数变大但代价是 r_t 超出了区间那么这个收益会被截断梯度不会鼓励你继续往那个方向走。我举个具体数字帮助理解。假设 ε0.2A_t1当前 r_t1.5。未裁剪的目标是 1.5×11.5裁剪后的目标是 1.2×11.2取较小值得到 1.2。梯度只会按照 1.2 这个水平来更新而不是 1.5。如果 A_t-1r_t0.5未裁剪目标是 -0.5裁剪后是 -0.8取较小值是 -0.8同样限制了更新幅度。3.2 优势函数估计GAE 的引入论文里用的优势函数估计方法是 GAE全称 Generalized Advantage Estimation。这个东西不是 PPO 原创的但 PPO 的实验里用了它而且效果很好所以现在大家说 PPO 基本默认配 GAE。GAE 的核心是一个参数 λ用来在偏差和方差之间做权衡。λ 接近 0优势估计的方差小但偏差大λ 接近 1方差大但偏差小。论文里用的 λ0.95这个值在实践里比较常用。我自己的经验是GAE 的 λ 和折扣因子 γ 需要一起调。γ 控制你有多看重远期回报λ 控制你有多信任多步估计。如果任务里奖励比较稀疏γ 要设大一点比如 0.99如果奖励很密集γ 可以小一点。λ 一般设在 0.9 到 0.95 之间很少需要大改。3.3 两种变体裁剪 vs 自适应 KL论文提出了两种 PPO 的实现方式。第一种就是上面说的裁剪目标第二种是自适应 KL 惩罚在目标函数里加一项 -β × KL(π_old, π_θ)然后根据实际 KL 散度的大小动态调整 β。如果 KL 太大就增大 β惩罚更重如果 KL 太小就减小 β让更新更积极。论文的实验结论是裁剪版本表现更好也更简单。所以后来大家说的 PPO默认指的都是裁剪版本。自适应 KL 版本现在很少单独用了但理解它有助于明白 PPO 的设计空间。我在实际项目里两种都试过。裁剪版本确实更省心基本不需要调 β 相关的参数。自适应 KL 版本在某些任务上初期收敛更快但后期容易在 β 的调整上震荡。如果你刚开始用 PPO直接上裁剪版本就行。3.4 实操中的关键参数与注意事项下面这张表是我自己总结的 PPO 常用参数范围结合了论文里的设置和我自己的经验参数含义论文常用值我的建议范围ε裁剪范围0.1 或 0.20.1~0.3任务越难越小γ折扣因子0.990.95~0.999λGAE 参数0.950.9~0.97学习率优化器步长3e-41e-4~1e-3每批更新次数同一批数据重复训练轮数103~20批量大小每次更新用的样本数2048 或 4096根据任务调整注意同一批数据重复训练太多轮比如超过 20 轮会导致新策略偏离旧策略太远裁剪机制也救不回来训练会崩。这是我最常踩的坑之一。还有一个细节论文里对优势函数做了标准化处理也就是减去均值、除以标准差。这个操作看起来不起眼但对训练稳定性影响很大。我在一个机械臂控制任务里忘了做优势标准化结果训练前期几乎不收敛加上之后立刻正常了。4. 实操过程与核心环节实现4.1 训练循环的完整流程PPO 的训练循环可以概括为“收集数据、计算优势、多轮更新、重复”。我用伪代码的形式写一遍然后逐段解释初始化策略网络 π_θ 和价值网络 V_φ for iteration in range(总迭代次数): # 1. 用当前策略收集一批轨迹 trajectories [] for step in range(每批步数): 用 π_θ 选择动作执行记录 (s, a, r, s, done) # 2. 计算回报和优势 计算每个时间步的折扣回报 用 GAE 计算优势 A_t 标准化优势 # 3. 多轮更新 for epoch in range(更新轮数): for minibatch in 打乱后的数据: 计算概率比 r_t 计算裁剪目标 L_clip 计算价值损失 L_vf 计算熵奖励 L_ent 总损失 -L_clip c1*L_vf - c2*L_ent 反向传播更新 θ 和 φ这个流程里第一步和第三步是分开的。也就是说你收集一批数据之后用这批数据反复训练很多轮然后再丢掉重新收集。这个设计是 PPO 样本效率高的关键同一批数据被充分利用了。4.2 价值网络和熵奖励的作用PPO 通常同时训练一个价值网络用来估计状态价值进而计算优势。价值网络的损失一般是均方误差预测值和实际折扣回报之间的差距。论文里价值损失的系数 c1 通常设 0.5。熵奖励是为了鼓励探索。如果策略过早地变得确定只选某一个动作就可能陷入局部最优。熵奖励的系数 c2 通常设 0.01 或者更小。这个系数不能太大否则策略会一直保持随机学不到东西。我在一个迷宫任务里试过把熵系数设成 0.1结果智能体一直在原地打转因为随机性太高了。后来降到 0.01很快就找到了出口。所以这个参数要根据任务的探索难度来调。4.3 一个具体的参数计算示例假设你在做一个连续控制任务动作是二维的力矩。你设每批收集 2048 个时间步的数据批量大小是 64那么每批数据可以分成 32 个 minibatch。更新轮数设为 10那么总共的梯度更新次数是 32×10320 次。学习率设 3e-4用 Adam 优化器。裁剪 ε 设 0.2。γ 设 0.99λ 设 0.95。这些值可以直接作为起点然后根据训练曲线调整。如果你发现训练前期奖励上升很慢可以先把学习率调大一点比如 1e-3看看是不是步长太小。如果训练中期出现剧烈震荡先把 ε 调小比如 0.1或者减少更新轮数。如果后期奖励卡住不涨检查一下熵系数是不是太小策略是不是过早确定了。4.4 代码实现里的几个易错点第一个易错点是概率比的计算。一定要用旧策略的概率做分母而且旧策略的概率要在收集数据时就算好、存下来不能在更新时重新算。因为更新过程中参数在变重新算的话分母就变了比值就错了。第二个易错点是裁剪目标的符号。论文里的目标是最大化 L_clip但深度学习框架通常做最小化所以损失要取负号。这个符号搞反了训练会完全反向奖励越训越低。第三个易错点是优势函数的标准化。要在每个 minibatch 内部做还是在整个批次上做论文里是在整个批次上做。我试过在 minibatch 上做效果也还行但批次大的时候差别不大。第四个易错点是 done 信号的处理。计算折扣回报时如果遇到终止状态下一状态的回报要置零。这个细节如果搞错价值网络的估计会有偏差进而影响优势计算。5. 常见问题与排查技巧实录5.1 训练不收敛的几种典型表现我在实际项目里遇到过好几种训练不收敛的情况下面整理成一张速查表现象可能原因排查方法解决思路奖励一直不涨学习率太小、探索不足打印策略熵、梯度范数调大学习率、增大熵系数奖励先涨后崩更新轮数太多、ε 太大看 KL 散度曲线减少更新轮数、调小 ε奖励剧烈震荡批量太小、优势方差大看优势的标准差增大批量、检查 GAE 参数价值损失不降价值网络容量不够看价值预测和实际回报的差距增大网络、调价值损失系数策略熵骤降熵系数太小看熵曲线增大熵系数、检查奖励尺度这张表里的每一条我基本都亲自踩过。印象最深的是“奖励先涨后崩”当时调了两天才发现是更新轮数设成了 50同一批数据训练太多轮新策略跑得太远裁剪也拉不回来。改成 10 之后立刻稳定了。5.2 奖励尺度的影响与处理奖励的尺度对 PPO 影响很大。如果奖励的绝对值很大比如几百上千那么优势函数的数值也会很大梯度更新会非常激进。这时候要么把奖励归一化要么把学习率调小。我一般会在环境包装器里做一个运行均值和标准差的归一化把奖励缩放到大致零均值、单位方差的水平。这个操作很简单但对训练稳定性的提升非常明显。论文里没有特别强调这一点但实践里几乎是标配。提示奖励归一化要在训练过程中动态更新均值和方差不要用固定的统计量。因为随着策略变好奖励的分布会变化。5.3 并行环境与数据收集效率PPO 是 on-policy 算法数据用完就丢所以数据收集效率很重要。如果只用一个环境收集 2048 步可能要等很久。常见的做法是开多个并行环境比如 8 个或 16 个每个环境跑 256 步合起来就是一批数据。并行环境的好处不只是快还能增加数据的多样性降低优势估计的方差。我在一个任务里把并行环境从 1 个增加到 16 个训练时间缩短了将近十倍而且最终性能还略有提升。不过并行环境也会带来新的问题不同环境的随机种子要设好否则可能所有环境都陷入同样的局部最优。我一般会给每个环境设不同的种子并且在环境初始化时加一点随机扰动。5.4 从论文到落地的几个经验论文里的实验主要是在 MuJoCo 的连续控制任务上做的这些任务的状态和动作空间都比较规整。如果你把它用到更复杂的场景比如图像输入或者离散动作空间有几个地方需要调整。图像输入的话策略网络和价值网络通常要共享卷积层减少参数量。离散动作空间的话概率比的计算要用 softmax 后的概率裁剪机制本身不变。如果动作空间是混合的既有连续又有离散那就要分别处理实现起来会复杂一些。还有一个经验是PPO 对超参数不算特别敏感但也不是完全不敏感。论文里说它“对超参数鲁棒”我理解是相对于 TRPO 那种需要精细调 KL 阈值的方法而言。实际上 ε、学习率、更新轮数这几个参数还是需要根据任务调一调的。我的建议是先用论文里的默认值跑一遍看看曲线再针对性地调。6. 我对这篇论文的几点个人体会读这篇论文最大的收获不是记住了那个裁剪公式而是理解了“限制更新幅度”这个思想在强化学习里的重要性。后来我看到很多新算法不管是离线强化学习还是扩散策略多多少少都有类似的思路不要让新策略偏离数据分布太远。PPO 把这个思想用最简单的方式实现了出来这大概就是它能流行起来的原因。另外一点体会是论文里的实验部分值得反复看。它不只是展示 PPO 比基线好还展示了在不同超参数下 PPO 的表现如何变化。这些图能帮你建立对参数敏感度的直觉比只看公式有用得多。最后分享一个小技巧如果你在实现 PPO 时遇到训练不稳定的问题先把更新轮数降到 1也就是每批数据只更新一次看看能不能跑通。如果能跑通再慢慢增加轮数找到稳定和效率的平衡点。这个方法帮我省了很多调试时间。
阅读完成 · 觉得有帮助?
咨询建站