☰
TRPO置信域策略优化:原理推导与实现要点
2026/10/14 7:18:47 网站建设 项目流程

如果你已经啃过策略梯度那一堆公式,大概率会跟它吵过很多次架:学习率调小了吧,一轮迭代下来参数几乎没动;调大一点,某个倒霉动作的概率直接冲到1,整条训练曲线当场崩给你看。这个系列前两篇聊的是策略梯度的基础形式和方差缩减手段,今天这篇文章进入“第三篇”的主题——直接策略搜索中的置信域策略优化(Trust Region Policy Optimization,TRPO)。

TRPO是强化学习里一个非常“硬核”的算法,它没有靠玄学调参,而是从数学上给策略更新画了一个圆圈:每次更新,只允许新策略和旧策略之间有界差异,这个界叫做置信域。整个算法围绕这个思想搭建目标函数和约束条件,再用自然梯度求解。文章适合已经掌握基本策略梯度、想深入理解稳定更新机制的人,也可以当作从零复现TRPO的实操笔记。我会把原理拆开讲,也会把实现上的坑和数据贴上。

1. 先搞清楚策略梯度方法到底哪里痛

1.1 从“参数更新”到“策略漂移”的距离错觉

策略梯度方法的基础形式是直接对期望回报求梯度:

[ \nabla_\theta J(\theta) = \mathbb{E}{\tau \sim \pi\theta} \left[ \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot A_t \right] ]

然后用梯度上升更新参数:(\theta \leftarrow \theta + \alpha \nabla_\theta J)。

看上去合理,但这里有一个很隐蔽的错觉:我们控制的是参数空间里的距离,真正想控制的是策略分布的变化程度。参数空间里走了一小步,策略分布可能已经跑出去十万八千里。举一个极端例子:在一个离散动作空间下,如果某一条路径上logits从[0.5, 0.5]变到[1.1, -0.1],参数变化并不大,但softmax出来的动作概率从0.5:0.5变成了大约0.77:0.23。这个变化对训练来说已经不算小了。

反过来,某些参数空间里看着很大的更新,因为激活函数饱和或者权重尺度问题,策略分布可能几乎没变。所以“步长小一点就稳”这句话,本质上是在用错误单位衡量距离,纯属碰运气。

1.2 学习率这座独木桥:大了崩,小了爬

用过策略梯度的人都有体验:学习率是第一个过不去的坎。学习率设成0.01可能梯度爆炸,设成0.0001又半天不涨。更恶性的是,策略梯度在一开始不稳定,一个坏掉的更新会把策略推到奖励很低的区域,之后即使回退,数据分布变了,后面也很难纠正。

有人会说:“那我动态降学习率不就行了?”但学习率本身只是对参数空间的线性缩放,它并不理解策略分布的几何结构。动态调度可以在训练后期降低风险,却仍然无法保证“每一步更新后的KL散度都有界”。TRPO的核心洞察就在这里:我们不应该在参数空间里用固定步长,而应该平均KL散度作为信任半径,在这个半径内部做优化。

1.3 更麻烦的是状态维度的均匀性问题

一个策略网络要对所有访问到的状态都“负责”。固定学习率对所有参数一视同仁,但实际不同状态对应的样本数量差异很大。有些状态频繁出现,它们贡献的梯度就大;有些状态偶尔出现,它们对应的策略可能一次更新就变化剧烈。TRPO后来的约束形式里特意把状态分布(d^{\pi_{\theta_{\text{old}}}})写进去,就是要平均掉这种不均匀性,保证任何一个状态子集都不会被“踢”得太狠。

我复现TRPO初期,几乎每天都耗在学习率厮杀里。直到我接受“参数欧氏距离无关紧要”这个事实,才真正理解为什么需要置信域——约束不是限制自由度,而是保护优化过程不被自己的莽撞打断。

2. 置信域思想:用分布距离给策略更新套上笼头

2.1 信任区域的定义和直觉

置信域翻译过来就是“我们相信模型在这一步内,近似关系是可靠的”。想象你摸黑上山,手里有根拐杖:每迈一步之前,你只会相信拐杖探过的一小片区域,保证脚底是稳的。TRPO中的“拐杖”就是当前策略(\pi_{\theta_{\text{old}}}),而“一小片区域”就是这个策略附近、KL散度不超过阈值(\delta)的邻域。

数学上,它要求更新前后策略满足:

[ \mathbb{E}{s \sim d^{\pi{\theta_{\text{old}}}}} \big[ \mathrm{KL}\left( \pi_{\theta_{\text{old}}}(\cdot|s) ,|, \pi_\theta(\cdot|s) \right) \big] \le \delta ]

也就是说,对所有采样到的状态,平均KL散度不超过一个指定的小常数。(\delta)通常取0.01到0.05之间。之所以取“平均”而不是“最大”,一是因为最大KL对异常状态太敏感,二是因为自然梯度理论本身就建立在平均信息度量的基础上。

2.2 KL散度为什么能充当策略距离

KL散度不是对称距离,但它衡量的是“用新策略编码旧策略的数据时,平均会额外丢失多少信息”。在策略更新里,旧策略采样的数据是我们唯一能用的数据,我们关心的是新策略在旧数据上会不会变得太离谱。

它和普通的欧氏距离有本质区别。比如两个高斯分布(N(\mu_1, \sigma^2))和(N(\mu_2, \sigma^2)),KL散度和((\mu_1-\mu_2)^2)成正比,看起来差不多;但当方差不同时,KL散度对方差差异的敏感程度和均值差异完全不同。高斯策略的输出方差直接关系到探索程度,如果一次更新把方差压掉太多,策略会过早确定性化,这在机器人控制任务里会表现为突然动作僵住。

因此,KL散度比参数二范数更能反映策略的真实“行为距离”。这也是TRPO取名“置信域”的原因:你要相信这个KL半径范围内的近似目标函数是足够准确的。

2.3 为什么这个约束能起作用

有了KL约束后,算法的每一次迭代都有理论上的单调提升保证。虽然实际估计有误差,但至少避免了最危险的跳变。每一次更新前,新策略都必须跟旧策略在已经见过的状态上保持相似——具体来说就是动作分布不过度偏移。

这样做有两个直接好处:

  • 旧策略收集到的轨迹数据,可以用来估计新策略的回报期望,重要性采样比率不会失控。
  • 如果某一步更新效果不好,我们可以通过线搜索回退,因为约束半径内总能找到一个“不让策略变坏”的改进点。

换句话说,置信域像一个安全笼头:马还是那个马,但每次只能往前蹿一小步,蹿错了还能拉回来。

3. TRPO的核心式子:目标函数与约束

3.1 替代目标:用旧数据评估新策略

TRPO的完整目标经常写成:

[ \max_{\theta} ; L_{\theta_{\text{old}}}(\theta) = \mathbb{E}{s \sim d^{\pi{\theta_{\text{old}}}}, a \sim \pi_{\theta_{\text{old}}}} \left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_{\text{old}}}(a|s)} A_{\theta_{\text{old}}}(s,a) \right] ]

subject to 上面那个KL约束。

这里为什么要用重要性采样比率(\frac{\pi_\theta(a|s)}{\pi_{\theta_{\text{old}}}(a|s)})?因为评估新策略期望回报,本应该从新策略中采样,但新策略还没被使用,无法采样。TRPO的做法是用旧策略的轨迹数据,乘以新旧动作概率之比,得到一个对期望回报的近似估计。这个估计在KL约束很小的时候是准的;一旦比率偏移太大,估计方差就爆炸。

很多初学者问:为什么不直接用新策略的log概率?因为那样的话,对新策略的替代目标和旧数据其实不是同一个分布。重要性采样是连接新旧策略的桥梁。

3.2 约束的精确形式与KL方向

TRPO原文里约束写作有限制敏感度和方差的形式,但实践中我们通常计算:

[ \bar{D}{KL}(\theta{\text{old}} ,|, \theta) = \mathbb{E}{s \sim d^{\pi{\theta_{\text{old}}}}} \left[ \mathrm{KL}\left( \pi_{\theta_{\text{old}}}(\cdot|s) ,|, \pi_\theta(\cdot|s) \right) \right] ]

注意KL方向是旧策略在前、新策略在后。虽然KL不具备对称性,但因为更新前后差异很小,方向差异在二阶近似下会消失(KL的二阶泰勒展开是相同Fisher矩阵)。所以实际代码里两种顺序都可以,但建议固定为旧策略在前,保持一致性。

3.3 常见策略类别的KL计算方式

最常用两种情况:

离散动作空间:

策略输出softmax概率向量(\mathbf{p}),新策略(\mathbf{q}),KL散度为:

[ \mathrm{KL}(\mathbf{p} |\mathbf{q}) = \sum_i p_i \log \frac{p_i}{q_i} ]

直接累计求和,注意数值下溢,加上1e-10级别的clip。

连续动作空间:

若策略是高斯对角分布,均值(\mu(s)),标准差异(\sigma(s))(log std可以单独学习),则:

[ \mathrm{KL}(N(\mu_1,\sigma_1^2)|N(\mu_2,\sigma_2^2)) = \log \frac{\sigma_2}{\sigma_1} + \frac{\sigma_1^2 + (\mu_1-\mu_2)^2}{2\sigma_2^2} - \frac{1}{2} ]

这个公式要记熟。我见过好几个实现里把均值和方差搞反,导致KL为负。总之,先把KL计算单元测试好,再跑算法。

4. 从约束优化到自然梯度:TRPO的底层推导

4.1 拉格朗日视角和两个近似

把原来的约束优化问题写成拉格朗日形式:

[ \max_\theta ; L(\theta) - \lambda \left( \bar{D}{KL}(\theta{\text{old}}, \theta) - \delta \right) ]

但λ的调节很麻烦。TRPO的思路是:在(\theta_{\text{old}})附近,把目标函数做一阶泰勒展开,把KL约束做二阶泰勒展开,得到一个可以直接求解的二次约束线性规划子问题。

设参数差为(\Delta\theta = \theta - \theta_{\text{old}}),则:

[ L(\theta) \approx L(\theta_{\text{old}}) + g^T \Delta\theta ]

其中(g = \nabla_\theta L(\theta)|{\theta=\theta{\text{old}}})就是策略梯度。

而KL散度在(\theta_{\text{old}})处取到最小值0,所以一阶项为0,二阶项为:

[ \bar{D}_{KL} \approx \frac{1}{2} \Delta\theta^T F \Delta\theta ]

这里的(F)是Fisher信息矩阵,稍后细讲。于是子问题变成:

[ \max_{\Delta\theta} ; g^T \Delta\theta \quad \text{s.t.} \quad \frac{1}{2} \Delta\theta^T F \Delta\theta \le \delta ]

4.2 解盒子:自己推导一遍步长公式

这是一个带不等式约束的二次规划。构造拉格朗日:

[ \mathcal{L} = g^T \Delta\theta - \mu \left( \frac{1}{2} \Delta\theta^T F \Delta\theta - \delta \right) ]

对(\Delta\theta)求导并令其等于0:

[ g - \mu F \Delta\theta = 0 ]

得到:

[ \Delta\theta = \frac{1}{\mu} F^{-1} g ]

也就是说,上升方向是(F^{-1}g),这个方向叫自然梯度方向。再用约束条件确定系数:

[ \frac{1}{2} \left(\frac{1}{\mu}\right)^2 g^T F^{-1} g = \delta ]

所以:

[ \frac{1}{\mu} = \sqrt{\frac{2\delta}{g^T F^{-1} g}} ]

最终:

[ \Delta\theta = \sqrt{\frac{2\delta}{g^T F^{-1} g}} \cdot F^{-1} g ]

这就是TRPO每一步的更新量。你能看到,它和普通梯度下降完全不同:步长不是人工设定的学习率,而是由KL半径(\delta)、梯度方向和Fisher矩阵共同决定的。

4.3 Fisher信息矩阵是什么,以及它的几何含义

Fisher信息矩阵的形式是:

[ F = \mathbb{E}{s \sim d^{\pi{\theta_{\text{old}}}}, a \sim \pi_{\theta_{\text{old}}}} \left[ \nabla_\theta \log \pi_\theta(a|s) \cdot \nabla_\theta \log \pi_\theta(a|s)^T \right] ]

在正则条件下,它也等于KL散度在(\theta_{\text{old}})处的Hessian矩阵。自然梯度(F^{-1}g)就是在黎曼度量下让目标函数上升最快的方向。

打个比方:地球球面上,你想从北京走到上海的“直线”,不能在平面地图上画直线,必须沿着球面走大圆。参数空间的高维流形就是弯曲的“球面”,欧氏梯度是平面地图上的箭头,而自然梯度是根据曲率校正过的方向。这个方向更准,所以对步长的容错率也更高。

5. 完整算法流程与关键实现

5.1 算法总览(伪代码)

下面这段伪代码匹配TRPO的经典流程:

for iteration in range(max_iterations): # 1. 用当前策略采样一批轨迹 traj = collect_trajectories(pi_old, environment) # 2. 计算优势估计 advantage = compute_gae(traj, gamma=0.99, lam=0.95) # 3. 计算策略梯度 g g = compute_gradient_of_surrogate(pi_old, traj, advantage) # 4. 用共轭梯度求解 F^{-1} g # 即解线性系统 F x = g x = conjugate_gradient(fisher_vector_product, g, max_iter=10) # 5. 计算步长 step_size = sqrt(2 * delta / (g.dot(x))) # 6. 更新参数 theta_new = theta_old + step_size * x # 7. 线搜索:不断减半步长,直到满足KL约束和surrogate提升 while KL(pi_old, pi_new) > delta or surrogate(pi_new) < surrogate(pi_old): step_size *= 0.5 theta_new = theta_old + step_size * x

这里每一步都重要。最容易被忽略的是第7步——即使理论步长算出来了,由于我们用了线性近似、样本估计有噪声,实际更新可能越界。线搜索是最后一道保险,千万不能省。

5.2 共轭梯度法解线性系统

Fisher信息矩阵的维度等于策略参数数量,在实际网络里动辄几万到几百万,直接求逆完全不可能。TRPO用的是**共轭梯度法(CG)**解线性系统(Fx = g)。CG的优势是不需要显式构建(F),只需要一个“Fisher向量积”算子:输入向量(v),输出(F v)。

如何高效计算(Fv)?不建矩阵,而是利用Autograd:

对于每个样本,计算(\nabla_\theta \log \pi_\theta(a|s)),记为(p)。那么(F v)可以估计为:

[ F v \approx \frac{1}{N} \sum_{i=1}^{N} p_i (p_i^T v) ]

如果网络输出是多维高斯,计算(p_i^T v)是一个标量,然后再反向传播一次得到这个标量对θ的梯度。PyTorch里可以用torch.autograd.grad(log_prob, params, grad_outputs=v, retain_graph=True)这类方式实现。

CG迭代次数通常取10到20。我发现取多了收益不大,取少了会解不干净。最实用的验证方法:随机一个向量(v),分别用上面的方法估计(Fv)和用数值二阶导算出的Hessian向量积对比,差在1e-5以内就说明算子没错。

5.3 线搜索与回溯:最后一道保险

线搜索的流程是:

  • 用自然梯度公式算出初始步长(s_0)
  • 更新参数(\theta' = \theta + s_0 \cdot \mathrm{dir})
  • 计算(\bar{D}{KL}(\theta{\text{old}} || \theta'))
  • 如果KL小于δ且surrogate不下降,就接受;否则步长乘以0.5继续尝试
  • 重复若干次(比如10次),还不行就不更新

注意检查surrogate时,要用旧策略采样的数据重新计算新旧策略的重要性比率。不要用旧目标函数的值,因为旧目标函数在新参数下本身就是约等于,线搜索中的surrogate提升还要考虑到其他项。

我踩过的坑是:线搜索只查KL,不查surrogate提升。结果有时候KL约束满足了,但替代目标已经显著下降,下一步训练就开始掉。加上surrogate检查之后稳定很多。

5.4 GAE优势估计

TRPO的目标函数里的(A)是优势函数,常用通用优势估计(GAE)。它结合了TD误差和蒙特卡洛回报,用参数λ控制方差和偏差的权衡:

[ \hat{A}t = \sum{l=0}^{T-t} (\gamma\lambda)^l \delta_{t+l} ]

其中(\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t))。

TRPO原文通常配合GAE使用,λ在0.95到0.98之间比较稳妥。如果λ太小,优势方差大;λ太大,偏差会随着折扣累积变大。训练初期可以用0.95,任务简单时也能用0.98。

6. 实操经验与调参心得

6.1 网络结构怎么搭

我建议用两个独立的网络:一个actor计算策略,一个critic计算价值。虽然共享底层特征可以省参数,但TRPO对策略更新的稳定性要求极高,共享层会让策略梯度也更新特征层,反过来影响critic对优势的估计,形成不稳定反馈。中小规模任务上,分离网络更省心。

actor输出的策略头:

  • 离散动作:输出logits,转softmax。
  • 连续动作:输出均值μ,和对数方差log_std,log_std可以当独立参数,不用从网络输出,这样能减少方差坍缩的风险。

critic输出一个标量值,使用MSE loss训练。critic的学习率可以用普通Adam,比如3e-4;actor的“学习率”天然由TRPO步长公式给出来,不需要再额外设置。

6.2 数值稳定性与常见隐藏坑

  • 重要性比率很容易产生极大值。代码里建议对(p_{\text{new}}/p_{\text{old}})做clip,比如限制到([0.01, 100])。虽然TRPO理论没写clip,但实际估计方差大,线搜索前clip一下能防止数值溢出。
  • 在高斯策略里计算log_prob时,记得把std也送入网络图。如果std被截断梯度过早,KL计算会失真。
  • 状态和奖励预处理非常关键。连续控制里状态量级差异大,不标准化的话Fisher矩阵病态,CG收敛慢。奖励也应酌情缩放,让优势量级在[0.1, 1]附近。
  • 熵正则:如果策略过早确定性化,即使KL约束仍在,探索能力也会下降。我习惯在目标里加一个小熵项(系数0.001~0.01),但熵项不参与KL约束的构造。

6.3 日志与监控指标

跑TRPO必看的五个指标:

  1. 平均KL散度:看是否经常触线。如果每步都被线搜索回退很多次,说明δ设太大或数据太少。
  2. surrogate loss变化:应该平缓上升,不能突然下跌。
  3. 熵:过高说明策略没学会,过低说明探索崩溃。
  4. 每一步回退次数:回退0次正常,回退3次以上就要检查。
  5. 有效样本量:重要性比率导致的ESS(有效样本数)如果掉得厉害,应该增大采样轨迹数。

我自己习惯把这些指标存成CSV,每次跑完拉出折线图看。很多问题根本不用看奖励曲线,看KL和熵就能判断。

7. 常见问题排查清单

问题现象可能原因排查办法
KL散度经常超过δ,线搜索频繁回退δ设太大或采样轨迹太少减小δ到0.01;增大batch size到4096以上
奖励停滞,KL一直很小梯度g太小,策略陷入局部平滑区域检查优势是否归零;增大GAE的λ;调整奖励scale
训练从头到尾奖励都不涨状态/奖励未标准化,或者critic误差太大是否加了状态归一化;critic loss是否收敛
策略熵快速归零KL约束形同虚设,或者熵正则没有加上检查KL实现;加大熵系数
CG迭代不收敛Fisher向量积实现错误,或参数尺度差大用随机向量数值验证Fv算子;标准化输入
同一段代码,换一个环境就炸动作空间范围差异大,高斯std初始化和动作缩放不匹配把动作从环境空间映射到tanh输出;初始化log_std适当
使用RNN策略时梯度爆炸时序依赖导致Fisher向量积包含时序反向传播误差截断时间步,或改用PPO等更稳定方法

排查的关键是每步只改一个变量。TRPO本身超参数少,比PPO好调,但正因为少,你更容易把所有问题错怪到δ头上。我见过有人调了一周δ,最后发现是critic没train好。

8. 从TRPO到PPO:置信域思想的后续

8.1 PPO的clip机制

PPO把TRPO的硬约束换成了目标函数里的clip:

[ L^{CLIP}(\theta) = \mathbb{E}\left[ \min\left( r(\theta) A, ; \mathrm{clip}(r(\theta), 1-\epsilon, 1+\epsilon) A \right) \right] ]

其中(r(\theta) = \pi_\theta(a|s) / \pi_{\theta_{\text{old}}}(a|s))。当重要性比率超过([1-\epsilon, 1+\epsilon])区间时,梯度被截断,等效于不允许新策略偏离旧策略太远。(\epsilon)一般取0.2,这就在每个状态上给策略分布施加了一个隐式的“信任区域”。

PPO相比TRPO的优点是实现简单、不需要CG和线搜索,也更适合大批量采样并行训练。但TRPO的置信域是基于KL散度的全局约束,而PPO的约束是比率数值clip,两者在理论上并不完全等价。PPO在复杂连续控制上往往比TRPO少一点稳定,但也够用。

8.2 什么时候选TRPO

如果有人问我,现在还该不该用TRPO?我的建议是:

  • 如果你的任务非常看重稳定性,并且你有耐心实现CG和线搜索,TRPO仍然值得。
  • 如果你在做大规模分布式训练的基准实验,希望快速迭代,PPO更省事。
  • TRPO的置信域框架对理解PPO、MPO、以及其他策略优化算法都有重要价值。哪怕最终使用PPO,也建议把TRPO推导一遍。

我个人在实际操作中的体会是:TRPO最迷人的地方在于“不靠玄学步长”的确定性。你不需要为一个算法烧香祈祷它的学习率恰好落在悬崖边缘。每一步都有一个明确的答案:先在KL半径内找一个方向,然后迈出置信心里有底的一步。很多新同学复现TRPO时卡在数学推导,建议先从小环境跑通,再回头把公式算一遍,你会发现那些“麻烦的近似”全都是为了让真实训练更可控而设计的。

如果你已经跑通了TRPO,下一步可以试试在连续控制任务里把δ从0.01扫到0.05,记录KL曲线和奖励曲线的关系。出来的规律,比看十篇理论文章都直观。最后一招:调试时把所有随机种子固定,开启确定性模式,不然你根本无法区分是算法问题还是抽样方差问题。把这个搞定,TRPO你就算真正上手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询