REINFORCE算法推导与RLHF落地:从策略梯度到大模型对齐 📅 发布时间:2026/8/30 6:04:07 👁 浏览次数: 强化学习里的 RLHFReinforcement Learning from Human Feedback是让大模型对齐人类偏好的核心路线而 REINFORCE 算法又是这条路线里几乎所有策略优化方法的起点。这篇文章想做的事情很明确把 REINFORCE 的数学公式一步一步拆开说明白它为什么能用“采样轨迹”来估计策略梯度再把它放到 RLHF 的完整流程里看它承担什么角色。适合刚接触强化学习的人也适合已经跑过 PPO 但想回头补基础的人。推导过程只用到概率论里最基本的链式法则和期望求导交换我会把每一步怎么来的都写出来。全文按“先理解任务再统一符号再推导再落地最后排错”的顺序走这样你拿到的不只是一个公式而是一套能迁移到其他策略梯度方法的思考方式。1. 先用一个生成任务想清楚 RLHF 在优化哪个环节1.1 从 SFT 到奖励模型三个阶段依次解决什么问题RLHF 不是单个算法而是一条流水线。最常见的拆法是三段第一阶段SFTSupervised Fine-Tuning用人工标注的高质量问答数据微调预训练模型让模型先学会“像人一样说话”。第二阶段训练奖励模型让模型对同一个问题的多个回答打分学习“哪种回答更符合人类偏好”。第三阶段用强化学习优化策略模型让模型在生成回答时倾向于拿到更高的奖励分数。REINFORCE 出现在第三阶段。它做的事可以概括成一句话给定一个提示词让当前策略模型生成一段回答用奖励模型给这段回答打分然后根据分数调整策略参数。分数高就增加这段回答的生成概率分数低就降低这段回答的生成概率。听起来很像“简单重复好行为”但它真正的难点在于生成回答这个过程是离散采样不是连续可微函数。你没法直接对“采样”求导必须借助策略梯度绕过去。1.2 策略模型在 RLHF 阶段被反复采样更新很多人第一次看 RLHF 会觉得奇怪为什么要反复让模型生成回答直接拿奖励模型当损失函数微调不就行了吗问题在于奖励模型的输出只对“已经生成出来的文本”有意义。你要知道模型生成某个词的概率是高是低更新参数后模型重新生成会不会更好这些都需要反复采样。每一次采样都是一次“试探”每一次试探都会留下一条轨迹REINFORCE 就是把这些轨迹变成梯度信号的数学工具。所以在实际项目里RLHF 阶段的显存占用、计算量都明显高于普通微调。原因不是模型本身变大了而是你要同时跑策略模型、奖励模型、参考模型还要维护一批生成样本。先把这点记在心里后面落地步骤会反复用到。2. 统一符号先搞清楚 REINFORCE 在优化什么2.1 轨迹、状态、动作定义要提前对齐推导公式前符号必须统一。RLHF 里的“动作”和普通强化学习里的动作含义不完全一样但数学形式是一致的。我用下面这套记号$s_t$状态泛指模型当前所处的上下文。在 RLHF 里通常把提示词和已经生成的前缀文本合并看成状态。$a_t$动作指模型在时间步 $t$ 生成的一个 token。$\pi_\theta(a_t|s_t)$带参数 $\theta$ 的策略模型表示在状态 $s_t$ 下生成动作 $a_t$ 的概率。$\tau (s_0, a_0, s_1, a_1, \dots, s_T)$一条完整轨迹也就是从第一个 token 生成到结束符的完整序列。$R(\tau)$整条轨迹的累积奖励。在 RLHF 里$R(\tau)$ 就是奖励模型给整段回答打的总分。$p(s_{t1}|s_t, a_t)$环境转移概率。文本生成任务里这个转移几乎是确定的生成一个 token 后新状态就是原来的上下文加上这个 token。这里要强调REINFORCE 是 on-policy 算法意思是更新参数用的样本必须来自当前策略。如果用的生成样本是旧模型产出的就属于 off-policy需要额外处理否则梯度估计是有偏的。2.2 目标函数期望奖励为什么不能直接求导REINFORCE 的优化目标很简单$$J(\theta) \mathbb{E}{\tau \sim \pi\theta(\tau)}[R(\tau)]$$展开成积分形式$$J(\theta) \int \pi_\theta(\tau) R(\tau) , d\tau$$这里的 $\pi_\theta(\tau)$ 是整条轨迹的概率$$\pi_\theta(\tau) p(s_0) \prod_{t0}^{T-1} \pi_\theta(a_t|s_t) p(s_{t1}|s_t, a_t)$$问题就在这里目标函数里含有期望而期望是通过采样得到的。你手里只有一批从 $\pi_\theta$ 采样出来的轨迹样本没有解析的积分表达式。直接对 $J(\theta)$ 求 $\theta$ 的导数没法把期望符号和求导符号直接交换因为被积函数里的 $\pi_\theta(\tau)$ 和 $R(\tau)$ 都跟采样过程纠缠在一起。解决办法就是接下来要讲的 log 技巧。3. 数学公式推导从期望到策略梯度的完整过程3.1 log 技巧核心的一步换元对 $\nabla_\theta J(\theta)$ 做展开$$\nabla_\theta J(\theta) \int \nabla_\theta \pi_\theta(\tau) R(\tau) , d\tau$$现在 $\nabla_\theta \pi_\theta(\tau)$ 不能直接算因为 $\pi_\theta(\tau)$ 是多项概率的连乘。这时候用对数求导的恒等式$$\nabla_\theta \pi_\theta(\tau) \pi_\theta(\tau) \nabla_\theta \log \pi_\theta(\tau)$$代入回去$$\nabla_\theta J(\theta) \int \pi_\theta(\tau) \nabla_\theta \log \pi_\theta(\tau) R(\tau) , d\tau$$写成期望形式$$\nabla_\theta J(\theta) \mathbb{E}{\tau \sim \pi\theta(\tau)}[\nabla_\theta \log \pi_\theta(\tau) R(\tau)]$$这一步就是 REINFORCE 的数学基础。它把一个“不知道期望怎么求导”的问题变成了“采样后统计 $\nabla_\theta \log \pi_\theta(\tau) R(\tau)$ 的均值”的问题。只要你能从策略模型里采样轨迹就能用这批样本估计梯度。这里要提醒$\log$ 技巧不是法术它没有改变梯度的真实值只是把 $\pi_\theta(\tau)$ 从分母挪到了采样分布的位置。换一种等价写法梯度估计仍然成立但数值稳定性和方差特性会不一样后面会专门讲方差。3.2 从轨迹概率到每一步动作的概率现在处理 $\nabla_\theta \log \pi_\theta(\tau)$。把轨迹概率的连乘形式取对数连乘变成连加$$\log \pi_\theta(\tau) \log p(s_0) \sum_{t0}^{T-1} \left[ \log \pi_\theta(a_t|s_t) \log p(s_{t1}|s_t, a_t) \right]$$对 $\theta$ 求导时$p(s_0)$ 和 $p(s_{t1}|s_t, a_t)$ 都不含参数 $\theta$导数直接消掉。于是$$\nabla_\theta \log \pi_\theta(\tau) \sum_{t0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t)$$这个结果很关键整条轨迹的梯度等于轨迹上每个 token 的“对数概率梯度”相加。也就是说优化策略模型时每个生成过的 token 都会贡献一份梯度贡献大小由整条轨迹的奖励 $R(\tau)$ 统一加权。代入梯度期望$$\nabla_\theta J(\theta) \mathbb{E}{\tau \sim \pi\theta(\tau)}\left[\sum_{t0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) R(\tau)\right]$$3.3 蒙特卡洛估计和参数更新公式实际训练时没有期望的解析形式只能用 $N$ 条采样轨迹做蒙特卡洛估计$$\nabla_\theta J(\theta) \approx \frac{1}{N} \sum_{i1}^{N} \sum_{t0}^{T-1} \nabla_\theta \log \pi_\theta(a_t^{(i)}|s_t^{(i)}) R(\tau^{(i)})$$参数更新$$\theta \leftarrow \theta \alpha \nabla_\theta J(\theta)$$其中 $\alpha$ 是学习率。这就是 REINFORCE 最原始的更新公式。如果按“因果性”改进可以把整条轨迹的奖励 $R(\tau)$ 换成从时刻 $t$ 开始的折扣回报 $G_t$$$G_t \sum_{k0}^{T-t} \gamma^k r_{tk}$$于是逐时间步的更新形式变成$$\theta \leftarrow \theta \alpha \sum_{t0}^{T-1} G_t \nabla_\theta \log \pi_\theta(a_t|s_t)$$为什么要换成 $G_t$因为时刻 $t$ 之前已经生成的动作不应该被 $t$ 之后的回报“回溯奖励”。这个因果性改进能降低一部分方差也是 REINFORCE 变体里最常见的一版。4. 回到 RLHFREINFORCE 的落地流程与代码思路4.1 RLHF 里的输入、输出和奖励来源把上面的通用框架映射到 RLHF每一环的细节都要对应清楚强化学习概念RLHF 里的对应物说明状态 $s_t$提示词 已生成前缀通常由模型内部维护动作 $a_t$生成的单个 token离散动作空间即词表策略 $\pi_\theta$正在微调的语言模型生成概率来自 softmax奖励 $r_t$奖励模型对整段回答的打分可以每个 token 都填总分也可以只在末尾给分轨迹 $\tau$一段完整回答从提示词到生成结束符在 RLHF 里最朴素的做法是生成完一整段回答后奖励模型给一个总分 $r$然后把 $r$ 当作轨迹上每个 token 的共享奖励。这样 $R(\tau)$ 就是 $r$$G_t$ 也简化成 $r$不考虑折扣时。这种叫“稀疏奖励”实现简单但每个 token 都分到同一个奖励梯度方差会偏大。更精细的做法是给每个 token 分配不同的奖励例如在末尾位置加权、或者按 token 级别训练奖励模型。但很多项目实际跑 RLHF 时还是会先退回整段打分因为 token 级奖励模型训练成本高、收益不一定明显。4.2 一次迭代的最小伪代码用伪代码把一次 RLHF 策略更新写清楚输入策略模型 π_θ奖励模型 r_φ提示词数据集 D学习率 α 1. 从 D 中采样一个 batch 的提示词 x 2. 用当前策略 π_θ 对每个 x 采样一段回答 y y ~ π_θ(·|x) 3. 用奖励模型打分r r_φ(x, y) 4. 计算每个 token 的对数概率 log π_θ(a_t | x, y_t) 5. 计算梯度 grad (1 / (N * T)) * Σ_i Σ_t ∇_θ log π_θ(a_t | x_i, y_{t}) * r_i 6. 更新参数θ ← θ α * grad 7. 回到第 1 步直到奖励稳定第 5 步是 REINFORCE 的核心也是很多新手写错的地方。常见错误是把整段回答当成一个动作直接求 log prob或者忘记了生成 y 时要用 stop-gradient 的旧策略采样更新时又要用当前策略计算 log prob。这两者必须使用同一份参数吗理论上是但实际实现中会因为梯度回传路径不同而出现偏差。写代码时要注意记录生成时的 log prob 和更新时的 log prob两个值要能对得上。奖励值先做标准化例如减均值除标准差能让训练稳定很多。不要直接更新完整参数先跑一个小 batch 验证 loss 数值是否在合理范围。4.3 为什么真实 RLHF 项目里更常看到 PPO说到这里可能有人会问既然 REINFORCE 推导这么干净为什么主流的 RLHF 框架用的都是 PPO核心原因是 REINFORCE 的梯度方差太大。纯 REINFORCE 用整条轨迹的累计奖励去加权所有 token 的梯度一条回答里可能只有末尾一个地方出了问题但整条轨迹的奖励都变了。这会导致同一批样本里梯度方向差异很大训练曲线跳来跳去学习率稍大一点就崩。PPO 在 REINFORCE 之上做了三件事引入重要性采样允许用旧策略采样提高样本利用率。用 clip 操作限制新旧策略的更新幅度防止一步更新过大。用 advantage 代替原始奖励配合 critic 网络降低方差。所以你可以把 PPO 理解为“REINFORCE 的工程增强版”。REINFORCE 是底层的策略梯度估计器PPO 是加了稳定性措施的同一个思想。理解 REINFORCE 之后再看 PPO会顺很多因为 PPO 的目标函数里依然存在 $\nabla_\theta \log \pi_\theta(a_t|s_t)$ 这个核心项。5. 方差、基线函数与训练不稳定的排查手势5.1 为什么方差大基线为什么有用REINFORCE 的梯度估计有一个明显问题不同轨迹的奖励 $R(\tau)$ 可能差异很大。有的回答质量高奖励是 2有的回答跑题奖励是 -1。直接用这些原始奖励加权梯度相当于让每一次采样都带一个很大的随机噪声。解决办法之一是引入基线 $b(s_t)$把梯度改成$$\nabla_\theta J(\theta) \mathbb{E}\left[\sum_{t0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \left(G_t - b(s_t)\right)\right]$$基线的作用是让加权项从“绝对回报”变成“相对优势”。奖励高不意味着这个动作就好要看它比“平均情况下的期望奖励”高多少。$b(s_t)$ 通常取状态值的估计或者直接用当前 batch 的奖励均值。为什么减基线不改变梯度的期望因为$$\mathbb{E}\left[\nabla_\theta \log \pi_\theta(a_t|s_t) b(s_t)\right] \int \nabla_\theta \pi_\theta(a_t|s_t) b(s_t) , da_t b(s_t) \nabla_\theta \int \pi_\theta(a_t|s_t) , da_t 0$$积分结果为 1对常数求导为 0。所以基线只改变方差不改变梯度期望。这个性质是策略梯度方法里最漂亮也最实用的结论之一。5.2 训练不稳定的排查顺序如果 RLHF 训练时奖励长期不涨、或者 loss 直接变成 NaN建议按下面顺序排查先看奖励分布。奖励模型给出的分数是否分布太集中集中到 0 附近时梯度信号太弱。再看 log prob。生成回答里是否出现大量重复 token导致某些 token 的 log prob 异常小。然后看学习率。REINFORCE 类方法对学习率非常敏感建议从 1e-6 到 1e-5 这个量级起步对比几次再确定。接着看 batch size。batch 太小蒙特卡洛估计噪声大batch 太大显存不够。先跑 8 条轨迹看梯度方向是否稳定。看是否用了参考模型和 KL 惩罚。RLHF 场景里策略模型和 SFT 模型差距过大会导致回答格式崩坏所以通常会加 KL 惩罚项。最后看奖励是否被 hack。模型可能学会了输出很长但内容空洞的回答来骗取高分。如果奖励不断上升但回答质量明显下降优先检查奖励模型和 KL 权重。5.3 新手最容易踩的几个坑第一个坑是忘了 stop-gradient。生成回答时模型在采样计算梯度时模型在反向传播。这两个阶段的模型参数必须是同一个但生成阶段不能把采样路径的梯度传回去。实现时一般把采样的 log prob 和更新时的 log prob 分开记录。第二个坑是符号方向搞错。梯度上升还是梯度下降取决于框架习惯。PyTorch 的 optimizer.step() 默认做梯度下降所以要把策略梯度目标写成负值或者手动对损失取负号。很多人第一次跑通发现奖励越来越低多半是这个原因。第三个坑是奖励没有归一化。不同提示词的奖励尺度可能差很多比如长回答的分数天然偏高短回答偏低。直接把原始奖励拿来加权梯度更新会被长回答主导。建议每个 batch 内做一次标准化。第四个坑是忽略 KL 惩罚。纯 REINFORCE 不加约束时模型会快速走向奖励模型的弱点区域。一般会把目标改成$$\nabla_\theta J(\theta) \mathbb{E}\left[\sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) \left(r(x, y) - \beta \log \frac{\pi_\theta(y|x)}{\pi_{\text{ref}}(y|x)}\right)\right]$$其中 $\pi_{\text{ref}}$ 是 SFT 阶段的参考模型$\beta$ 控制偏离程度。这样既能优化奖励又不会让模型跑得太远。这个 KL 项在实际 RLHF 工程里几乎必加。6. 从 REINFORCE 出发还能往哪走REINFORCE 不是终点但它是一把钥匙。理解了它的推导后面的策略梯度家族都会好懂很多Actor-Critic用 critic 网络估计状态值替代简单基线进一步降低方差。PPO在策略梯度上加重要性采样和 clip让更新更稳。GAE广义优势估计用多步 TD 折中偏差和方差是现代强化学习的标配组件。DPODirect Preference Optimization直接绕开显式奖励模型用偏好数据优化策略但其推导里依然有“对数概率差”和“隐式奖励”的影子。如果你现在是在学习阶段建议亲手做一个最小的文本生成实验用一个小模型、几组提示词把 REINFORCE 更新跑通打印每个 token 的 log prob、gradient norm 和奖励值。这样比直接加载一个大型 RLHF 框架更能理解内部机制。等你能预测某一步梯度变化大概会带来什么效果时再上 PPO 或者接入完整 RLHF 管线会从容很多。最后留几个我自己排查时会优先看的点训练曲线的奖励是否与人工评估一致KL 惩罚项是否失控采样回答的平均长度是否有突变以及模型是否开始出现重复或空洞的套话。这些信号比 loss 数值本身更能说明问题。真正的 RLHF 工程不只是调好一个公式而是要把采样、奖励、KL、日志和人工抽检整条链路都管住。