ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PPO与GRPO更新的是什么?策略网络参数与优势函数详解

PPO与GRPO更新的是什么?策略网络参数与优势函数详解 最近好几个做强化学习的朋友问我同一个问题PPO 和 GRPO 反复刷榜各种开源项目里都在用但这两个算法到底在更新什么是更新奖励函数更新 Q 值还是更新那个 critic 网络刚开始接触 RLHF 和策略梯度的人很容易被 PPO 那套“actor-critic GAE clip”的组合拳绕晕更别说 GRPO 把 critic 整个砍掉之后很多人连“它凭什么还能学”都没想明白。先给一个最直接的回答PPO 和 GRPO 更新的是同一个东西——策略网络里的参数也就是让网络输出的动作概率分布发生改变。更直白地说它们在调整“在某个状态下更可能选哪个动作”的形状。PPO 是把“某个动作比平均好多少”这个信号换算成对概率分布的拉扯方向GRPO 走的是同一套逻辑只不过它用来衡量“好多少”的参照物不是来自价值网络而是来自同一批样本内部的相对表现。这个回答虽然一句话就能说完但要真正理解“更新什么、为什么这么更新、两者差异在哪”得把策略梯度、优势函数、重要性采样和 clip 机制一层层拆开看。这篇文章就干这件事顺便把 dual-clip PPO、连续动作场景下的代码实现也一起讲透。1. PPO 到底在改什么策略分布与概率比1.1 策略网络输出的不是“动作”而是“分布”先理清一个最容易混淆的点。策略网络 π_θ(a|s) 在离散动作空间里输出的是每个动作的概率在连续动作空间里输出的是高斯分布的均值 μ 和标准差 σ然后从 N(μ, σ) 里采样得到具体动作。无论哪种情况网络的输出本质上不是一个“动作值”而是一个分布的形状参数。PPO 做的就是调整这些形状参数。比如在某个状态下动作 A 原本被采到的概率是 0.2动作 B 是 0.5。如果执行动作 A 之后发现收益很高PPO 会把 A 对应的概率往上抬把 B 往下压如果某次训练里网络把某个动作的概率推到 0.9但实际收益很差PPO 会立刻把它拽回来。整个训练过程可以理解成策略网络是手里抓着一堆“概率旋钮”的操作员PPO 根据环境反馈决定每个旋钮往哪个方向拧、拧多大力。很多人看 PPO 的代码时发现 loss 里有个 ratio π_θ_new(a|s) / π_θ_old(a|s)然后对着这个 ratio 反复看。这个 ratio 的含义不是“新旧策略的差异程度”而是“新策略相对于旧策略在某个动作上的放大倍数”。ratio 1 说明新策略更容易采到这个动作ratio 1 说明新策略更不倾向采这个动作。梯度更新的时候ratio 乘以优势函数 A就决定了这个动作的概率是该调大还是调小、调多少。1.2 为什么不能直接用新的 log 概率算 loss这里有个很关键的设计动机如果直接用 π_θ_new 的动作概率去算梯度那每次更新后都要重新采样一批数据样本效率极低。PPO 用的是 off-policy 式的重要性采样——用旧策略 π_θ_old 采样一批轨迹然后通过 ratio 把“旧策略采样计算出的期望”换算成“新策略下的期望”这样同一批数据可以反复用来更新好几次。重要性采样本身有个毛病如果新旧策略差异太大ratio 的方差会爆炸梯度更新就会非常不稳定。举个例子旧策略下某个动作的概率是 0.01新策略把它调到 0.1ratio 直接变成 10这个样本对梯度的贡献就被放大了 10 倍如果优势函数数值也大一步更新就可能把策略推歪。clip 机制就是专门来压制这种“极端放大”的。1.3 连续动作场景下更新什么更新 μ 和 σ连续动作是很多人上手 PPO 时最容易写错的地方。离散动作下网络输出一个概率向量loss 里直接拿交叉熵或 log 概率算就行连续动作下网络输出的是 μ 和 σ动作是从高斯分布里采样出来的log 概率要按高斯分布公式计算log π(a|s) -0.5 * ((a - μ) / σ)^2 - log(σ) - 0.5 * log(2π)训练的时候μ 决定了“网络最倾向输出的动作中心值”σ 决定了“探索范围”。PPO 更新时如果某个动作的优势是正的会把 μ 往这个动作方向推如果优势是负的会把 μ 往反方向推。σ 的更新则更有意思——如果处于高优势区域网络会倾向于缩小 σ让动作更集中、更确定如果某个区域的样本优势忽高忽低不确定性大σ 会被拉大保持探索度。我在实际调连续动作 PPO 时踩过一个大坑σ 被初始化得太小导致前期采样动作都挤在 μ 附近探索不够策略陷入局部最优。后来把 σ 初始化为 1.0 左右或者在 loss 里对 σ 加一点熵正则项情况立刻好转。所以别小看这个 σ它的更新方向和幅度和 μ 一样重要甚至更容易出问题。2. PPO 的 loss 函数clip 与优势函数在干什么2.1 loss 的完整长啥样以及每一块的意图PPO 的优化目标通常写成L E[min(ratio * A, clip(ratio, 1-ε, 1ε) * A)]拆开看就是两项取最小值。第一项 ratio * A 是原始的策略梯度目标想让“好动作出现的概率变大、坏动作出现的概率变小”第二项 clip(ratio, 1-ε, 1ε) * A 是裁剪后的目标限制 ratio 不能在单次更新中跑得太远。取 min 的意图是当一个动作的优势 A 0 时我们希望 ratio 尽量大但如果 ratio 已经超过 1ε说明这一步更新幅度太大clip 后的值会被固定住min 结果就取 clip 项梯度不再继续鼓励 ratio 变大——这就是在“踩刹车”。当 A 0 时我们希望 ratio 尽量小但如果 ratio 已经跌到 1-ε 以下再往下减小反而会让 loss 更小、梯度继续推动 ratio 变小所以 min 在负优势区间几乎不起限制作用这也是原版 PPO 在极端负优势场景下不稳的根源dual-clip 解决的问题就在这。2.2 优势函数 A 是“相对表现”不是绝对奖励很多初学者把优势函数直接等同于奖励值这是理解偏差的起点。优势函数 A(s,a) 的定义是在状态 s 下选择动作 a 比“按平均策略水平行动”好多少。数学上是 Q(s,a) - V(s)也就是动作价值减去状态价值。正的 A 表示这个动作比平均水平好负的 A 表示比平均水平差。为什么要用“相对值”而不是“绝对值”因为强化学习任务里奖励的绝对数值往往没有太多意义。比如自动驾驶场景奖励值可能是安全系数、舒适度、时间成本加权求和数值范围随环境变化很大用绝对奖励作为更新信号不同 batch 之间的数值量纲和波动会让训练不稳定。用优势函数等于做了一次“去均值”处理让更新信号聚焦在“这个动作到底是优于还是劣于平均水平”而不是被环境本身的数值偏移带跑。GAEGeneralized Advantage Estimation是 PPO 里估计 A 的标配工具它用 λ 参数在“偏差大但方差小”和“偏差小但方差大”之间做平衡。λ 取 0 时只看即时奖励方差小但忽略了未来影响λ 取 1 时看完整回报偏差小但方差可能很大。实际操作里 λ 一般取 0.95 左右配合 GAE 的递推公式可以大大降低优势估计的方差。2.3 为什么说 PPO 是在“上一层保险”地更新策略PPO 能成为主流核心就是“稳”。它不是发明了新的策略梯度方向而是把一个容易失控的优化过程加上了两道保险第一道是重要性采样的 ratio 本身第二道是 clip 限制。很多人问我那为什么不直接用 TRPO 的 KL 散度约束TRPO 本质上也是做同样的事情用 KL 散度限制新旧策略的差异但实现起来涉及二阶 Hessian 矩阵或共轭梯度工程复杂度高、资源消耗大。PPO 的发明者用 clip 这个一阶近似来代替复杂的二阶约束效果接近但实现成本低得多。所以 PPO 更新的“保险”不是数学上最严格的而是工程上最高效的。3. GRPO不学 critic组内相对优势怎么算3.1 最大的架构差异没有 critic 网络GRPO 最抓眼球的设计就是它直接干掉了 critic。传统 PPO 需要两个网络actor 输出动作分布critic 输出状态价值 V(s)GAE 的计算依赖 critic 的输出来估计优势。而 GRPO 完全不训练价值网络那它拿什么当 baseline答案是用同一组采样样本的内部统计量当 baseline。具体操作是对同一个状态或同一个 prompt从旧策略里采样 G 个动作分别得到奖励 r_1 到 r_G。然后对这 G 个奖励做标准化A_i (r_i - mean(r)) / std(r)这里 mean 和 std 都是按这 G 个样本内部计算的。这样每个动作的优势就是“它在同组兄弟里的相对位置”。虽然这个估计不如 GA E 那样有理论上的无偏保证但在 reward model 打分或规则奖励的场景下组内相对优势恰恰更符合“我们要什么”——我们关心的不是某个回复的绝对得分而是同一问题上哪个回复更好。3.2 为什么去掉 critic 反而撑得住早期做策略梯度大家不太敢去掉 critic因为优势估计的方差会变大训练容易崩。GRPO 能扛住是因为它的使用场景和普通强化学习不一样。在 RLHF 或大模型对齐这类任务里同一个 prompt 下采样多个回答的成本相对可控而且 reward model 的打分分布不一定适合训练一个稳定的 critic。如果 critic 网络估值不准反而会往策略里注入偏差GRPO 直接避开了这个坑用采样的“兄弟样本”做自我对比天然带上了“同题对比”的语义。另一个关键点是 GRPO 通常配合 KL 散度正则使用。RLHF 里我们不希望模型在优化奖励时偏离参考模型太远所以损失会加上 β * KL(π_θ, π_ref) 项。这个 KL 正则相当于给策略更新加了一个“半径”即使优势估计的方差比 PPO 大也不会跑飞。3.3 组大小怎么定奖励归一化细节GRPO 里一个非常重要的超参数是 G也就是每个 prompt 采样的回答数量。G 太小组内统计量方差大baseline 不稳定G 太大采样成本高。我在实际跑 RLHF 时G 一般取 8~16 之间。如果 reward model 本身有随机性G 得大一点否则优势被噪声污染。还有一个细节容易被忽略如果同一组里所有奖励都相同std 为 0标准化会出 NaN。这时需要加一个极小值 ε 防止除零比如 A_i (r_i - mean) / (std 1e-8)。还有一个更稳妥的做法是直接用 z-score 加上 clip 限制防止某个极端奖励把整个组的优势拉爆。用伪代码展示 GRPO 的核心更新片段# 假设同一个 prompt 采了 G 个 response得到 rewards: [r1, r2, ..., rG] def grpo_advantages(rewards): mean np.mean(rewards) std np.std(rewards) 1e-8 return [(r - mean) / std for r in rewards] # 训练时用旧的 log_prob 和新的 log_prob 计算 ratio for prompt, old_log_probs, new_log_probs, rewards in batch: A grpo_advantages(rewards) # 组内相对优势 ratio torch.exp(new_log_probs - old_log_probs) loss -torch.mean(torch.min(ratio * A, torch.clamp(ratio, 1 - eps, 1 eps) * A))这段代码里最核心的就是“先组内归一化再算策略损失”理解了这一点GRPO 就不再是黑盒。4. dual-clip PPO负优势极端情况下的补丁4.1 原版 PPO 在负优势下为什么可能崩前面提到过clip 在 A 0 区间能有效踩刹车但在 A 0 区间几乎起不到限制作用。详细推导是这样当 A 0 时目标函数 min(ratio * A, clip(ratio) * A)。由于 A 是负数ratio * A 会随 ratio 减小而增大注意 loss 要取负号才是梯度这本来符合“负优势动作应该降低概率”的逻辑。但如果某次更新后 ratio 已经小于 1-εclip 项就变成了 (1-ε) * A这是一个固定值而 ratio * A 仍然随 ratio 减小而增大取 min 时如果 ratio * A 更大也就是数值上更接近正数min 会选择 ratio * A 而不是 clip 项。结果是什么在极端负优势下ratio 越小loss 反而越小梯度继续鼓励 ratio 压得更低于是陷入“负优势 → 概率骤降 → 极端负优势 → 概率再降”的恶性循环。少数几个极端样本就可能把策略推向崩溃尤其是奖励稀疏或者 KL 惩罚特别大的场景ratio 本来就容易波动更危险。4.2 dual-clip 的核心思想多设置一道下界dual-clip 的思路非常直接在 A 0 时给 loss 增加一个额外的下限防止极小 ratio 对应的梯度继续放大。常见实现如下L min(ratio * A, clip(ratio, 1-ε, 1ε) * A, 1ε 或一个固定常数下界对应的项)具体来说当 A 0 时原版 clip 的公式只在 ratio 太大时起作用对 ratio 太小没有下界。dual-clip 会引入一个新的 clip 上限针对负样本的或者用一个常数下界 c把 loss 在负优势区域的最低值限制住。这样即使 ratio 已经极度小梯度也不会继续推着策略往深渊里钻。从工程视角看dual-clip 是 PPO 在“长尾风险”上的补丁。它不是每次训练都被激活但一旦激活救的就是整个训练流程。4.3 什么时候一定要上 dual-clip我自己的经验是三类场景优先考虑 dual-clip第一奖励信号特别稀疏大多数样本优势都是 0 或负少数极端正样本又特别强第二KL 正则系数较大策略每步都被压向参考模型ratio 波动明显第三训练初期策略分布还没稳定随手一个 batch 里可能混着好几个异常样本。举个例子用 PPO 训练对话模型做 RLHF 时reward model 对某些安全问题的打分特别极端个别回答被打了 -5 分而正常回答都是 0.1、0.2 这种量级。这种情况下 ratio 被大面积压低负优势的极端样本开始主导梯度训练曲线会出现莫名其妙的断崖式下跌。加上 dual-clip 后这种断崖明显减少虽然有时候更新会显得“钝”一点但稳定性高了好几个档次。5. 代码视角离散与连续动作的 PPO 核心实现5.1 离散动作版 PPO 的更新核心离散动作版代码其实非常简洁核心就是用策略网络算 logits取 log_softmax 拿到 log 概率再算 ratio 和 loss。关键是更新循环里要把“旧策略的 log prob”保存下来不能重新算。def ppo_update(policy, optimizer, batch, epsilon0.2): states batch[states] actions batch[actions] old_log_probs batch[log_probs] advantages batch[advantages] logits policy(states) log_probs F.log_softmax(logits, dim-1) action_log_probs log_probs.gather(1, actions.unsqueeze(-1)).squeeze(-1) ratio torch.exp(action_log_probs - old_log_probs) clipped_ratio torch.clamp(ratio, 1 - epsilon, 1 epsilon) loss -torch.min(ratio * advantages, clipped_ratio * advantages).mean() optimizer.zero_grad() loss.backward() optimizer.step()注意这里 gather 的用法离散动作必须用动作索引去取对应位置的 log 概率而不是把整个 logits 都拿来算。初学者最容易在 log_probs 的形状上裁跟头。5.2 连续动作版高斯分布和 log 概率别再写错连续动作版复杂在 log 概率的计算。网络输出 μ 和 log_stdlog_std 可以通过 log(σ) 参数化保证 σ 恒为正。计算 log 概率时要套高斯分布公式def gaussian_log_prob(mu, log_std, actions): std log_std.exp() return -0.5 * (((actions - mu) / std) ** 2).sum(dim-1) \ - log_std.sum(dim-1) \ - 0.5 * mu.shape[-1] * np.log(2 * np.pi)写代码时最容易踩的坑就是维度μ 和 actions 的形状必须一致通常是 (batch_size, action_dim)。如果 log_std 是一个可学习的参数向量注意它在 loss.backward() 时会正常收到梯度不需要手动 detach但如果用了 tanh 或 sigmoid 压缩动作到固定范围就需要在 log 概率里加上对应的雅可比修正项否则策略梯度是有偏的。5.3 训练循环里那些“默认没人告诉你”的细节实际训练 PPO 时我总结过几个值得注意的细节。第一同一批数据不要更新太多次PPO 设计上允许 epoch 大于 1但 10 个 epoch 以上 ratio 很容易越界通常 2~4 个 epoch 比较合适。第二batch size 太小时 clip 形同虚设因为优势估计本身噪声大clip 只是在噪声之上加了个限幅底噪太大限幅也没用。第三学习率和 clip 范围要联动调整如果你把 ε 从 0.2 降到 0.1学习率最好也降一点否则更新步长会被 clip 频繁截断策略更新效率反而低。一个很反直觉的经验是连续动作场景下如果动作维度很高比如机械臂的 7 自由度控制log_std 的初始化特别关键。把 log_std 初始化为 0即 σ1通常是个不错的起点但如果动作范围本身就很小比如角度控制不超过 ±1σ1 会导致采样动作频繁撞边界这时反而要把 log_std 初始化成 -1 或 -2缩小初始探索范围。6. 常见问题与排查技巧实录6.1 训练时 loss 全是 NaNNaN 大概率出现在 log_prob 计算或 advantage 归一化上。连续动作版检查 (actions - mu) 是否出现极端值特别是动作没有 clip 时个别异常轨迹可能产生巨大差值GRPO 版检查 std 是否为 0建议所有归一化都加上 1e-8 的 epsilon。还有一个隐蔽点如果用 float16 混合精度训练log_std.exp() 很容易溢出可以考虑把 log_std 限制在 [-20, 5] 之类的范围内防止 σ 太小或太大。6.2 策略快速收敛到“什么都不做”或“狂按同一个动作”这是熵崩塌的典型症状。观察策略分布的熵如果熵值快速掉到 0 附近说明策略丧失探索能力。解决办法是三选一或组合用降低学习率、增大 clip 范围让更新更“敢动”、在 loss 里加上熵奖励项 coefficient * entropy。我通常把熵系数初始化为 0.01 左右然后根据训练曲线动态调整如果熵下降太快就提高系数如果最终性能上不去就降低。6.3 GRPO 组内奖励方差过低优势几乎全为 0当 reward model 对同一个 prompt 的多个 response 打分都差不多时组内标准化后优势接近 0策略更新信号极弱。这时单纯加大 G 不一定有用因为 reward model 本身分辨率不够。可以考虑调整 reward 的 scale比如把奖励从 [0,1] 映射到 [-1,1]或者对奖励做指数化放大也可以引入更细粒度的过程奖励而不是只用最终打分。注意不要为了制造方差而人为加入噪声那会让策略学到错误的偏好。6.4 该用 PPO 还是 GRPO我的选择标准如果你在做一个传统强化学习任务环境奖励信号来自仿真器状态价值函数 V(s) 可以被有效学习那 PPO GAE 仍然是最稳的选择。如果你的任务是大模型对齐、LLM 的 RLHF状态空间接近无限、单次采样成本高、奖励来自 reward model那 GRPO 更省心。原因在于LLM 场景下 critic 网络很难训好价值估计不准还不如用组内相对优势而且同一个 prompt 采样多个回答的成本远比传统 RL 低因为你只需要调一次批量生成接口。6.5 一个容易被忽略的“元问题”最后说一个经常出现但又容易忽略的问题很多人把 PPO 和 GRPO 的 loss 细节讨论得很细却没想清楚 reward 信号本身从哪来、是否可信。我见过不少项目算法细节调得无可挑剔但 reward model 本身有偏向最后策略被带到沟里去了。算法只是在更新“概率分布的旋钮”旋钮往哪边拧取决于你给的信号信号错了再稳的更新也是在错误方向上走得更远。先校验 reward 可靠性再做算法调优顺序不要搞反。就我自己的实操体会而言PPO 和 GRPO 的差别真正落地时最影响手感的是“信号来源的稳定性”和“评估基线的设计”。PPO 的 critic 是个学习出来的 baseline状态价值函数在复杂任务里不一定学得准GRPO 的组内均值是采样出来的 baseline可靠性取决于组内样本量。两者都有各自适合的战场没有绝对的优劣。至于 dual-clip 这类补丁学的时候可以往后放但真正跑大模型训练时最好提前加上因为极端样本的破坏力往往比想象中来得更突然。
返回列表