说来也惭愧,这个系列写到了第四十三篇,我才终于敢认真碰“策略的单调提升”这块硬骨头。之前每次看到 TRPO、PPO 论文里的那个下界公式,我的第一反应都是跳过,心里想着“反正代码能跑”。但等到真的在连续控制任务上调参,被策略崩坏、性能骤降、训练曲线自由落体折磨过几轮之后,我才意识到:不搞懂单调提升理论,你根本不知道 PPO 那个 clip 到底在防什么,也不知道为什么算法叫“信任域”策略优化。这篇我就把这条学习线完整梳理一遍,从“为什么策略梯度会翻车”讲到“怎么用替代目标和约束把它拉住”,最后配合工程实现里最常见的坑,给你一份能直接照看的避坑手册。
这一讲的内容不要求你立刻看懂所有数学推导,但我会把每一项的来龙去脉讲清楚。适合正在学强化学习、尤其是被策略梯度系列算法折磨过的同学,也适合跑过 PPO 但说不清“为什么这样改就能更稳”的调参型选手。
1. 单调提升理论到底在解决什么问题
1.1 策略梯度为什么容易“翻车”
先从最熟悉的策略梯度说起。你用一条策略π_θ去和环境交互,收集一批轨迹,估算出期望回报关于参数θ的梯度,然后沿着梯度方向更新参数。听起来很自然,就像爬山的时候沿着最陡的方向迈一步。但问题来了:你算出的这个梯度,只是“当前策略”下的局部方向,并不是整座山的全局地图。
一旦你沿着这个方向更新得太多,新策略和旧策略的差别就会变得很大。新策略会开始访问旧策略很少访问的状态,原来算出来的梯度在新策略下已经不再准确,更新方向可能变成错误的。强化学习的损失和监督学习不一样,监督学习里你拿一批固定样本反复迭代没关系,但强化学习里样本分布会随着策略改变而漂移。用昨天的人数据训练今天的人,翻车是常态。
我见过一个特别典型的例子:某个连续控制任务里,策略梯度方法普普通通跑了几百轮,回报一直在涨,然后某一轮学习率稍微调大了一点,下一次更新之后策略直接“瘫”掉了,整个评估回报掉到负值,再也回不来。这就是没有单调性保证的后果——每一步都在说“我要变好”,但没有任何东西承诺“你不会变差”。
1.2 从“一步更新”到“保证变好”的思维转变
单调提升理论的思路很朴素:我不追求每一步都迈得最大,我只追求每一步都保证不后退。数学上说,如果第 k 轮策略是π_k,更新完之后是π_{k+1},我们希望
η(π_{k+1}) ≥ η(π_k)
其中η(π)表示策略的期望折扣回报。如果每一轮都满足这个不等式,那么最终性能曲线就是单调不减的,至少理论上是这样。
这不是硬凑出来的口号。2002 年某个经典工作提出过“conservative policy iteration”的思路,后来 TRPO 论文里把它推广成更实用的形式。核心思想是:与其去优化真实回报η(π_new)(因为这东西很难直接算),不如优化一个真实回报的下界。只要这个下界在旧策略处和真实回报相等,并且下界函数永远不高于真实回报,那你优化下界得到的策略,就不会比旧策略差。
这个过程有点像你评估一笔投资:真实回报有很多不确定性,但你用一个保守的估值模型做决策,只要这个模型永远不给你“虚高”的预期,那你每换一次策略,至少不会亏太多。单调提升就是给强化学习装了一个“绝不虚标”的估值模型。
1.3 一句话概括:约束替代目标,替换真实回报
把单调提升理论的核心压缩成一句话,就是:
用旧策略采样数据构造一个替代目标函数,同时限制新旧策略不要差太远,在“信任域”内优化替代目标,从而保证真实回报单调不减。
对应 TRPO 论文里的经典不等式,形式上可以写成:
η(π_new) ≥ L_π_old(π_new) − C * D_KL_max(π_old, π_new)
这里L_π_old(π_new)是替代目标,可以理解为“用旧策略数据估计的新策略回报”;D_KL_max是新旧策略在每个状态上的 KL 散度的最大值,衡量行为差异;C是一个由优势函数最大值和折扣因子决定的常数。理论告诉你:只要右边这一项比当前回报高,那么真实的新策略回报也会比当前回报高。
这个式子里的“约束”不是生硬加进去的。它来自替代目标与真实回报之间的误差上界:如果你完全不限制新旧策略差异,替代目标的估计误差会无限放大;如果限制得很死,那每次只能走一小步。单调提升理论本质上是在替你回答一个经典问题:强化学习里每一步到底应该走多远、往哪个方向走,才能既不莽撞又不原地踏步。
2. 替代目标函数:为什么可以“骗过”优化器
2.1 直接优化真实回报为什么做不到
很多第一次接触 TRPO 的人都会有个疑问:为什么不直接最大化真实回报η(π_new),非要搞一个替代目标?
因为真实回报不是一个能对策略参数直接求梯度的简单函数。η(π_new)的定义涉及轨迹的分布,而轨迹分布又受到环境动态的影响。环境转移函数P(s'|s,a)对你来说通常是未知的,你不能把这个依赖关系展开后对策略参数求梯度。策略梯度方法之所以能工作,靠的是对数似然技巧把环境动态从梯度里“消掉”了。但如果你想求“新策略回报关于新策略参数的梯度”,你仍然需要知道新策略访问每个状态的分布,这个分布是策略变化之后才产生的,你手里根本没有新策略的样本。
所以现实的做法是:我们只能从旧策略的采样里“猜测”新策略如果上线,大概能拿多少分。替代目标就是干这个的。它可以被优化器直接算梯度,同时它和真实回报之间有一个可控的误差范围。
2.2 替代目标L的构造
替代目标的标准形式写作:
L_π_old(π_new) = E_{s∼d_π_old, a∼π_old} [ (π_new(a|s) / π_old(a|s)) * A_π_old(s,a) ]
拆开看,主要有三块。第一块是状态分布d_π_old:我们用旧策略访问过的状态来抽样。第二块是重要性采样比率π_new / π_old:用来修正新旧策略的动作概率差异。第三块是优势函数A_π_old(s,a):表示在状态 s 下选动作 a 比选其他动作平均优势高多少。
如果新策略在某个状态上更容易采取高优势动作,替代目标就会变大;如果新策略倾向于采取低优势动作,替代目标就会变小。梯度优化看到这个目标,就会把概率质量往高优势动作上搬。这就是“骗过优化器”的地方:优化器以为自己是在最大化新策略的真实回报,实际上它只是在最大化一个旧策略数据下的加权估计。
这里有一个特别重要的点:替代目标在π_new = π_old这一点上,与真实回报不仅函数值相等,一阶导数也是相等的。也就是说,在旧策略附近,用替代目标估计出来的“本地改进方向”是准确的。这就是为什么策略梯度类方法可以在小步更新下工作——步长一大,误差就出来了。单调提升理论正是在这个“一阶近似准确”的基础上,再给你补一个误差界。
2.3 替代目标与真实回报的差距来源
替代目标毕竟不是真实回报,两者的差距主要来自状态分布漂移。旧策略访问过的状态集合,可能和新策略真正的状态访问分布差别很大。新策略可能更激进,跑到一些旧策略几乎不会去的状态;在这些新状态上,替代目标给出的估计基本属于“盲猜”,因为没有任何真实样本覆盖。
生活类比:你在川菜馆吃得很开心,然后向朋友推荐这家馆子,但你的朋友口味清淡。你用自己的偏好去估朋友吃得开不开心,误差一定存在。替代目标就是“用旧策略的口味去预测新策略的感受”,在新旧策略行为接近时,预测还算靠谱;一旦行为差异变大,预测就失灵了。
所以,理论里必须加一个约束项,明确告诉你:你要优化替代目标可以,但新旧策略之间的差异必须控制住。这个约束不是锦上添花,而是替代目标能否成立的“前提条件”。没了这个约束,你优化的对象几乎可以完全脱离真实回报,单调提升就成了空谈。
3. 信任域与KL散度:单调提升的“安全带”
3.1 为什么选择KL散度而不是欧氏距离
既然要限制新旧策略差异,为什么不直接用参数距离,比如||θ_new − θ_old||?因为参数距离小,不代表行为距离小。策略网络最后一层稍微改一点点权重,输出分布可能完全变了个形状。反过来,参数距离很大,但只要经过归一化或者某些层结构,最终策略行为可能没有太大变化。
强化学习关心的是“行为”上的差异,而不是“参数”上的差异。KL 散度衡量的是两个概率分布之间的差异,正好落在行为层面。举个具体例子:有两个高斯策略,均值一个在0,一个在0.5,方差一样。参数层面你可能只动了均值这个数,但从动作采样来看,已经偏移了不少;如果用 KL 散度计算,这个差异会被如实反映出来。
KL 散度本身非对称,一般写成KL(π_old || π_new)或KL(π_new || π_old),实际工程里可以取对称形式,或者用二阶近似变成费雪信息矩阵。理论推导中用 KL 的主要好处是它和总变差距离之间有可靠的不等式关系,能用来给回报误差定界。这就像坐车系安全带:椭圆形的安全带约束比单纯限制“方向盘转几度”更贴合实际风险。
3.2 界函数与理论保证
回到那个核心不等式。TRPO 论文里的常数C大概长这样:
C = (2 * ε * γ) / (1 − γ)^2
其中ε是优势函数绝对值的最大值,γ是折扣因子。C越大,约束越紧,因为一旦新旧策略差异变大,惩罚项会被放大。这里不需要背公式,关键是要理解它的几何意义:真实回报曲线和替代目标曲线之间的“缝隙”,被一个关于新旧策略 KL 散度的函数从上方夹住了。
只要你在满足D_KL_max(π_old, π_new) ≤ δ的信任域里面去最大化替代目标,那新策略的真实回报就有一个理论下界。换句话说,每次优化之后,要么你找到的策略确实更好,要么最坏情况下也会保持在一个可接受的底线之上。注意,这里的“最坏情况”考虑的是所有状态上的最大 KL,所以工程实现里很多人用平均 KL 代替,理论上会稍微松一点,但实践中更好算。
有了这个界,策略更新就从“赌博”变成了“有安全网的攀岩”。你可以在信任域内放心大胆地找局部最优,因为理论保证你不会掉下去。当然,这只是理论层面的保证,实际工程里优势函数是估计出来的,模型也有误差,后面我会专门展开说。
3.3 从理论到TRPO/PPO的落地
理论看着很美,但真正落地到算法,大家发现“严格保证单调”太奢侈了。TRPO 的做法是把 KL 散度约束写成硬约束,每次更新解一个带约束的优化问题:
maximize L_π_old(π_new) subject to KL(π_old, π_new) ≤ δ
TRPO 用共轭梯度法避开直接构造和求逆费雪信息矩阵,计算量比一阶方法大不少,但确实稳。后来大家觉得每次解约束优化太麻烦,而且 TRPO 对噪声和实现细节很敏感,于是 PPO 换了个思路:不显式求解约束问题,而是用一个裁剪的替代目标,让优化器在“越过信任域边界的瞬间”感受到惩罚。
PPO 的裁剪目标大概是:当新旧策略概率比超过某个区间时,把目标函数的增益压住。这个操作看起来不像单调提升理论那么严谨,但它暗含了同样的思维:允许优化替代目标,但限制新旧策略不要差太远。实践里 PPO 的表现非常稳,于是成了默认选择。理解了单调提升理论,你就能理解 PPO 的 clip 参数ε到底在限制什么——它不是一个随机的小技巧,而是对“信任域”的一种松弛实现。
4. 工程实践:让单调提升在代码里不至于“翻车”
4.1 优势估计和重要性采样:最容易埋雷的两个地方
先说优势估计。PPO 类算法里最常用的不是 TD 残差,而是广义优势估计 GAE。我第一次写 PPO 的时候,图简单直接用discounted_return − value,结果训练曲线抖得像心电图。后来换成 GAE,λ取 0.95 到 0.97 之间,方差一下子降下来。
GAE 的核心是平衡偏差和方差:λ越大,越偏向蒙特卡洛长程回报,方差大但偏差小;λ越小,越偏向一步 TD,方差小但偏差大。实践里 0.95 是很好的起点。需要注意的是,GAE 计算需要价值网络预测的value,如果价值网络还没拟合好,优势估计一开始就是歪的。所以建议在早期多更新几轮价值网络,或者给价值网络单独一个稍小的学习率。
再说重要性采样。PPO 里计算 ratio 的标准写法是:
ratio = exp(log_pi_new − log_pi_old)
然后loss = −min(ratio * A, clip(ratio, 1−ε, 1+ε) * A)。这里最容易犯的错是:log_pi_old必须是你“采数据时那个旧策略”的对数概率,而不是当前策略更新前的对数概率。如果你在同一个 epoch 里重复使用同一批数据,但每次计算log_pi_old时都重新用当前策略算一遍,那就等于把重要性采样比率算错了,clip 也会失效。
还有一个常见现象:ratio 动不动爆表。这说明新旧策略差异过大,已经跑出信任域了。这时候就算 clip 能压住一部分梯度,更新也已经很危险。要做的不是调大 clip 范围,而是降低学习率、增加 KL 约束或者减小每个批次里的更新次数。
4.2 KL仪表盘:理论中的安全带,工程里的指示灯
理论里的 KL 散度是约束条件,工程里它就是仪表盘。我给所有跑 PPO 的人一个建议:训练日志里一定要记录近似 KL 散度,就是新旧策略输出分布的 KL。很多库会直接给你approx_kl,大概计算方式是:
KL ≈ mean(log_pi_old − log_pi_new)
如果近似的 KL 每轮都小于 0.001,说明策略基本没动,模型在“原地踏步”,可能是学习率太低或者 clip 范围太小。如果 KL 突然跳到 0.05、0.1 以上,说明这次更新步子太大,已经跑出信任域,这时需要立刻警惕。在连续控制任务里,我一般把目标 KL 设在 0.01 到 0.03 之间,超过 0.05 基本就可以判断这次更新有问题。
更讲究一点的做法是自适应 KL 惩罚:目标里加一项β * KL(π_old, π_new),每轮根据实际 KL 和目标 KL 的偏差调整β。KL 太大就调大β压住步长,KL 太小就调小β让策略更多探索。这个思路比单纯调学习率更贴原理,因为它直接监控并约束了“行为差异”。
如果你看到 PPO 的回报曲线一开始还行,后面突然出现一根长针式下跌,先别急着怪随机种子,去翻一下那步的 KL 散度。八成是某个 batch 算出了特别极端的重要性权重,或者价值网络发生了剧烈震荡。把握好 KL 仪表盘,等于多了一双眼睛盯住单调提升的“安全底线”。
4.3 调参避坑清单
我把这几年调参踩过的坑整理成列表,你可以对照着查:
- 不要直接对原始回报做标准化。回报分布可能高度倾斜,建议对优势
A做标准化,而不是对回报做标准化,否则会引入环境本身的偏移。 - 价值网络和策略网络不要用同一个学习率。我的经验是价值网络学习率比策略网络小 2 到 5 倍,稳定性会好很多。
- GAE 的
λ不要默认取 0.99。在任务步数长、稀疏奖励的情况下,0.99 可能方差过大;我一般先用 0.95,密集奖励可以降到 0.9。 - 重要:确认你的
log_pi_old是采样时存的,不是临时算的。很多人调了一天 bug 都出在这里。 - 使用高斯策略时,如果动作经过 tanh 压缩,
log_prob需要修正 Jacobian 项。网上很多 demo 直接输出动作不压缩,但真实控制任务里动作范围有限,不修正算出来概率是错的。 - 尝试不同随机种子对比时,保证旧策略的初始化状态一致,否则一开始的 KL 和回报基线就不同,后续对比没有意义。
- 每收集一次数据后不要更新太多次。PPO 原始实现里往往一个 batch 重复更新 4 到 10 次,但如果你发现 KL 爆表,优先减少更新次数,其次才调 clip。
- 如果训练后期回报上不去,可以适当放宽 clip 范围,比如从 0.2 调到 0.3,给策略更多更新空间。但前提是 KL 散度不能出现持续的尖刺。
5. 从单调提升出发:重新理解PPO与后续算法
5.1 TRPO之后为什么PPO成了默认
TRPO 有漂亮的理论保证,但工程上痛点很明显:费雪信息矩阵的近似、共轭梯度求解、线性搜索回退,每一步都考验实现细节。很多人在小规模任务上复现 TRPO,结果发现效果不稳定,甚至不如调好参数的 PPO。PPO 把“硬约束”变成“软惩罚”,用一个裁剪目标把算法控制在一阶优化的范畴,复杂度低、兼容性好、分布式也好写。
但 PPO 的 clip 目标从理论上看并没有严格的单调提升保证。它只是在旧策略附近把目标函数做了“钳制”:当概率比超出[1−ε, 1+ε],就不再鼓励继续往外走。这个机制能有效防止一步跨太远,但它并不能保证真实回报每轮都上升。实践中 PPO 效果好,主要原因是“大部分时候不翻车”,而不是“理论上必然单调”。理解了这一点,你就不会迷信某个算法的名字。
5.2 单调提升不是免费午餐
任何理论保证都建立在若干理想假设上。第一,优势函数必须是真实优势函数,或者至少是无偏估计。但实际你用的是价值网络估计,价值网络本身有误差,优势估计偏差会导致替代目标算出来的方向并不精确。第二,理论里的常数C往往非常大,如果真按它做严格约束,每次更新会小到令人绝望;所以实际实现都采用平均 KL 或 clip 这类近似,牺牲了一部分理论保证换来可用性。
换句话说,单调提升在代码里更像是“努力的方向”,而不是“必然的结果”。需要持续监控 KL、回报、优势分布的变化,发现偏离立刻修正。这就像开车时的车道保持辅助:它能帮你修正方向,但你不能因此闭眼。理解理论不会让你免于调参,但会让你知道该往哪个方向调。
5.3 这条理论线给我的启发
学完单调提升理论之后,我再回看自然策略梯度、TRPO、PPO、以及后来的许多最大熵或正则化方法,发现他们都共享一个内核:不放心让策略自由飞翔,必须给它加一个“靠近旧策略”的约束或先验。这种约束有时是 KL,有时是熵正则,有时是隐式的裁剪范围,本质都是为了防止“过犹不及”。
我做连续控制实验的时候,现在会同时记录三个指标:平均回报、近似 KL、新旧策略的期望优势差。只盯回报容易自欺欺人,因为你不知道一次大涨是不是透支了后面的稳定性。把这三个指标放在一起看,你就能像看仪表盘一样,随时判断当前训练是在“健康走步”还是在“疯跑”。这套思路不仅适用于 PPO,你转到任何策略优化算法上都能用。
最后说一个我在实践里的小技巧:不要只关注每轮更新后的最终回报,多画一条“按照理论下界计算的保守回报预测线”。如果真实回报反复跌到预测线以下,说明你的优势估计或 KL 监控出了问题;如果真实回报一直压在预测线上方,说明你跑在了一个比较健康的“单调提升走廊”里。这个习惯帮你把抽象理论变成实际的训练质量指标,比单纯盯 loss 曲线有用得多。