LECTURE 06

Actor-Critic:用学出来的值函数给策略梯度降方差

策略梯度里那个「reward-to-go」只是一次采样的粗糙估计。把它换成一个神经网络拟合的值函数,方差立刻塌下来——代价是引入偏差。本讲就是这场偏差-方差交易的完整说明书。

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

0. 本讲导读

上一讲我们推出了策略梯度(policy gradient)的最终形式,并用因果性(causality)把它化简成了「reward-to-go」加权的对数似然梯度:

$$ \nabla_\theta J(\theta) \approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H} \nabla_\theta \log \pi_\theta(a_t^{(i)}|s_t^{(i)})\,\hat Q_t^{(i)}, \qquad \hat Q_t^{(i)} = \sum_{t'=t}^{H} r(s_{t'}^{(i)}, a_{t'}^{(i)}). $$

这个式子是无偏的,但它的方差大到令人绝望。原因藏在 $\hat Q_t^{(i)}$ 这一项里:它是「在状态 $s_t^{(i)}$ 下采取动作 $a_t^{(i)}$ 之后能拿到多少总回报」这个期望值的单样本估计。一条轨迹就是一次抽样,而未来几十上百步的随机性(策略的随机采样、环境转移的随机性)全都压缩进了这一个数字里。同一个 $(s_t,a_t)$,这次跑出来 $+100$,下次可能跑出来 $-30$,梯度方向就跟着乱抖。

本讲的核心思想只有一句话:与其用一条轨迹的单样本回报,不如训练一个神经网络去拟合这个期望,再把拟合值代进策略梯度里。 拟合出来的那个值函数网络就叫 critic(评论家),被它指导的策略网络就叫 actor(演员)。actor 负责做动作,critic 负责打分——这就是 actor-critic 这个名字的来历。

围绕这一句话,本讲要展开的问题链条是:

  • 拟合谁? 我们可以拟合 $Q^\pi$、$V^\pi$ 或 $A^\pi$。哪一个进策略梯度方差最低?哪一个最容易学?(结论:用 $A^\pi$ 进梯度,但只学 $V^\pi$。)
  • 怎么学? 拟合 $V^\pi$ 这件事叫策略评估(policy evaluation)。监督标签从哪来?蒙特卡洛回报(无偏、方差大)还是自举目标 $r+\gamma \hat V_\phi(s')$(有偏、方差小)?
  • 无限长的任务怎么办? $H\to\infty$ 时 $V^\pi$ 会发散,于是引入折扣因子 $\gamma$。而 $\gamma$ 放在策略梯度的哪个位置,会得到两个不一样的梯度——几乎所有人都在用「错的那个」,为什么?
  • 算法怎么组织? 批量(batch)版本 vs 在线(online)版本;在线版本为什么必须靠并行 worker 才能跑起来;actor 和 critic 该共享网络还是各用一个。
  • 能不能既降方差又不引入偏差? 能——把 critic 只当 baseline 用。这条路通向 n-step return 和 GAE(generalized advantage estimation),用一个旋钮 $\lambda$ 连续调节偏差与方差。
  • 能不能用 replay buffer 变成 off-policy? 能,但天真的做法是错的。修正它需要把 critic 从 $V$ 换成 $Q$,再加上重参数化技巧——终点就是 SAC。

与前后讲的关系:上一讲(策略梯度)给了 actor 的更新公式,本讲给它配一个 critic;下一讲(基于值的 RL)会把这件事推到极端——干脆不要 actor,只学一个 $Q$,用 $\argmax_a Q(s,a)$ 当策略。可以说 actor-critic 正好坐在纯策略梯度和纯值函数方法的中间,理解了它,两边都通了。本讲还会第一次完整推导 GAE,它是 PPO 实现里的标配部件,也是作业 2 的必做项。

核心结论
  • 策略梯度权重的「理想值」是真实的 $Q^\pi(s_t,a_t)$;减去只依赖状态的 baseline $V^\pi(s_t)$ 得到优势函数(advantage) $A^\pi=Q^\pi-V^\pi$,它是方差最低且仍然无偏的选择。
  • 由 $Q^\pi(s_t,a_t)=r(s_t,a_t)+\gamma\,\E_{s_{t+1}}[V^\pi(s_{t+1})]$,只要有 $V^\pi$ 就能用单样本拼出 $A^\pi\approx r+\gamma \hat V_\phi(s')-\hat V_\phi(s)$。所以只拟合 $V^\pi$ 一个网络就够了——它输入维度最低、最好学。
  • 拟合 $V^\pi$ 的目标有两种:蒙特卡洛 $y_t=\sum_{t'\ge t} r_{t'}$(无偏、高方差)与自举 $y_t = r_t+\gamma\hat V_\phi(s_{t+1})$(有偏、低方差)。后者复用了函数逼近器的泛化能力,实践中通常更好。
  • $\gamma$ 有两种解释:数学上是给 MDP 加一个概率 $1-\gamma$ 的死亡状态;实践上是一个方差削减旋钮。把 $\gamma$ 放在不同位置会得到 option 1 / option 2 两个不同梯度,理论上正确的是带 $\gamma^{t-1}$ 前缀的 option 2,但大家用的是 option 1。
  • 把 critic 只当 baseline(而不是替换整个回报)可以保持完全无偏:因为 $\E_{a\sim\pi}[\nabla_\theta\log\pi_\theta(a|s)\,b(s)]=0$ 对任意只依赖 $s$ 的 $b$ 成立。
  • n-step return 在「前 n 步用真实奖励、n 步之后交给 critic」处切一刀;GAE 干脆在所有 n 处都切,按 $\lambda^{n-1}$ 指数加权,化简后得到极其简洁的 $\hat A^{\text{GAE}}_t=\sum_{l\ge 0}(\gamma\lambda)^l\delta_{t+l}$,其中 $\delta_t = r_t+\gamma\hat V_\phi(s_{t+1})-\hat V_\phi(s_t)$。
  • 直接给在线 actor-critic 加 replay buffer 会两处出错:$V^\pi$ 的自举目标用了旧策略的动作,且 $\log\pi_\theta$ 前面乘的动作不是当前策略会选的。修法是改学 $Q_\phi(s,a)$、并从当前策略重新采样动作。

1. 从单样本 reward-to-go 到期望:Q、V、A 三个值函数

1.1 回顾:REINFORCE 里那个刺眼的单样本回报

REINFORCE 算法三步循环与 reward-to-go
REINFORCE 的三步循环:跑策略采样 → 用 reward-to-go 估计回报 → 沿梯度改进策略。右边的绿-橙-蓝循环图是全课的统一框架,绿框「fit a model to estimate return」在纯策略梯度里其实什么模型都没有,只是把采样到的奖励求和;本讲要做的就是把这个绿框换成一个真正的神经网络。

先统一记号:$s_t$ 是状态(满足马尔可夫性),$a_t$ 是动作,$\pi_\theta(a|s)$ 是参数为 $\theta$ 的策略,$r(s,a)$ 是奖励,$p(s'|s,a)$ 是环境转移,$H$ 是回合长度(horizon),$\tau=(s_1,a_1,\dots,s_H,a_H)$ 是轨迹,$\gamma$ 是折扣因子。上标 $(i)$ 表示第 $i$ 条采样轨迹。

REINFORCE 的完整算法是:

  1. 从 $\pi_\theta(a_t|s_t)$ 采样 $N$ 条轨迹 $\{\tau^{(i)}\}$(就是跑策略);
  2. 计算 $\nabla_\theta J(\theta)\approx \frac1N\sum_i\sum_t \nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)})\,\hat Q_t^{(i)}$,其中 $\hat Q_t^{(i)}=\sum_{t'=t}^{H} r(s_{t'}^{(i)},a_{t'}^{(i)})$;
  3. $\theta \leftarrow \theta + \alpha\nabla_\theta J(\theta)$。

关键在于理解 $\hat Q_t^{(i)}$ 到底代表什么。它想表达的意思是:「如果我在状态 $s_t^{(i)}$ 采取动作 $a_t^{(i)}$,之后预期能拿到多少总奖励?」 但一条轨迹只能给出这个问题的一个样本。真正的答案应该是对所有可能的未来求期望:

$$ Q^\pi(s_t,a_t) = \sum_{t'=t}^{H}\E_{\pi_\theta}\big[r(s_{t'},a_{t'})\,\big|\,s_t,a_t\big]. $$

这就是状态-动作值函数(state-action value function),也叫 Q 函数:真实的期望 reward-to-go。

从单样本 reward-to-go 到真实期望 Q 函数
右侧的示意图是理解方差来源的关键:绿点表示某个 $(s_t,a_t)$,从它出发的黑色曲线是许多条可能的未来轨迹,它们扇形散开。红色箭头标注的「sum together these rewards」是把一条轨迹上的奖励加起来。$\hat Q_t^{(i)}$ 只沿着其中一条曲线求和,而 $Q^\pi(s_t,a_t)$ 是所有这些曲线的加权平均。轨迹扇形张得越开,单样本估计的方差就越大。

把单样本换成真实期望,策略梯度变成

$$ \nabla_\theta J(\theta) \approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)})\,Q^\pi(s_t^{(i)},a_t^{(i)}). $$
直觉

这一步没有改变梯度的期望值——因为 $\hat Q_t^{(i)}$ 本来就是 $Q^\pi(s_t,a_t)$ 的无偏估计,把一个无偏估计换成它自己的期望,外层期望不变。改变的只有方差:我们把「未来所有随机性」这一坨方差从估计量里彻底抹掉了,只剩下「当前状态-动作对本身是随机采的」这一层方差。这是免费的午餐——如果我们真的知道 $Q^\pi$ 的话。本讲剩下的篇幅就是在讨论:不知道 $Q^\pi$ 时,用一个近似的 $\hat Q$ 代替,要付出什么代价。

1.2 baseline 应该取什么:状态值函数出场

上一讲已经证明:给策略梯度的权重减去任意一个与动作无关的 baseline $b$,梯度期望不变。当时我们用的是最简单的 $b=\frac1N\sum_i r(\tau^{(i)})$,即「平均回报」。现在有了 $Q^\pi$,一个更好的 baseline 呼之欲出。

用 V 作为 baseline 得到优势函数
把常数 baseline「平均回报」升级为依赖状态的 baseline $V^\pi(s_t)$:它恰好是 $Q^\pi(s_t,a_t)$ 在当前策略动作分布下的平均值。相减得到的 $A^\pi(s_t,a_t)=Q^\pi(s_t,a_t)-V^\pi(s_t)$ 衡量「在这个状态下,这个动作比平均水平好多少」。

定义状态值函数(state value function)

$$ V^\pi(s_t)=\E_{a_t\sim\pi_\theta(a_t|s_t)}\big[Q^\pi(s_t,a_t)\big], $$

即「从状态 $s_t$ 出发,按当前策略行动,期望能拿多少总奖励」。它与 $Q^\pi$ 的差值就是优势函数(advantage function):

$$ A^\pi(s_t,a_t)=Q^\pi(s_t,a_t)-V^\pi(s_t). $$

$A^\pi$ 的语义是「$a_t$ 比在 $s_t$ 处随便按策略抽一个动作好多少」。它有一个非常漂亮的性质:在当前策略下的期望恒为零,$\E_{a\sim\pi}[A^\pi(s,a)]=0$。这意味着在任何状态下,总有一半左右(按概率加权)的动作优势为正、一半为负,梯度会均匀地「推好的、压坏的」。

为什么这是好事?考虑一个所有奖励都是正数的环境(比如每步 $+1$ 的生存奖励)。用原始 reward-to-go 时,所有动作的权重都是正的,策略梯度会同时推高所有采样到的动作的概率——只是推的力度不同。由于概率必须归一化,最终效果是相对的,能学,但梯度里混进了大量与「哪个动作更好」无关的公共分量,这部分纯粹是噪声放大器。减去 $V^\pi$ 之后,公共分量被扣掉了,剩下的每一个数字都直接回答「这个动作值不值得强化」。

推导:$V^\pi$ 是不是最优的 baseline?

严格来说不是。上一讲推过,使梯度方差最小的 baseline 是「用梯度模长平方加权的期望回报」

$$ b^\star = \frac{\E\big[\|\nabla_\theta\log\pi_\theta(\tau)\|^2\, r(\tau)\big]}{\E\big[\|\nabla_\theta\log\pi_\theta(\tau)\|^2\big]}, $$

而 $V^\pi$ 相当于把权重 $\|\nabla_\theta\log\pi_\theta\|^2$ 换成常数 1 之后的结果。但 $b^\star$ 需要额外估计梯度模长的二阶统计量,每个参数分量还各有一个最优值,实现麻烦、本身估计噪声大。Levine 的判断很直接:$V^\pi$ 在实践中已经几乎抓住了全部收益,而且它有清晰的语义(「这个状态的平均水平」),还能被一个神经网络高效地共享学习。所以后面所有算法都用 $V^\pi$。

1.3 三个函数,学哪一个?

Q、V、A 三个值函数的定义与在策略梯度中的位置
三个值函数并列:$Q^\pi$ 是在 $s_t$ 采取 $a_t$ 之后的总奖励,$V^\pi$ 是从 $s_t$ 出发的总奖励,$A^\pi$ 是「$a_t$ 好多少」。中间那句手写批注「the better this estimate, the lower the variance」是本讲的一句话总结。底部红线标出的是纯策略梯度用的单样本估计——无偏但高方差。右上角提示:三步循环里的绿框现在要拟合 $Q^\pi$、$V^\pi$ 或 $A^\pi$。

于是策略梯度的最终形态是

$$ \nabla_\theta J(\theta)\approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)})\,A^\pi(s_t^{(i)},a_t^{(i)}). $$

注意这里的核心权衡:$A^\pi$ 估计得越准,梯度方差越低。注意是「方差」而不是「偏差」——如果我们的 $\hat A$ 有系统性误差,梯度就是有偏的,可能收敛到错误的地方。这与纯策略梯度形成鲜明对比:后者的 $\hat A$(单样本回报减常数 baseline)永远无偏,但方差大到需要海量样本。本讲从这里开始,就一直在这条偏差-方差的钢丝上走。

用作梯度权重的量是否无偏方差需要学什么
$\sum_{t'\ge t} r_{t'}$(reward-to-go)无偏极高什么都不用学
$\sum_{t'\ge t} r_{t'} - b$(常数 baseline)无偏高一个标量
$\sum_{t'\ge t} r_{t'} - \hat V_\phi(s_t)$无偏中$V^\pi$
$r_t + \gamma\hat V_\phi(s_{t+1}) - \hat V_\phi(s_t)$(actor-critic)有偏(critic 不完美时)低$V^\pi$
真实 $A^\pi(s_t,a_t)$无偏最低不可得

接下来的问题是:$Q^\pi$、$V^\pi$、$A^\pi$ 这三个函数,我们到底该用神经网络去拟合哪一个?答案在下一节。

2. 只拟合状态值函数就够了

2.1 fit what to what?

值函数拟合:为什么只需要拟合 V
这一页回答「拟合什么到什么」。关键是中间两行:$Q^\pi(s_t,a_t)=r(s_t,a_t)+\E_{s_{t+1}}[V^\pi(s_{t+1})]$ 把 Q 完全用 V 表达出来;再代进 $A^\pi=Q^\pi-V^\pi$ 得到 $A^\pi(s_t,a_t)\approx r(s_t,a_t)+V^\pi(s_{t+1})-V^\pi(s_t)$。于是结论是最后一行的「let's just fit $V^\pi(s)$!」。右下角是一个输入 $s$、输出标量 $\hat V^\pi_\phi(s)$ 的神经网络,参数记作 $\phi$,与策略参数 $\theta$ 区分开。

三个候选里,$Q^\pi$ 和 $A^\pi$ 都是 $(s,a)$ 的二元函数,而 $V^\pi$ 只是 $s$ 的一元函数。输入维度更低意味着更少的样本就能学好,这是选 $V^\pi$ 的第一个理由。第二个理由更本质:有了 $V^\pi$,另外两个都能拼出来。

由 Q 函数的定义与马尔可夫性,把第一步的奖励从求和里拿出来:

$$ Q^\pi(s_t,a_t)=r(s_t,a_t)+\sum_{t'=t+1}^{H}\E_{\pi_\theta}\big[r(s_{t'},a_{t'})\,\big|\,s_t,a_t\big] = r(s_t,a_t)+\E_{s_{t+1}\sim p(s_{t+1}|s_t,a_t)}\big[V^\pi(s_{t+1})\big]. $$

这一步用到了:给定 $s_{t+1}$ 之后,后续的期望回报只依赖 $s_{t+1}$(马尔可夫性),也就是 $V^\pi(s_{t+1})$ 的定义。注意 $r(s_t,a_t)$ 是确定的(已知 $s_t,a_t$),不需要期望。

代入优势函数:

$$ A^\pi(s_t,a_t)=r(s_t,a_t)+\E_{s_{t+1}}\big[V^\pi(s_{t+1})\big]-V^\pi(s_t) \;\approx\; r(s_t,a_t)+V^\pi(s_{t+1})-V^\pi(s_t). $$

最后那个 $\approx$ 是把对 $s_{t+1}$ 的期望换成了实际采样到的那一个 $s_{t+1}$。这是一次单样本近似,会引入方差——但请注意它和之前那个单样本 reward-to-go 的量级差别:之前是把整条未来轨迹压成一个样本,现在只把一步转移压成一个样本,后面所有步的随机性都被 $V^\pi$ 平滑掉了。这就是方差骤降的来源。

注意

如果环境转移是确定性的(很多机器人仿真环境近似如此),那么这个 $\approx$ 直接变成等号,一点误差都没有。如果转移随机性很强(比如带风的导航、纸牌游戏的发牌),这一项就会贡献可观的方差——但仍然远小于蒙特卡洛。

2.2 策略评估:值函数的监督标签从哪来

策略评估:J 与 V 的关系、蒙特卡洛策略评估
「拟合 $V^\pi$」这件事有个正式名字:策略评估。注意第二行 $J(\theta)=\E_{s_1\sim p(s_1)}[V^\pi(s_1)]$——RL 的目标函数其实就是初始状态值函数的期望,所以评估策略和优化目标是同一件事的两面。下面两行是两种蒙特卡洛估计:单条轨迹求和,或者从同一个状态重启多次求平均(后者需要能重置模拟器到任意状态,现实中往往做不到)。

策略评估(policy evaluation)指的就是「给定策略 $\pi$,算出它的 $V^\pi$」,不涉及任何策略改进。它和 RL 目标的关系是

$$ J(\theta)=\E_{s_1\sim p(s_1)}\big[V^\pi(s_1)\big], $$

即目标函数就是初始状态的值在初始状态分布下的平均。

最朴素的做法是蒙特卡洛策略评估(Monte Carlo policy evaluation):

$$ V^\pi(s_t)\approx \sum_{t'=t}^{H} r(s_{t'},a_{t'}) \qquad\text{(单条轨迹)} $$

或者更好的

$$ V^\pi(s_t)\approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t'=t}^{H} r(s_{t'}^{(i)},a_{t'}^{(i)}) \qquad\text{(从 } s_t \text{ 重启 } N \text{ 次)}. $$

第二式方差更小,但它要求能把模拟器重置到任意指定状态并从那里重跑多次。真实机器人做不到,大多数标准 gym 环境也做不到(只能 reset 到初始分布)。所以实践中我们只有第一式——每个状态只有一条样本轨迹。这正是纯策略梯度在做的事。

2.3 函数逼近让单样本也够用

用函数逼近做蒙特卡洛策略评估:监督回归
右下角的红圈是这一页的灵魂:两条不同轨迹上有两个相近但不相同的状态(两个绿点),它们各自只有一条单样本回报,但神经网络必须用同一个函数去同时拟合它们。这种「被迫平均」正是函数逼近带来的额外方差削减,标注写着「the same function should fit multiple samples!」。左边把它写成标准监督回归:训练数据是 $(s_t^{(i)},\,y_t^{(i)})$,损失是平方误差。

虽然每个状态只有一条样本,但如果我们用神经网络 $\hat V^\pi_\phi$ 去拟合,情况会好很多。构造训练集

$$ \mathcal{D}=\Big\{\Big(s_t^{(i)},\ \underbrace{\textstyle\sum_{t'=t}^{H} r(s_{t'}^{(i)},a_{t'}^{(i)})}_{y_t^{(i)}}\Big)\Big\}, $$

然后做最普通的监督回归:

$$ \mathcal{L}(\phi)=\frac{1}{2}\sum_{i=1}^{N}\sum_{t=1}^{H}\big\|\hat V^\pi_\phi(s_t^{(i)})-y_t^{(i)}\big\|^2 . $$

关键洞察在于:神经网络是连续、光滑的函数。当两个状态 $s$ 和 $s'$ 很接近时,网络给它们的输出也接近,于是它们各自的单样本标签会被隐式地平均。相当于免费获得了「从同一状态重启多次」的效果——只不过是近似的、基于泛化的。Levine 的原话是「not as good as this(指真正的多次重启平均),but still pretty good!」。

2.4 自举(bootstrapping):把 critic 自己当标签

自举目标:用上一轮拟合的值函数构造标签
「Can we do better?」的答案是自举。第一行推导链条:理想目标 $y_t=\sum_{t'\ge t}\E[r_{t'}|s_t]$ 精确等于 $r(s_t,a_t)+V^\pi(s_{t+1})$ 的期望,而 $V^\pi$ 我们没有——那就用上一轮拟合出来的 $\hat V^\pi_\phi$ 顶上(右侧标注 directly use previous fitted value function)。训练数据变成 $(s_t^{(i)},\,r_t^{(i)}+\hat V^\pi_\phi(s_{t+1}^{(i)}))$,损失形式完全不变。底部一行:这被称为「bootstrapped」估计。

蒙特卡洛标签 $y_t=\sum_{t'\ge t} r_{t'}$ 是无偏的,但它把未来 $H-t$ 步的所有随机性都塞进了一个标签里,方差随剩余步数增长。有没有方差更小的标签?

回到理想目标的定义并做一次一步展开:

$$ y_t^{(i)}=\sum_{t'=t}^{H}\E_{\pi_\theta}\big[r(s_{t'},a_{t'})\,\big|\,s_t^{(i)}\big] \;\approx\; r(s_t^{(i)},a_t^{(i)})+V^\pi(s_{t+1}^{(i)}) \;\approx\; r(s_t^{(i)},a_t^{(i)})+\hat V^\pi_\phi(s_{t+1}^{(i)}). $$

第一个 $\approx$ 只是把一步的期望换成采样(同样是低方差的单步近似);第二个 $\approx$ 是关键:我们把 $V^\pi$ 替换成了自己当前的估计 $\hat V^\pi_\phi$。这就是「自举」——用自己的估计去构造自己的训练标签,像揪着自己的鞋带把自己拎起来。

训练集变成

$$ \mathcal{D}=\Big\{\Big(s_t^{(i)},\ r(s_t^{(i)},a_t^{(i)})+\hat V^\pi_\phi(s_{t+1}^{(i)})\Big)\Big\}, $$

损失函数一模一样。实现上要注意:标签里的 $\hat V^\pi_\phi(s_{t+1})$ 必须被当成常数(PyTorch 里 .detach()),不能让梯度穿过去,否则优化的就不是回归问题了。

推导:两种目标的偏差-方差账

设真实值为 $V^\pi(s_t)$,剩余步数为 $k=H-t$,每步奖励的方差为 $\sigma_r^2$(简化起见假设各步独立)。

  • 蒙特卡洛目标:$\E[y_t]=V^\pi(s_t)$,零偏差;方差约 $\mathrm{Var}(y_t)\approx k\sigma_r^2$,随剩余步数线性增长。$H=1000$ 时这个数字是 $H=10$ 时的 100 倍。
  • 自举目标:$\E[y_t]=r_t+\E[\hat V_\phi(s_{t+1})]$。若 $\hat V_\phi$ 有系统误差 $\varepsilon$,标签就带上 $\varepsilon$ 的偏差,并且这个偏差会沿着自举链条向前传播($s_t$ 的标签错了会让 $s_{t-1}$ 的标签也错);但方差只有 $\sigma_r^2+\mathrm{Var}(\hat V_\phi(s_{t+1}))$,与 $k$ 无关。

直觉上:蒙特卡洛是「实测但只测一次」,自举是「测一步 + 查表」。查的表可能是错的,但至少每次查出来的结果稳定。深度 RL 里 $H$ 通常几百到几千,$k\sigma_r^2$ 大得离谱,所以自举几乎总是赢。第 6 节的 GAE 会告诉我们如何在两者之间连续插值。

2.5 两个历史性的例子

TD-Gammon 与 AlphaGo 中的策略评估
两个把策略评估用到极致的里程碑。左:Tesauro 1992 的 TD-Gammon,用一个浅层多层感知机拟合双陆棋局面的值函数,靠自我对弈 + TD 学习达到人类顶级水平——这是第一个证明「神经网络 + 自举」可行的重量级案例。右:Silver 等 2016 的 AlphaGo,同样用 $\hat V^\pi_\phi(s_t)$ 预测给定棋盘状态下的期望比赛结果。两者的奖励都极其稀疏(只有终局输赢),值函数网络的作用就是把这个稀疏信号「传播」到中间局面。

这两个例子的共同点值得强调:奖励只在回合结束时出现(赢 $+1$ / 输 $-1$),中间几十上百步全是 $0$。如果只用蒙特卡洛,每个局面的标签就是最终输赢,噪声极大;而值函数网络学到的是「这个局面的胜率」,它把终局信号沿着自举链条一路回传,同时用泛化能力在相似局面之间共享信息。这正是 2.3 和 2.4 两节说的机制在真实系统里的体现。

3. 折扣因子 γ:它到底是什么,该放在哪里

3.1 无限长任务会让值函数发散

折扣因子:episodic 与 continuous 任务、γ 改变 MDP
左上是自举目标与回归损失;紧接着提出问题「what if $H$ is $\infty$?」——$\hat V^\pi_\phi$ 会无限增大。右上两张图区分两类任务:episodic(机械臂搭积木,做完就结束)与 continuous/cyclical(仿人形机器人跑步,可以永远跑下去)。左下给出补救办法:在自举目标里加一个 $\gamma$,$\gamma\in[0,1]$,实践中 $0.99$ 好用;等价于把目标改成 $\E[\sum_t\gamma^t r_t]$。右下是 $\gamma$ 的数学解释:在原 MDP 上加一个「死亡状态」(骷髅),每一步有 $1-\gamma$ 的概率直接掉进去(进去就再也拿不到奖励),其余转移概率整体缩放为 $\gamma p(s'|s,a)$。

到目前为止我们都假设回合长度 $H$ 有限。但很多任务本质上是连续/循环型(continuous / cyclical)的:让机器人一直跑下去、让服务器一直调度请求。此时 $H=\infty$,如果每步奖励都是正的,$V^\pi(s)=\sum_{t=1}^{\infty} r_t$ 就是 $+\infty$,网络要去拟合一个发散的目标,训练必然崩掉。

标准解法是折扣因子(discount factor) $\gamma\in[0,1]$。自举目标改成

$$ y_t^{(i)}=r(s_t^{(i)},a_t^{(i)})+\gamma\,\hat V^\pi_\phi(s_{t+1}^{(i)}), $$

对应的优化目标变成

$$ \theta^\star=\argmax_\theta\ \E_{\tau\sim p_\theta(\tau)}\Big[\sum_{t=1}^{H}\gamma^{t}r(s_t,a_t)\Big]. $$

只要奖励有界 $|r|\le R_{\max}$,$|V^\pi|\le R_{\max}/(1-\gamma)$ 就是有限的。$\gamma=0.99$ 时上界是 $100R_{\max}$,完全在网络能表达的范围内。CS285 里 $\gamma=0.99$ 是几乎所有连续控制任务的默认值。

3.2 折扣因子的两种解释

解释一:$\gamma$ 改变了 MDP(数学上的严格解释)

给原 MDP 增加一个吸收态 $s_\dagger$(幻灯片里画成骷髅头),定义新的转移

$$ \tilde p(s'|s,a)=\gamma\, p(s'|s,a)\quad (s'\ne s_\dagger),\qquad \tilde p(s_\dagger|s,a)=1-\gamma . $$

验证归一化:$\sum_{s'\ne s_\dagger}\gamma p(s'|s,a)+(1-\gamma)=\gamma+1-\gamma=1$,合法。进入 $s_\dagger$ 后永远留在那里且奖励恒为 $0$。那么在新 MDP 上的无折扣期望回报,恰好等于原 MDP 上的 $\gamma$-折扣期望回报:因为「活到第 $t$ 步」的概率正好是 $\gamma^{t-1}$。

所以 $\gamma$ 的语义是「每一步都有 $1-\gamma$ 的概率世界末日」。这也解释了为什么它叫「better to get rewards sooner than later」:既然随时可能死,当然要先拿现钱。$\gamma=0.99$ 对应的「期望寿命」是 $1/(1-\gamma)=100$ 步——这也是一个很有用的心算:$\gamma$ 决定了值函数关心多远的未来,有效视界约为 $\frac{1}{1-\gamma}$ 步。$\gamma=0.9$ 只看 10 步,$\gamma=0.999$ 看 1000 步。

解释二:$\gamma$ 是一个方差削减旋钮(实践上的解释)

即使任务本身是有限长的、$V^\pi$ 根本不会发散,人们照样会加 $\gamma$。原因是:遥远未来的奖励与当前这个动作的因果关联通常很弱,但它们贡献的方差却一点不少。用 $\gamma$ 把它们指数衰减掉,等于砍掉了「信噪比最差的那部分信号」。代价是偏差——如果任务真的需要 200 步之后才有回报,$\gamma=0.99$ 会把它压到 $0.99^{200}\approx 0.13$,几乎看不见了。所以 $\gamma$ 的选择本质上是「有效视界 vs 方差」的权衡,而不只是数值稳定性问题。

3.3 γ 放在策略梯度的哪里:option 1 vs option 2

这是本讲最容易被跳过、但概念上最微妙的一点。我们已经知道 $\gamma$ 怎么进值函数了,那它怎么进策略梯度?有两种自然的写法,而它们不相等。

写法 A(option 1):先写出无折扣的 causality 形式,再把 reward-to-go 换成折扣 reward-to-go:

$$ \nabla_\theta J(\theta)\approx \frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)}) \Big(\sum_{t'=t}^{H}\gamma^{\,t'-t}\,r(s_{t'}^{(i)},a_{t'}^{(i)})\Big). $$

注意折扣的指数是 $t'-t$:每个时间步都以「自己所在的时刻」为原点重新开始打折。

写法 B(option 2):从折扣目标 $J(\theta)=\E[\sum_t \gamma^{t-1}r_t]$ 出发,老老实实推策略梯度。上一讲的推导给出

$$ \nabla_\theta J(\theta)\approx\frac{1}{N}\sum_{i=1}^{N}\Big(\sum_{t=1}^{H}\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)})\Big)\Big(\sum_{t'=1}^{H}\gamma^{\,t'-1}r(s_{t'}^{(i)},a_{t'}^{(i)})\Big), $$

再用因果性去掉 $t'\lt t$ 的项,并把 $\gamma^{t'-1}$ 拆成 $\gamma^{t-1}\cdot\gamma^{t'-t}$:

$$ \nabla_\theta J(\theta)\approx\frac{1}{N}\sum_{i=1}^{N}\sum_{t=1}^{H}\ \gamma^{\,t-1}\,\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)}) \Big(\sum_{t'=t}^{H}\gamma^{\,t'-t}r(s_{t'}^{(i)},a_{t'}^{(i)})\Big). $$

与写法 A 相比,多了一个前置因子 $\gamma^{t-1}$。

推导:这个 $\gamma^{t-1}$ 意味着什么

option 2 是「$\gamma$-折扣目标函数」的数学上正确的梯度。它说的是:第 100 步的那个动作,即使它在局部看起来非常关键,对目标函数的贡献也只有第 1 步动作的 $\gamma^{99}$ 倍。换句话说,option 2 认为「越晚做的决策越不重要」,因为按解释一,你多半已经死在半路上了。

option 1 没有这个前置因子,它对所有时间步一视同仁。它不是 $\gamma$-折扣目标的梯度。那它是什么的梯度?粗略地说,它对应的是平均奖励(average reward)目标的梯度的一个近似——每个时刻都被同等看待,$\gamma$ 只在「估计当前时刻的 reward-to-go」时起作用,纯粹作为一个降方差手段。Philip Thomas 在 2014 年的论文《Bias in Natural Actor-Critic Algorithms》里详细分析了这件事:几乎所有实现(包括 A3C、PPO、SAC)用的都是 option 1,因此从「折扣目标」的角度看它们全都是有偏的。

为什么大家还是用 option 1? 因为 option 2 在实践中很糟糕:$\gamma^{t-1}$ 会让 $t$ 较大的样本的梯度指数级衰减到零。$\gamma=0.99$、$t=1000$ 时前置因子是 $\gamma^{999}\approx 4\times 10^{-5}$,等于把回合后半段的所有数据统统扔掉。我们通常真的希望机器人在第 1000 步也走得好,而不是「反正它大概率活不到那时候」。所以:我们真正想优化的是平均奖励式的目标,$\gamma$ 只是我们借来降方差的工具,option 1 恰好匹配这个意图。

常见误区

「既然 option 2 才是对的,那我的实现是不是错了?」——不是。要意识到这是一个有意为之的取舍:我们放弃了「优化折扣目标」这个理论保证,换来了「所有时间步的数据都能用」这个实际收益。写作业时如果你在代码里写了 discount ** t 作为 log-prob 的前置系数,反而会发现学不动。标准实现(包括 CS285 作业 2 的参考答案)都是 option 1。

3.4 从「时变」到「时不变」:为什么可以只写转移三元组

时变与时不变记号:把带 t 下标的数据写成 transition 元组
上半部分是带时间下标的训练数据 $(s_t^{(i)},\,r_t^{(i)}+\gamma\hat V^\pi_\phi(s_{t+1}^{(i)}))$,四个箭头指向它,配文「we often don't actually care at which time step the data was collected」,旁边黄色高亮问「why?」。右下的图模型说明理由:转移概率 $p(s_{t+1}|s_t,a_t)$ 对所有 $t$ 都是同一个函数(时齐 MDP),加上 $\gamma$-折扣目标本身不显含 $t$,所以值函数也不依赖 $t$。于是数据可以写成打散的转移四元组 $(s_i,a_i,s_i')$,损失变成对 $N$ 个独立样本求和。底部黄色标注强调:这只是记号上的便利,什么都没有改变。

这一小节表面上只是换记号,实际影响很大。原来我们写 $s_t^{(i)}$,需要同时记住「第几条轨迹、第几步」。但在时齐(time-invariant / stationary)的无限视界 MDP 中:

  • 转移 $p(s'|s,a)$ 不随 $t$ 变化;
  • 折扣目标 $\sum_{t'\ge t}\gamma^{t'-t}r_{t'}$ 从任何时刻看都长得一样;
  • 因此 $V^\pi(s)$ 只是 $s$ 的函数,与 $t$ 无关。

于是我们可以把所有轨迹拆散成一堆独立的转移(transition) $(s_i,a_i,s_i')$,训练集写成

$$ \mathcal{D}=\big\{\big(s_i,\ r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')\big)\big\}_{i=1}^{N}, \qquad \mathcal{L}(\phi)=\frac{1}{2}\sum_{i=1}^{N}\big\|\hat V^\pi_\phi(s_i)-y_i\big\|^2 . $$

这个记号的转变是后面所有内容的前提:只有把数据看成「一袋转移」而不是「一批轨迹」,replay buffer、mini-batch 随机梯度、异步并行采样这些工程手段才有意义。 反之,如果任务是有限视界且策略/值函数依赖时间(比如「第 10 步必须抓住杯子」),就必须保留 $t$,此时 $V$ 要写成 $V(s,t)$,或者把 $t$ 作为观测的一部分喂进网络。

4. 基本的 actor-critic 算法:批量版与在线版

4.1 批量 actor-critic

批量 actor-critic 算法伪代码与两个网络
批量 actor-critic 的完整六步。红框圈出的第 2–4 步是本讲新增的部分:算自举目标 $y_i$、把 $\hat V^\pi_\phi$ 回归到这些目标、再用 $\hat V^\pi_\phi$ 拼出优势 $\hat A^\pi(s_i,a_i)=r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')-\hat V^\pi_\phi(s_i)$;第 5–6 步和纯策略梯度一模一样,只是权重换成了 $\hat A^\pi$。下方两个网络示意图强调 actor(参数 $\theta$,输出 $\pi_\theta(a|s)$)与 critic(参数 $\phi$,输出标量 $\hat V^\pi_\phi(s)$)是两套独立参数。

把前三节的结论装配起来,就得到批量 actor-critic 算法(batch actor-critic):

  1. 用当前策略 $\pi_\theta(a|s)$ 采样一批转移 $\{(s_i,a_i,r_i,s_i')\}$(通常是若干条完整轨迹拆散得到);
  2. 计算自举目标 $y_i = r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')$;
  3. 把 $\hat V^\pi_\phi(s)$ 回归到目标 $\{y_i\}$,即最小化 $\sum_i\|\hat V^\pi_\phi(s_i)-y_i\|^2$;
  4. 计算优势 $\hat A^\pi(s_i,a_i)=r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')-\hat V^\pi_\phi(s_i)$;
  5. $\nabla_\theta J(\theta)\approx\sum_i \nabla_\theta\log\pi_\theta(a_i|s_i)\,\hat A^\pi(s_i,a_i)$;
  6. $\theta\leftarrow\theta+\alpha\nabla_\theta J(\theta)$,回到第 1 步。

几点必须讲清楚的细节:

  • 第 2 步和第 4 步里的 $\hat V^\pi_\phi$ 用的是哪一版参数? 第 2 步用的是更新前的 $\phi$(老的 critic 给出目标),第 4 步一般用更新后的 $\phi$(新 critic 给出更准的优势)。这个细节在实现里很容易搞混,但影响不大。
  • 第 3 步做几次梯度下降? 可以只做一次(此时更像在线 TD),也可以做几十次直到收敛(此时更像「拟合值迭代」)。做太多次会过拟合到旧目标上,做太少 critic 跟不上 actor。常见做法是每轮做几到几十次 mini-batch 更新。
  • 终止状态的处理:如果 $s_i'$ 是回合结束(terminal),必须令 $y_i=r_i$,即把 $\gamma\hat V^\pi_\phi(s_i')$ 强制置零。忘了这一点是最常见的 bug 之一:网络会认为「死了以后还能继续拿奖励」,学出来的策略会主动求死或主动求生(取决于奖励符号)。注意要区分「真正终止」和「因为时间上限被截断(time limit)」,后者理论上仍应自举。
  • on-policy 性质:第 1 步的样本必须来自当前的 $\pi_\theta$。一旦 $\theta$ 更新,之前的样本就作废了,因为 $V^\pi$ 是对特定策略 $\pi$ 定义的。这是 actor-critic 样本效率低的根源,第 7 节会试图修补它。

4.2 在线 actor-critic:只有一条转移怎么办

从批量到在线:单条转移的 actor-critic 更新
黄色标注问「what if we only have one transition?」。左下的轨迹图上,两个绿点标出的正是刚刚走过的一步 $(s_i,a_i,s_i')$,由它直接算出 $y_i=r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')$。右侧竖排的五行公式就是完整的单样本更新:critic 损失 $\mathcal{L}_i(\phi)=\|\hat V^\pi_\phi(s_i)-y_i\|^2$、$\phi$ 的一步下降、优势、actor 的单样本梯度、$\theta$ 的一步上升。

批量版本要求先攒够一批数据。但既然我们已经把数据看成「一袋转移」,理论上每走一步就能更新一次。这就是在线 actor-critic(online actor-critic):

在线 actor-critic 算法伪代码,A3C 与并行 worker
在线版本的六步:第 1 步从「采样一批」变成「走一步」。右侧标注给出两条关键信息:这类算法的代表是 A3C;以及「Getting this to work in practice typically requires multiple parallel workers」——单 worker 的在线 actor-critic 几乎跑不起来。
  1. 用 $a\sim\pi_\theta(a|s)$ 走一步,得到 $(s_i,a_i,r_i,s_i')$;
  2. $y_i = r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')$;
  3. 用 $\nabla_\phi\mathcal{L}_i(\phi)$ 更新 $\phi$,其中 $\mathcal{L}_i(\phi)=\|\hat V^\pi_\phi(s_i)-y_i\|^2$;
  4. $\hat A^\pi(s_i,a_i)=r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')-\hat V^\pi_\phi(s_i)$;
  5. $\nabla_\theta J(\theta)\approx\nabla_\theta\log\pi_\theta(a_i|s_i)\,\hat A^\pi(s_i,a_i)$;
  6. $\theta\leftarrow\theta+\alpha\nabla_\theta J(\theta)$。
注意:为什么单 worker 的在线版几乎一定失败

用 batch size $=1$ 做深度学习,本身就是灾难:梯度噪声极大,BatchNorm 之类的组件直接失效。更糟的是 RL 特有的问题——连续采到的转移高度相关。$s_1,s_2,s_3,\dots$ 是同一条轨迹上相邻的状态,它们几乎一样。用它们逐个做 SGD,等于反复用几乎相同的样本更新网络,网络会剧烈地过拟合到当前这一小段轨迹上,然后在下一段轨迹上被推向另一个方向。这叫「样本相关性」问题,在 Q-learning 那一讲会再次以完整形态出现。

4.3 并行化:同步 vs 异步

解决办法是并行化:同时跑 $K$ 个环境实例,每步收集 $K$ 条互不相关的转移,凑成一个 batch 再更新。这样既保持了「在线」(每步一更新),又拿到了 i.i.d. 程度足够好的 mini-batch。

同步并行(synchronized)异步并行(asynchronous)
做法$K$ 个 worker 各走一步,全部等齐,汇总梯度后统一更新参数,再把新参数广播回去每个 worker 自己走、自己算梯度,随时把梯度推给参数服务器;参数服务器立即更新并返回最新参数
数据是否严格 on-policy是否——worker 用的参数可能已经落后几步(stale gradients)
吞吐受最慢 worker 拖累(straggler 问题)高,无需等待
代表算法A2C(Advantage Actor-Critic)A3C(Asynchronous Advantage Actor-Critic)
实践评价实现简单、复现性好;GPU 时代通常更快历史上很重要;理论上不完全正确,但实践中「基本没事」

Levine 对异步版本的评价值得记下来:从理论上讲,异步 worker 用的是稍旧的策略产生数据,所以严格来说已经 off-policy 了,这是个「错误」;但只要参数变化不大(学习率小、更新频繁),这个偏差在实践中可以忽略。这是深度 RL 里非常典型的态度——大量方法在理论上是不严格的,但工程上有效。后来的研究(如 OpenAI 的 A2C 实现)发现同步版本在同等计算量下往往更好且更容易调试,所以现在同步并行是主流。

4.4 网络架构:两个网络还是一个网络

幻灯片上画的是两个独立的网络(一个吐 $\pi_\theta(a|s)$,一个吐 $\hat V^\pi_\phi(s)$)。但在图像输入的任务里,两个网络都要从像素里提取特征,这份工作重复了两遍。于是有两种设计:

双网络(separate)共享主干(shared trunk / two-head)
结构actor 与 critic 各一套完整参数共享卷积/编码器,顶部分出策略头与值函数头
优点简单、稳定;两个损失互不干扰,学习率可以分别调特征共享、样本效率高;像素输入时省一半算力
缺点无特征共享,图像任务下浪费严重两个梯度在共享层相互干扰,值函数的回归梯度量级往往远大于策略梯度,容易「压垮」策略学习
常见对策—给两个损失加权:$\mathcal{L}=\mathcal{L}_{\text{actor}}+c_v\mathcal{L}_{\text{critic}}$,$c_v$ 常取 $0.5$;或对回报做标准化
适用低维状态输入(MuJoCo 之类)高维像素输入(Atari、A3C 原论文的设定)
常见误区

共享主干时如果不加权直接把两个 loss 相加,训练常常表现为「critic 学得飞快,actor 完全不动」。原因是值函数回归的损失尺度由奖励量级决定(可能是几百),而策略梯度项的尺度由 $\log\pi$ 和优势决定(通常是个位数)。诊断方法:分别打印两个 loss 的梯度范数。修法:调 $c_v$、对优势做标准化(见 §6.4)、或干脆改用双网络。

5. critic 当 baseline:要方差还是要无偏

5.1 要让它在实践中真正好用,还缺什么

让 actor-critic 在实践中工作需要的两件事
基本 actor-critic 只是骨架。红框圈出的是本讲后半段要解决的问题:「even better ways to estimate $\hat A^\pi$」——今天讲的 GAE;另一条线「better ways to estimate $\nabla_\theta J(\theta)$」——以后讲的 PPO。这张图把本讲和后续几讲的分工划得很清楚:本讲改进的是优势估计,PPO 改进的是梯度估计本身。

把 §4 的算法直接实现出来,在简单任务上能跑,但在稍难的任务上通常表现平庸。要让它变成「真正能用的策略梯度」,需要两方面的改进:

  • 更好的 $\hat A^\pi$ 估计——本讲剩余部分(n-step、GAE);
  • 更好的 $\nabla_\theta J(\theta)$ 估计——即在同一批数据上做多次策略更新而不崩掉,这是 PPO 的内容,留到后面的讲次。

5.2 三种估计量的偏差-方差谱

critics and baselines:三种梯度估计量对比
三个估计量并排对比。第一行 actor-critic:优势用 $r+\gamma\hat V^\pi_\phi(s')-\hat V^\pi_\phi(s)$,绿色标注「lower variance (due to critic)」,红色标注「not unbiased (if the critic is not perfect)」。第二行纯策略梯度:折扣 reward-to-go 减常数 baseline $b_t$,无偏但高方差。第三行是折中方案:仍用完整的蒙特卡洛折扣 reward-to-go,但 baseline 换成学出来的 $\hat V^\pi_\phi(s_t)$——同时享有「no bias」和「lower variance」。黄色标注留了一道练习:证明它仍然无偏。

写在一起看得更清楚。记 $g_t = \nabla_\theta\log\pi_\theta(a_t|s_t)$:

名称权重偏差方差
Actor-critic$r(s_t,a_t)+\gamma\hat V^\pi_\phi(s_{t+1})-\hat V^\pi_\phi(s_t)$有(critic 不完美时)低
Policy gradient(常数 baseline)$\big(\sum_{t'=t}^{H}\gamma^{t'-t}r(s_{t'},a_{t'})\big)-b_t$无高
PG + 状态相关 baseline$\big(\sum_{t'=t}^{H}\gamma^{t'-t}r(s_{t'},a_{t'})\big)-\hat V^\pi_\phi(s_t)$无中(baseline 更贴近回报)

第三行是个非常有意思的位置:它用了 critic,却没有引入任何偏差。原因是 critic 只出现在 baseline 的位置,而 baseline 的无偏性对任意只依赖状态的函数都成立——哪怕这个函数学得一塌糊涂。学得越准,方差降得越多;学得不准,最多是白干,绝不会把梯度带偏。这是一个「只赚不赔」的用法。

5.3 证明:状态相关的 baseline 不引入偏差

推导

要证的是:对任意只依赖状态的函数 $b(s_t)$(可以是 $\hat V^\pi_\phi$,也可以是任何别的东西,只要不依赖 $a_t$),有

$$ \E_{\tau\sim p_\theta(\tau)}\big[\nabla_\theta\log\pi_\theta(a_t|s_t)\,b(s_t)\big]=0 . $$

第一步:先对 $a_t$ 求条件期望。 用全期望公式,把对整条轨迹的期望拆成「先对 $s_t$,再对 $a_t$,再对其余部分」。由于被求期望的量只依赖 $(s_t,a_t)$,其余部分积掉后剩下

$$ \E_{\tau}\big[g_t\, b(s_t)\big] =\E_{s_t\sim p_\theta(s_t)}\Big[\ \E_{a_t\sim\pi_\theta(a_t|s_t)}\big[\nabla_\theta\log\pi_\theta(a_t|s_t)\big]\cdot b(s_t)\Big]. $$

这里把 $b(s_t)$ 提到内层期望外面是合法的——正因为它不依赖 $a_t$。这一步就是整个证明的全部要害。

第二步:内层期望恒为零。 用 log-derivative 恒等式 $\pi\nabla\log\pi=\nabla\pi$ 反向走一遍:

$$ \E_{a_t\sim\pi_\theta}\big[\nabla_\theta\log\pi_\theta(a_t|s_t)\big] =\int \pi_\theta(a_t|s_t)\,\nabla_\theta\log\pi_\theta(a_t|s_t)\,\mathrm{d}a_t =\int \nabla_\theta \pi_\theta(a_t|s_t)\,\mathrm{d}a_t . $$

交换积分与求导(对神经网络这类光滑参数化是允许的),

$$ =\nabla_\theta\int \pi_\theta(a_t|s_t)\,\mathrm{d}a_t=\nabla_\theta 1 = 0 . $$

第三步:合并。 内层为 $0$,整体就是 $0$。$\square$

值得注意的是,这个证明只用到了「$b$ 与 $a_t$ 无关」。它没有要求 $b$ 是常数、没有要求 $b$ 准确、也没有要求 $b$ 与 $\theta$ 无关(只要 $b$ 是用其他数据估计出来的、与当前这个 $a_t$ 样本独立即可——实践中我们从同一批数据里拟合 $\hat V_\phi$,严格说会带来一点点相关性,但影响极小,大家都这么干)。

反过来也说明了为什么 actor-critic 那一行是有偏的:那里 $\hat V^\pi_\phi(s_{t+1})$ 出现在被减数的位置上,它替换的是「未来的真实回报」,而这一项本来就依赖于 $a_t$($a_t$ 决定了会到哪个 $s_{t+1}$)。$\hat V^\pi_\phi$ 一旦有误差,这个误差就直接进入梯度的期望,无法被抵消。

直觉:为什么好的 baseline 能降方差

考虑一个奖励全为正的环境,某状态下三个动作的 reward-to-go 分别是 $98,100,102$。不减 baseline 时,三个动作的梯度权重都是接近 $100$ 的大正数,梯度向量 $\sum_a g_a\cdot 100$ 主要由「$100$ 这个公共分量乘以随机采到哪个动作」决定,信号($\pm 2$ 的差异)淹没在噪声里。减去 $\hat V\approx 100$ 之后,权重变成 $-2,0,+2$,梯度的量级降了 50 倍,而方向信息完全保留。这就是为什么「baseline 越贴近真实回报,方差越低」——它扣掉的正是那个不携带信息的公共分量。

6. n-step return 与 GAE:把偏差-方差变成一个旋钮

6.1 在哪里「切一刀」

n-step return:在第 n 步处切断,前面用真实奖励后面交给 critic
上面两行是两个极端:critic 版优势 $\hat A^\pi_{\mathrm{C}}=r_t+\gamma\hat V_\phi(s_{t+1})-\hat V_\phi(s_t)$(低方差、有偏,标注「higher bias if value is wrong — it always is」)与蒙特卡洛版 $\hat A^\pi_{\mathrm{MC}}=\sum_{t'\ge t}\gamma^{t'-t}r_{t'}-\hat V_\phi(s_t)$(无偏、高方差)。右侧的轨迹扇形图是理解 n-step 的关键:从绿点出发的轨迹束越往后张得越开,近处「smaller variance」,远处「bigger variance」;红色斜线标出「cut here before variance gets too big!」——在方差还没爆炸之前切断,后面的部分交给 critic。左下角的 $r_t$-$t$ 衰减曲线说明折扣本身也让远处奖励的贡献变小。最下面一行是 n-step 优势的公式,以及结论「choosing $n\gt 1$ often works better!」。

把两个极端写在一起:

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

第一个只信任一步真实奖励,之后全部交给 critic;第二个信任全部真实奖励,critic 只当 baseline。自然的折中是:信任前 $n$ 步真实奖励,第 $n$ 步之后交给 critic。这就是 n-step return 优势:

$$ \hat A^\pi_n(s_t,a_t)=\sum_{t'=t}^{t+n-1}\gamma^{\,t'-t}r(s_{t'},a_{t'})\ +\ \gamma^{\,n}\hat V^\pi_\phi(s_{t+n})\ -\ \hat V^\pi_\phi(s_t). $$

三项的含义依次是:前 $n$ 步的真实折扣奖励、第 $n$ 步之后的估计值(打 $\gamma^n$ 的折扣)、以及作为 baseline 的当前值。令 $n=1$ 退化为 $\hat A^\pi_{\mathrm{C}}$,令 $n\to\infty$ 退化为 $\hat A^\pi_{\mathrm{MC}}$。

直觉:为什么切在中间最好

右图的轨迹束在近处很紧、在远处发散,这是因为随机性是累积的:走一步的不确定性小,走一百步的不确定性大。所以「用真实样本」在近处几乎不花方差代价,在远处代价高昂。反过来,critic 的偏差在近处影响小(只占一步权重),在远处影响大——但远处的一切都被 $\gamma^n$ 压缩了。综合起来,最优的切点在中间某处。经验上 $n$ 取 $5\sim 20$ 对大多数任务都不错。注意 $\gamma^n$ 这个因子同时压制了 critic 偏差和 critic 方差的贡献,所以 $n$ 稍大一点是相当安全的。

6.2 GAE:不用选 n,全都要

Generalized Advantage Estimation 的推导
GAE 的完整推导。左上仍是 n-step 公式;右上给出核心想法:$\hat A^\pi_{\mathrm{GAE}}=\sum_{n=1}^{\infty}w_n\hat A^\pi_n$,即所有 $n$ 的加权组合(「Do we have to choose just one n? Cut everywhere all at once!」)。权重怎么选?因为「mostly prefer cutting earlier (less variance)」,所以取指数衰减 $w_n\propto\lambda^{n-1}$。中间那一长行是把加权和展开后的嵌套形式,可以看到每一层都是「以 $1-\lambda$ 的比例信任 critic、以 $\lambda$ 的比例继续往下展开」。最后化简成惊人简洁的 $\hat A^\pi_{\mathrm{GAE}}(s_t,a_t)=\sum_{t'=t}^{\infty}(\gamma\lambda)^{t'-t}\delta_{t'}$,其中 $\delta_{t'}=r(s_{t'},a_{t'})+\gamma\hat V_\phi(s_{t'+1})-\hat V_\phi(s_{t'})$ 就是单步 TD 误差。箭头标注「similar effect as discount!」——$\lambda$ 起到的作用就像又加了一层折扣。

选 $n$ 是个超参数搜索问题,而且不同状态的最优 $n$ 未必一样。广义优势估计(Generalized Advantage Estimation, GAE)的想法是干脆不选:把所有 $n$ 的 n-step 优势加权平均起来。

$$ \hat A^\pi_{\mathrm{GAE}}(s_t,a_t)=\sum_{n=1}^{\infty}w_n\,\hat A^\pi_n(s_t,a_t),\qquad \sum_n w_n=1 . $$

权重怎么选?既然「早点切」方差更小,那就让权重随 $n$ 指数衰减:

$$ w_n\propto \lambda^{\,n-1},\qquad \lambda\in[0,1] . $$

归一化系数是 $(1-\lambda)$,即 $w_n=(1-\lambda)\lambda^{n-1}$(几何分布)。$\lambda=0$ 时只保留 $n=1$(纯 critic);$\lambda\to 1$ 时权重趋于均匀并把质量推向大 $n$(纯蒙特卡洛)。

6.3 化简:为什么会得到 TD 误差的指数加权和

推导:从 $\sum_n w_n\hat A_n$ 到 $\sum_l(\gamma\lambda)^l\delta_{t+l}$

第一步:把 n-step 优势写成 TD 误差之和。 定义单步 TD 误差(temporal difference error)

$$ \delta_{t}=r(s_{t},a_{t})+\gamma\hat V^\pi_\phi(s_{t+1})-\hat V^\pi_\phi(s_{t}). $$

断言:$\hat A^\pi_n(s_t,a_t)=\sum_{l=0}^{n-1}\gamma^{\,l}\delta_{t+l}$。验证方法是把右边展开,看中间项如何抵消(望远镜求和):

$$ \begin{aligned} \sum_{l=0}^{n-1}\gamma^{l}\delta_{t+l} &=\sum_{l=0}^{n-1}\gamma^{l}\Big(r_{t+l}+\gamma\hat V_\phi(s_{t+l+1})-\hat V_\phi(s_{t+l})\Big)\\ &=\sum_{l=0}^{n-1}\gamma^{l}r_{t+l} +\sum_{l=0}^{n-1}\gamma^{l+1}\hat V_\phi(s_{t+l+1}) -\sum_{l=0}^{n-1}\gamma^{l}\hat V_\phi(s_{t+l}). \end{aligned} $$

后两个和式里,第二个和的第 $l$ 项 $\gamma^{l+1}\hat V_\phi(s_{t+l+1})$ 与第三个和的第 $l+1$ 项 $\gamma^{l+1}\hat V_\phi(s_{t+l+1})$ 完全相同,逐项抵消。只剩下第二个和的最后一项 $\gamma^{n}\hat V_\phi(s_{t+n})$ 和第三个和的第一项 $-\hat V_\phi(s_t)$。于是

$$ \sum_{l=0}^{n-1}\gamma^{l}\delta_{t+l}=\sum_{l=0}^{n-1}\gamma^{l}r_{t+l}+\gamma^{n}\hat V_\phi(s_{t+n})-\hat V_\phi(s_t)=\hat A^\pi_n(s_t,a_t).\ \checkmark $$

第二步:代入加权和并交换求和次序。

$$ \hat A^\pi_{\mathrm{GAE}}=\sum_{n=1}^{\infty}(1-\lambda)\lambda^{\,n-1}\sum_{l=0}^{n-1}\gamma^{l}\delta_{t+l} =(1-\lambda)\sum_{l=0}^{\infty}\gamma^{l}\delta_{t+l}\sum_{n=l+1}^{\infty}\lambda^{\,n-1}. $$

(交换次序时注意约束是 $l\le n-1$,即 $n\ge l+1$。)

第三步:算内层几何级数。

$$ \sum_{n=l+1}^{\infty}\lambda^{\,n-1}=\lambda^{l}+\lambda^{l+1}+\cdots=\frac{\lambda^{l}}{1-\lambda}. $$

第四步:合并。 $(1-\lambda)$ 与 $\frac{1}{1-\lambda}$ 抵消:

$$ \boxed{\ \hat A^\pi_{\mathrm{GAE}}(s_t,a_t)=\sum_{l=0}^{\infty}(\gamma\lambda)^{l}\,\delta_{t+l} =\sum_{t'=t}^{\infty}(\gamma\lambda)^{\,t'-t}\,\delta_{t'}\ } $$

整个「所有 n-step 的指数加权组合」塌缩成了一行:TD 误差按 $\gamma\lambda$ 指数加权求和。$\square$

这个结果有几个值得反复品味的地方:

  • $\lambda$ 和 $\gamma$ 在公式里以乘积 $\gamma\lambda$ 出现,所以幻灯片上那句「similar effect as discount!」是字面意义上的:调小 $\lambda$ 与调小 $\gamma$ 对优势估计的影响形式完全一样,都是缩短有效视界、降低方差、引入偏差。区别在于 $\gamma$ 同时改变了我们优化的目标(它出现在 $\delta$ 的定义里、也定义了 $V^\pi$ 是什么),而 $\lambda$ 只影响估计量,不改变目标。所以 $\lambda$ 是一个更「干净」的方差旋钮:可以放心把 $\gamma$ 设成 $0.99$ 来定义任务,再用 $\lambda=0.95$ 单独控制估计噪声。
  • $\lambda=0$:只剩 $l=0$ 项,$\hat A=\delta_t$,就是最基本的 actor-critic(最低方差、最大偏差)。$\lambda=1$:$\hat A=\sum_l\gamma^l\delta_{t+l}$,望远镜全部抵消后正好是 $\sum_{t'\ge t}\gamma^{t'-t}r_{t'}-\hat V_\phi(s_t)$,即蒙特卡洛优势(无偏、最大方差)。
  • 常用取值是 $\lambda\in[0.9,0.99]$,其中 $0.95$ 是最常见的默认值(PPO 原论文用的就是 $\gamma=0.99,\lambda=0.95$)。以 $\gamma\lambda=0.99\times0.95\approx0.94$ 计,有效视界约 $1/(1-0.94)\approx 17$ 步——和 §6.1 里 n-step 的经验值 $5\sim 20$ 完全吻合。
  • 计算极其便宜:从轨迹末尾往前做一次线性扫描即可,$\hat A_t = \delta_t+\gamma\lambda\hat A_{t+1}$。这个递推式直接由上面的求和形式得到。

6.4 用 GAE 的策略梯度算法与优势标准化

带 GAE 的策略梯度算法与优势标准化
完整算法:采样轨迹 → 算自举目标 → 拟合 $\hat V^\pi_\phi$ → 用 GAE 算优势 → 策略梯度 → 更新。左侧箭头指出第 2 步的目标其实也可以用 GAE 估计量来构造。右侧公式是「优势标准化」:先算全批数据的均值 $\mu$ 和标准差 $\sigma$,再令 $\bar A=(\hat A-\mu)/\sigma$。下方两条黄色标注:这已经是实现 PPO 的第一部分;PPO 的另一半是一个略有不同的梯度估计量(重要性采样比 + 裁剪)。左下角还列了两个同样好用的选项:n-step 估计量、以及带 $\hat V^\pi$ baseline 的纯蒙特卡洛估计量。

完整算法:

  1. 用 $\pi_\theta$ 采样一批轨迹 $\{\tau^{(i)}\}$;
  2. 算值函数回归目标 $y_t^{(i)}=r(s_t^{(i)},a_t^{(i)})+\gamma\hat V^\pi_\phi(s_{t+1}^{(i)})$(也可以用 $\lambda$-return 作为目标,即 $\hat V_\phi(s_t)+\hat A^{\mathrm{GAE}}_t$);
  3. 把 $\hat V^\pi_\phi$ 拟合到 $\{y_t^{(i)}\}$;
  4. 算 $\hat A^\pi_{\mathrm{GAE}}(s_t^{(i)},a_t^{(i)})=\sum_{t'\ge t}(\gamma\lambda)^{t'-t}\delta_{t'}^{(i)}$;
  5. $\nabla_\theta J(\theta)\approx\sum_i\sum_t\nabla_\theta\log\pi_\theta(a_t^{(i)}|s_t^{(i)})\,\bar A(s_t^{(i)},a_t^{(i)})$;
  6. $\theta\leftarrow\theta+\alpha\nabla_\theta J(\theta)$。

第 5 步用的是标准化后的优势:

$$ \mu=\frac{1}{HN}\sum_{i,t}\hat A^\pi(s_t^{(i)},a_t^{(i)}),\qquad \sigma=\sqrt{\frac{1}{HN}\sum_{i,t}\big(\hat A^\pi(s_t^{(i)},a_t^{(i)})-\mu\big)^2}, $$ $$ \bar A^\pi(s_t^{(i)},a_t^{(i)})=\frac{\hat A^\pi(s_t^{(i)},a_t^{(i)})-\mu}{\sigma}. $$

减去 $\mu$ 是一个(近似的)常数 baseline,理论上无偏;除以 $\sigma$ 严格来说改变了梯度的大小(相当于自适应地缩放学习率),但不改变方向。这在实践中是必备操作:它让同一套超参数能跨越奖励量级差好几个数量级的任务,也让训练早期(优势尺度大)和后期(优势尺度小)的有效步长保持一致。

核心结论

做到这一步,你已经写出了 PPO 的一半。PPO = 「GAE 优势估计 + 优势标准化」 + 「重要性采样比与裁剪目标,允许在同一批数据上做多轮更新」。前一半是本讲内容,后一半留给后面的讲次。这也说明为什么本讲的标题里 Levine 写着「policy gradient that actually works」。

7. Off-policy actor-critic:给它接上 replay buffer

7.1 天真的做法为什么是错的

给在线 actor-critic 直接加 replay buffer 的两个错误
把每步产生的转移 $(s_i,a_i,s_i')$ 存进 replay buffer,再从中随机抽 batch 来更新——听起来天经地义,但两个红圈标出了两处致命错误。上面的红圈圈住第 2 步的目标 $y_i=r(s_i,a_i)+\gamma\hat V^\pi_\phi(s_i')$,标注「not the right target value」;下面的红圈圈住第 5 步的 $\nabla_\theta\log\pi_\theta(a_i|s_i)$,标注「not the action $\pi_\theta$ would have taken!」。右下的提示点破根源:$(s_i,a_i)$ 已经不再来自最新的 $\pi_\theta(a_i|s_i)$。黄色标注直言「This algorithm is broken!」。

在线 actor-critic 的样本效率极低:每条转移只用一次就扔掉。深度学习的本能反应是加一个 replay buffer(经验回放池):把走过的转移都存起来,每次训练从池子里随机抽一个 batch。这既解决了「batch size 只有 1」的问题,也解决了「连续样本高度相关」的问题,还能把每条数据反复利用。

但直接这么做是错的,而且错在两个不同的地方。设 buffer 里的数据来自某个较旧的策略 $\bar\pi$:

  1. critic 的目标错了。 我们想学的是 $V^\pi$,即当前策略的值函数,它满足 $V^\pi(s)=\E_{a\sim\pi_\theta(a|s)}\big[r(s,a)+\gamma V^\pi(s')\big]$。但 buffer 里的 $a_i$ 是 $\bar\pi$ 采的,用它算出来的目标对应的是 $\E_{a\sim\bar\pi(a|s)}[r(s,a)+\gamma V^\pi(s')]$——这是旧策略的值函数,不是我们要的。
  2. actor 的梯度错了。 策略梯度公式 $\E_{a\sim\pi_\theta}[\nabla_\theta\log\pi_\theta(a|s)\hat A(s,a)]$ 要求动作从当前策略采样。buffer 里的 $a_i$ 不是当前策略会选的动作,这个期望的采样分布不对。
关键想法:改学 Q 函数
上半部分诊断第一个错误:「want」的是 $V^\pi(s_i)=\E_{a\sim\pi_\theta}[r+\gamma V^\pi(s')]$,「got」的却是 $\E_{a\sim\bar\pi}[\cdot]$,因为 $a_i\sim\bar\pi(a|s_i)$。下半部分给出修法:改学 $Q$ 函数。$Q^\pi(s,a)$ 的定义里 $a$ 是给定的(不是从策略采的),所以第一步动作来自哪个策略无关紧要;只有下一步的动作需要来自当前策略——而那个我们可以自己重新采:$a_i'\sim\pi_\theta(a'|s_i')$,然后 $y_i=r(s_i,a_i)+\gamma\hat Q^\pi_\phi(s_i',a_i')$。黄色标注强调「Note that we use the latest policy!」。

7.2 第一处修补:把 critic 从 V 换成 Q

第一个错误的根源是:$V^\pi(s)$ 的定义里,「第一步的动作」是由策略决定的,所以数据里的动作必须来自当前策略。而 $Q^\pi(s,a)$ 的定义里,第一步的动作是作为输入给定的:

$$ Q^\pi(s,a)=r(s,a)+\gamma\,\E_{s'\sim p(s'|s,a)}\big[V^\pi(s')\big] = r(s,a)+\gamma\,\E_{s'}\Big[\E_{a'\sim\pi_\theta(a'|s')}\big[Q^\pi(s',a')\big]\Big]. $$

这个式子里对 $(s,a)$ 没有任何分布假设——它对任意 $(s,a)$ 都成立。所以 buffer 里的 $(s_i,a_i,s_i')$ 无论来自哪个策略都能用:$s_i'$ 是环境按 $p(s'|s_i,a_i)$ 给的,这与策略无关。唯一需要来自当前策略的是 $a'$——而它我们可以自己现场采,因为采样动作不需要与环境交互,只要跑一次策略网络的前向:

$$ a_i'\sim\pi_\theta(a'|s_i'),\qquad y_i=r(s_i,a_i)+\gamma\,\hat Q^\pi_\phi(s_i',a_i'),\qquad \mathcal{L}(\phi)=\frac{1}{2}\sum_i\big\|\hat Q^\pi_\phi(s_i,a_i)-y_i\big\|^2 . $$

这就是「用最新策略」的含义:$a_i'$ 永远由当前的 $\pi_\theta$ 产生,所以学出来的是当前策略的 $Q^\pi$,而不是旧策略的。代价是 critic 的输入从 $s$ 变成了 $(s,a)$,学起来更难一些——这是为拿到 off-policy 能力付的学费。

7.3 第二处修补:actor 用的动作要重新采

actor 侧的修补:重新从当前策略采样动作
算法里被红线划掉的是原来第 4、5 步(用 buffer 里的 $a_i$ 算优势和梯度)。取而代之的是下面手写的方案:「use the same trick, but this time for $a_i$ rather than $a_i'$」——从当前策略重新采一个动作 $a_i^\pi\sim\pi_\theta(a|s_i)$,用它来算梯度 $\nabla_\theta J\approx\frac1N\sum_i\nabla_\theta\log\pi_\theta(a_i^\pi|s_i)\hat A^\pi(s_i,a_i^\pi)$。注意标注「not from replay buffer」。最后一行说明实践中直接用 $\hat Q^\pi_\phi(s_i,a_i^\pi)$ 而不是优势,并问「higher variance, but convenient — why is higher variance OK here?」

同样的技巧用在 actor 侧:既然采样动作不需要与环境交互,那就对 buffer 里的每个状态 $s_i$,重新从当前策略采一个动作 $a_i^\pi\sim\pi_\theta(a|s_i)$,然后

$$ \nabla_\theta J(\theta)\approx\frac{1}{N}\sum_i\nabla_\theta\log\pi_\theta(a_i^\pi|s_i)\,\hat Q^\pi_\phi(s_i,a_i^\pi). $$

注意这里直接用了 $\hat Q^\pi_\phi$ 而不是优势 $\hat Q-\hat V$。这确实方差更高(没有减 baseline),但方便——我们只有 $Q$ 网络,没有 $V$ 网络。幻灯片留下的问题「为什么这里高方差是可以接受的?」答案是:因为我们可以对同一个 $s_i$ 采任意多个动作而不花任何环境交互成本。想降方差就多采几个 $a^\pi$ 求平均,或者用 $\hat V_\phi(s_i)\approx\frac{1}{M}\sum_{m}\hat Q_\phi(s_i,a^{\pi,m}_i)$ 现算一个 baseline。在 on-policy 情形下你做不到这一点(每个动作都必须真的执行),但 off-policy + Q 函数让「重复采样动作」变成了免费操作。

完整的 off-policy actor-critic 算法与剩余问题
修补后的完整算法:走一步存进 buffer → 抽 batch → 用当前策略采 $a'$ 算目标 → 更新 $Q_\phi$ → 用当前策略采 $a$ 算策略梯度 → 更新 $\theta$。黄色标注问「Is there any remaining problem?」,答案写在下面:$s_i$ 并不来自 $p_\theta(s)$,即状态分布仍然是旧的、更宽的。而且「nothing we can do here, just accept it」。直觉解释:我们想要的是在 $p_\theta(s)$ 上最优的策略,实际得到的是在一个更宽的状态分布上最优的策略。

7.4 剩下那个改不掉的问题:状态分布

修完两处之后还有第三个偏差:$s_i$ 不来自当前策略的状态分布 $p_\theta(s)$。我们能重新采动作,却不能重新采状态——状态是环境交互的产物,要想按 $p_\theta(s)$ 采状态就必须用当前策略真的跑一遍,那 replay buffer 就白搭了。

Levine 对此的态度很干脆:「nothing we can do here, just accept it」。并给出一个安慰性的直觉:我们本来想要「在 $p_\theta(s)$ 上表现最优的策略」,现在得到的是「在一个更宽的状态分布(历史上所有策略访问过的状态的混合)上表现最优的策略」。这个策略在我们真正关心的那部分状态上通常也不差——只是我们浪费了一些容量去优化那些当前策略根本不会去的地方。在后面的离线 RL 讲次里会看到,当这个分布偏移变得极端时(数据全部来自完全不同的策略),它会从「可以接受」变成「致命」。

7.5 重参数化技巧:绕开对数概率

实现技巧:这个梯度到底在估计什么
红框圈出策略梯度那一步,然后问「which gradient is this estimating?」。答案写在下面:它估计的是 $\nabla_\theta\E_{a\sim\pi_\theta(a|s_i)}[\hat Q^\pi_\phi(s_i,a)]$,也就是「在状态 $s_i$ 下,让当前策略的期望 Q 值最大」。关键提示:$\hat Q^\pi_\phi$ 是一个神经网络,我们可以直接算 $\frac{\mathrm{d}}{\mathrm{d}a}\hat Q^\pi_\phi(s_i,a)$——对动作求导!那能不能利用这个导数?

先看清楚我们在优化什么。第 4 步的梯度

$$ \sum_i\E_{a\sim\pi_\theta(a|s_i)}\big[\nabla_\theta\log\pi_\theta(a|s_i)\,\hat Q^\pi_\phi(s_i,a)\big] =\sum_i\nabla_\theta\,\E_{a\sim\pi_\theta(a|s_i)}\big[\hat Q^\pi_\phi(s_i,a)\big], $$

目标就是「让策略在每个状态下选出 Q 值高的动作」。这是一个很朴素的目标。而 log-derivative(也叫 REINFORCE / score function)估计量只是计算这个梯度的一种方法——它把 $\hat Q_\phi$ 当成一个黑箱,只用它的取值。可是 $\hat Q_\phi$ 明明是一个可微的神经网络,我们完全可以对它的输入 $a$ 求导!

重参数化技巧
重参数化技巧的完整推导。设 $\pi_\theta(a|s)=\mathcal{N}(\mu_\theta(s),\sigma_\theta(s))$,采样可以写成两步:先抽标准正态噪声 $\epsilon\sim\mathcal{N}(0,1)$,再令 $a=\mu_\theta(s)+\sigma_\theta(s)\epsilon$。于是 $\E_{a\sim\pi_\theta}[Q(s,a)]=\E_{\epsilon\sim\mathcal{N}(0,1)}[Q(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon)]$——随机性被挪到了与 $\theta$ 无关的 $\epsilon$ 上。单样本近似后得到的表达式对 $\theta$ 是直接可微的(标注:easy to differentiate with any autograd library),梯度 $\nabla_\theta Q(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon)$ 称为「reparameterized gradient」。

重参数化技巧(reparameterization trick)的关键观察:高斯采样可以拆成「确定性变换 + 与参数无关的噪声」。设 $\pi_\theta(a|s)=\mathcal{N}(\mu_\theta(s),\sigma_\theta(s))$,则

$$ \epsilon\sim\mathcal{N}(0,1),\qquad a=\mu_\theta(s)+\sigma_\theta(s)\,\epsilon $$

与直接从 $\pi_\theta$ 采样等价。因此

$$ \E_{a\sim\pi_\theta(a|s)}\big[Q(s,a)\big] =\E_{\epsilon\sim\mathcal{N}(0,1)}\big[Q\big(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon\big)\big] \approx Q\big(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon\big), $$

而右边这个式子里 $\theta$ 出现在可微函数的内部,autograd 直接就能求导:

$$ \nabla_\theta\,\E_{a\sim\pi_\theta(a|s)}\big[Q(s,a)\big]\approx\nabla_\theta\,Q\big(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon\big) =\frac{\partial Q}{\partial a}\Big(\frac{\partial\mu_\theta}{\partial\theta}+\epsilon\frac{\partial\sigma_\theta}{\partial\theta}\Big). $$
直觉:为什么重参数化梯度方差低得多

score function 估计量只知道「这个动作得了 8 分」,它靠比较不同随机动作的分数来推断该往哪走——本质上是零阶(数值差分式)的搜索。重参数化梯度知道「往这个方向挪动动作,分数会上升」,它直接读取了 $Q$ 网络关于动作的一阶导数信息。低维连续动作空间下方差可以低一两个数量级。代价是:它要求动作空间连续且策略可重参数化(高斯可以,离散分类分布不行),并且需要 $Q$ 对 $a$ 可导——所以离散动作的 RL 用不了它。

带重参数化的 off-policy actor-critic,通向 SAC
最终形态:第 4 步从 $\sum_i\nabla_\theta\log\pi_\theta\hat Q$ 换成了 $\sum_i\nabla_\theta\hat Q^\pi_\phi(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon_i)$。底部指出这套算法的实用版本就是 Soft Actor-Critic (SAC),它额外用了两个东西:Q 函数估计的若干技巧(双 Q 网络、目标网络,Q-learning 讲次会讲)和熵正则化(控制即推断的讲次会讲)。最后一行预告:以后还会学到用确定性策略做同样事情的算法(DDPG / TD3)。

把所有修补装配起来,得到带重参数化的 off-policy actor-critic:

  1. 用 $a\sim\pi_\theta(a|s)$ 走一步,把 $(s_i,a_i,r_i,s_i')$ 存入 replay buffer $\mathcal{R}$;
  2. 从 $\mathcal{R}$ 抽一个 batch,采 $a_i'\sim\pi_\theta(a'|s_i')$,算目标 $y_i=r(s_i,a_i)+\gamma\hat Q^\pi_\phi(s_i',a_i')$;
  3. 用 $\nabla_\phi\sum_i\|\hat Q^\pi_\phi(s_i,a_i)-y_i\|^2$ 更新 $\phi$;
  4. 采 $\epsilon_i\sim\mathcal{N}(0,1)$,用 $\nabla_\theta\sum_i \hat Q^\pi_\phi\big(s_i,\mu_\theta(s_i)+\sigma_\theta(s_i)\epsilon_i\big)$ 更新 $\theta$;
  5. 回到第 1 步。
On-policy AC(§4–6)Off-policy AC(§7)
critic 学什么$V^\pi(s)$$Q^\pi(s,a)$
数据来源当前策略刚采的一批replay buffer(历史所有策略)
优势估计可用 n-step / GAE只能用 1 步自举(多步会引入 off-policy 偏差)
actor 梯度$\nabla\log\pi\cdot\hat A$$\nabla\log\pi\cdot\hat Q$ 或重参数化梯度
样本效率低(每条数据用一次)高(反复利用)
稳定性较好较差(自举 + off-policy + 函数逼近的「致命三角」)
代表算法A2C / A3C / PPOSAC / DDPG / TD3

8. 实现细节与最小可跑代码

8.1 一份 on-policy actor-critic + GAE 的最小实现

下面这段代码把 §4 的算法框、§6 的 GAE 递推和 §6.4 的优势标准化全部串起来。它是可以真正跑通的逻辑(配一个 Gym 连续控制环境即可),只是省略了日志、评估、随机种子等工程外壳。

import torch
import torch.nn as nn


class Actor(nn.Module):
    """高斯策略 pi(a|s) = N(mu_theta(s), diag(exp(log_std)))。
    log_std 做成与状态无关的全局参数,这是连续控制里最常用的做法。"""

    def __init__(self, obs_dim, act_dim, hidden=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(obs_dim, hidden), nn.Tanh(),
            nn.Linear(hidden, hidden), nn.Tanh(),
            nn.Linear(hidden, act_dim),
        )
        self.log_std = nn.Parameter(torch.zeros(act_dim))

    def dist(self, obs):
        mu = self.net(obs)
        return torch.distributions.Normal(mu, self.log_std.exp())


class Critic(nn.Module):
    """V_phi(s):输入状态,输出一个标量。"""

    def __init__(self, obs_dim, hidden=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(obs_dim, hidden), nn.Tanh(),
            nn.Linear(hidden, hidden), nn.Tanh(),
            nn.Linear(hidden, 1),
        )

    def forward(self, obs):
        return self.net(obs).squeeze(-1)


def compute_gae(rewards, values, dones, last_value, gamma=0.99, lam=0.95):
    """GAE 的反向递推:A_t = delta_t + gamma * lam * A_{t+1}。

    rewards, values, dones : 形状 (T,) 的一维张量
    last_value             : V_phi(s_T),即这一段 rollout 之后的自举值
    返回 (advantages, returns),returns 用作 critic 的回归目标。
    """
    T = rewards.shape[0]
    adv = torch.zeros(T)
    running = torch.zeros(())
    for t in reversed(range(T)):
        next_value = last_value if t == T - 1 else values[t + 1]
        nonterminal = 1.0 - dones[t]                      # 终止状态不自举
        delta = rewards[t] + gamma * next_value * nonterminal - values[t]
        running = delta + gamma * lam * nonterminal * running
        adv[t] = running
    returns = adv + values          # lambda-return = A^GAE + V(s)
    return adv, returns


def update(actor, critic, opt_a, opt_c, obs, act, adv, ret, n_critic_steps=8):
    # --- actor:伪损失,只有梯度有意义,数值本身没有意义 ---
    adv = (adv - adv.mean()) / (adv.std() + 1e-8)         # 优势标准化
    logp = actor.dist(obs).log_prob(act).sum(dim=-1)      # 各维独立,log 概率相加
    actor_loss = -(logp * adv).mean()                    # 取负号是因为要做梯度上升
    opt_a.zero_grad()
    actor_loss.backward()
    nn.utils.clip_grad_norm_(actor.parameters(), 0.5)    # 梯度裁剪,防单批数据把策略打飞
    opt_a.step()

    # --- critic:普通的平方误差回归,目标已 detach ---
    for _ in range(n_critic_steps):
        critic_loss = ((critic(obs) - ret) ** 2).mean()
        opt_c.zero_grad()
        critic_loss.backward()
        opt_c.step()

配套的采样与主循环(以 Gym 接口为例):

def collect_rollout(env, actor, critic, n_steps=2048):
    obs_buf, act_buf, rew_buf, done_buf, val_buf = [], [], [], [], []
    obs, _ = env.reset()
    for _ in range(n_steps):
        obs_t = torch.as_tensor(obs, dtype=torch.float32)
        with torch.no_grad():
            dist = actor.dist(obs_t)
            action = dist.sample()
            value = critic(obs_t)
        nxt, rew, terminated, truncated, _ = env.step(action.numpy())

        obs_buf.append(obs_t)
        act_buf.append(action)
        rew_buf.append(float(rew))
        val_buf.append(value)
        # 注意:只有"真正终止"才算 done;因超时被截断时仍应自举
        done_buf.append(float(terminated))

        obs = nxt
        if terminated or truncated:
            obs, _ = env.reset()

    with torch.no_grad():
        last_value = critic(torch.as_tensor(obs, dtype=torch.float32))
    return (torch.stack(obs_buf), torch.stack(act_buf),
            torch.tensor(rew_buf), torch.tensor(done_buf),
            torch.stack(val_buf), last_value)


env = ...                                    # 任一连续动作环境
actor = Actor(obs_dim, act_dim)
critic = Critic(obs_dim)
opt_a = torch.optim.Adam(actor.parameters(), lr=3e-4)
opt_c = torch.optim.Adam(critic.parameters(), lr=1e-3)

for itr in range(1000):
    obs, act, rew, done, val, last_v = collect_rollout(env, actor, critic)
    adv, ret = compute_gae(rew, val, done, last_v, gamma=0.99, lam=0.95)
    update(actor, critic, opt_a, opt_c, obs, act, adv.detach(), ret.detach())

8.2 off-policy 版本的 actor 更新(重参数化)

§7.5 的重参数化梯度写成代码只有几行,关键是不要 detach 动作——梯度必须能从 $Q$ 网络穿过动作回到策略参数:

def reparam_actor_loss(actor, q_net, obs):
    """off-policy actor 更新:直接最大化 Q(s, a(theta))。"""
    dist = actor.dist(obs)
    action = dist.rsample()        # rsample 而不是 sample:保留计算图(重参数化)
    q_value = q_net(obs, action)   # 梯度经由 action 传回 mu_theta / sigma_theta
    return -q_value.mean()


def q_target(q_net, actor, rew, next_obs, done, gamma=0.99):
    """critic 目标:动作 a' 一定要用最新策略现采,而不是 buffer 里的动作。"""
    with torch.no_grad():
        next_action = actor.dist(next_obs).sample()
        next_q = q_net(next_obs, next_action)
        return rew + gamma * (1.0 - done) * next_q

把 sample() 写成 rsample() 是这段代码里唯一但也是最关键的区别:前者切断梯度(只能配 score function 估计量用),后者保留 $a=\mu_\theta+\sigma_\theta\epsilon$ 这条计算路径,让 autograd 自动算出重参数化梯度。

8.3 实现清单:最容易踩的坑

坑症状正确做法
忘记 detach 值函数目标critic loss 迅速降到 0,但策略毫无长进目标 $y$ 一律 .detach() 或在 torch.no_grad() 下计算
终止状态仍然自举值函数在回合末尾发散或塌陷;策略学会「自杀」或「拖时间」$y_i=r_i+\gamma(1-\text{done}_i)\hat V_\phi(s_i')$;区分「真终止」与「超时截断」
把优势也算进反传图梯度含有 $\nabla_\theta \hat A$ 项,公式变味on-policy 版本里 advantage 必须 detach
没做优势标准化换个奖励量级就要重调学习率每批数据减均值除标准差(分母加 $10^{-8}$)
共享主干但不加权 losscritic 收敛、actor 不动$\mathcal{L}=\mathcal{L}_a+c_v\mathcal{L}_c$,$c_v\approx0.5$;或改用双网络
critic 学习率与 actor 相同critic 跟不上,优势估计常年不准critic 学习率通常取 actor 的 $2\sim 10$ 倍,或每轮多做几次 critic 更新
on-policy 数据复用多轮训练初期上升然后突然崩掉朴素策略梯度只能用一次;要复用请上 PPO 的裁剪目标
batch size 太小曲线剧烈震荡、不可复现并行环境凑大 batch,$2048\sim 16384$ 步是常见量级
观测未标准化网络输入尺度差几个数量级,训练极慢用运行时均值/方差做 observation normalization
注意:调参的先后顺序

actor-critic 出问题时,先单独验证 critic:固定策略不更新,只训 critic,看它能否把 $\hat V_\phi(s)$ 拟合到与蒙特卡洛回报接近(可以画 $\hat V_\phi(s_t)$ 与实际 reward-to-go 的散点图,理想情况沿对角线分布)。critic 都学不好的话,调 actor 是没有意义的——因为你的优势估计全是噪声,actor 只是在随机游走。这个「先分模块验证」的习惯在整个深度 RL 里都非常值钱。

本讲小结

一句话:把策略梯度里那个单样本的 reward-to-go 换成学出来的值函数,用可控的偏差换取巨大的方差削减,这就是 actor-critic。

概念公式一句话
Q 函数$Q^\pi(s_t,a_t)=\sum_{t'\ge t}\E_\pi[r_{t'}|s_t,a_t]$期望 reward-to-go
V 函数$V^\pi(s_t)=\E_{a\sim\pi}[Q^\pi(s_t,a)]$状态的平均水平
优势$A^\pi=Q^\pi-V^\pi$这个动作比平均好多少
Q 由 V 拼出$Q^\pi(s,a)=r(s,a)+\gamma\E_{s'}[V^\pi(s')]$所以只学 $V^\pi$ 就够
蒙特卡洛目标$y_t=\sum_{t'\ge t}\gamma^{t'-t}r_{t'}$无偏、方差随视界线性增长
自举目标$y_t=r_t+\gamma\hat V_\phi(s_{t+1})$有偏、方差与视界无关
TD 误差$\delta_t=r_t+\gamma\hat V_\phi(s_{t+1})-\hat V_\phi(s_t)$1-step 优势估计
n-step 优势$\hat A_n=\sum_{l=0}^{n-1}\gamma^l\delta_{t+l}$在方差爆炸前切一刀
GAE$\hat A^{\mathrm{GAE}}_t=\sum_{l\ge0}(\gamma\lambda)^l\delta_{t+l}$所有 n 的指数加权组合,$\lambda$ 是偏差-方差旋钮
baseline 无偏性$\E_{a\sim\pi}[\nabla_\theta\log\pi_\theta(a|s)b(s)]=0$只要 $b$ 不依赖 $a$,随便怎么烂都不引偏差
折扣的 MDP 解释$\tilde p(s_\dagger|s,a)=1-\gamma$每步 $1-\gamma$ 概率世界末日,有效视界 $\frac{1}{1-\gamma}$
off-policy 修补$y_i=r_i+\gamma\hat Q_\phi(s_i',a_i')$,$a_i'\sim\pi_\theta$学 $Q$ 而非 $V$,动作现采
重参数化梯度$\nabla_\theta Q(s,\mu_\theta(s)+\sigma_\theta(s)\epsilon)$利用 $Q$ 对动作的导数,方差远低于 score function

要点清单:

  • 方差的根源是「用一条轨迹估计一个期望」。critic 的全部价值在于把这个期望估出来。
  • 只学 $V^\pi$(输入维度最低),$Q^\pi$ 和 $A^\pi$ 都能由它和一步转移拼出来。
  • 自举 vs 蒙特卡洛 = 偏差 vs 方差。深度 RL 里视界长,自举几乎总是赢。
  • $\gamma$ 既是数学上的死亡概率,也是实践中的方差旋钮;$\gamma$ 放在 $\log\pi$ 前面(option 2)才是折扣目标的真梯度,但大家都用 option 1,因为我们其实想要的是无折扣/平均奖励目标。
  • 在线 actor-critic 必须并行化才能跑;同步(A2C)比异步(A3C)更简单也通常更好。
  • critic 放在 baseline 位置 → 完全无偏;放在「替换未来回报」的位置 → 有偏但低方差。GAE 用 $\lambda$ 在两者之间连续插值。
  • GAE 的推导核心是「n-step 优势 = 前 n 个 TD 误差的折扣和」这个望远镜恒等式。
  • 加 replay buffer 需要三处审视:critic 目标(换 $Q$ + 现采 $a'$)、actor 动作(现采 $a^\pi$)、状态分布(无解,接受它)。
  • 写完 §6 的算法你就有了 PPO 的一半;写完 §7 你就到了 SAC 的门口。

延伸阅读

核心论文

偏差、方差与折扣的理论

  • Bias in Natural Actor-Critic Algorithms (Philip Thomas, 2014) — 严格分析了「option 1 少了 $\gamma^{t-1}$」造成的偏差,是理解 §3.3 的必读文献。
  • Learning to Predict by the Methods of Temporal Differences (Sutton, 1988) — TD($\lambda$) 与资格迹(eligibility traces)的源头,GAE 本质上是它在优势估计上的对应物。
  • Q-Prop (Gu et al., 2016) — 把 on-policy 无偏梯度与 off-policy critic 结合,用 critic 的一阶泰勒展开做控制变量(control variate),是「既要无偏又要低方差」这条思路的代表。
  • Sample Efficient Actor-Critic with Experience Replay / ACER (Wang et al., 2016) — 用截断重要性采样 + Retrace 修正让多步回报也能 off-policy 使用,正好补上 §7 表格里「off-policy 只能 1 步自举」那一格。

历史与实践

  • Temporal Difference Learning and TD-Gammon (Tesauro, 1992/1995) — 第 2.5 节那个例子的原文,第一个证明神经网络 + 自举可以在真实任务上达到人类水平的系统。
  • Mastering the Game of Go with Deep Neural Networks and Tree Search (Silver et al., 2016) — AlphaGo,值函数网络在稀疏终局奖励下的大规模应用。
  • What Matters In On-Policy Reinforcement Learning? (Andrychowicz et al., 2020) — 对 on-policy actor-critic 的几十个实现细节做了大规模消融实验,§8.3 那张坑表里的很多结论都能在这里找到定量证据。