第一篇章:RLHF 的地基 —— 这里的“反馈”到底是什么?
在进入复杂的公式之前,我们必须先对齐一个最底层的认知:为什么仅仅靠“训练数据”是不够的?
1. 核心直觉:鹦鹉 vs. 导盲犬
要理解 RLHF,你首先要理解现在的 AI(LLM)面临的根本矛盾:
-
预训练(Pre-training)出来的模型是“鹦鹉”: 它读了互联网上所有的书。它知道下一词填什么概率最高,但它不知道什么叫“对”,什么叫“错”。如果你问它“如何制造毒药?”,它会很高兴地预测出毒药的配方,因为在训练数据里,问题后面通常紧跟答案。
-
我们想要的是“导盲犬”: 它不仅要懂指令,还要懂人类的价值观(安全、有用、诚实)。
RLHF 的本质,就是把一只“博学但混乱的鹦鹉”,训练成一只“懂规矩的导盲犬”。
2. 地基知识 A:语言模型的本质(Next Token Prediction)
一切的起点是 GPT(Generative Pre-trained Transformer)。在 RLHF 介入之前,模型只做一件事:最大化似然估计(Maximum Likelihood Estimation)。
数学原理
假设一个句子由序列 组成。模型的训练目标是最大化以下概率:
在训练时,我们使用交叉熵损失函数(Cross-Entropy Loss):
它的局限性(为什么需要 RLHF?)
这个 Loss 只能告诉模型:“在这个语境下,这也是人说过的某个词”。它无法告诉模型:
-
这句话是粗鲁的。
-
这句话是错误的。
-
这句话虽然语法通顺,但是没有任何逻辑。
结论: 传统的监督学习(Supervised Learning)只能教模型“像人一样说话”,不能教模型“像好人一样说话”。
3. RLHF 的第一步:有监督微调 (SFT - Supervised Fine-Tuning)
在直接上强化学习之前,我们需要让模型先入个门。这一步叫 SFT(冷启动阶段)。
-
原理: 既然预训练模型太发散,我们就人工写一批完美的问答对(Prompt + Answer)。
-
做法: 还是用上面的“交叉熵损失函数”,让模型死记硬背这些高质量的回答。
-
目的: 让模型从“续写模式”切换到“问答模式”。
注意: 到这一步,模型还是不知道哪个更好,它只是在模仿人类的句式。SFT 模型通常被称为 Policy Model (
)。
4. RLHF 的第二步:奖励模型 (Reward Model, RM) —— 这里的核心数学
这是 RLHF 中最关键的“地基”。如果不能定义“什么是好”,就无法进行强化学习。
我们不可能让真人盯着模型每一步的输出去打分(效率太低)。所以,我们需要训练一个 AI(奖励模型)来模仿人类的评分标准。
核心难题:人类很难给出绝对分数
如果你问人:“这句话得几分(1-10)?”
-
张三可能觉得是 7 分。
-
李四心情不好,觉得是 4 分。
-
绝对分数噪声极大,无法用于训练。
解决方案:Bradley-Terry 模型(成对比较)
我们不让打分,而是让做选择题。
给定一个问题 ,模型生成两个回答
和
。让人类标注员选:哪个更好?(假设
是胜者,
是败者)。
我们假设,一个回答比另一个回答好的概率,取决于它们潜在得分(Reward)的差值。
其中:
-
是我们要训练的奖励模型给出的分数(这是一个标量,Scalar)。
-
是 Sigmoid 函数,把分差映射到 (0, 1)的概率区间:
。
奖励模型的损失函数 (Ranking Loss)
我们要训练奖励模型,使得它给
(胜者)的分数尽可能比
(败者)高。
根据最大似然估计,我们要最小化以下 Loss:
和
:奖励模型给胜者和败者分别打了个分,比如胜者得 5 分,败者得 2 分。
差值 (5 - 2) = 3:分差越大,说明胜者越明显。
:经过 Sigmoid,说明模型认为
胜出的概率是 95%。
:取对数。
:取负号,Loss 很小。
反之: 如果奖励模型眼瞎了,给败者打 5 分,胜者打 2 分。
-
差值
。
-
。
-
。
-
-Loss = 3。Loss 巨大!模型会被狠狠惩罚,被迫调整参数。
第一篇章总结
到目前为止,我们还没开始“强化学习”,我们只是做好了准备工作:
-
SFT 模型: 一个模仿了人类高质量回答,但不知道背后逻辑的“演员”。
-
Reward 模型: 一个学会了人类喜好,能通过对比两个答案,精准判断哪个更好的“裁判”。
接下来的挑战: 既然有了裁判(Reward Model),为什么不直接用梯度上升去优化 LLM,让它生成 Reward 最高的词呢?
-
因为 LLM 的输出是离散的(Token),离散数据不可导,无法直接反向传播 Reward 的梯度给 LLM。
-
这就是为什么我们需要 PPO(Proximal Policy Optimization) 这种强化学习算法介入的原因。
在 RLHF 中,奖励模型(Reward Model, RM)是裁判。裁判不会去评价“概率”,裁判只评价“结果”。
请看这个断裂的计算图:
-
LLM 输出 Logits: [0.1, 0.8, 0.05, ...](对应词表里的词)。
-
Softmax 变成概率: 还是连续的数值。
-
关键动作 —— 生成 (Generation):
为了把这些概率变成一段话喂给奖励模型,模型必须做一个决定:选哪个词?
-
要么 Argmax(选概率最大的)。
-
要么 Sampling(按概率随机采样)。
-
-
奖励模型 (RM) 输入: RM 接收的是具体的 token ID(比如单词 "cat"),而不是 "cat" 的概率值。
-
RM 输出: 5 分。
奖励模型给出的分数(Reward),被“生成具体的词”这个动作挡住了,梯度传不回去给 LLM 的参数。
怎么办?这就需要“策略梯度”(Policy Gradient)
既然链式法则(Chain Rule)在“生成”这一步断了,我们就不能用标准的梯度下降。
我们需要一种算法,它不需要知道“这一步具体该怎么微调”,它只需要知道: “刚才这次尝试(Sampling),结果是好的(Reward高)。既然是好的,那就增加‘刚才那个动作’出现的概率!”
这就是 Reinforcement Learning 的魔法。它不是通过误差反向传播来直接修改参数,而是通过调整概率来间接优化策略。
这个公式的意思是:
-
我不关心 Reward 怎么对 Action 求导(因为不可导)。
-
我只把 Reward 当作一个权重(Weight)。
-
如果 Reward 是正的,我就增大
的梯度(让这个词下次出现概率更高)。
-
如果 Reward 是负的,我就减小它。
你不能直接输出 Reward 最高的词,是因为:
-
Reward Model 需要具体的词(Token)作为输入,而不是概率。
-
从概率到具体的词(Sampling/Argmax)是一个“离散化”过程,在数学上不可导(梯度阻断)。
这就是为什么我们需要引入 PPO 这样的强化学习算法,而不是简单地把它当作一个 Loss 函数来跑 SGD。
第二篇章:强化学习的核心 —— 越过不可导的悬崖
1. 核心思想:我不求导,我改概率
既然我不能计算 ,那我就换个思路。
我们回顾一下目标:我们希望 Reward 高的动作(词),出现的概率(Probability)变大。
在数学上,我们的目标函数 是期望回报(Expected Reward)最大化:
其中:
-
是我们的 LLM(策略网络)。
-
是生成的轨迹(一句话,Trajectory)。
-
是这句话得到的奖励分数。
魔法:对数导数技巧 (Log-Derivative Trick)
我们想要求 关于参数
的梯度
。
这里的推导请紧跟:
展开期望公式(积分/求和):
(期望 = 概率 分数)
求梯度(梯度符号 可以穿过求和符号,因为
对于当前参数来说是固定的标量值,真正受
影响的是概率
):
引入恒等变换: 根据高中数学导数公式 ,我们可以推出
。 把这个代入上式:
变回期望形式:
这就是策略梯度定理 (Policy Gradient Theorem)。
这个公式告诉了我们什么?
-
:这是 LLM 自身输出概率的梯度(这是完全可导的!就是常规的反向传播)。
-
:这是奖励模型给的一个数字(标量)。
-
结论:我们不需要对 R求导。我们只需要把 R 当作一个权重系数。
-
如果 R 是正的大数
这里的梯度权重很大
猛烈更新参数,让这个 token 概率变大。
-
如果 R 是负数
梯度反向
降低这个 token 的概率。
-
2. 进阶:为什么要引入 Critic (AC 架构)?
上面的公式有一个大问题:方差(Variance)极大。
有些句子虽然写得烂,但运气好碰巧某些词用对了,Reward 可能是正的;有些句子写得好,但运气不好,Reward 低。如果直接用原始 去乘梯度,训练会像心电图一样乱跳,根本收敛不了。
解决方案:引入基准线 (Baseline) 与优势函数 (Advantage)
我们不应该看“绝对得分”,应该看“相对得分”。 比如,考试考了 60 分。
-
如果平均分是 30 分,那你很棒(Reward 正向)。
-
如果平均分是 90 分,那你很差(Reward 负向)。
我们需要一个模型来预测这个“平均分”。这个模型就是 Value Model (Critic),记作 。 它不生成文本,它只负责读入当前的上文,预测:“按照现在的局势,这一把大概能得多少分?”
我们用 优势函数 (Advantage Function) 来代替原始 Reward:
(实际 PPO 中通常使用 GAE: Generalized Advantage Estimation)
简单理解:
-
:实际执行动作
后拿到的真实回报(现实)。
-
:Critic 预测的回报(预期)。
-
(优势):现实 - 预期。
-
:惊喜!表现超乎预期,增加该动作概率。
-
:失望!表现不如预期,降低该动作概率。
-
3. 终极形态:PPO (Proximal Policy Optimization)
有了策略梯度和 Advantage,我们其实已经可以用 TRPO (Trust Region Policy Optimization) 等算法训练了。但 TRPO 计算太复杂(涉及海森矩阵的逆)。
OpenAI 提出了 PPO,用最简单的数学实现了稳扎稳打的训练。后面的DEEPSEEK又推出了GRPO,但是我们还是先从PPO开始吧
PPO 解决的核心痛点:步子大了容易混乱
在强化学习中,数据是“由当前的策略”生成的(On-Policy)。 如果你一次更新把参数改猛了,策略变了,原来采集的数据就全部作废了。而且如果策略变差了,下次采的数据会更差,导致模型直接崩盘,再也救不回来。
PPO 的核心:限制更新幅度。
PPO 的 Loss 函数 (Clip 版本)
我们定义一个概率比率 :
-
如果
,说明新老策略一样。
-
如果
,说明新策略提高了该动作的概率。
PPO 的目标函数是取下面两项的最小值:
让我们像拆解代码一样拆解这个公式(假设 ):
情况一:动作是好的 ()
-
我们需要提高概率,也就是让
。
-
第一项
想让
无限变大。
-
第二项
clip限制了最大只能是 1.2。
-
取最小值
:如果
超过 1.2,Loss 就不再增加了(梯度消失)。
-
含义: 只要你把概率提高一点点(20%)我就满意了,不要改得太夸张!
情况二:动作是坏的 ()
-
我们需要降低概率,也就是让
。
-
clip限制了最小只能是
。
-
取最小值(这里因为
是负数,其实是看谁更负):如果
低于 0.8,也不再惩罚了。
-
含义: 只要你把概率降低一点点我就放过你,别把模型改废了。
第二篇章总结
在这一章,我们解决了一切数学障碍:
-
策略梯度 (Policy Gradient): 利用
绕过了采样不可导的问题,把 Reward 变成了梯度权重的系数。
-
Critic (Value Model): 引入“预期管理”,计算 Advantage,减少方差,让训练更稳定。
-
PPO Clipping: 给模型带上“手铐”,限制每次参数更新的幅度,防止模型在强化学习的深渊中“步子太大”导致崩溃。
现在的架构里有了四个东西在跑:
-
Actor (生成模型,我们需要训练的)
-
Critic (价值模型,辅助训练的)
-
Reward Model (给分的裁判,冻结的)
-
Reference Model (SFT模型,作为对照组,冻结的 —— 为什么需要它?)
这里埋下了一个伏笔:Reference Model。 即使有了 PPO 的 Clip,模型还是可能会为了高分去“作弊”(Reward Hacking),比如一直重复说“谢谢谢谢”(如果裁判模型喜欢礼貌的话)。 我们需要一个东西来约束它:KL 散度(KL Divergence)。
在前两章中,我们造出了一个懂人类喜好的“裁判”(Reward Model),并且找到了训练不可导模型的数学工具(PPO)。
现在的最大风险是:模型变坏(Reward Hacking)。
如果你只告诉模型“得分越高越好”,模型很快就会发现 Reward Model 的漏洞。比如,Reward Model 可能稍微倾向于长的句子,模型就会开始输出几千字的废话;或者 Reward Model 觉得“我不知道”很安全,模型就对所有问题都回答“我不知道”。
第三篇章:RLHF 的完整闭环 —— KL 约束与代码实现逻辑
1. 地基知识 B:KL 散度 (Kullback-Leibler Divergence)
在 RLHF 中,我们不希望强化学习训练后的模型(Actor)偏离原来的 SFT 模型(Reference)太远。因为 SFT 模型虽然不够“好”,但它至少通顺、符合语法。
我们不仅要“分高”,还要“不忘初心”。
数学定义
KL 散度用于衡量两个概率分布 和
之间的差异:
利用对数性质 ,公式可以写成:
在 RLHF 中的实际意义:
-
:当前的强化学习模型(Actor)生成某个 token 的概率。
-
:原始的 SFT 模型(Reference Model)生成同一个 token 的概率。
-
:这就是我们要计算的差值。
修正后的总奖励 (Total Reward)
我们在训练 PPO 时,不再只给模型 Reward Model 的打分,而是给一个组合分:
-
:裁判给的分(比如 5 分)。
-
:惩罚系数(比如 0.1)。
-
:如果你生成的词,SFT 模型觉得“很离谱”(概率很低),KL 值就会很大,你的总分就会被狠狠扣除。
2. RLHF 的“四马车”架构
在代码实现层面(例如 DeepSpeed-Chat 或 TRL 库),当你按下“开始训练”按钮时,显存里同时跑着 4 个神经网络。这是一场消耗显存的盛宴:
| 模型名称 | 角色 | 状态 | 作用 |
| Actor (Policy) | 演员 | 训练中 | 我们要优化的 LLM,负责生成文本。 |
| Critic (Value) | 评论家 | 训练中 | 预测每个状态的价值 (Value),用于计算 Advantage。 |
| Reward Model | 裁判 | 冻结 | 给生成的文本打分。 |
| Reference Model | 老师 | 冻结 | 原始的 SFT 模型,用于计算 KL 散度,防止 Actor 跑偏。 |
显存优化提示: 通常 Reference Model 和 Reward Model 不需要计算梯度,可以进行量化(Int8/FP16)或者在推理完后卸载到 CPU(Offload)以节省显存。
3. 完整的 RLHF 训练循环 (The Loop)
请仔细阅读这个流程,这是所有 RLHF 代码(如 TRL / DeepSpeed)的核心骨架。我们将训练过程分为两个阶段:采样(Rollout) 和 更新(Update)。
阶段一:采样 (Make Experience)
我们需要先让模型去“玩”一下,收集数据。
-
取 Prompt: 从数据集中拿一个问题,比如 "如何做红烧肉?"
-
生成 (Generate): 让 Actor 生成回答 "先把肉切块..."。
-
打分 (Reward): 把“问题+回答”喂给 Reward Model,得到分数
(比如 0.8)。
-
计算约束 (KL):
-
把同样的“问题+回答”喂给 Reference Model。
-
计算 Actor 和 Reference 在每个 Token 上的概率对数差:
log_prob_actor - log_prob_ref。 -
计算修正后的奖励:
。
-
在最后一个 Token 加上
。
-
-
价值预测 (Value): 让 Critic 预测每一步的价值
。
此时,我们手里有了一堆数据(Trajectory):状态、动作、概率、奖励、价值。这些数据存入 Replay Buffer。
阶段二:更新 (PPO Update)
现在用收集的数据来更新 Actor 和 Critic 的参数。
-
计算优势 (GAE): 使用
和
计算优势函数
(这一步其实比预期好多少)。
-
PPO Loss 计算:
-
从 Buffer 中取出数据。
-
Actor Loss: 计算新旧策略的比率
,用
clip方法限制更新幅度,最大化优势。 -
Critic Loss: 计算 Critic 预测的
和真实回报的均方误差 (MSE),让 Critic 预测更准。
-
-
梯度反向传播:
(Actor_Loss + Critic_Loss).backward()。 -
更新参数:
optimizer.step()。
4. 代码级逻辑 (Python 伪代码)
为了让你完全看懂,我去掉所有复杂的工程细节,只保留最核心的逻辑流:
# 初始化四个模型
actor = GPT2_Policy() # 可训练
critic = GPT2_Value() # 可训练
reward_model = GPT2_RM() # 冻结
ref_model = GPT2_Policy() # 冻结 (这是Actor的初始副本)
optimizer = AdamW(actor.parameters() + critic.parameters())
# 训练循环
for epoch in range(total_epochs):
# -------------------------------------------------------
# 步骤 1: 采样 (Rollout) - 不计算梯度
# -------------------------------------------------------
with torch.no_grad():
prompt = get_batch_prompt() # "今天天气"
# Actor 生成回答
response_seq, old_log_probs = actor.generate(prompt)
# Output: "今天天气不错"
# Reference Model 计算原始概率 (用于 KL)
ref_log_probs = ref_model.forward(prompt + response_seq)
# Reward Model 打分
score = reward_model.forward(prompt + response_seq) # scalar: 5.0
# Critic 预测价值
old_values = critic.forward(prompt + response_seq)
# -------------------------------------------------------
# 步骤 2: 计算 奖励 和 优势 (Process Rewards)
# -------------------------------------------------------
# 计算 KL 惩罚 (逐个 Token 的惩罚)
# KL = log P_actor - log P_ref
kl_divergence = old_log_probs - ref_log_probs
# 组合奖励 = 最后的评分 - 过程中的KL惩罚
rewards = -beta * kl_divergence
rewards[-1] += score # 只在最后一个 token 加上真正的 RM 分数
# 使用 GAE (Generalized Advantage Estimation) 计算优势
advantages, returns = compute_gae(rewards, old_values)
# -------------------------------------------------------
# 步骤 3: PPO 更新 (Optimize) - 计算梯度
# -------------------------------------------------------
for _ in range(ppo_epochs): # 同一批数据可以重复利用几次
# 重新计算当前概率和价值 (因为参数在变)
new_log_probs = actor.forward_log_probs(prompt + response_seq)
new_values = critic.forward(prompt + response_seq)
# --- Actor Loss (PPO Clip) ---
ratio = torch.exp(new_log_probs - old_log_probs)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1-eps, 1+eps) * advantages
actor_loss = -torch.min(surr1, surr2).mean() # 负号因为要做梯度下降
# --- Critic Loss (MSE) ---
critic_loss = (new_values - returns).pow(2).mean()
# 总 Loss
loss = actor_loss + 0.5 * critic_loss
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("训练完成!现在的 Actor 既懂人话,又懂规矩。")
我们设定一个极简场景:
举例
-
Prompt (题目):
"你好"(2个字) -
Response (回答):
"开心"(2个字) -
总长度: 4个字。
现在,请想象这 4 个字正在传送带上流动。
第一阶段:生产线(生成数据)
这时候,只有**Actor(学生)**在工作,其他模型在后面盯着。
1. 初始状态
-
传送带上只有:
["你", "好"]
2. 第一步生成 (T=2)
-
Actor (学生): 看到
["你", "好"]。-
它想说
"开"。 -
它心里对
"开"的自信程度(概率)是 0.6。 -
记录数据:
Token: "开",Old_Prob: 0.6。
-
-
Critic (助教): 看到
["你", "好"]。-
它预测:这局大概能得 0.2 分。
-
记录数据:
Value: 0.2。
-
3. 第二步生成 (T=3)
-
Actor (学生): 看到
["你", "好", "开"]。-
它想说
"心"。 -
它心里对
"心"的自信程度(概率)是 0.8。 -
记录数据:
Token: "心",Old_Prob: 0.8。
-
-
Critic (助教): 看到
["你", "好", "开"]。-
它预测:这局大概能得 0.9 分(因为它觉得"开"后面接"心"是个好词,胜算大了)。
-
记录数据:
Value: 0.9。
-
4. 结束生成
-
句子结束。现在传送带上有完整的:
["你", "好", "开", "心"]。
5. 老师和裁判入场 现在拿着这完整的句子,给另外两个冻结的模型看:
-
Ref (老师): 看着
["你", "好", "开", "心"]。-
老师想:如果要我写,
"开"的概率是 0.5,"心"的概率是 0.9。 -
记录数据:
Ref_Prob: [0.5, 0.9]。
-
-
Reward Model (裁判): 看着
["你", "好", "开", "心"]。-
裁判觉得这句话很礼貌,给个高分。
-
记录数据:
Final_Score: 3.0。
-
第二阶段:贴标签(计算奖励流)
这是最关键的一步。我们要给 "开" 和 "心" 这两个字分别贴上“好坏标签”。
1. 算 KL 惩罚 (是不是说人话?)
-
字
"开": 学生概率 0.6,老师概率 0.5。差别不大。-
KL 惩罚 ≈ -0.01(微小的扣分)。
-
-
字
"心": 学生概率 0.8,老师概率 0.9。差别也不大。-
KL 惩罚 ≈ -0.01。
-
2. 算总奖励 (Reward分配) 这里就是数据的“流动”点:Final Score 只加在最后一个字上!
-
字
"开"的奖励: 只有 KL 惩罚 = -0.01。 -
字
"心"的奖励: KL 惩罚 + 裁判大分 = -0.01 + 3.0 = 2.99。
3. 算优势 (Advantage - 也就是倒推功劳) 这是 RL 的灵魂。
-
对于字
"心":-
实际拿到 2.99 分。
-
Critic 刚才预测只有 0.9 分。
-
优势 (Advantage): 2.99 - 0.9 = +2.09。
-
结论: 这是一个巨大的惊喜!
"心"这个字选得太棒了!
-
-
对于字
"开":-
虽然
"开"自己只拿了 -0.01 分。 -
但是!它后面紧跟着一个拿了超级高分的
"心"。 -
优势 (Advantage): 根据 GAE 算法,未来的好事也要算在前人头上。
-
结论:
"开"也是个好动作,因为它引出了后面的好结果。优势也是 正的(比如 +1.5)。
-
第三阶段:修改大脑(反向传播)
现在,数据流向了 Actor 和 Critic 的参数。
1. 修改 Actor (学生)
-
回顾数据:
-
"开"的优势是 +1.5 (正)。 -> 指令: 下次把"开"的概率从 0.6 提上去! -
"心"的优势是 +2.09 (大正)。-> 指令: 下次把"心"的概率从 0.8 狠狠提上去!
-
2. 修改 Critic (助教)
-
回顾数据:
-
在
"开"的时候,你预测 0.2,实际后面拿了 3.0。你太保守了!-> 指令: 下次把这里的预测值调高。 -
在
"心"的时候,你预测 0.9,实际拿了 2.99。你还是太保守了!-> 指令: 调高!
-
总结:数据流动的全景图
把这个过程想象成一条河流:
-
源头 (Actor): 流出了
"开"(0.6)->"心"(0.8)。 -
支流 (Critic): 伴随着流出了
0.2->0.9的预测。 -
入海口 (Reward): 在终点
"心"处,裁判注入了3.0的染料(分数)。 -
回流 (Backprop):
-
3.0的染料染红了"心",告诉它“你很棒”。 -
3.0的染料逆流而上,染红了"开",告诉它“你铺垫得好”。
-
-
河床变形 (Update): 河道(参数)被染料改变了形状,下次水流(概率)就会流向分数高的地方。
679

被折叠的 条评论
为什么被折叠?



