上一课我们用价值函数与贝尔曼方程刻画了"一个状态/动作有多好",并通过"估价值 → 贪心选动作"间接得到策略。这一课换一条主线:不绕道价值,直接把策略本身参数化、对参数求梯度往上爬。这条路通向今天大模型对齐的核心——RLHF 里的 PPO。
读完这一课,你将能够
- 用一句话说清策略梯度定理的直觉,并写出 \(\nabla_\theta J=\mathbb{E}[\nabla_\theta\log\pi_\theta(a\mid s)\,G]\);
- 证明加基线 \(b(s)\) 不改变梯度期望,并说清承重的是哪两步、它为何能降方差;
- 区分优势函数 \(A=Q-V\)、actor–critic 与 GAE,说清各自解决什么问题;
- 解释 PPO 裁剪代理目标为什么要把重要性比 \(r\) 限制在 \([1-\epsilon,1+\epsilon]\),逐字说清 \(A>0\) 与 \(A<0\) 两种情形各裁掉了什么,以及它与 KL 惩罚的等价直觉;
- 画出 RLHF 的优化目标 \(\text{reward}-\beta\,D_{\mathrm{KL}}(\pi_\theta\|\pi_{\text{ref}})\),并说清 DPO / GRPO 各省掉了哪一步。
1. 为什么要"直接优化策略"?
价值方法(上一课的 Q-learning 思路)有个隐含假设:先精确估出每个动作的价值,再取 \(\arg\max\)。可一旦动作是连续的(机械臂的力矩)或巨大离散(语言模型下一个 token 有几万种选择),"对所有动作取 max"就变得不现实。
换个思路:把策略写成一个带参数的概率分布 \(\pi_\theta(a\mid s)\)(比如 softmax 或高斯),定义目标为期望回报 \[ J(\theta)=\mathbb{E}_{\tau\sim\pi_\theta}\!\left[\,G(\tau)\,\right],\qquad G(\tau)=\sum_{t=0}^{T}\gamma^{t} r_t, \] 其中 \(\tau=(s_0,a_0,r_0,s_1,\dots)\) 是一条采样轨迹。我们想做的事极其朴素:对 \(\theta\) 做梯度上升 \(\theta\leftarrow\theta+\eta\,\nabla_\theta J\)。问题只剩一个:\(J\) 是对"按 \(\pi_\theta\) 采样的轨迹"求期望,而采样分布本身依赖 \(\theta\),这个梯度怎么算?
2. 策略梯度定理:好结果就抬高导致它的动作的对数概率
核心是一个叫 log-derivative trick(对数求导技巧)的小恒等式。对任意依赖 \(\theta\) 的概率 \(p_\theta\): \[ \nabla_\theta p_\theta = p_\theta\,\frac{\nabla_\theta p_\theta}{p_\theta}=p_\theta\,\nabla_\theta\log p_\theta . \] 这一步把"对概率求梯度"换成了"概率 × 对数概率的梯度",而后者正好能写回期望里。把它用在整条轨迹上:一条轨迹的概率是初始分布乘上每一步"策略 × 转移"的连乘,取 \(\log\) 把连乘变连加, \[ \log p_\theta(\tau)=\log\rho(s_0)+\sum_t\Big[\log\pi_\theta(a_t\mid s_t)+\log P(s_{t+1}\mid s_t,a_t)\Big]. \] 对 \(\theta\) 求梯度时,初始分布 \(\rho(s_0)\) 与环境转移 \(P(s_{t+1}\mid s_t,a_t)\) 都不含 \(\theta\),梯度为 0,只剩下 \(\sum_t\nabla_\theta\log\pi_\theta(a_t\mid s_t)\)。这就是"为何转移会消失"的完整机制。代回去就得到策略梯度定理:
\[ \boxed{\;\nabla_\theta J(\theta)=\mathbb{E}_{\tau\sim\pi_\theta}\!\left[\Big(\sum_{t}\nabla_\theta\log\pi_\theta(a_t\mid s_t)\Big)\;G(\tau)\right]\;} \]读这条式子:\(\nabla_\theta\log\pi_\theta(a_t\mid s_t)\) 是"朝着让动作 \(a_t\) 更可能的方向",整条轨迹的回报 \(G(\tau)\) 当作这一批动作共同的权重。\(G\) 大且为正,就大步抬高这些动作的概率;\(G\) 为负,就压低。这就是第 1 节那句"赢了全调高"的严格版。下一节的 pitfall 会把这个"整条回报"收紧成更省方差的形式。
ML 和 ML 的联系
这个形式你其实见过。监督学习里最小化交叉熵,梯度是 \(\nabla_\theta\big(-\log\pi_\theta(y\mid x)\big)\)——"抬高正确标签的对数概率"。策略梯度几乎一模一样,只是把"正确标签"换成"实际采到的动作",并用回报当作这条样本的权重(软标签)。所以可以把 REINFORCE 理解成"加权的最大似然":表现好的轨迹权重大,被更用力地模仿。
REINFORCE 算法(最朴素的策略梯度)
- 用当前 \(\pi_\theta\) 跑一整条(或一批)轨迹,记录 \((s_t,a_t,r_t)\);
- 算每步的未来回报(reward-to-go)\(G_t=\sum_{k\ge t}\gamma^{k-t}r_k\);
- 用 \(\hat g=\frac1N\sum_t \nabla_\theta\log\pi_\theta(a_t\mid s_t)\,G_t\) 估计梯度;
- 更新 \(\theta\leftarrow\theta+\eta\,\hat g\),回到第 1 步。
3. 高方差是头号敌人,基线是第一剂解药
REINFORCE 能用,但方差极大:回报 \(G_t\) 受整条轨迹的随机性影响,同一个策略两次采样可能差很远,梯度估计像在噪声里找方向。更糟的是一个"尺度问题":如果所有回报都是正的(比如奖励恒非负),那么每个动作的概率都被往上抬,只是被抬的多少不同——这显然浪费,我们真正想要的是"比平均好的抬、比平均差的压"。
解药叫基线(baseline):从回报里减去一个只依赖状态、不依赖动作的量 \(b(s)\): \[ \nabla_\theta J=\mathbb{E}\!\left[\nabla_\theta\log\pi_\theta(a\mid s)\,\big(G_t-b(s_t)\big)\right]. \] 最常用的基线就是状态价值 \(b(s)=V^\pi(s)\)("这个状态平均能拿多少分")。减掉它之后,括号里变成"这次比平均好还是差",有正有负,更新方向立刻变得有意义。
例题:证明加基线不改变梯度期望
要证 \(\mathbb{E}_{a\sim\pi_\theta}\big[\nabla_\theta\log\pi_\theta(a\mid s)\,b(s)\big]=0\)。
\[ \sum_{a}\pi_\theta(a\mid s)\,\nabla_\theta\log\pi_\theta(a\mid s)\,b(s) = b(s)\sum_a \pi_\theta(a\mid s)\,\frac{\nabla_\theta\pi_\theta(a\mid s)}{\pi_\theta(a\mid s)} \] \[ = b(s)\sum_a \nabla_\theta\pi_\theta(a\mid s) = b(s)\,\nabla_\theta\underbrace{\sum_a \pi_\theta(a\mid s)}_{=\,1} = b(s)\,\nabla_\theta 1 = 0 . \]关键是两步:(1) \(b(s)\) 与 \(a\) 无关,作为常数提到求和外(这一步靠的是"常数提取",不是别的);(2) 真正承重的一步——梯度与求和可交换,把 \(\sum_a\nabla_\theta\pi=\nabla_\theta\sum_a\pi\),而 \(\sum_a\pi\equiv 1\),其梯度恒为 0。所以减基线不引入偏差,却能把方差里那一大坨"共同的正偏移"消掉。
4. 优势函数与 Actor–Critic
把"回报减基线"取它的期望版本,就得到优势函数(advantage): \[ A^\pi(s,a)=Q^\pi(s,a)-V^\pi(s). \] 它回答一个极其具体的问题:"在状态 \(s\) 下选 \(a\),比这个状态的平均水平好多少?" \(A>0\) 就该抬概率,\(A<0\) 就该压。理想的策略梯度其实是 \[ \nabla_\theta J=\mathbb{E}\big[\nabla_\theta\log\pi_\theta(a\mid s)\,A^\pi(s,a)\big]. \]
但 \(Q,V\) 我们并不知道,得估。于是有了 actor–critic(演员—评论家)双网络结构:
- Actor(演员)= 策略 \(\pi_\theta\),负责出动作;
- Critic(评论家)= 价值估计 \(V_\phi\)(或 \(Q\)),负责打分;
- 用 critic 算出优势 \(\hat A\),回传给 actor 做策略梯度更新;critic 自己则用上一课的 TD / 贝尔曼回归来训练。
最简单的优势估计就是单步 TD 误差 \(\delta_t=r_t+\gamma V(s_{t+1})-V(s_t)\)。当 \(V=V^\pi\)(真值函数)时,\(\delta_t\) 是 \(A^\pi\) 的无偏估计(因为 \(\mathbb{E}[\delta_t\mid s_t,a_t]=Q^\pi-V^\pi=A^\pi\));实践中我们只有近似的 critic \(V_\phi\),用它替代时 \(\delta_t\) 就带偏差(偏差正比于 \(V_\phi-V^\pi\)),但换来的是很低的方差。这个"低方差、有偏"正是下面 GAE 在 \(\lambda=0\) 一端的特征。
GAE:在偏差和方差之间拨一个旋钮
"用几步回报来估优势"有个两难:步数少(如单步 TD)方差小但偏差大(严重依赖不准的 \(V_\phi\));步数多(如蒙特卡洛全回报)偏差小但方差大。GAE(广义优势估计,Generalized Advantage Estimation)用一个衰减因子 \(\lambda\in[0,1]\) 把各步 TD 误差指数加权平均: \[ \hat A_t^{\text{GAE}}=\sum_{l\ge 0}(\gamma\lambda)^l\,\delta_{t+l}. \] \(\lambda=0\) 退化成单步 TD(低方差高偏差),\(\lambda=1\) 退化成蒙特卡洛(高方差低偏差)。\(\lambda\) 就是那个偏差—方差旋钮,实践里常取 0.95 左右。现在点到为止,PPO 里会用到它。
5. On-policy vs Off-policy:能不能重用旧数据?
策略梯度有个昂贵的前提:梯度是对"当前策略 \(\pi_\theta\) 采的样"求期望。一旦更新了 \(\theta\),旧数据就"过期"了,严格来说得重新采样——这叫 on-policy(同策略),REINFORCE、A2C、PPO 都属此类,数据利用率低但稳定。
与之相对,off-policy(异策略)(如 Q-learning、DQN)允许用另一个策略(甚至历史回放池)采的数据来学习,数据利用率高,但训练更易不稳定。
PPO 想要"鱼和熊掌兼得":用一批旧策略 \(\pi_{\text{old}}\) 的数据做好几步更新(提高数据利用率),但通过重要性采样修正分布差异,并限制每步别走太远以保持稳定。这就引出了重要性比与裁剪。
6. PPO:别一步把策略毁掉
朴素策略梯度有个致命脆弱点:学习率稍大,一步更新就可能把策略推到一个糟糕区域,而坏策略采的数据又更糟,负反馈崩盘、再也回不来。信任域(trust region)思想说:每步更新要限制在"离旧策略不太远"的范围里,小步快走才稳。这个名字借自优化——就像优化里只在当前点的一个"可信半径"内相信你的近似模型,超出半径模型就不可靠了;在这里,"模型"是用旧数据估出来的优势,"半径"就是新旧策略的偏移量。
怎么衡量"用旧数据评估新策略"?用重要性比(importance ratio) \[ r_t(\theta)=\frac{\pi_\theta(a_t\mid s_t)}{\pi_{\theta_{\text{old}}}(a_t\mid s_t)} . \] \(r>1\) 表示新策略比旧策略更想选这个动作。代理目标 \(\mathbb{E}[\,r_t(\theta)\,\hat A_t\,]\) 就是"用旧数据估的、新策略的期望优势"。但如果不加约束,模型会为了拉高一个高优势动作把 \(r\) 推到天上去——一步走太远。
PPO 的招数干净利落:裁剪代理目标(clipped surrogate objective) \[ L^{\text{CLIP}}(\theta)=\mathbb{E}_t\Big[\min\big(r_t\hat A_t,\;\operatorname{clip}(r_t,1-\epsilon,1+\epsilon)\,\hat A_t\big)\Big]. \] 逐字读这个 \(\min\)(下面以 \(|\hat A|=1\) 为例):
- 当 \(\hat A>0\)(好动作):目标随 \(r\) 增大而增大,但 \(r\) 一旦超过 \(1+\epsilon\),被裁平——再怎么想抬这个动作,超过 \(1+\epsilon\) 就没有额外奖励了,于是没动机往大走太远。被裁平发生在 \(r\) 偏大的一侧。
- 当 \(\hat A<0\)(坏动作):注意这里的图景不对称。在 \(r>1-\epsilon\) 区间,目标随 \(r\) 减小而增大(越不选这个坏动作越好);可一旦 \(r\) 低于 \(1-\epsilon\) 就被裁平(封底在 \(-(1-\epsilon)\cdot|\hat A|\)),再压低也不给额外收益——被裁平发生在 \(r\) 偏小的一侧。而 \(r\) 偏大的那一侧不裁,惩罚照常随 \(r\) 增大(更负)。换句话说,\(A<0\) 时大 \(r\) 不会被裁,它本就该被狠狠惩罚。
- 那个外层 \(\min\) 保证裁剪只会让目标更保守,不会因裁剪反而鼓励更大步。
这恰好和下面调一调③ 的数值对上:\(A=-1\) 时 \(r=0.8\) 与 \(r=0.5\) 都给 \(-0.80\)(封底),而 \(r=1.5\) 给 \(-1.50\)(未裁、更负)。
7. ★ 直通 RLHF:奖励模型 + PPO + KL 锚
现在把这套机器接到大模型上。RLHF(基于人类反馈的强化学习)分两步:
- 训练奖励模型(reward model)\(R_\psi\):用人类对"哪个回答更好"的偏好数据,学一个给回答打分的函数;
- 用 PPO 优化语言模型策略\(\pi_\theta\),去最大化奖励——但带一个 KL 锚: \[ \max_\theta\;\mathbb{E}_{x,\,y\sim\pi_\theta}\Big[\,R_\psi(x,y)\;-\;\beta\,D_{\mathrm{KL}}\big(\pi_\theta(\cdot\mid x)\,\big\|\,\pi_{\text{ref}}(\cdot\mid x)\big)\Big]. \]
这里把语言生成看成 RL:状态 = 已生成的前缀,动作 = 下一个 token,奖励 = 整段回答的得分 \(R_\psi\)。注意奖励只在序列末尾给一个标量,怎么把它摊到中间每个 token(靠 \(\gamma\) 折扣、价值函数 bootstrap 或 GAE 把末端信号往前回传)是 RLHF 里最关键、也最容易把初学者绕晕的工程问题——见下方 skip。
那个 \(-\beta\,D_{\mathrm{KL}}(\pi_\theta\|\pi_{\text{ref}})\) 是灵魂所在——\(\pi_{\text{ref}}\) 是微调前的参考模型,KL 惩罚把策略拴在参考模型附近,防止它为了骗高奖励而说出语无伦次的胡话(reward hacking)或丢掉原有的语言能力。
ML 呼应第 5 课的 KL 埋点
还记得第 5 课讲信息论时埋下的 \(D_{\mathrm{KL}}(p\|q)\)——它度量"用 \(q\) 近似 \(p\) 多吃亏",非负、不对称。在 RLHF 里它摇身一变成了"别跑偏"的正则项:\(\beta\) 大则策略保守、贴着参考模型;\(\beta\) 小则更敢追高奖励但更冒险。这与第 6 节 PPO 的 KL 视角是同一根弹簧——两处都把新策略 \(\pi_\theta\) 写在前(KL 不对称,方向取决于对谁采样,这里都是对新策略采样估计),只不过第 6 节锚的是上一步的旧策略,这里锚的是参考模型。
两个"省事"的后继者(点到,留作预告)
- DPO(直接偏好优化):数学上可以证明,"奖励 \(-\,\beta\)KL"这个带约束的最优解有闭式形式,于是能把整套 RL 重写成一个直接在偏好数据上的监督式损失——不再需要奖励模型,也不再需要 PPO 采样循环。训练像普通分类一样稳。细节留到 M12。
- GRPO(组相对策略优化):PPO 要训一个 critic 来估 \(V\) 当基线,又贵又难调。GRPO 干脆去掉 critic:对同一道题采一组回答,用组内的相对好坏(减组内均值、除组内标准差)当优势。这在数学推理类任务上特别好用,是 DeepSeek 系列推理模型的关键,留到 M14。
例题:对二动作 softmax 策略手算 \(\nabla_\theta\log\pi\)
设两个动作的 logits 为 \(\theta=(\theta_0,\theta_1)\),\(\pi(a)=\mathrm{softmax}(\theta)_a\)。对实际选到的动作 \(a\): \[ \log\pi(a)=\theta_a-\log\!\big(e^{\theta_0}+e^{\theta_1}\big). \] 对 \(\theta_j\) 求偏导: \[ \frac{\partial\log\pi(a)}{\partial\theta_j}=\mathbb{1}[j=a]-\pi(j). \] 即梯度向量 \(\nabla_\theta\log\pi(a)=\mathbf{e}_a-\pi\)。取 \(\theta=(0.5,-0.5)\),则 \(\pi=(0.731,0.269)\),若选到 \(a=0\): \[ \nabla_\theta\log\pi(0)=(1-0.731,\;-0.269)=(0.269,\,-0.269). \] 直觉:抬高被选动作的 logit、压低其余动作的 logit,压低多少正比于它们当前的概率。我用有限差分验证过,两者完全吻合(见下方代码)。
调一调,观察现象
下面三个小实验都用 numpy 现场写的最小环境,几秒跑完。建议每个都先猜结果再运行。
调一调 ①:有无基线的方差对比
改什么:在伯努利老虎机上,固定策略,分别用"裸回报"和"回报减基线 \(V\)"估策略梯度,比较方差。
预期看到:两者梯度均值几乎相同(基线不改期望),但带基线的方差明显更小(这里约降到 42%)。
为什么:基线把所有动作共有的"正偏移"消掉,只留下"比平均好/差"的部分。
import numpy as np
rng = np.random.default_rng(0)
p = np.array([0.25, 0.75]) # 动作1更好
pi = np.array([0.5, 0.5]) # 固定均匀策略
b = (pi*p).sum() # 基线 = V = 期望奖励
def grad_logpi(a, pi):
g = -pi.copy(); g[a] += 1.0; return g
g_nob, g_b = [], []
for _ in range(2000):
a = rng.choice(2, p=pi)
r = float(rng.random() < p[a])
gl = grad_logpi(a, pi)
g_nob.append(gl*r) # 无基线
g_b.append(gl*(r-b)) # 有基线
g_nob, g_b = np.array(g_nob), np.array(g_b)
print("mean no-baseline :", g_nob.mean(0))
print("mean baseline :", g_b.mean(0))
print("var no-baseline :", g_nob.var(0).sum())
print("var baseline :", g_b.var(0).sum())
print("variance ratio :", g_b.var(0).sum()/g_nob.var(0).sum())
# 预期: 两个 mean 几乎相等, ratio < 1 (约 0.42)
调一调 ②:跑一个最小 REINFORCE,看基线如何稳住训练
改什么:放开策略让它更新,看 \(\pi(\text{好动作})\) 是否爬向 1。再把 use_baseline 切成 False。注意这里特意把奖励整体抬高了一个常数偏移 OFFSET=5——让所有回报都远离 0,正是基线最该出手的场景。
预期看到:好动作概率从 0.5 稳步升到 ~0.9+;去掉基线后,收敛后的曲线明显更抖(这里跨 50 个种子,收敛段的步间抖动方差约差几百倍)。如果你只跑一条种子、且不加偏移,这个差别很可能肉眼看不出来——降方差的效果要么靠"奖励远离 0"放大,要么靠"多种子平均"才稳定显现。
为什么:基线把回报里那一大坨"共同正偏移"(这里是 5)减掉,梯度信号才不被它淹没;不减的话,每个动作都被那个大正数往上猛推,方向信号被噪声盖住。
import numpy as np
def softmax(z): z=z-z.max(); e=np.exp(z); return e/e.sum()
def run(seed, use_baseline, OFFSET=5.0, eta=0.1, steps=400):
rng = np.random.default_rng(seed)
p = np.array([0.1, 0.9]); theta = np.array([0.0, 0.0])
traj = []
for it in range(steps):
pi = softmax(theta)
a = rng.choice(2, p=pi)
r = float(rng.random() < p[a]) + OFFSET # 奖励整体抬高
base = (pi*p).sum() + OFFSET if use_baseline else 0.0
adv = r - base
g = -pi.copy(); g[a] += 1.0 # grad log pi(a)
theta += eta * g * adv
traj.append(softmax(theta)[1])
return np.array(traj)
# 单条种子先看学没学会
tr = run(seed=1, use_baseline=True)
print("final pi(good) =", round(tr[-1], 3))
# 跨 50 种子比较收敛段(后200步)的步间抖动方差
import numpy as np
def jitter(use_b):
return np.mean([np.diff(run(s, use_b)[200:]).var() for s in range(50)])
print("jitter var baseline :", jitter(True))
print("jitter var no-base :", jitter(False))
print("ratio (b/nob) :", jitter(True)/jitter(False))
# 预期: pi(good) 升向 0.9+; ratio 远小于 1 (带基线抖动小得多)
调一调 ③:PPO 裁剪到底裁掉了什么
改什么:扫一遍重要性比 \(r\),分别在 \(A=+1\) 与 \(A=-1\) 下打印裁剪目标,改 eps 看护栏宽窄。
预期看到:\(A=+1\) 时目标在 \(r>1+\epsilon\) 后封顶不再增(\(r\) 偏大一侧被裁);\(A=-1\) 时在 \(r<1-\epsilon\) 后封底不再降(\(r\) 偏小一侧被裁),而 \(r\) 偏大一侧不裁、惩罚继续加深;\(\epsilon\) 越大护栏越宽。
为什么:裁剪只取消"把 \(r\) 推向对自己有利方向"的额外收益(好动作往大推、坏动作往小推),从而限制单步偏移;坏动作被推向大 \(r\) 本就该重罚,不裁。
import numpy as np
def clip_obj(r, A, eps=0.2):
return min(r*A, np.clip(r, 1-eps, 1+eps)*A)
for r in [0.5, 0.8, 1.0, 1.2, 1.5]:
print(f"r={r:>3}: A=+1 -> {clip_obj(r, 1.0):+.2f} "
f"A=-1 -> {clip_obj(r, -1.0):+.2f}")
# 预期(eps=0.2):
# A=+1 时 r=1.2 与 r=1.5 都给 +1.20 (封顶), r 偏小一侧不裁
# A=-1 时 r=0.8 与 r=0.5 都给 -0.80 (封底), 而 r=1.5 给 -1.50 (未裁、更负)
动手练习
- 基线最优值(推导):已知减任意 \(b(s)\) 不改期望。证明使梯度估计方差最小的基线是 \(b^*(s)=\dfrac{\mathbb{E}[(\nabla_\theta\log\pi)^2 G]}{\mathbb{E}[(\nabla_\theta\log\pi)^2]}\)(对方差关于 \(b\) 求导置零)。说明为什么实践里仍常用 \(V(s)\) 近似它。
- reward-to-go(代码):把调一调②改成一个两状态、最长 5 步的小 MDP,分别用"整条回报 \(G(\tau)\)"和"未来回报 \(G_t\)"更新,打印两种梯度估计的方差。骨架:
import numpy as np # 两状态 MDP: s0--a->s1, 终点给奖励; 自己设转移与奖励 # 采样若干轨迹, 对每条算 G(tau) 与各步 G_t # 比较 sum_t gradlogpi*G(tau) vs sum_t gradlogpi*G_t 的方差 # 预期: 用 G_t 的方差更小 - 单步 TD 优势(代码):在调一调②的老虎机上把基线从"真实 \(V\)"换成一个在线估计的 \(\hat V\)(用 \(\hat V\leftarrow\hat V+\alpha(r-\hat V)\) 滑动平均),观察它是否收敛到 \(\sum_a\pi(a)p_a\)(注意若加了偏移则再加上偏移)并依然降方差。
- KL 惩罚 vs 裁剪(思考+代码):把调一调③的裁剪目标换成 \(r\hat A-\beta\,(r-1-\log r)\),扫 \(\beta\) 看它如何同样地"抑制 \(r\) 偏离 1"。这里的 \(r-1-\log r\) 是用单个样本的重要性比 \(r\) 去估计 \(D_{\mathrm{KL}}(\pi_\theta\|\pi_{\text{old}})\) 的常用估计量(在 \(r=1\) 处取最小值 0、两侧上翘,恒非负),所以它扮演的正是把 \(r\) 拉回 1 的那根"弹簧",与正文里分布层面的 KL 锚是一回事。定性比较"弹簧"(KL)和"护栏"(裁剪)两种形状的差异。
- RLHF 目标实验(代码):构造一个 5-动作离散"句子"分布,给定一个玩具奖励向量与参考分布 \(\pi_{\text{ref}}\),数值优化 \(\sum_a\pi(a)R(a)-\beta D_{\mathrm{KL}}(\pi\|\pi_{\text{ref}})\)。改 \(\beta\):\(\beta\to0\) 时 \(\pi\) 退化成"全压在最高奖励动作",\(\beta\) 大时 \(\pi\to\pi_{\text{ref}}\)。验证这个趋势。
掌握自检
- 我能不查资料写出策略梯度定理,并解释 \(\nabla\log\pi\) 与回报相乘的物理意义吗?还能说清为什么实现里用未来回报 \(G_t\) 而非整条 \(G(\tau)\) 吗?
- 我能独立证明 \(\mathbb{E}[\nabla\log\pi\cdot b(s)]=0\) 吗?我能指出承重的两步(常数提取 + "梯度与求和交换、\(\sum\pi=1\) 梯度为 0")分别是哪步吗?
- 我能区分 \(Q\)、\(V\)、\(A\) 三者,并说清 actor 和 critic 各负责什么吗?我能说清单步 TD 误差何时无偏、何时有偏吗?
- 我能解释 PPO 裁剪在 \(A>0\) 和 \(A<0\) 两种情形下分别在 \(r\) 的哪一侧裁平、哪一侧不裁吗?\(\epsilon\) 和学习率有何不同?
- 我能写出 RLHF 的优化目标,并说清 \(-\beta\,D_{\mathrm{KL}}(\pi_\theta\|\pi_{\text{ref}})\) 防止了什么问题吗?DPO 和 GRPO 各省掉了哪一步?
可以先放过的点
- GAE 的完整推导:现在只需记住"\(\lambda\) 是偏差—方差旋钮"。等你真正实现 PPO 时再回来推那个等比级数。
- PPO 外层 \(\min\) 为何能保证保守:直觉够用了;想吃透可以画出 \(A>0\) 与 \(A<0\) 两条裁剪曲线逐段分析,等做调一调③有手感后再回看。
- DPO 的闭式解推导:涉及带 KL 约束的最优策略形式,留到 M12 偏好优化专题。
- 语言生成里"逐 token 奖励分配"的工程细节:第 7 节那个"末端一个标量怎么摊到每个 token"的问题,正是这里要替你挡掉的——等到真正跑 RLHF(M12/M14)再深究即可。
模块 2 到此完成!你已经把概率、信息论、强化学习的地基打牢。下一站:进模块 3(Python/PyTorch 工程)把这些算法真正写成能跑的代码,或者直接进入深度学习。这两条路最终会在 RLHF 这里重逢。