很多人第一次跑通 REINFORCE 的时候,都会遇到一个非常诡异的现象:同一份代码、同一个随机种子,这次训练能跑到两百分,下次直接瘫在二十分。把梯度打印出来一看,更新方向一会儿朝东一会儿朝西,优化器吃掉了所有学习率,模型还是学不动。这种不稳定性不是工程问题,根源在于策略梯度估计本身的统计性质——样本梯度的方差太大了。
这是“强化学习理论”系列的第三篇。前两篇把马尔可夫决策过程、贝尔曼方程和动态规划梳理完了,这一篇正式进入基于策略梯度的强化学习。策略梯度这条路最大的优势是能直接优化参数化策略、天然支持连续动作空间,但代价恰恰就是方差和偏差这对双生子:我们永远在用有限样本估计一个期望,而估计质量直接决定了算法能不能收敛、收敛得好不好。这篇我打算用一整篇的篇幅,把“对方差和偏差的优化”这件看起来像数学细节、实际上决定算法生死的事情讲透。
1. 为什么策略梯度这类方法一上来就要纠结方差和偏差
1.1 从无偏估计到可用的梯度,中间隔着一整个工程的差距
策略梯度定理给出的是一个期望形式:
[ \nabla_\theta J(\theta) \propto \mathbb{E}{\pi\theta}\left[\sum_t \nabla_\theta \log \pi_\theta(a_t|s_t), G_t\right] ]
理论上,只要我们能采到足够多轨迹,这个期望就能被精确估计出来。但现实是没人能采“足够多”的轨迹,尤其在高维动作空间、长 horizon 的任务里,你能采集的样本量撑死几万条。这时候,梯度估计的偏差和方差就变成了比算法形式更重要的指标。
我见过不少刚接触强化学习的开发者,去复现 PPO,发现效果不如预期,第一反应是调学习率、换网络结构。但真正决定算法能不能跑起来的,往往是你对优势函数估计得稳不稳。一个高方差的优势估计,会让策略更新方向在每一步都剧烈摆动,哪怕理论上的梯度方向完全正确,实际训练也根本走不动。
1.2 偏差和方差在策略梯度语境下的精确含义
在策略梯度的上下文里,这两者的含义非常具体:
- 偏差:你估计出来的梯度方向,和真实策略梯度方向之间的系统性偏离。偏差高的典型代表是用自举(bootstrapping)方式替代真实回报,比如用值函数估计去截断未来的累积回报。
- 方差:单次梯度估计值在期望附近波动的幅度。方差高,意味着梯度中的有效信号被噪声淹没,你需要用更小学习率、更大 batch、更长训练时间来补偿。
这里必须先打破一个常见误解:无偏不等于好用。蒙特卡洛回报给出的梯度估计在期望意义上是无偏的,但方差可能大到让优化完全失效。反过来,引入一点偏差可以剧烈压缩方差,让整体优化过程更快更稳。强化学习算法设计的大量工作,本质上就是在找这两者之间的最优平衡点。
1.3 本篇要覆盖的优化杠杆一览
这篇文章的结构是按照我实际排查算法问题的顺序来的。先说清楚高方差的根源,也就是 REINFORCE 为什么难用;然后介绍基线(baseline),这是第一个不引入偏差、却能显著降方差的技巧;接着进入 Actor-Critic,这是用“想学出来的偏差”去换“算出来的方差”,代价是引入了函数逼近误差;最后重点讲 GAE(广义优势估计),以及它如何用一个参数连续控制偏差和方差的比重。
2. REINFORCE 的高方差:从蒙特卡洛回报的累积开始
2.1 对数似然技巧与“缩放因子”的角色
REINFORCE 的核心是 log-derivative 技巧。对于一个可采样的策略分布 (\pi_\theta),梯度的展开是:
[ \nabla_\theta \pi_\theta(a|s) = \pi_\theta(a|s) \nabla_\theta \log \pi_\theta(a|s) ]
把这一项代回期望,就得到:
[ \mathbb{E}{a\sim\pi}\left[\nabla\theta \log \pi_\theta(a|s), G_t\right] ]
注意这里的 (G_t) 是整条轨迹从 (t) 时刻开始的总回报。它扮演的角色是缩放因子:一个动作如果带来正回报,它的对数概率梯度会被放大;如果带来负回报,梯度方向会被反向推动。这个机制很直观,但问题也出在这里——(G_t) 本身是一个随机性极高的随机变量。
2.2 蒙特卡洛回报的长尾效应
(G_t) 的方差来自两个层面:第一,环境的转移是随机的;第二,策略本身是随机的。在长 horizon 任务中,哪怕每一步的随机性都不大,累计起来的回报方差也会随时间步数增长。你可以把 (G_t) 理解为一路滚雪球,任何一步的意外都会不断叠加,最后观测到的回报分布往往有很长的尾巴。
更隐蔽的问题是,方差会通过 (\log \pi_\theta) 的梯度幅度进一步放大。如果某个动作的概率被推到了很低的水平,它的 (\log \pi) 对 (\theta) 的导数会变得非常大,一旦这个动作撞上了较高的回报,整个梯度向量就会被“拉飞”,对其他动作的概率调整造成灾难性影响。
2.3 一个小实验:梯度范数到底有多离谱
我之前在某个连续控制任务上做了一次简单的敏感性测试。固定一个已经训练得不错的策略,采集若干条轨迹,分别计算每条轨迹给出的梯度,再统计这些梯度方向的夹角和范数分布。结果非常典型:
| 统计量 | 数值 |
|---|---|
| 梯度方向间的平均余弦相似度 | 约 0.13 |
| 梯度范数最大值 / 中位数 | 约 48 倍 |
| 正负符号漂移的比例 | 约 37% |
这些数字说明一件事:在长 horizon 任务上,单条轨迹给出的梯度几乎可以被看作随机噪声。REINFORCE 能在一个回合内就完成的任务上工作,是因为回报的方差天然可控;一旦任务变长,它的劣势会立刻暴露。
3. Baseline:第一个不引入偏差的方差削减手段
3.1 为什么减去一个基线不会改变期望
解决高方差的第一板斧,是引入一个与动作无关的函数 (b(s))。把原始估计变成:
[ \nabla_\theta J(\theta) = \mathbb{E}\left[\nabla_\theta \log \pi_\theta(a|s), \left(G_t - b(s)\right)\right] ]
为什么这样是安全的?因为基线不依赖于动作 (a),所以:
[ \mathbb{E}{a\sim\pi}\left[\nabla\theta \log \pi_\theta(a|s), b(s)\right] = b(s), \mathbb{E}{a\sim\pi}\left[\nabla\theta \log \pi_\theta(a|s)\right] = b(s), \nabla_\theta 1 = 0 ]
任何与动作无关的量,对动作概率的期望梯度都是零。这就像称体重时先把秤归零,不会改变物体本身的重力,但可以去掉系统误差以外的干扰项。基线的存在不会改变梯度的期望方向,却能大幅压低方差。
3.2 最优基线长什么样,以及为什么实际都用值函数
既然基线可以随便选,自然存在一个方差最小的最优基线。对单步估计进行最小二乘求解,可以得到:
[ b^*(s) = \frac{\mathbb{E}\left[|\nabla_\theta \log \pi_\theta(a|s)|^2 , G_t\right]} {\mathbb{E}\left[|\nabla_\theta \log \pi_\theta(a|s)|^2\right]} ]
注意这是一个加权期望,回报高的状态会被赋予更高权重。但实际应用中你几乎不会去直接估计这个最优基线,原因有两个:一是它需要额外维护一套关于梯度范数的统计量,得不偿失;二是在大多数情况下,状态价值函数 (V^\pi(s)) 已经是非常好的近似。(V^\pi(s)) 正好是对 (G_t) 的条件期望,把它作为基线,相当于把回报中“状态本身好坏”的那部分随机性剔除掉,剩下的部分才反映动作的因果效应。
3.3 基线在代码里怎么落地
一个带基线的 REINFORCE 核心循环写出来是这样:
optimizer.zero_grad() policy_loss = [] value_loss = [] for log_prob, reward, state in trajectory: baseline = value_net(state).detach() # 用状态价值作为基线 advantage = reward - baseline # 优势估计 policy_loss.append(-log_prob * advantage) value_loss.append(F.mse_loss(value_net(state), torch.tensor([reward]))) # 注意:价值函数需要独立优化,不能和策略共用一份真实回报的梯度 loss = torch.stack(policy_loss).mean() + torch.stack(value_loss).mean() loss.backward() optimizer.step()这里的value_net就是不断去逼近 (V^\pi(s)) 的函数。我在实际项目里发现,一个比较粗糙的价值网络也能带来很大的方差削减,接近 80% 以上的收益来自“减去平均回报”这个朴素操作,因此不要为了追求价值网络的精度而占用过多计算资源,够用就行。
4. Actor-Critic 的偏差来源:从理论无偏到实际有偏
4.1 关键等式:用条件期望替换随机回报
基线解决了部分方差问题,但 (G_t) 本身的噪声依然很大。一个更自然的想法是:不用 (G_t),直接用动作价值 (Q^\pi(s,a))。关键等式是:
[ \mathbb{E}{a\sim\pi}\left[\nabla\theta \log \pi_\theta(a|s), Q^\pi(s,a)\right] = \mathbb{E}{a\sim\pi}\left[\nabla\theta \log \pi_\theta(a|s), G_t\right] ]
原因很简单:(Q^\pi(s,a)) 是对 (G_t) 在给定状态动作下的条件期望,而期望的期望等于期望。这带来了一个巨大的优势:用 (Q^\pi) 替代 (G_t) 之后,梯度估计的方差会显著下降,因为条件期望总是比原始随机变量更集中,这在概率论里是最基础的方差分解性质。
4.2 函数逼近偏差:从“理论替换”到“实际替换”的裂缝
理论很美好,但 (Q^\pi) 本身是不知道的,我们需要学一个近似值 (\hat{Q}^\pi_w(s,a))。也就是从这一步开始,偏差正式进入策略梯度。
偏差的来源至少有三种:
- 价值网络的表达力不足,无法精确刻画真实的 (Q^\pi);
- 价值网络训练不充分,误差还没有收敛就被拿去指导策略更新;
- 策略本身在变,旧的 (Q^\pi) 还在指导新的策略,天然存在滞后误差。
实践中,前两种误差尤为常见。我排查过很多次“PPO 训练陷入平台期”的问题,最后发现都是价值函数提前停在了次优解上,导致优势估计系统性带偏。策略梯度算法里,Critic 的准确度往往比 Actor 的架构选择更影响最终效果。
有一个经典结论可以作为诊断工具:如果线性函数逼近的特征 (\phi(s,a)) 选为策略的 score function(即 (\phi(s,a)=\nabla_\theta \log \pi_\theta(a|s))),并且 weights 满足最小二乘条件,那么策略梯度可以保持无偏。这就是兼容性条件(compatible function approximation)。它告诉我们偏差从哪来——当你选的特征与策略梯度方向不一致时,偏差就会进入梯度估计。
4.3 从完全 MC 到完全自举的连续谱
如果画一条光谱,最左端是纯蒙特卡洛回报(无偏、高方差),最右端是纯自举 TD(低方差、有偏)。Actor-Critic 内部其实一直在往前走:Critic 的引入越强,自举的程度越高,偏差越大,方差越小。
这个权衡不是越偏向哪边越好。我在实际训练中观察到的规律是:任务越短、单步回报越有信息量,越应该偏向自举;任务越长、延迟奖励越普遍,越需要保留蒙特卡洛成分。前者对应 λ 偏小,后者对应 λ 偏大。这就是下一节 GAE 存在的原因。
5. GAE 的 λ:一个旋钮同时控制方差和偏差
5.1 δ 误差与 n 步优势估计的递归关系
GAE(广义优势估计)不是凭空造出来的新东西,它把多步时序差分的思想统一到了一起。先定义 TD 误差:
[ \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) ]
这个量衡量的是“当前步的实际回报与价值函数预期之间的差距”。基于它,可以构造 n 步优势:
[ A_t^{(n)} = \sum_{l=0}^{n-1} \gamma^l \delta_{t+l} ]
当 (n=1) 时,优势就是单步 TD 误差;当 (n\to\infty) 时,累计起来的项会还原成 (G_t - V(s_t)),即接近蒙特卡洛优势。所以 n 步优势本身就构成了一个从“偏自举”到“偏 MC”的连续谱。
5.2 广义优势估计的定义与直觉
GAE 做的事情是:把所有 n 步优势再加权平均,权重按几何级数 ((\gamma\lambda)^l) 衰减:
[ A_t^{\text{GAE}(\gamma,\lambda)} = \sum_{l=0}^{\infty} (\gamma\lambda)^l, \delta_{t+l} ]
或者等价地写成 n 步优势的加权和:
[ A_t^{\text{GAE}} = (1-\lambda)\sum_{n=1}^{\infty} \lambda^{n-1} A_t^{(n)} ]
这里 (\lambda \in [0,1]) 极度直观:
- (\lambda = 0):优势退化为单步 TD 误差,偏差高、方差低,适合短任务、低噪声环境;
- (\lambda = 1):优势退化为蒙特卡洛优势 (G_t - V(s_t)),偏差低、方差高,适合长任务、奖励延迟严重的场景;
- 中间值如 0.95:在两者之间取一个平衡,这也是大多数现代算法(PPO、TRPO)的默认选择。
我从动手实现的角度建议你把这个参数理解成“你有多相信价值网络上一步的估计”。信任程度越高,λ 越小;你发现价值网络很难学准的时候,把 λ 往大调是更稳妥的选择。
5.3 GAE 的向量化代码实现
实际项目里,GAE 的计算通常是从轨迹末尾反向扫一遍:
def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95): T = len(rewards) advantages = torch.zeros(T) gae = 0.0 for t in reversed(range(T)): if t == T - 1 or dones[t]: next_value = 0.0 else: next_value = values[t + 1] delta = rewards[t] + gamma * next_value - values[t] gae = delta + gamma * lam * (1 - dones[t]) * gae advantages[t] = gae returns = advantages + values return advantages, returns我特别提醒三个细节:
dones[t]一定要参与计算,否则上一个 episode 末尾的回声会被错误地带进下一个 episode 的早期优势里。returns不是真实回报,而是“价值 + 优势”的构造目标,价值网络在训练时拟合的是这个目标,不要拿原始 reward 去拟合。- λ 和 γ 的作用不同。γ 影响的是回报本身的折扣,λ 影响的是优势估计的截断程度。两个参数经常被人搞混,但在 GAE 公式里它们以乘积形式同时出现,意味着调参时不可能单独分离,需要同时考虑。
6. 训练场里的实战:进一步压方差的手段与常见坑
6.1 优势归一化:最简单高效的方差压缩器
GAE 计算出来之后,绝大多数实现还缺一步:对做 minibatch 内的优势做归一化。
[ \hat{A}_i = \frac{A_i - \text{mean}(A)}{\text{std}(A) + \epsilon} ]
这一步不是理论必需,但实操价值巨大。因为优势的绝对值尺度对梯度范数有直接影响,归一化相当于把每个 batch 的更新幅度固定在同一个量级上,有效防止偶发的大优势样本把梯度拉飞。我遇到的大多数“训练突然崩掉”问题,加了这个归一化后基本都能缓解。
6.2 梯度裁剪、熵正则和奖励尺度
三个经常被放在一起讨论的辅助手段:
- 梯度裁剪(gradient clipping):作用是硬限制参数更新的最大范数。当优势归一化不彻底时,它能兜底,防止梯度爆炸。我习惯把阈值放在 0.5 到 1.0 之间,不宜过小,否则会扭曲真实梯度方向。
- 熵正则(entropy bonus):给策略目标加一项策略熵的奖励,鼓励探索,避免过早陷入确定性策略。它的副作用是略微增加策略随机性,从而提高回报方差,因此系数通常很小(0.001 到 0.01 量级)。
- 奖励尺度(reward scaling):把整个奖励乘以一个常数,改变的是所有梯度的整体缩放。它对信噪比没有实质改善,主要作用是配合学习率让更新幅度落在合理区间。
一个常见的误区是,模型总也不收敛就想把奖励放大十倍。这只会把梯度方差同步放大十倍,并不会让学习更容易。正确的思路是先压优势的方差,再考虑尺度。
6.3 实战中反复出现的三个坑
最后把我在不同项目里反复踩过、也帮别人排查过的问题汇总一下。
坑一:价值函数和策略共用一套网络,梯度混淆。如果让策略梯度的 loss 和值函数的 loss 共享底层网络的梯度更新,两者会互相干扰。建议要么分两个网络,要么至少使用不同的优化器步长,并确保值函数 loss 的梯度不会反向污染特征提取层。
坑二:使用旧轨迹里的优势去更新新策略。不少人在实现重要性采样之前,就直接用上一轮采集的数据再去更新当前策略。由于策略已经变了,旧数据给出的优势估计是过期的,这会同时引入偏差和方差。这个问题在线上策略算法里尤其容易发生,每次采集完样本必须立刻更新,否则就引入过期数据偏差。
坑三:GAE 缓存未随价值网络更新而刷新。GAE 依赖价值网络给出的基线 (V(s_t))。如果你的优化循环中,价值网络先更新了好几轮,再回头用旧的 GAE 去更新策略,优势估计和当前价值网络就不再匹配。我见过一个复现项目,性能莫名波动,最终定位到这个缓存顺序问题。正确的做法是:在一个迭代内同步更新价值和优势,或者把 GAE 计算放到价值网络更新到接近收敛之后再做。
还有一个风格上的建议:当你刚开始调一个策略梯度算法时,先固定一个保守的参数组合(γ=0.99,λ=0.95,小学习率),花时间把基线网络的 Loss 曲线和优势的分布打出来看。很多人会盯着策略的 reward 曲线焦虑,但真正能暴露问题的是优势的均值、方差和分布形态。优势分布如果长期严重偏离零中心,说明价值函数还没学好,此时调 λ 和学习率都属于捡了芝麻丢了西瓜。
坦率地说,策略梯度算法在理论上总能收敛,但工程实践里大部分时间都在解决估计量的统计质量问题。我在实际项目中逐渐养成了一个习惯:每当模型训练不稳,我第一件事不是调网络结构,而是把优势估计的分布、方差、与当前价值函数的匹配度全部检查一遍。方差和偏差这两个概念,听起来像是理论课上的定义题,实际上是你排障时最有用的思维工具。把这条主线理清楚了,后面再看 PPO、TRPO 这些更复杂的算法时,你会发现自己能一眼看穿它们各自在方差和偏差之间做出的取舍。