RLHF中的Actor-Critic与PPO:从TD误差到优势函数实战解析
RLHFReinforcement Learning from Human Feedback在工程落地时最容易让人困惑的是语言模型已经能生成文本为什么还要引入一整套强化学习训练流程原因是“生成流畅的文本”和“生成符合人类偏好的文本”是两个目标。H10 这条主线要拆开的正是强化学习里的 Actor-Critic 算法以及它如何在 RLHF 中通过 TD 误差、优势函数和 PPO 完成策略更新。读者只要具备基础策略梯度概念能读懂积分和期望记号就能把下面这套数学推导接到实际训练代码上。1. 理解 RLHF 中的 Actor-Critic它到底负责哪一层1.1 RLHF 的三段式链路RLHF 不是从零训练一个模型而是让一个已经具备语言能力的模型进一步对齐人类偏好。常见流程分为三段。第一段是拿到一个预训练语言模型它已经知道怎么续写、回答问题或生成代码。第二段是训练一个奖励模型 Reward Model输入是“prompt response”输出一个标量分数表示这段回复在人类偏好上的质量。第三段才是真正用到强化学习的地方把语言模型的生成过程当作一个策略用奖励模型的分数作为奖励信号通过策略梯度去更新语言模型。这里容易出现的误解是奖励模型直接接在语言模型后面用普通交叉熵就能继续训。实际上奖励模型只给出一个分数没有给出“应该输出哪个 token”的监督标签。要找到一个能提高奖励分数、但又不能完全偏离原模型的策略就需要强化学习。1.2 为什么普通策略梯度在 RLHF 里不够用普通策略梯度也叫 REINFORCE它的更新方向来自“一条完整轨迹的累计回报”。在 CartPole 这类短轨迹环境中REINFORCE 可以工作但方差很大。在语言生成任务中一条 response 可能有几百个 token而奖励模型只在结尾给一个总分数中间每个 token 对最终分数的贡献是间接的、有噪声的。直接拿整条序列的总回报去更新每一个 token 的概率就会出现“某条序列得分高但高分数可能是运气好不是当前 token 动作好”的问题。Actor-Critic 的解决思路是引入一个 Critic也就是价值网络。Critic 负责估计“当前状态下未来预期能拿多少回报”然后把策略梯度里的累计回报替换成“实际回报减去预期回报”也就是优势 Advantage。这个差值能剔除掉状态本身带来的影响保留的是动作相对于平均水平的优势。这种去基线操作不改变策略梯度的期望方向但能显著降低方差。Q-learning 或者 DQN 这类 value-based 方法在离散动作、表格型或低维连续控制任务中很有效但 RLHF 的动作空间是“整个词表”的 token 选择且策略本身需要在生成时被反复采样。单纯学习 Q 值很难覆盖如此大的动作空间而 Actor-Critic 可以同时维护策略网络和价值网络策略网络输出动作分布价值网络输出当前状态的期望回报两者配合起来更稳定。方法更新对象能否直接处理高维动作方差RLHF 中的典型角色REINFORCE策略能采样高理论基底Q-learning / DQN价值函数需要额外处理动作空间较低但过估计风险不适合直接做 token 级生成Actor-Critic策略 价值函数能采样且可扩展较低RLHF/PPO 的核心结构2. 数学基础回报、价值函数与 TD 误差2.1 回报与状态价值函数强化学习里的“回报” G_t 是从 t 时刻开始沿轨迹继续往后收集的所有折扣奖励之和[ G_t r_{t1} \gamma r_{t2} \gamma^2 r_{t3} \cdots ]其中 (\gamma \in [0,1]) 是折扣因子。(\gamma) 越小模型越看重短期奖励(\gamma) 越大模型越愿意为远期结果牺牲当前收益。在 RLHF 里一个 response 的最终分数往往在最后才出现因此折扣因子通常取得接近 1但不能直接取 1否则长序列的回报方差会很大。状态价值函数 (V^\pi(s)) 表示在状态 s 下按当前策略 (\pi) 继续执行动作期望能拿到的回报[ V^\pi(s) \mathbb{E}{a \sim \pi} \left[\sum{k0}^{\infty} \gamma^k r_{tk1} ,\middle|, s_t s \right] ]动作价值函数 (Q^\pi(s,a)) 表示在状态 s 下已经选择了动作 a之后继续按 (\pi) 执行期望能拿到的回报。两者之间的关系非常直接[ V^\pi(s) \sum_{a} \pi(a|s) Q^\pi(s,a) ]这个关系说明状态价值是所有可能动作价值的“策略加权平均”而优势函数定义成[ A^\pi(s,a) Q^\pi(s,a) - V^\pi(s) ]它衡量的是在状态 s 下选择动作 a比当前策略的平均水平好多少。如果优势为正这个动作值得提高概率如果优势为负这个动作应该降低概率。2.2 TD 误差用下一步估计当前步前面说 Actor-Critic 要用优势函数但完整计算 (Q^\pi(s,a)) 并不现实因为需要枚举未来所有可能性。TD 目标给出了一种 bootstrap 的做法。假设当前已经走了一步得到转移序列 ((s_t, a_t, r_{t1}, s_{t1}))。用当前 Critic 网络近似 (V^\pi)那么一步 TD 目标是[ y_t r_{t1} \gamma V^\pi(s_{t1}) ]TD 误差就是实际的一步目标与当前状态价值估计之间的差[ \delta_t y_t - V^\pi(s_t) r_{t1} \gamma V^\pi(s_{t1}) - V^\pi(s_t) ]这个式子看起来只是“预测值和目标值的误差”但它同时是优势函数的一个估计。因为 (r_{t1} \gamma V^\pi(s_{t1})) 可以看作 (Q^\pi(s_t,a_t)) 的一个样本估计减去 (V^\pi(s_t)) 后就得到优势估计。这里有一个重要的偏差-方差权衡。直接使用完整回报 (G_t) 去更新策略方差大但无偏使用 TD 误差去更新依赖 Critic 的估计是否准确如果 Critic 估计有偏TD 误差也会有偏但方差大大降低。Actor-Critic 之所以在实践中稳定正是因为它在无偏性和低方差之间选择了后者并把这种偏置控制在价值网络能承受的范围内。3. 用公式推导为什么 Advantage 比累计回报更适合做更新信号3.1 策略梯度的基础方向策略梯度方法的优化目标是最大化期望累计回报。策略由参数 (\theta) 表示目标函数可以写成[ J(\theta) \mathbb{E}{\tau \sim \pi\theta} \left[\sum_{t} \gamma^t r_t \right] ]策略梯度定理给出[ \nabla_\theta J(\theta) \mathbb{E}{\tau \sim \pi\theta} \left[ \sum_{t0}^{T} \nabla_\theta \log \pi_\theta(a_t | s_t) , G_t \right] ]这个式子说明要提高一个动作的概率不能只看“动作本身是否好”还要看它所在整条轨迹的累计回报。逻辑上如果一条轨迹回报高轨迹里所有动作的概率都会上升。这样做的问题在于不同状态本身有难易差异。有些状态天然容易拿高分有些状态很难直接使用 (G_t) 会把“状态难度”误当成“动作质量”。3.2 减去基线不改变梯度期望为了剔除状态自身的效应可以在策略梯度里给 (G_t) 减去一个只依赖状态、不依赖动作的基线 (b(s_t))[ \nabla_\theta J(\theta) \mathbb{E} \left[ \sum_{t} \nabla_\theta \log \pi_\theta(a_t | s_t) , (G_t - b(s_t)) \right] ]为什么可以减因为对任意 (b(s_t))下面期望都等于零[ \mathbb{E}{a \sim \pi\theta} \left[ \nabla_\theta \log \pi_\theta(a|s) , b(s) \right] ]展开后就是[ \sum_a \pi_\theta(a|s) \frac{\nabla_\theta \pi_\theta(a|s)}{\pi_\theta(a|s)} b(s)\sum_a \nabla_\theta \pi_\theta(a|s) , b(s) ]把求和移动到梯度符号内部[ b(s) \nabla_\theta \sum_a \pi_\theta(a|s) b(s) \nabla_\theta 1 0 ]所以减基线不会改变策略梯度的期望方向只会影响方差。理论上最好的基线就是状态价值函数 (V^\pi(s_t))因为它正好是 (G_t) 在给定状态下的期望估计。于是更新信号变成[ G_t - V^\pi(s_t) ]这个差值不是别的正是优势函数 (A^\pi(s_t,a_t)) 的逼近。3.3 用 TD 误差替换完整优势完整计算 (G_t - V(s_t)) 仍然需要走完一条轨迹。Actor-Critic 进一步用 TD 误差替代它[ A(s_t,a_t) \approx \delta_t r_{t1} \gamma V(s_{t1}) - V(s_t) ]在 RLHF 的 token 级更新中每个 token 位置都可以计算这样的 (\delta_t)。这样就不需要为每个 token 单独设定完整回报只需要当前一步的实际奖励、下一步价值估计和当前状态价值估计。这也正是 Critic 存在的意义价值网络承担了“多步回报压缩成一个数值”的任务策略网络只负责根据优势调整动作概率。需要明确TD 误差不是优势函数的无偏估计的唯一形式。在更复杂的场景里会使用 Generalized Advantage Estimation也就是 GAE把多步 TD 误差做加权累加在偏差和方差之间取得更平滑的折中。4. 最小 Actor-Critic 实现网络结构与更新顺序4.1 环境准备与依赖要验证 Actor-Critic 的更新逻辑可以在小型离散动作环境上跑一个最小实现。示例使用 Python 3.9 以上环境依赖 PyTorch 和 gymnasium。pip install torch gymnasium numpy下面代码以 CartPole 这类环境为例说明结构。RLHF 场景中把这里的 Actor 替换成语言模型把 Critic 替换成语言模型上的 value head 即可。4.2 定义 Actor 和 Critic 网络Actor 输出动作概率分布Critic 输出状态价值。import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 32), nn.ReLU(), nn.Linear(32, action_dim) ) def forward(self, state): logits self.net(state) return torch.softmax(logits, dim-1) def sample(self, state): probs self.forward(state) dist torch.distributions.Categorical(probs) action dist.sample() return action.item(), dist.log_prob(action) class Critic(nn.Module): def __init__(self, state_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, state): return self.net(state).squeeze(-1)这里 Actor 使用 Categorical 分布适用于离散动作。连续动作环境需要换成高斯分布输出均值和方差。Critic 输出值是标量代表当前状态在未来能得到多少期望回报。4.3 单步更新顺序核心更新过程可以写成下面这段骨架代码def update_step(actor, critic, actor_opt, critic_opt, state, action, log_prob, reward, next_state, done, gamma0.99): with torch.no_grad(): td_target reward gamma * critic(next_state) * (0 if done else 1) td_error td_target - critic(state) actor_loss -(log_prob * td_error.detach()) critic_loss F.mse_loss(critic(state), td_target) actor_opt.zero_grad() actor_loss.backward() actor_opt.step() critic_opt.zero_grad() critic_loss.backward() critic_opt.step()这段代码展示了三个关键点。第一td_target在torch.no_grad()下计算。Critic 更新的目标是“固定值”不能让梯度回流到目标本身否则价值网络会不断追逐一个移动靶训练发散。第二Actor 的损失里使用了td_error.detach()。Actor 只需要利用优势的数值方向不需要通过优势把 Critic 的梯度带到 Actor 里。如果不 detachActor 的梯度会同时影响 Critic 的td_target路径造成训练不稳定。第三Critic 的损失是当前价值估计与 TD 目标之间的均方误差。TD 目标本身由当前 Critic 和一步奖励组成所以 Critic 每次更新都是在“用自己下一步估计自己当前步”这种 bootstrap 让训练样本利用率提高但也会让价值估计存在偏差。这个最小实现在小环境中可以工作但它还缺少两个 RLHF 必备组件一个是多步优势估计 GAE另一个是策略更新幅度限制 PPO。这两件事都在下一节展开。5. RLHF 场景中的 Actor-CriticReward Model、KL 惩罚与 GAE5.1 Reward Model 输出的是序列级分数不是 token 级奖励在 Gym 环境里每一步环境都会返回一个即时奖励。RLHF 里奖励模型通常输入整段 response输出一个标量。也就是说序列中间大多数 token 没有即时环境奖励只有在最后一个 token 才能得到奖励模型分数。为了让 Actor-Critic 在 token 级别更新常见的做法是把奖励模型分数落在序列末尾中间每个 token 位置只计算 KL 惩罚项[ R_t - \beta \cdot \mathrm{KL}\left( \pi_\theta(\cdot | x, s_{t}) ,\middle|, \pi_{\mathrm{ref}}(\cdot | x, s_{t}) \right) ]最后一个 token 位置再额外加上奖励模型分数 (r_{\mathrm{RM}})。这里的 (\pi_{\mathrm{ref}}) 是冻结的参考模型一般是 RLHF 训练前的策略模型。KL 惩罚约束当前策略不要偏离参考模型太远避免模型为了对齐人类偏好而牺牲原有的语言能力。5.2 GAE把连续多个 TD 误差加权起来单步 TD 误差方差低但偏差明显完整回报偏差低但方差大。RLHF 中一条 response 可能有几百个 token单纯用单步 TD 误差容易出现“奖励传播不到前面 token”的问题。Generalized Advantage Estimation 用两个参数控制偏差方差[ \delta_t r_{t1} \gamma V(s_{t1}) - V(s_t) ][ A_t^{\mathrm{GAE}} \sum_{l0}^{\infty} (\gamma \lambda)^l \delta_{tl} ]当 (\lambda0) 时GAE 退化成单步 TD 误差偏差大但方差小。当 (\lambda1) 时GAE 接近完整回报偏差小但方差大。实际语言模型训练中(\lambda) 通常取 0.9 到 0.995 之间需要在实验里观察训练稳定性和最终对齐效果。5.3 PPO 是 Actor-Critic 的稳定化扩展RLHF 中最常用的强化学习算法是 PPO它本质上是在 Actor-Critic 之上增加了重要性采样和 clip 机制。策略在每轮 rollout 时用旧策略生成数据更新时计算当前策略和旧策略的概率比值[ \rho_t(\theta) \frac{\pi_\theta(a_t | s_t)}{\pi_{\theta_{\mathrm{old}}}(a_t | s_t)} ]PPO 的目标函数为[ L^{\mathrm{CLIP}}(\theta) \mathbb{E} \left[ \min\left( \rho_t(\theta) , A_t, ; \mathrm{clip}(\rho_t(\theta), 1-\epsilon, 1\epsilon) , A_t \right) \right] ]当 (\rho_t(\theta)) 超出 ([1-\epsilon, 1\epsilon]) 时clip 会裁掉过大或过小的更新幅度防止策略单次更新跑太远。这里的 (A_t) 通常就是用 GAE 估计出的优势。因此可以这样理解RLHF 的更新主线就是 Actor-CriticPPO 解决的是“怎么在 Actor-Critic 基础上安全更新策略”的问题。RLHF 组件对应 Actor-Critic 概念作用语言模型Actor输出 token 概率分布Value HeadCritic输出当前状态的期望累积分数Reward Model KLReward提供对齐信号和漂移约束GAEAdvantage压缩多步 return降低方差PPO clip更新规则限制策略单步更新幅度6. 常见训练异常与排查路径6.1 训练长期不收敛时先看这几类信号RLHF 训练不收敛问题往往不在单行代码而在更新链路里的某个环节失配。排查时要看的关键标量包括。指标含义出现什么值需要警惕Advantage Mean优势均值长时间明显偏离 0说明价值估计没有校准Advantage Std优势标准差过大说明 reward 尺度异常建议归一化Critic Loss价值估计误差持续上升或震荡剧烈需要降低学习率Entropy策略熵下降过快说明策略过早确定缺少探索KL当前策略与参考策略距离增长过快说明 KL 系数过小或学习率过大Reward Mean平均 reward上升后转跌可能发生 reward hacking排查顺序不要从模型结构开始而是从输入开始。先确认奖励模型分数是否已经归一化KL 惩罚是逐 token 计算还是手工拼凑GAE 里的 (\lambda) 是否设置极端再检查 value head 输出和回报量级是否匹配。6.2 三个容易踩的坑第一个坑是把 TD 目标当普通预测值直接用于 Critic 损失计算但没有 detach。这样梯度会沿着“目标”这个路径回流到价值网络价值网络每次更新都在改变自己的目标损失会震荡。第二个坑是用原始累计回报 (G_t) 直接给 Actor 做更新而没有减去 baseline。在短轨迹小环境中还能忍耐在 RLHF 的 token 数十万量级上这种方差会淹没训练信号。正确做法是至少使用 (G_t - V(s_t))更推荐 GAE。第三个坑是忽略了优势窗口的量级。如果 reward 模型输出的分数在 0 到 1 之间而 KL 惩罚项只有 0.01 级最终优势会被 RM 分数主导如果反过来 KL 系数太大优势会被 KL 惩罚主导模型会退化到和参考模型几乎一样没有对齐效果。另一种常见情况是策略熵过早下降。Actor 一旦过早选择确定动作奖励信号再合理也无法探索到更优路径。解决方式是在 Actor 损失中加入熵正则项[ L_{\mathrm{actor}} -\mathbb{E}\left[\log \pi_\theta(a_t|s_t) A_t \right]\alpha , H(\pi_\theta(\cdot|s_t)) ](\alpha) 通常设一个很小的值例如 0.01 或 0.001一般会随着训练逐步衰减。陷入过慢探索时再调大不要一开始就加很大。7. 可执行的训练实践清单与扩展方向7.1 开始训练前先对齐这些配置开发环境和生产环境差距较大下面这份清单可以作为 Actor-Critic 相关项目开始前的检查项。确认奖励信号维度奖励模型分数、KL 惩罚、即时奖励是否合并到同一个reward张量中。确认 (\gamma) 和 (\lambda)语言生成长序列时(\gamma) 一般接近 1GAE 的 (\lambda) 推荐从 0.95 开始。固定参考模型(\pi_{\mathrm{ref}}) 必须冻结不能参与梯度更新。记录 old policy 的 log-probPPO 更新时计算 (\rho_t) 需要当前策略 log-prob 和旧策略 log-prob。对 Advantage 做 batch 级归一化先减均值再除标准差避免不同批次 reward 尺度差异导致更新抖动。设置梯度裁剪大型语言模型训练时clip_grad_norm_ 是常用保护手段避免单条样本导致的梯度爆炸。保存中间 checkpoint至少保留最近三个稳定 checkpoint出现 reward 崩溃时可以回滚。7.2 学习环境与生产环境的差异对比项学习/玩具环境RLHF 生产环境模型规模两层 MLP数亿参数以上语言模型奖励来源Gym/env 即时奖励Reward Model KL 惩罚Advantage单步 TDGAE多步加权更新方式每步在线更新多轮 rollout 后 batch 更新存储成本单机内存需要 rollout buffer、参考模型、reward model 同时驻留监控打印 reward/loss分布式日志、指标曲线、模型权重监控在生产环境中actor、critic、reference model、reward model 通常不是四个独立大模型。常见做法是 actor 和 critic 共享主干只在顶层增加一个 value head。这样能节省大量显存但 value head 和 policy 头共享底层特征后需要观察价值估计是否出现滞后。另一种做法是把 actor 参数做成 LoRA只训练低秩适配层这样 checkpoint 小也更容易回滚。7.3 下一步扩展路径理解 Actor-Critic 后下一步可以依次扩展三块。第一是 GAE 的完整推导。要弄清楚它在多步 TD 误差上为什么能形成递推加权以及 (\lambda0) 和 (\lambda1) 两个极端如何对应单步 TD 和 Monte Carlo。第二是 PPO 的实现细节。可以自己写一个 CartPole 上的 PPO 版本对比它和裸 Actor-Critic 在收敛速度、策略熵、回报稳定性上的差异。先跑通再读 trl 这类 RLHF 工具库里的 PPO 代码会更容易理解大模型场景中的日志序列存储和 advantage 计算。第三是围绕奖励函数做实验。RLHF 的效果很大程度上取决于 Reward Model 的质量和 KL 惩罚系数。同一个 Actor-Critic 框架不同的 reward shaping 会得到完全不同的行为。把奖励信号可视化出来比反复调整网络结构更能发现训练问题。如果要给 H10 之后的学习定一个优先级先把 GAE 的公式推到能自己写出来再把 PPO clip 的 min-clip 表达式落到代码里跑一遍最后回到 RLHF 的 reward design 和 KL 系数上。Actor-Critic 不是这条路的终点它只是从单步 TD 走向完整 RLHF 训练框架的那根主线把这条线的数学和代码对齐后面调模型时才不会靠猜。

相关新闻