LECTURE 09

Off-Policy 策略梯度:重要性采样、裁剪与 PPO

策略梯度必须用最新策略采样,所以每批数据只能走一步梯度。本讲用重要性采样把「旧数据」救回来,并解释为什么必须裁剪权重才能真正跑通。

讲师:Sergey Levine UC Berkeley 原始材料:lec-9.pdf(16 页)

0. 本讲导读

第 5–6 讲把策略梯度(policy gradient)和 actor-critic 建立起来,第 7–8 讲则走了完全另一条路:值函数方法(Q-learning、DQN 及其工程实践)。这两条路线的分水岭是一个非常实际的问题——数据能不能重复使用。Q-learning 之所以能塞进百万级的回放缓冲区(replay buffer)反复训练,是因为 Bellman 方程本身不依赖数据是谁产生的;而策略梯度的期望是明明白白写在 $\tau\sim p_\theta(\tau)$ 下面的,参数一动,这个分布就变了,旧样本立刻「过期」。

于是我们陷入一个尴尬处境:策略梯度稳定、无偏、对 critic 的错误免疫,但样本效率低得离谱;Q-learning 样本效率高,却难调、可能不收敛。本讲要问的问题非常具体:

能不能对旧策略采到的数据做某种「修正」,使得我们可以在同一批数据上连续走 $K$ 步梯度?

答案是重要性采样(importance sampling, IS)。但重要性采样在长时域序贯问题上有一个恶名昭著的失败模式:轨迹级权重是 $T$ 个比值的连乘,方差随时域指数爆炸。本讲的主线就是:先老老实实推出精确的 off-policy 策略梯度(它是正确但没法用的),再一步步做近似和工程加固——丢掉未来项、丢掉状态边缘分布比、裁剪权重、加 $\min$——最后落地成今天工业界用得最多的算法 PPO(Proximal Policy Optimization)。

核心结论
  • 精确的 off-policy 策略梯度需要轨迹级 IS 权重 $\prod_t \frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$,其方差随时域 $H$ 指数增长,$H\sim 10^3$ 时轻易达到 $10^{17}$ 量级,实践中完全不可用。
  • 利用因果性可以把权重拆成「$t$ 时刻之前的连乘」和「$t$ 时刻之后的连乘」。后者可以丢掉——丢掉之后得到的不是策略梯度,而是一个策略迭代(policy iteration)式的算法,下一讲会证明这个替换的代价可控。
  • 再做一步一阶近似:忽略状态边缘分布 $d^{\pi_{\theta'}}$ 与 $d^{\pi_\theta}$ 的差异,只保留单步动作 IS 比值,得到实际可用的 off-policy 策略梯度。这个近似只在两个策略足够接近时成立,因此必须约束更新幅度——这正是下一讲信赖域方法的动机。
  • 即便只剩单步比值,权重远离 $1.0$ 时估计量依然失效。工程上的三件套是:权重裁剪、自归一化 IS、有效样本量(ESS)诊断。
  • 把裁剪写成 $\min\{w\hat A,\ \text{clip}(w,1-\epsilon,1+\epsilon)\hat A\}$(而不是直接裁剪 $w$),才能保证「跑得太远」的方向没有梯度、「跑回来」的方向仍有梯度。这就是 PPO 的 $\mathcal{L}_{\text{CLIP}}$。

1. 我们为什么喜欢策略梯度

在动手改造之前,先说清楚我们到底在保护什么。本讲的起点是第 6 讲结尾那个「带 GAE 的 on-policy 批量 actor-critic」算法,Levine 把它完整重述了一遍:

带 GAE 的 on-policy 批量 actor-critic 算法框,以及策略梯度的三个优点
左侧绿色循环是完整算法:采样 → 算 TD 目标 → 拟合 $\hat V^\pi_\phi$ → 算 GAE 优势 → 组装策略梯度 → 走一步。右侧黄色框是三条「为什么喜欢它」的理由:Q 函数方法难调且可能不收敛;on-policy 允许使用蒙特卡洛优势估计;由此得到可证明无偏的梯度估计——无论 critic 多离谱。

算法本身逐条写出来是这样的($i$ 索引轨迹,$t$ 索引时间步):

  1. 用当前策略 $\pi_\theta$ 与环境交互,采样一批轨迹 $\{\tau^{(i)}\}$;
  2. 计算自举目标 $y^{(i)}_t = r(s^{(i)}_t,a^{(i)}_t) + \hat V^\pi_\phi(s^{(i)}_{t+1})$;
  3. 用回归把 $\hat V^\pi_\phi(s)$ 拟合到目标 $\{y^{(i)}_t\}$ 上;
  4. 计算 GAE 优势 $\hat A^\pi_{\text{GAE}}(s_t,a_t)=\sum_{t'=t}^{\infty}(\gamma\lambda)^{t'-t}\delta_{t'}$,其中 $\delta_{t'}=r_{t'}+\gamma \hat V^\pi_\phi(s_{t'+1})-\hat V^\pi_\phi(s_{t'})$;
  5. 组装梯度 $\nabla_\theta J(\theta)\approx \sum_i\sum_t \nabla_\theta\log\pi_\theta(a^{(i)}_t|s^{(i)}_t)\hat A^\pi(s^{(i)}_t,a^{(i)}_t)$;
  6. 更新 $\theta \leftarrow \theta + \alpha\nabla_\theta J(\theta)$,然后回到第 1 步。

无偏性是一张「保险单」

幻灯片右侧特意点出 $\lambda=1$ 这个特例。当 GAE 的 $\lambda$ 取 1 时,优势估计退化成纯蒙特卡洛形式:

$$ \hat A^\pi_{\text{MC}}(s_t,a_t)=\sum_{t'=t}^{\infty}\gamma^{t'-t}r(s_{t'},a_{t'}) - \hat V^\pi_\phi(s_t) $$

这里 $\hat V^\pi_\phi(s_t)$ 只作为基线(baseline)出现,而基线只依赖状态、不依赖动作,因此按第 5 讲的推导它对期望的贡献为零:$\E_{a\sim\pi_\theta}[\nabla_\theta\log\pi_\theta(a|s)\,b(s)]=b(s)\nabla_\theta\sum_a \pi_\theta(a|s)=b(s)\nabla_\theta 1=0$。这就是幻灯片上那句「no matter how “wrong” the critic is」的含义:

直觉

$\lambda=1$ 时 critic 只影响梯度估计的方差,不影响它的期望。critic 拟合得好,方差小;critic 完全是随机噪声网络,方差大得离谱,但梯度方向的期望依旧正确,跑得慢却不会跑歪。Q-learning 没有这张保险单:$Q_\phi$ 一旦偏了,$\argmax_a Q_\phi(s,a)$ 就直接选错动作,而且这个错误会通过自举不断放大。

Levine 由此给出一个非常实际的判断:如果你要的是一个稳定、可靠、不用反复调参就能跑起来的 RL 方法,而且你对样本效率没那么在意,策略梯度是最好的选择。他特别点名两个场景——

  • sim-to-real 机器人:仿真器里样本近乎免费,可以并行开几千个环境,瓶颈在「训练出来的策略能不能稳定迁移」,而不在「用了多少帧」。
  • 训练大语言模型(LLM):一次 rollout 就是生成一段文本,代价可控且高度可并行;反过来,RLHF/RLVR 场景里训练极其怕不稳定(一崩就是模型退化成乱码),所以宁可用无偏但慢的方法。今天 LLM 后训练的主力算法 PPO / GRPO 全部属于本讲要推导的这一族。

2. 致命伤:一批数据只能走一步

同一个算法框,右侧列出 on-policy 策略梯度的四条缺点
同一个算法,换个角度看就全是缺点:每次迭代只走一步梯度;每次迭代都要重新采样;高方差迫使我们用比监督学习大得多的 batch 和小得多的学习率;三者叠加,代价迅速变得无法承受。

请注意上面算法框里绿色箭头的位置:它从第 6 步直接绕回第 1 步。这就是全部问题所在。用一个粗略的账算一下就明白代价有多大:

环节监督学习on-policy 策略梯度
一批数据能走几步梯度反复走很多个 epoch严格一步
batch size32 – 512常见 $10^3$ – $10^5$ 个时间步
学习率$10^{-3}$ 量级$10^{-4}$ – $10^{-5}$,还常需自适应
数据来源磁盘上现成的每一步都要跑仿真 / 真机 / 生成模型
总样本消耗数据集大小batch × 迭代数,两者都很大

为什么策略梯度非要大 batch?因为它的方差来自两处:一是轨迹回报本身的随机性(环境噪声 + 策略随机性),二是 $\nabla_\theta\log\pi_\theta$ 在动作空间上的高维波动。第 5 讲已经看到,即使加了 baseline 和因果性截断,梯度估计的相对标准差仍然常常是 $O(1)$ 甚至更大。要把噪声压到能安全走一步梯度的水平,只能靠 $1/\sqrt{N}$ 硬堆样本。而学习率又不能大——因为策略梯度是在参数空间里走直线,一步走远了策略分布可能剧变,下一批数据的分布也随之剧变,训练直接发散。

两个约束互相牵制:大 batch 意味着每次迭代很贵,小学习率意味着需要很多次迭代。代价是相乘的,所以 Levine 说「this quickly becomes intractable」。

我们想要的算法长什么样

虚构的 multi-step 策略梯度算法,内层循环 K 次,下方写出为什么不合法
虚构的「多步」策略梯度:在第 5–6 步外面套一个 $K\times$ 的内层循环,用同一批数据连走 $K$ 次梯度。右侧黄框问「为什么这不合法?」,下方给出答案的关键——策略梯度是对 $p_\theta(s_t,a_t)$ 求的期望,写成求和就是 $\sum_{s_t,a_t}p_\theta(s_t,a_t)\nabla_\theta\log\pi_\theta(a_t|s_t)\hat A^\pi(s_t,a_t)$,一旦 $\theta$ 变了,前面那个权重 $p_\theta$ 就不再是数据的分布了。

把这个「不合法」讲透一点。策略梯度的定义式是

$$ \nabla_\theta J(\theta) = \E_{(s_t,a_t)\sim p_\theta(s_t,a_t)}\big[\nabla_\theta \log\pi_\theta(a_t|s_t)\,\hat A^\pi(s_t,a_t)\big] = \sum_{s_t,a_t} p_\theta(s_t,a_t)\,\nabla_\theta\log\pi_\theta(a_t|s_t)\,\hat A^\pi(s_t,a_t) $$

我们用样本平均去估计它,靠的是「样本按 $p_\theta$ 分布」这个前提,把 $\sum_{s_t,a_t}p_\theta(\cdot)$ 换成 $\frac1N\sum_i$。走完第一步梯度后参数变成 $\theta'$,此时正确的梯度是在 $p_{\theta'}(s_t,a_t)$ 下取期望,但我们手上的样本仍然来自 $p_\theta$。被积函数换了没关系($\nabla\log\pi_{\theta'}$ 是解析式,代入即可),换不了的是采样分布。

常见误区

「反正 $\theta'$ 离 $\theta$ 很近,直接把旧数据再用一遍不就行了?」——很多人第一次写 PPO 时会把 IS 比值删掉,发现「好像也能跑」。它确实经常能跑,因为在 $\theta'\approx\theta$ 时 IS 比值 $\approx 1$。但删掉比值后你就失去了唯一的诊断信号:比值告诉你策略已经漂多远了。裁剪、早停、ESS 全都建立在这个比值之上。没有它,训练是在无仪表盘的情况下飞行,$K$ 稍微调大一点就会静默发散。

所以真正的问题是:能不能把 $p_\theta$ 下的样本「重新加权」,使它等效于 $p_{\theta'}$ 下的样本?这正是重要性采样要干的事。

3. 重要性采样:把期望搬到另一个分布下

重要性采样是一个纯粹的恒等式,和 RL 没关系。设我们想算 $\E_{x\sim p(x)}[f(x)]$,但只能从 $q(x)$ 采样,那么只要 $q$ 的支撑集覆盖 $p$(即 $p(x)>0 \Rightarrow q(x)>0$),就有:

$$ \E_{x\sim p(x)}[f(x)] = \int p(x)f(x)\,dx = \int \frac{q(x)}{q(x)}p(x)f(x)\,dx = \int q(x)\frac{p(x)}{q(x)}f(x)\,dx = \E_{x\sim q(x)}\!\left[\frac{p(x)}{q(x)}f(x)\right] $$

推导只用了「乘以 $q/q$ 再重新分组」这一个动作。$\frac{p(x)}{q(x)}$ 叫重要性权重(importance weight)。它的含义很直白:某个 $x$ 在 $q$ 下被采到得太频繁,就给它降权;采到得太少,就给它升权。

off-policy 学习与重要性采样:右侧绿框是 IS 恒等式,左侧把它套到轨迹分布上,转移概率和初始分布约掉
右侧绿框是通用的 IS 恒等式四行推导。左侧把它用到 RL 目标 $J(\theta)=\E_{\tau\sim p_\theta(\tau)}[r(\tau)]$ 上:手里只有来自 $\bar p(\tau)$ 的轨迹,于是写成 $\E_{\tau\sim\bar p(\tau)}\big[\frac{p_\theta(\tau)}{\bar p(\tau)}r(\tau)\big]$。最下面一行是关键——展开轨迹概率后,红色划掉的是初始状态分布 $p(s_1)$ 和转移 $p(s_{t+1}|s_t,a_t)$,它们在分子分母里完全相同,全部约掉,只剩策略概率的连乘。

为什么模型项能约掉——这是整套方法能成立的前提

轨迹概率写全是

$$ p_\theta(\tau) = p(s_1)\prod_{t=1}^{H}\pi_\theta(a_t|s_t)\,p(s_{t+1}|s_t,a_t) $$

其中 $p(s_1)$ 是初始状态分布,$p(s_{t+1}|s_t,a_t)$ 是环境转移。这两项不含 $\theta$——它们是环境的性质,不管你用哪个策略采样都一样。于是权重变成

$$ \frac{p_\theta(\tau)}{\bar p(\tau)} = \frac{\cancel{p(s_1)}\prod_{t=1}^{H}\pi_\theta(a_t|s_t)\cancel{p(s_{t+1}|s_t,a_t)}}{\cancel{p(s_1)}\prod_{t=1}^{H}\bar\pi(a_t|s_t)\cancel{p(s_{t+1}|s_t,a_t)}} = \prod_{t=1}^{H}\frac{\pi_\theta(a_t|s_t)}{\bar\pi(a_t|s_t)} $$
核心结论

轨迹级重要性权重只依赖策略概率,完全不需要知道环境模型。这是 model-free off-policy 学习能成立的根本原因:我们无法计算 $p(s'|s,a)$,但幸运的是根本不需要计算它。代价是这个权重是 $H$ 个数的连乘——下一节我们会看到,这个连乘就是万恶之源。

记号约定:本讲里 $\bar\pi$(或 $\pi_\theta$,当我们把当前参数 $\theta$ 当作「采样策略」时)表示行为策略 / 旧策略,即真正与环境交互产生数据的那个策略;$\pi_{\theta'}$ 表示我们正在优化的目标策略 / 新策略。二者一开始是同一个($\theta'\leftarrow\theta$),随着内层循环推进逐渐分开。

4. 用重要性采样推导策略梯度

现在把 IS 从「估计目标值」升级到「估计梯度」。Levine 在这里给出了一个很漂亮的推导,顺便重新导出了 on-policy 策略梯度作为特例。

用 IS 推导策略梯度:对 IS 形式的目标关于 θ′ 求导,用 log-derivative 恒等式,最后令 θ′=θ 得到原始策略梯度
三步推导。第一步:把 $J(\theta')$ 写成在 $p_\theta$ 下的 IS 期望,红圈标出「唯一依赖 $\theta'$ 的部分」就是分子 $p_{\theta'}(\tau)$。第二步:对 $\theta'$ 求梯度,分母是常数,用右上角蓝框的恒等式 $p\nabla\log p=\nabla p$ 反向替换。第三步:令 $\theta'=\theta$,分子分母约掉,恰好还原成第 5 讲的 on-policy 策略梯度。

逐步展开

第一步:写出可微的目标。 我们想知道「换成新参数 $\theta'$ 后目标会变成多少」,但只有旧策略的样本:

$$ J(\theta') = \E_{\tau\sim p_\theta(\tau)}\!\left[\frac{p_{\theta'}(\tau)}{p_\theta(\tau)}r(\tau)\right] $$

这里 $r(\tau)=\sum_t r(s_t,a_t)$ 是轨迹总回报。注意这个式子里 期望的下标是 $\theta$(固定的),被积函数里唯一含 $\theta'$ 的是 $p_{\theta'}(\tau)$。这就是为什么它可以直接对 $\theta'$ 求导——梯度算子可以穿过期望符号,因为期望本身不依赖 $\theta'$。这跟 on-policy 时「必须用 log-derivative trick 才能把梯度搬进期望」形成鲜明对比。

第二步:求梯度。

$$ \nabla_{\theta'}J(\theta') = \E_{\tau\sim p_\theta(\tau)}\!\left[\frac{\nabla_{\theta'} p_{\theta'}(\tau)}{p_\theta(\tau)}r(\tau)\right] $$

现在用那个「方便的恒等式」(convenient identity):由链式法则 $\nabla_{\theta'}\log p_{\theta'}(\tau) = \frac{\nabla_{\theta'}p_{\theta'}(\tau)}{p_{\theta'}(\tau)}$,两边乘 $p_{\theta'}(\tau)$ 得

$$ p_{\theta'}(\tau)\nabla_{\theta'}\log p_{\theta'}(\tau) = \nabla_{\theta'}p_{\theta'}(\tau) $$

把它代回去:

$$ \nabla_{\theta'}J(\theta') = \E_{\tau\sim p_\theta(\tau)}\!\left[\frac{p_{\theta'}(\tau)}{p_\theta(\tau)}\nabla_{\theta'}\log p_{\theta'}(\tau)\,r(\tau)\right] $$

这就是精确的 off-policy 策略梯度:一个 IS 权重乘以熟悉的 score function 乘以回报。它对任意 $\theta'\neq\theta$ 都成立,没有任何近似。

第三步:验证一致性。 令 $\theta'=\theta$(即在采样策略处「就地」估计梯度),此时 $\frac{p_{\theta}(\tau)}{p_\theta(\tau)}=1$,立刻得到

$$ \nabla_{\theta}J(\theta) = \E_{\tau\sim p_\theta(\tau)}\big[\nabla_{\theta}\log p_{\theta}(\tau)\,r(\tau)\big] $$

正是第 5 讲的 REINFORCE 梯度。所以 on-policy 策略梯度只是 off-policy 版本在 $\theta'=\theta$ 处的特例,两条线索在这里合上了。

推导

再展开 $\nabla_{\theta'}\log p_{\theta'}(\tau)$:因为 $\log p_{\theta'}(\tau)=\log p(s_1)+\sum_t[\log\pi_{\theta'}(a_t|s_t)+\log p(s_{t+1}|s_t,a_t)]$,而模型项不含 $\theta'$,求梯度后全部消失,只剩 $\sum_{t=1}^{H}\nabla_{\theta'}\log\pi_{\theta'}(a_t|s_t)$。再次强调:无需环境模型。 这一点在 off-policy 情形下依然成立,因为分子分母共用同一个环境。

5. 因果性拆分与指数爆炸的方差

上一节的公式还很抽象,把 $p_{\theta'}(\tau)/p_\theta(\tau)$ 和 $\nabla\log p_{\theta'}(\tau)$ 都展开成逐时刻的形式,问题的严重性才会暴露出来。

off-policy 策略梯度展开成三个连乘/求和的乘积,再按因果性重排,未来项被红叉划掉
第二行把权重展开成 $\prod_{t=1}^{H}\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$,整个式子成了「一个连乘 × 一个 score 求和 × 一个回报求和」的三重乘积,右边写着「what about causality?」。第三行按时间重排:$t$ 时刻的 score 只配上 $t$ 之前的权重连乘(future actions don’t affect current weight),奖励只取 $t'\ge t$ 的部分,而 $t''$ 从 $t$ 到 $t'$ 的那段权重连乘被红叉划掉——注释说「如果忽略它,我们得到的是一个策略迭代算法(下一讲细讲)」。

5.1 展开与重排

把两个连乘/求和代入,精确梯度是

$$ \nabla_{\theta'}J(\theta') = \E_{\tau\sim p_\theta(\tau)}\!\left[\left(\prod_{t=1}^{H}\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}\right)\left(\sum_{t=1}^{H}\nabla_{\theta'}\log\pi_{\theta'}(a_t|s_t)\right)\left(\sum_{t=1}^{H}r(s_t,a_t)\right)\right] $$

第 5 讲我们已经知道,对 on-policy 版本可以用因果性(causality)把它改写:$t$ 时刻的动作不影响 $t$ 之前的奖励,所以第二个求和的第 $t$ 项只需要配上 $t'\ge t$ 的奖励。off-policy 版本还多一层因果性:$t$ 时刻之后的动作也不影响 $t$ 时刻这个 score 项的「合法性」。严格重排后得到

$$ \nabla_{\theta'}J(\theta') = \E_{\tau\sim p_\theta(\tau)}\!\left[\sum_{t=1}^{H}\nabla_{\theta'}\log\pi_{\theta'}(a_t|s_t)\left(\prod_{t'=1}^{t}\frac{\pi_{\theta'}(a_{t'}|s_{t'})}{\pi_\theta(a_{t'}|s_{t'})}\right)\left(\sum_{t'=t}^{H}r(s_{t'},a_{t'})\left(\prod_{t''=t}^{t'}\frac{\pi_{\theta'}(a_{t''}|s_{t''})}{\pi_\theta(a_{t''}|s_{t''})}\right)\right)\right] $$

这个式子里出现了两段权重,含义完全不同:

项范围含义能否丢弃
过去项 $\prod_{t'=1}^{t}$从起点到当前修正「新策略走到状态 $s_t$ 并选出动作 $a_t$ 的概率」与旧策略的差异,即状态-动作访问分布的修正丢了就不再是 $J$ 的梯度
未来项 $\prod_{t''=t}^{t'}$从当前到未来某个奖励修正「在 $s_t$ 之后,新策略取得这些未来奖励的概率」与旧策略的差异,即把 $Q^{\pi_\theta}$ 修正成 $Q^{\pi_{\theta'}}$可以丢——丢掉相当于用旧策略的 $Q$ 值来评估新策略
直觉

丢掉未来项 = 「用旧策略的价值函数评估动作,然后按这个评估去改策略」。这不就是策略迭代吗:策略评估(算 $Q^{\pi_\theta}$)→ 策略改进(往 $Q^{\pi_\theta}$ 高的方向挪 $\pi_{\theta'}$)。所以幻灯片说「if we ignore this, we get a policy iteration algorithm」。这不是随便的近似,而是换了一个有自己理论保证的算法族。下一讲的性能差分引理(performance difference lemma)会精确刻画这个替换带来的误差有多大,并证明只要 $\pi_{\theta'}$ 和 $\pi_\theta$ 的 KL 散度足够小,误差就被 $O(\epsilon^2)$ 级别的量控制住。

顺带一提:把上式中的「过去项权重 + 未来奖励」组合起来,就是把 $\sum_{t'\ge t} r$ 换成优势估计 $\hat A^\pi_t$ 的更实用形式。后面我们统一写成 $\hat A_t$。

5.2 权重为什么随 $H$ 指数爆炸

就算丢掉未来项,过去项 $\prod_{t'=1}^{t}\rho_{t'}$(记 $\rho_t = \frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$)在 $t$ 接近 $H$ 时依然是 $H$ 个数的连乘。下面用具体数字说明这有多糟。

基本事实。 每一步的比值满足 $\E_{a\sim\pi_\theta}[\rho_t]=\sum_a \pi_\theta(a|s)\frac{\pi_{\theta'}(a|s)}{\pi_\theta(a|s)}=1$。所以权重的均值恒为 1,看起来很温和。但方差不是:记二阶矩 $\E[\rho_t^2]=1+\sigma^2$,若各步近似独立,则

$$ \E[w^2] = \prod_{t=1}^{H}\E[\rho_t^2] = (1+\sigma^2)^H,\qquad \operatorname{Var}(w) = (1+\sigma^2)^H - 1 $$

数值例子一(离散动作)。 设两个动作,旧策略 $\pi_\theta=(0.5,\,0.5)$,新策略 $\pi_{\theta'}=(0.6,\,0.4)$——这是一个非常温和的更新,谁看了都会说「两个策略几乎一样」。此时

$$ \E[\rho^2] = 0.5\times 1.2^2 + 0.5\times 0.8^2 = 0.72+0.32 = 1.04 $$
时域 $H$$\E[w^2]=1.04^H$$\operatorname{Var}(w)$标准差 $\approx$解读
101.480.480.69完全可用
10050.549.57.0已经需要几百个样本才稳
500$3.3\times10^{8}$$3.3\times10^{8}$$1.8\times10^{4}$基本报废
1000$1.1\times10^{17}$$1.1\times10^{17}$$3.3\times10^{8}$估计量毫无意义

读一下最后一行:估计量的均值是 1,标准差是 3 亿。这意味着在几乎所有样本上权重都接近 0,偶尔某一条轨迹的权重是天文数字,整个 batch 的梯度被这一条轨迹独占。这就是 IS 崩溃的典型形态——不是「误差大一点」,而是估计量退化成「一条样本说了算」。

数值例子二(连续动作,高斯策略)。 设 $\pi_\theta(\cdot|s)=\mathcal N(\mu,\sigma^2)$,$\pi_{\theta'}(\cdot|s)=\mathcal N(\mu',\sigma^2)$,两者只差均值。此时单步二阶矩有闭式解:

$$ \E_{a\sim\pi_\theta}\!\left[\left(\frac{\pi_{\theta'}(a)}{\pi_\theta(a)}\right)^{\!2}\right] = \exp\!\left(\frac{(\mu-\mu')^2}{\sigma^2}\right) $$

于是 $\E[w^2]=\exp\big(H\,\Delta^2/\sigma^2\big)$,其中 $\Delta=|\mu-\mu'|$。取 $\Delta = 0.1\sigma$(均值只挪了十分之一个标准差,肉眼看不出差别),$H=1000$:$\E[w^2]=e^{10}\approx 2.2\times10^4$,标准差约 148。若 $\Delta=0.3\sigma$,同样 $H=1000$:$\E[w^2]=e^{90}\approx 1.2\times10^{39}$。指数上的指数——这就是「exponential in $T$」的字面含义。

注意

这里的爆炸与状态空间、网络结构、奖励尺度都无关,它纯粹来自「概率连乘」这个结构。所以你不可能通过换更大的网络、调 batch size 或做奖励归一化来解决它。唯一的出路是结构性地改写估计量:要么把连乘变成单项(第 6 节的一阶近似),要么强行限制权重的取值范围(第 7 节的裁剪)。

5.3 有效样本量:给崩溃一个可测量的指标

「方差太大」这句话在实践中需要一个可以打印到日志里的数字。标准做法是有效样本量(effective sample size, ESS):

$$ \widehat{\text{ESS}} = \frac{\left(\sum_{i=1}^{N} w_i\right)^2}{\sum_{i=1}^{N} w_i^2} $$

它的含义是:这 $N$ 个加权样本,其统计效力相当于多少个等权样本。若所有权重相等,$\widehat{\text{ESS}}=N$;若只有一个权重非零,$\widehat{\text{ESS}}=1$。可以证明当 $N$ 较大时 $\widehat{\text{ESS}}\approx \frac{N}{1+\operatorname{Var}(w)}$。代入上面表格 $H=1000$ 的那一行:$\operatorname{Var}(w)\approx10^{17}$,即使采 $N=10^6$ 条轨迹,$\widehat{\text{ESS}}$ 仍然远小于 1。

import torch

def ess(logp_new, logp_old):
    """logp_new / logp_old: 形状 (N,) 的对数概率(轨迹级则为整条轨迹求和)。
    返回 (ESS, ESS 占比, 权重张量)。全程在对数域做减法,避免连乘溢出。"""
    logw = logp_new - logp_old
    logw = logw - logw.max()          # 数值稳定:整体平移不改变 ESS
    w = logw.exp()
    ess_val = w.sum() ** 2 / (w ** 2).sum()
    return ess_val.item(), (ess_val / w.numel()).item(), w

# 经验法则:ESS 占比 < 0.3 时该批数据已经不可信,应停止内层循环、重新采样。

注意代码里的两个细节:一是永远在对数域计算,$\prod_t \rho_t = \exp(\sum_t \log\rho_t)$,直接连乘在 $H=1000$ 时必然下溢成 0;二是减去最大值再取指数,这个平移会同时出现在 ESS 的分子分母上,因此结果不变,但避免了溢出。

6. 一阶近似:只保留单步动作比值

第 5 节告诉我们连乘不能要。这一节做第二次「有原则的偷懒」:把连乘砍成一项。

一阶近似:把 on-policy 与 off-policy 策略梯度并排写成状态-动作对上的求和,状态边缘比值被红线划掉
上方标注「exponential in $T$」指向那个连乘。下面把目标换个写法:on-policy 策略梯度是对 $(s_t,a_t)\sim\pi_\theta(s_t,a_t)$ 的样本平均;off-policy 版本则在每项前乘上联合分布比 $\frac{\pi_{\theta'}(s_t,a_t)}{\pi_\theta(s_t,a_t)}$。最后一行把联合比拆成 $\frac{\pi_{\theta'}(s_t)}{\pi_\theta(s_t)}\cdot\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$,并把状态边缘比那一项红线划掉(“ignore this part”)。左侧写着「this is ‘a little bit wrong’,we’ll see next time why it’s mostly ok」。

6.1 从轨迹视角切换到状态-动作视角

先把 on-policy 策略梯度写成边缘分布形式(第 5 讲已推过):

$$ \nabla_\theta J(\theta) \approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\nabla_\theta\log\pi_\theta(a^{(i)}_t|s^{(i)}_t)\,\hat A^{(i)}_t,\qquad (s^{(i)}_t,a^{(i)}_t)\sim \pi_\theta(s_t,a_t) $$

这里 $\pi_\theta(s_t,a_t)$ 指的是策略 $\pi_\theta$ 诱导的状态-动作联合边缘分布(marginal),即「跑策略 $\pi_\theta$ 时,第 $t$ 步落在 $(s,a)$ 的概率」。既然样本按这个分布来,换成 $\theta'$ 时用 IS 修正就是把联合分布的比值乘上去:

$$ \nabla_{\theta'} J(\theta') \approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\frac{\pi_{\theta'}(s^{(i)}_t,a^{(i)}_t)}{\pi_{\theta}(s^{(i)}_t,a^{(i)}_t)}\nabla_{\theta'}\log\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)\,\hat A^{(i)}_t $$

关键在于用条件概率的链式法则把联合比拆开:

$$ \frac{\pi_{\theta'}(s_t,a_t)}{\pi_{\theta}(s_t,a_t)} = \underbrace{\frac{\pi_{\theta'}(s_t)}{\pi_{\theta}(s_t)}}_{\text{状态边缘比:难算、爆炸}}\cdot \underbrace{\frac{\pi_{\theta'}(a_t|s_t)}{\pi_{\theta}(a_t|s_t)}}_{\text{单步动作比:一个除法就完了}} $$

两项的性质天差地别。动作比是两个网络前向传播的输出之比,$O(1)$ 的计算量,取值范围也容易控制。状态边缘比则要求我们知道两个策略各自的状态占用分布——这需要在环境里展开整条轨迹,实际上它就是那个 $\prod_{t'=1}^{t-1}$ 连乘的另一种写法(先前的所有动作选择共同决定了你落在哪个状态)。所以它既算不出来,就算算得出来也会爆炸。

6.2 扔掉状态边缘比,得到实用形式

于是我们直接把它设为 1:

$$ \nabla_{\theta'} J(\theta') \approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\frac{\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)}{\pi_{\theta}(a^{(i)}_t|s^{(i)}_t)}\nabla_{\theta'}\log\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)\,\hat A^{(i)}_t $$

写成期望形式($d^{\pi}$ 表示状态占用分布 / state visitation distribution)就是本讲最重要的公式:

$$ \nabla_{\theta'} J(\theta') \approx \E_{s\sim d^{\pi_\theta}}\ \E_{a\sim \pi_\theta(\cdot|s)}\!\left[\frac{\pi_{\theta'}(a|s)}{\pi_{\theta}(a|s)}\nabla_{\theta'}\log\pi_{\theta'}(a|s)\,A^{\pi_\theta}(s,a)\right] $$

逐项对照就能看出它做了两次替换:状态从 $d^{\pi_{\theta'}}$ 换成了 $d^{\pi_\theta}$(这一步是「错的」),优势从 $A^{\pi_{\theta'}}$ 换成了 $A^{\pi_\theta}$(这是第 5 节丢掉未来项的结果)。剩下的动作分布差异由 IS 比值精确修正。

推导

还有一个漂亮的等价写法。注意 $\frac{\pi_{\theta'}}{\pi_\theta}\nabla_{\theta'}\log\pi_{\theta'} = \frac{\nabla_{\theta'}\pi_{\theta'}}{\pi_\theta} = \nabla_{\theta'}\!\left(\frac{\pi_{\theta'}(a|s)}{\pi_\theta(a|s)}\right)$,因为分母 $\pi_\theta$ 关于 $\theta'$ 是常数。也就是说上面这个梯度恰好是某个标量目标的梯度:

$$ \mathcal L^{\text{IS}}(\theta') = \E_{s\sim d^{\pi_\theta},\,a\sim\pi_\theta}\!\left[\frac{\pi_{\theta'}(a|s)}{\pi_{\theta}(a|s)}A^{\pi_\theta}(s,a)\right] $$

这个 $\mathcal L^{\text{IS}}$ 就是幻灯片里说的「代理目标(surrogate objective)」,也是 TRPO/PPO 论文里那个 $L(\theta')$。实践上你不需要手写梯度公式,只要把 $\mathcal L^{\text{IS}}$ 写成 PyTorch 的标量 loss,反向传播自动给出正确梯度。而且它可以对同一批数据反复求梯度——每次 $\theta'$ 变了,比值和 loss 都跟着变,这正是我们要的「多步」。

6.3 这个近似什么时候可靠

Levine 在幻灯片上只写了一句「this is a little bit wrong, we’ll see next time why it’s mostly ok」。这里先给出结论的形状,下一讲会补上证明:

  • 被忽略的是 $d^{\pi_{\theta'}}(s)$ 与 $d^{\pi_\theta}(s)$ 的差异。可以证明,若两个策略在每个状态上的总变差距离都不超过 $\epsilon$,则它们的状态占用分布的总变差距离不超过 $2\epsilon H$(有限时域)或 $\frac{2\epsilon\gamma}{1-\gamma}$(折扣无限时域)。
  • 把这个偏差代回目标,$J(\theta')$ 与代理目标 $\mathcal L^{\text{IS}}$ 的差被 $O(\epsilon^2)$ 的项界住(在合适的常数下)。也就是说,只要策略变化是「小量」,误差就是「小量的平方」——这正是「一阶近似」这个名字的来源:它在 $\theta'=\theta$ 处与真实目标一阶相切。
  • 反过来:$\epsilon$ 一旦不小,界就失效,代理目标可以任意地误导优化。你会看到 $\mathcal L^{\text{IS}}$ 一路上升而真实回报一路下跌。
核心结论

「用旧状态分布代替新状态分布」这个近似只在两个策略足够接近时成立。因此 off-policy 策略梯度天生带着一个附加约束:你必须主动限制 $\pi_{\theta'}$ 离 $\pi_\theta$ 有多远。怎么限制?下一讲给出原理性答案(KL 约束 + 自然梯度 = 信赖域),本讲第 7–8 节给出工程答案(裁剪)。二者解决的是同一个问题。

6.4 真正能写出来的「多步」算法

实际可行的 multi-step 策略梯度算法框,外层采样,内层 K 次用 IS 加权梯度更新 θ′
把第 2 节那个「虚构」算法改成能跑的版本。差别全在第 5–7 步:先令 $\theta'\leftarrow\theta$,然后内层循环 $K$ 次,每次用 IS 加权的梯度更新 $\theta'$;$K$ 次结束后再把 $\theta\leftarrow\theta'$,回到外层重新采样。注意优势 $\hat A^{(i)}_t$ 在内层循环里是固定的——它由采样时的旧策略和旧 critic 算出,不随 $\theta'$ 更新。
  1. 用 $\pi_\theta$ 采样 $\{\tau^{(i)}\}$,并记录每个动作的旧对数概率 $\log\pi_\theta(a^{(i)}_t|s^{(i)}_t)$(这一步极易漏掉,漏了就没法算比值);
  2. 计算目标 $y^{(i)}_t=r^{(i)}_t+\hat V^\pi_\phi(s^{(i)}_{t+1})$;
  3. 拟合 $\hat V^\pi_\phi$;
  4. 计算 GAE 优势 $\hat A^{(i)}_t$ 并冻结;
  5. $\theta'\leftarrow\theta$;
  6. 内层重复 $K$ 次:$\nabla_{\theta'}J(\theta')\approx\sum_i\sum_t \frac{\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)}{\pi_{\theta}(a^{(i)}_t|s^{(i)}_t)}\nabla_{\theta'}\log\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)\hat A^{(i)}_t$,然后 $\theta'\leftarrow\theta'+\alpha\nabla_{\theta'}J(\theta')$;
  7. $\theta\leftarrow\theta'$,回到第 1 步。

$K$ 是核心超参:$K=1$ 退化成原来的 on-policy 算法;$K$ 越大样本利用率越高,但 $\pi_{\theta'}$ 漂离 $\pi_\theta$ 越远,近似越不可靠。典型取值是 $K=4\sim10$ 个 epoch(每个 epoch 内再切成若干 minibatch)。

7. 还没完:让重要性采样真正跑起来

做完两次近似,公式里只剩下单步比值 $w_t=\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$,看上去问题已经解决了。但只要 $K$ 稍微调大一点,你就会发现训练仍然崩。

算法框旁边的等高线图,内层循环让 θ′ 一路走出旧策略数据的可信区域
右边的等高线图是目标函数的地形,几个叉号是内层循环里 $\theta'$ 依次经过的位置,虚线椭圆表示「这批数据还能说明问题的区域」。头一两步(黄色叉附近)没问题,走到第三、四步时 $\theta'$ 已经跑到椭圆外面去了:那里的地形完全是被 IS 权重外推出来的幻觉。下方两行注释总结得很到位:一旦权重远离 1.0,估计量就没用了;形式地说,它的方差变得太大。

7.1 单步比值也会失控

为什么单步比值也不安全?做个小实验:设动作空间有 10 个动作,旧策略是均匀分布(每个 0.1)。内层循环跑了几步之后,新策略在某个动作上的概率涨到 0.9,那么这个样本的权重是 $0.9/0.1=9$;同一状态下另一个概率跌到 0.005 的动作,权重是 $0.05$。一个 batch 里如果有 1% 的样本权重是 9,它们贡献的梯度分量就相当于 9% 的样本量——少数样本主导了整个更新。更糟的是,$w_t$ 与 $\hat A_t$ 是相关的:优化器会主动去增大那些 $\hat A_t>0$ 的样本的概率,也就是主动把权重推向大值。这是一个正反馈。

方差的形式化表达:对单步 IS 估计量 $\hat g = \frac1N\sum_i w_i \nabla\log\pi_{\theta'}(a_i|s_i)\hat A_i$,其方差含有 $\E[w^2\|\nabla\log\pi\|^2\hat A^2]$ 这一项。$w$ 的分布是重尾的——极端情况下(比如 $\pi_\theta$ 在某个动作上概率极小而 $\pi_{\theta'}$ 概率不小)$\E[w^2]$ 可以直接发散,此时中心极限定理都不再适用,样本平均不再以 $1/\sqrt N$ 收敛。

7.2 三件套之一:权重裁剪

代理目标与裁剪后的重要性权重定义,右侧图示权重在两个方向上分别持续增大和减小
左上是代理目标 $J(\theta')=\E_{\tau\sim p_\theta}[w(\tau)r(\tau)]$ 及其梯度,$w(\tau)$ 用大括号标出。右侧箭头指向地形图上的两个方向:往一个方向走 $w(\tau)$ 一路减小,往另一个方向一路增大——两种都是失控。左下给出补救办法:重新定义 $w(\tau)=\max\{1-\epsilon,\ \min\{1+\epsilon,\ \frac{p_{\theta'}(\tau)}{p_\theta(\tau)}\}\}$,箭头标注 “try 0.1”。右下黄框:这实际上给概率能增大或减小多少设了一个上限。

裁剪的定义就是把权重强行压回区间 $[1-\epsilon,\,1+\epsilon]$:

$$ w_c = \max\Big\{1-\epsilon,\ \min\big\{1+\epsilon,\ w\big\}\Big\} = \operatorname{clip}(w,\,1-\epsilon,\,1+\epsilon) $$

$\epsilon=0.1$ 是 Levine 建议的起手值(PPO 原论文常用 0.2;LLM 后训练里常用 0.2 甚至更小的非对称区间)。它带来两个立竿见影的好处:

  1. 方差有界。 $w_c\in[0.9,1.1]$,所以 $\E[w_c^2]\le 1.21$,$\operatorname{Var}$ 被硬性封顶,无论 $\theta'$ 漂多远。
  2. 隐式的信赖域。 一旦某个样本的比值超出 $[1-\epsilon,1+\epsilon]$,裁剪函数在那里是常数,梯度为零。优化器不再有动力继续推大这个动作的概率。这就等价于给「概率能改变多少」加了一道闸。
注意

裁剪引入的是偏差:$\E[w_c f]\ne\E[wf]$。我们是在用「一个有偏但方差有限的估计量」换掉「一个无偏但方差无限的估计量」。这在 RL 里是完全值得的交易——无偏估计如果方差是 $10^{17}$,它的实际用途为零。但要清楚这笔账:裁剪版本不再是任何目标函数的无偏梯度,收敛性分析也随之失效,PPO 至今没有干净的收敛保证。它靠的是大量经验验证。

7.3 三件套之二:自归一化重要性采样

另一个独立的技巧:与其用 $\frac1N\sum_i w_i f_i$(普通 IS,无偏),不如用

$$ \hat\mu_{\text{SNIS}} = \frac{\sum_{i=1}^{N} w_i f_i}{\sum_{i=1}^{N} w_i} $$

即用权重的和而不是 $N$ 来归一化,这叫自归一化重要性采样(self-normalized IS, SNIS)。它有几个重要性质:

性质普通 IS自归一化 IS
偏差无偏有偏,偏差 $O(1/N)$
方差常常更大通常显著更小(权重的整体缩放被抵消)
需要归一化常数需要 $p,q$ 都是正确归一化的密度只需知道 $p/q$ 相差一个常数因子
数值稳定性差(一个巨大权重直接毁掉均值)好(巨大权重同时出现在分子分母)
在 RL 中的对应标准 IS 策略梯度常见于 off-policy 评估、GRPO 一类的组内归一化

直觉:如果所有权重整体偏大 10 倍(比如新策略普遍比旧策略更「确定」),普通 IS 会把梯度整体放大 10 倍,学习率相当于翻了 10 倍;SNIS 则自动把这个共同因子除掉,只保留样本之间的相对权重。RL 里我们关心的本来就是相对的优劣排序,所以这个性质非常合用。

7.4 三件套之三:把诊断打进日志

裁剪和自归一化都是被动防御,主动的做法是监控,一旦指标恶化就提前结束内层循环(early stopping)。下面是一份可以直接嵌进训练循环的实现:

import torch
import torch.nn.functional as F

def is_diagnostics(logp_new, logp_old, eps=0.2):
    """返回一批 off-policy 诊断指标。logp_* 形状均为 (B,)。"""
    logr = logp_new - logp_old
    ratio = logr.exp()

    # 1) 有效样本量占比:越接近 1 越健康,低于 ~0.3 说明数据已失效
    ess_frac = ratio.sum() ** 2 / (ratio.pow(2).sum() * ratio.numel())

    # 2) 近似 KL(old || new)。直接用 -logr 的均值是无偏但方差大且可能为负;
    #    Schulman 的 k3 估计量恒非负、方差更小:k3 = r - 1 - log r
    approx_kl = (ratio - 1.0 - logr).mean()

    # 3) 被裁剪样本的比例:PPO 里典型健康值是 0.05 ~ 0.3
    clip_frac = ((ratio - 1.0).abs() > eps).float().mean()

    return {"ess_frac": ess_frac.item(),
            "approx_kl": approx_kl.item(),
            "clip_frac": clip_frac.item()}


def should_stop_inner_loop(diag, kl_target=0.015, ess_floor=0.3):
    """内层循环的早停判据:任一指标越线就停止本批数据的训练。"""
    return diag["approx_kl"] > 1.5 * kl_target or diag["ess_frac"] < ess_floor

三个指标各管一段:approx_kl 衡量策略整体漂移了多少(这是下一讲 TRPO 约束的经验版本);clip_frac 太低说明 $\epsilon$ 设得过松、裁剪没起作用,太高说明学习率或 $K$ 太大;ess_frac 是最本质的——它直接回答「这批数据还剩多少统计效力」。实践中 KL 早停(PPO 的一个常见变体,也是 OpenAI Spinning Up 里推荐的做法)往往比调 $\epsilon$ 更有效。

常见误区

用 $-\E[\log r]$ 来估计 $\KL(\pi_\theta\|\pi_{\theta'})$ 是无偏的,但它可能取到负值(KL 本身恒非负),而且方差很大,做早停判据时会疯狂误触发。代码里的 $k_3 = r-1-\log r$ 估计量同样无偏(因为 $\E_{\pi_\theta}[r]=1$,所以 $\E[r-1]=0$),却逐样本恒非负($x-1\ge\log x$),方差也小得多。这是一个非常值得记住的小工具。

8. 从裁剪到 PPO:那个 min 是干什么的

直接裁剪权重有一个微妙但致命的漏洞,Levine 用一整页专门讲它。

裁剪权重的问题:两幅图分别画出回报为正和为负时目标随比值变化的形状,右图显示往错误方向漂移不受惩罚
左上那个「先裁剪再乘回报」的写法被红线整个划掉。右侧两幅图横轴都是比值 $\frac{p_{\theta'}}{p_\theta}$,纵轴是目标 $J$。$r(\tau)>0$ 时(左图)目标随比值上升,超过 $1+\epsilon$ 后被削平——正确,不鼓励继续冒进。$r(\tau)<0$ 时(右图)目标随比值下降;但如果样本已经漂到 $1+\epsilon$ 之外(图中三颗星),裁剪把那里也削平了,于是「继续往数据外面漂」不付出任何代价。修正办法写在左下:改用 $\min\{w(\tau)r(\tau),\,w_c(\tau)r(\tau)\}$,其中 $w$ 是原始比值、$w_c$ 是裁剪后的比值。

8.1 为什么 min 是必要的

把两种写法逐情形对照一遍就清楚了。设 $w=\frac{\pi_{\theta'}(a|s)}{\pi_\theta(a|s)}$,$w_c=\operatorname{clip}(w,1-\epsilon,1+\epsilon)$,目标是最大化。

情形朴素裁剪 $w_c\hat A$PPO 的 $\min\{w\hat A,\ w_c\hat A\}$我们想要的行为
$\hat A>0$,$w\in[1-\epsilon,1+\epsilon]$正常梯度两项相等,正常梯度推大这个动作的概率 ✓
$\hat A>0$,$w>1+\epsilon$(已推得太多)削平,无梯度$w\hat A>w_c\hat A$,取 $w_c\hat A$,无梯度停止继续推 ✓
$\hat A>0$,$w<1-\epsilon$(反而压小了)削平,无梯度,卡死$w\hat A<w_c\hat A$,取 $w\hat A$,有梯度应该把它推回来 ✓
$\hat A<0$,$w<1-\epsilon$(已压得太狠)削平,无梯度取 $w_c\hat A$,无梯度停止继续压 ✓
$\hat A<0$,$w>1+\epsilon$(反而推大了坏动作)削平,无梯度,坏动作被永久锁在高概率上$w\hat A<w_c\hat A$(因为 $\hat A<0$ 且 $w>w_c$),取 $w\hat A$,有强梯度把它压回来赶紧压回来 ✓

第三行和第五行就是幻灯片右图那句「not paying any price for drifting away from data here!」所指的漏洞。朴素裁剪是对称地削平两侧,它不区分「已经漂出去了但方向是对的」和「已经漂出去了而且方向是错的」。而 $\min$ 是单边的:它永远取两者中较小(即较保守)的那个,效果是「往外漂时封顶,往回收时放行」。

直觉

$\min\{w\hat A, w_c\hat A\}$ 可以理解为「代理目标的悲观下界」。我们最大化的是一个 pessimistic bound:只要有可能高估真实改进,就取更保守的那一支。往数据分布外走时,目标不再增加(没有收益),但如果发现走错了,损失照旧全额计入(有惩罚)。这种「收益封顶、损失不封顶」的不对称性,正是它能约束住策略漂移的原因。

8.2 PPO 完整算法

PPO 算法框、CLIP 损失完整公式、熵正则化说明与熵示意图
左侧是完整的 PPO 算法框:外层采样 + 拟合 critic + 算 GAE,内层 $K$ 次对 $\mathcal L_{\text{CLIP}}$ 做梯度上升。底部是 $\mathcal L_{\text{CLIP}}$ 的完整定义。右侧「Some more details」列出两个实践要点:critic 的回归目标 $y^{(i)}_t$ 也用 GAE(即 $\lambda$-return)而不是单步 TD;目标里加上 $\sum_i\sum_t\mathcal H(\pi_{\theta'}(\cdot|s^{(i)}_t))$ 这个熵正则项,右下的钟形曲线示意熵度量的是分布的「宽度」。

PPO 的裁剪损失(clipped surrogate objective):

$$ \mathcal L_{\text{CLIP}}(\theta') = \sum_{i=1}^{N}\sum_{t=1}^{H} \min\left\{ \frac{\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)}{\pi_{\theta}(a^{(i)}_t|s^{(i)}_t)}\hat A^{(i)}_t,\ \ \operatorname{clip}\!\left(\frac{\pi_{\theta'}(a^{(i)}_t|s^{(i)}_t)}{\pi_{\theta}(a^{(i)}_t|s^{(i)}_t)},\,1-\epsilon,\,1+\epsilon\right)\hat A^{(i)}_t \right\} $$

算法流程与第 6.4 节完全一样,只是把第 6 步的梯度换成 $\nabla_{\theta'}\mathcal L_{\text{CLIP}}(\theta')$。另外两个细节:

  • critic 目标也用 GAE。 与其用单步目标 $y_t=r_t+\hat V_\phi(s_{t+1})$,不如用 $\lambda$-return $y_t = \hat A^{\text{GAE}}_t + \hat V_\phi(s_t)$。这在偏差-方差之间取得与 actor 一致的折中,实现上也只是一行加法。
  • 熵正则化(entropy regularization)。 在目标里加上 $\beta\sum_i\sum_t \mathcal H\big(\pi_{\theta'}(\cdot|s^{(i)}_t)\big)$,其中 $\mathcal H(p)=-\sum_a p(a)\log p(a)$。作用是防止策略过早坍缩成确定性——一旦某个动作概率接近 1,其他动作再也采不到,探索停止,而且 IS 比值会变得极度不稳定(分母趋于 0)。典型 $\beta\in[0.0,\,0.01]$,连续控制里常设 0,离散动作和 LLM 场景常设小正数。

8.3 最小可运行实现

import torch
import torch.nn.functional as F

def ppo_loss(logp_new, logp_old, adv, entropy,
             clip_eps=0.2, ent_coef=0.01):
    """
    logp_new : (B,) 当前 θ′ 下 log π_θ′(a|s),带梯度
    logp_old : (B,) 采样时记录的 log π_θ(a|s),detach 过,无梯度
    adv      : (B,) 冻结的 GAE 优势
    entropy  : (B,) 当前策略在这些状态上的熵
    """
    # 优势标准化:这不是理论要求,但对学习率的鲁棒性影响极大
    adv = (adv - adv.mean()) / (adv.std() + 1e-8)

    ratio = (logp_new - logp_old).exp()          # w = π_θ′ / π_θ
    surr1 = ratio * adv                          # 未裁剪支
    surr2 = ratio.clamp(1 - clip_eps, 1 + clip_eps) * adv   # 裁剪支
    policy_loss = -torch.min(surr1, surr2).mean()          # 取 min 后最大化 → 加负号

    return policy_loss - ent_coef * entropy.mean()


def train_on_batch(policy, value_fn, opt_pi, opt_v, batch,
                   K=4, minibatch=64, kl_target=0.015):
    obs, act, logp_old, adv, ret = batch          # 全部 detach 过
    n = obs.shape[0]
    for epoch in range(K):                        # 内层 K 次复用同一批数据
        perm = torch.randperm(n)
        for i in range(0, n, minibatch):
            idx = perm[i:i + minibatch]
            dist = policy(obs[idx])
            logp_new = dist.log_prob(act[idx])
            loss = ppo_loss(logp_new, logp_old[idx], adv[idx],
                            dist.entropy())
            opt_pi.zero_grad(); loss.backward()
            torch.nn.utils.clip_grad_norm_(policy.parameters(), 0.5)
            opt_pi.step()

            v_loss = F.mse_loss(value_fn(obs[idx]).squeeze(-1), ret[idx])
            opt_v.zero_grad(); v_loss.backward(); opt_v.step()

        # 每个 epoch 结束检查一次整体漂移,越线就提前结束内层循环
        with torch.no_grad():
            logr = policy(obs).log_prob(act) - logp_old
            approx_kl = (logr.exp() - 1.0 - logr).mean()
        if approx_kl > 1.5 * kl_target:
            break

几个容易踩的实现细节:logp_old 必须是采样当时记录的值,不能在训练循环里用「上一个 minibatch 后的策略」重算,否则比值的基准一直在动,裁剪区间就失去意义;adv 的标准化要在整批上做(而不是每个 minibatch 单独做)才不会引入额外偏差;梯度裁剪 clip_grad_norm_ 是 PPO 事实上的标配,与 IS 权重裁剪是两回事,前者管参数更新的步长,后者管分布漂移。

9. 走得更远:replay buffer 与 off-policy actor-critic

PPO 的 off-policy 程度其实很有限:数据只在被采集后的几次内层迭代里复用,然后立刻丢掉。它常被称为「近-on-policy(near-on-policy)」。那能不能像 DQN 一样,维护一个百万条转移的 replay buffer,让策略梯度也享受高样本效率?可以,但要付出的代价完全不同。

9.1 buffer 里的数据来自「一群陈年策略」

replay buffer 里的样本 $(s,a,r,s')$ 来自过去许多个不同的策略 $\pi_{\theta_1},\pi_{\theta_2},\dots$,合起来是一个混合行为策略 $\beta$。要把第 6 节的公式用上,我们需要

$$ \nabla_{\theta}J(\theta) \approx \E_{s\sim d^{\beta}}\ \E_{a\sim\beta(\cdot|s)}\!\left[\frac{\pi_{\theta}(a|s)}{\beta(a|s)}\nabla_{\theta}\log\pi_{\theta}(a|s)\,A^{\pi_\theta}(s,a)\right] $$

比 PPO 的情形多出三个新麻烦:

麻烦具体表现常见对策
$\beta$ 未知混合策略的密度没有解析式(除非你为每条转移都存下当时的 $\log\beta(a|s)$)存 logp_behavior 到 buffer;或干脆假装 $\beta$ 就是采集时的那个策略
比值巨大老策略与当前策略差异远大于 $\epsilon$,$\frac{\pi_\theta}{\beta}$ 可能是 $10^3$ 量级激进裁剪($\bar\rho=1$ 的 V-trace)、只保留新鲜数据、优先采样新样本
状态分布错配$d^{\beta}\ne d^{\pi_\theta}$,且这次差距不小,第 6 节的一阶近似彻底失效无法用 IS 修正(状态边缘比不可计算),只能接受这个偏差
注意

请把两种偏差分清楚:动作分布的错配是可以用 IS 精确修正的(我们知道 $\pi_\theta$ 和 $\beta$ 的密度);状态分布的错配原则上无法修正(需要环境模型或整条轨迹的连乘)。所有 off-policy 策略梯度算法,从 PPO 到 SAC,本质上都是在「状态分布错配的偏差」上下注:赌它足够小,或者赌它不影响最终收敛到的策略。这也是 off-policy 方法调参更玄学的根源。

9.2 用 critic 绕开动作上的 IS:DDPG 与 SAC 的思路

还有一条更彻底的路线:既然动作上的期望难采,那就不要采样,直接对当前策略重新采样动作。 注意上式里对动作的期望本来就是 $\E_{a\sim\pi_\theta}$——如果我们有一个能对任意 $(s,a)$ 求值的 critic $Q_\phi(s,a)$,那么给定 buffer 里的状态 $s$,我们可以就地从 $\pi_\theta(\cdot|s)$ 重新采一个动作 $\tilde a$,代入 $Q_\phi(s,\tilde a)$,这样动作分布天然就是 on-policy 的,IS 比值直接消失:

$$ \nabla_\theta J(\theta) \approx \E_{s\sim d^{\beta}}\ \E_{\tilde a\sim\pi_\theta(\cdot|s)}\big[\nabla_\theta\log\pi_\theta(\tilde a|s)\,Q_\phi(s,\tilde a)\big] $$

这就是 off-policy actor-critic 的核心构造。它把「$Q$ 值从哪来」和「动作从哪来」解耦了:$Q_\phi$ 用 buffer 里的转移做 Bellman 回归(这部分本来就是 off-policy 安全的),策略梯度则用重新采样的动作。代价是:Monte-Carlo 优势估计不能用了,你必须依赖 critic,于是第 1 节那张「无偏保险单」被撕毁——critic 有偏,梯度就有偏。这正是 Levine 在开头强调的那个取舍。

算法动作期望怎么处理数据复用程度梯度偏差来源
on-policy PG / A2C用采样动作,$w\equiv1$用一次即丢只有 critic 引入的方差($\lambda=1$ 时无偏)
PPOIS 比值 + 裁剪同一批复用 $K$ 轮状态分布错配 + 裁剪偏差
IMPALA / V-trace裁剪的 IS 比值($\bar\rho,\bar c$ 两个上限)分布式,数据滞后几十步裁剪导致收敛到 $\pi_{\bar\rho}$ 而非 $\pi$
DDPG / TD3确定性策略,用 $\nabla_a Q\cdot\nabla_\theta\mu$ 链式法则百万级 buffercritic 的近似误差 + 状态分布错配
SAC重参数化重采样 $\tilde a$,最大熵目标百万级 buffer同上,但熵项缓解了过度确定化

DDPG(deep deterministic policy gradient)走到了极致:策略是确定性的 $a=\mu_\theta(s)$,此时 $\log\pi_\theta$ 根本没有定义,梯度改用链式法则 $\nabla_\theta J=\E_{s\sim d^\beta}[\nabla_a Q_\phi(s,a)|_{a=\mu_\theta(s)}\nabla_\theta\mu_\theta(s)]$。SAC(soft actor-critic)则保留随机策略,用重参数化技巧(reparameterization trick)$\tilde a = f_\theta(\varepsilon;s)$ 让梯度穿过采样,并在目标里加上熵项 $\alpha\mathcal H(\pi_\theta(\cdot|s))$——这与第 8 节的熵正则化是同一个想法,只是 SAC 把它提升为目标函数的一部分而非附加惩罚。这两个算法是第 17–18 讲的主题,这里只需要建立一个认识:

核心结论

处理 off-policy 数据只有三种手段,且各有代价:(1)用 IS 权重修正——正确但方差爆炸,必须裁剪,于是引入偏差;(2)用 critic 重新采样动作——消除动作错配,但把正确性完全押在 critic 上;(3)限制数据的陈旧程度——最简单也最有效,PPO 就是这么做的。真实系统里三者常常混用。而没有任何手段能修正状态分布的错配,这是 off-policy 学习不可消除的偏差底线。

9.3 一张决策表

你的情况推荐做法理由
仿真器很快、可大规模并行on-policy PG / PPO,$K$ 取小样本便宜,买稳定性最划算
真机采样、每条数据都昂贵SAC / TD3 + 大 buffer样本效率是唯一硬约束,接受偏差
LLM 后训练(RLHF / RLVR)PPO / GRPO 一族,强裁剪 + KL 惩罚怕崩溃甚于怕慢;生成可并行;KL 还兼作「别跑离基座模型太远」的正则
动作空间离散且很小可以考虑 Q-learning 系$\argmax$ 廉价,值函数方法样本效率优势明显
动作空间连续且高维策略梯度系$\argmax_a Q$ 在连续空间要么解不出、要么很贵

本讲小结

本讲的逻辑是一条完整的「妥协链」:每一步都明确写出「这样做是错的,但错得可控」,最后落在一个能真跑的算法上。

步骤做了什么付出的代价为什么值得
① IS 恒等式$J(\theta')=\E_{\tau\sim p_\theta}\big[\frac{p_{\theta'}(\tau)}{p_\theta(\tau)}r(\tau)\big]$无(精确)模型项约掉,只剩策略概率连乘
② 对 $\theta'$ 求导用 $p\nabla\log p=\nabla p$ 得到 off-policy PG无(精确)$\theta'=\theta$ 时还原成 REINFORCE
③ 丢掉未来项连乘用 $A^{\pi_\theta}$ 代替 $A^{\pi_{\theta'}}$不再是 $J$ 的梯度,变成策略迭代策略迭代自身有单调改进保证(下一讲)
④ 丢掉状态边缘比$d^{\pi_{\theta'}}\to d^{\pi_\theta}$,只留单步动作比一阶近似,误差 $O(\epsilon^2)$把指数级连乘降成一次除法
⑤ 裁剪权重$w\to\operatorname{clip}(w,1-\epsilon,1+\epsilon)$引入偏差,失去收敛保证方差硬性封顶;隐式信赖域
⑥ 取 $\min$$\min\{w\hat A,\ w_c\hat A\}$目标不再光滑堵住「往外漂不付代价」的漏洞

一页速查

  • on-policy 策略梯度的死穴:一批数据只能走一步梯度,而高方差又逼你用大 batch、小学习率,代价相乘。
  • IS 恒等式:$\E_{p}[f]=\E_{q}\big[\frac pq f\big]$,前提是 $q$ 覆盖 $p$ 的支撑集。
  • 轨迹权重:$\frac{p_{\theta'}(\tau)}{p_\theta(\tau)}=\prod_{t=1}^{H}\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}$,初始分布与转移全部约掉,不需要模型。
  • 方差爆炸:若单步 $\E[\rho^2]=1+\sigma^2$,则 $\operatorname{Var}(w)=(1+\sigma^2)^H-1$。$(0.5,0.5)\to(0.6,0.4)$ 这种微小改动在 $H=1000$ 时就把方差推到 $10^{17}$。
  • 因果性拆分:过去项修正访问分布(不能丢),未来项修正 $Q$ 值(可以丢,丢了得到策略迭代)。
  • 可用形式:$\nabla J\approx\E_{s\sim d^{\pi_\theta},a\sim\pi_\theta}\big[\frac{\pi_{\theta'}(a|s)}{\pi_\theta(a|s)}\nabla\log\pi_{\theta'}(a|s)A^{\pi_\theta}(s,a)\big]$,等价于最大化代理目标 $\E\big[\frac{\pi_{\theta'}}{\pi_\theta}A\big]$。
  • 诊断三件套:ESS 占比(> 0.3 才可信)、近似 KL(用 $r-1-\log r$,恒非负)、裁剪比例(0.05–0.3 为宜)。
  • PPO:$\mathcal L_{\text{CLIP}}=\sum\min\{w\hat A,\ \operatorname{clip}(w,1-\epsilon,1+\epsilon)\hat A\}$,$\epsilon$ 取 0.1–0.2,内层 $K$ 取 4–10,配 GAE + 熵正则 + 优势标准化 + 梯度裁剪。
  • 偏差的两个来源:动作分布错配(可用 IS 精确修正)与状态分布错配(无法修正,只能靠「策略别变太快」来控制)。

与前后讲的接口

上一讲(Q-learning 的工程实践)解决的是「值函数方法如何在深度网络下不崩」,本讲解决的是「策略梯度方法如何不那么浪费样本」。两条线在第 9.2 节短暂交汇:off-policy actor-critic 同时需要 replay buffer(Q-learning 的技术)和策略梯度(本讲的技术)。

本讲留下的最大欠账是那句「this is a little bit wrong」:我们凭什么相信忽略状态分布差异是可以接受的?下一讲会用性能差分引理给出严格答案,并顺势推出以 KL 散度定义的信赖域、Fisher 信息矩阵与自然梯度,以及 TRPO。到那时你会发现,本讲的「裁剪」只是信赖域约束的一个粗糙但极其好用的近似——理解了原理再看 PPO,会觉得它每一处设计都恰好落在正确的位置上。

延伸阅读

本讲直接对应的论文

重要性采样与方差控制

off-policy actor-critic 一族

工程实践与复现