1.1 PPO 的关键角色
- 策略(LLM):我们正在训练的 LLM,用于生成更好的文本。
- 奖励模型:根据人类偏好对文本打分的 AI 裁判。
- 价值函数(辅助教练):另一个 AI 模型,充当“辅助教练”。它估计每个状态的“好坏”(当前文本生成的前景如何)。这有助于 PPO 进行更智能的更新。
1.2 训练 —— 五步之舞
-
生成文本(Rollout):LLM(策略)为不同的提示生成大量文本样本。
-
获取分数(奖励模型):奖励模型对每个文本样本进行打分。
-
计算优势(GAE —— “好多少”分数):这就是 GAE 的作用!它是一种巧妙的方法,用于计算每个单词选择的优劣,考虑奖励和价值函数的预测。(关于 GAE 的更多内容见下文!)
-
优化 LLM(策略更新):我们更新 LLM 的策略,以最大化一个特殊的 PPO 目标函数。这个目标函数现在有三个关键部分:
- 鼓励更高奖励:它推动 LLM 生成能够获得更高分数的文本。
- 限制策略变化(剪切代理目标):它防止策略在一次更新中变化过大,确保稳定性。
- KL 散度惩罚:如果新策略与旧策略偏离太远,它会增加惩罚,进一步增强稳定性。
- 熵奖励:它还包括一个熵奖励。简单来说,熵衡量 LLM 文本生成的“随机性”或“多样性”。增加熵奖励可以鼓励 LLM 更多地探索,而不是总是生成相同、可预测的响应。它有助于防止 LLM 过早变得“过于确定”,从而错过可能更好的策略。
- 更新价值函数(辅助教练更新):训练价值函数成为一个更好的“辅助教练”——更准确地预测不同文本生成的“好坏”。为什么选择 GAE?(蒙特卡洛与时间差分 —— 方差与偏差)
- 蒙特卡洛(MC)—— 高方差,低偏差:想象一下等到整个文本生成后再获得奖励,然后将该奖励分配给文本中的每一个单词。就像只有在小狗完成整个“坐下、待命、取回”动作序列后才给予奖励。对整个序列的奖励是准确的,但对单个动作(“坐下”与“待命”与“取回”)的信号非常嘈杂。高方差,学习速度慢。
- 时间差分(TD)—— 低方差,高偏差:想象一下在每个单词生成后给予奖励。“好单词!”“普通单词!”“很棒的单词!”信号不那么嘈杂,学习速度更快。但是,我们只是局部地判断单词,没有考虑整个文本的长期质量。可能会有偏差,可能会错过“大局”。
- GAE —— 平衡:广义优势估计(GAE)就像“多步 TD”。它考虑了多个步骤(单词)上的奖励,平衡了方差(MC)与偏差(TD)之间的权衡。就像不仅在结束时给予奖励,还在价值函数预测的指导下,为沿途的“小步骤”给予奖励。
概念性 PPO —— Python 伪代码
# 注意:这不是实际公式。
# 这是一个高度简化的预期目标版本
def ppo_loss_with_gae_entropy(old_policy_logprobs, new_policy_logprobs, advantages, kl_penalty_coef, clip_epsilon, entropy_bonus_coef):
"""概念性 PPO 损失函数,带有 GAE 和熵奖励(简化版)。"""
ratio = np.exp(new_policy_logprobs - old_policy_logprobs) # 概率比
# 剪切代理目标(限制策略变化)
surrogate_objective = np.minimum(ratio * advantages, np.clip(ratio, 1 - clip_epsilon, 1 + clip_epsilon) * advantages)
policy_loss = -np.mean(surrogate_objective)
# KL 散度惩罚(保持接近旧策略)
kl_divergence = np.mean(new_policy_logprobs - old_policy_logprobs)
kl_penalty = kl_penalty_coef * kl_divergence
# 熵奖励(鼓励探索)
entropy = -np.mean(new_policy_logprobs) # 简化版熵(概率越高 = 熵越低,取负值以最大化熵)
entropy_bonus = entropy_bonus_coef * entropy
total_loss = policy_loss + kl_penalty - entropy_bonus # 减去熵奖励,因为我们希望*最大化*熵
return total_loss
2.1 直接偏好优化(DPO)
DPO 是“新晋成员”——一种更简单、更高效的方式来进行偏好学习,跳过了 RL 的复杂性。
直截了当:DPO 就像是直接告诉 LLM:“响应 A 比响应 B 更好。多生成像 A 这样的响应,少生成像 B 这样的响应!”它省略了 RL 中用于策略优化的奖励模型这一中间环节。DPO —— 没有 RL 循环,只有偏好
DPO 避免了 PPO 的迭代 RL 循环。它直接基于人类偏好数据利用一个巧妙的损失函数对 LLM 进行优化。DPO 训练流程(简化版——强调简洁性):
• 偏好数据仍然是关键:与 PPO 一样,DPO 仍然从相同的关键成分开始:人类偏好数据(成对的响应,带有标签,指示哪个响应更受青睐)。人类反馈仍然是基础!
• 直接策略更新(分类式损失——直接使用 logits!):这是 DPO 的魔法所在。DPO 使用一个特殊的损失函数直接比较两个模型的 logits(概率之前的原始输出分数):
• 当前模型(正在训练中):我们将首选响应(响应 A)和非首选响应(响应 B)都输入到我们正在训练的当前 LLM 中,得到两者的 logits。
• 参考模型(旧版本):我们还将响应 A 和响应 B 输入到一个参考模型中。这通常是 LLM 的旧版本(比如我们开始时的 SFT 模型)。我们也会从参考模型中得到 logits。
• **DPO 的损失函数直接使用这两个模型的 logits 来计算损失,这与分类任务中使用的二元交叉熵损失非常相似。这个损失函数旨在:
• 增加首选响应的 logits(和概率):让当前模型在未来更有可能生成像响应 A 这样的响应。
• 减少非首选响应的 logits(和概率):让当前模型在未来更不可能生成像响应 B 这样的响应。
• 保持接近参考模型(隐式 KL 控制):损失函数还隐式地鼓励当前模型在行为上保持与参考模型的接近(使用参考模型的 logits),这有助于稳定性,类似于 PPO 的 KL 惩罚,但直接嵌入在损失函数中!
可以这样理解:DPO 的损失函数就像一个“偏好指南针”,直接根据首选和非首选响应的相对 logits 指导 LLM 的权重,而无需显式预测奖励
概念性 DPO 损失函数 —— Python 伪代码
# 注意:这不是实际公式。
# 这是一个高度简化的预期目标版本
def dpo_loss(policy_logits_preferred, policy_logits_dispreferred, ref_logits_preferred, ref_logits_dispreferred, beta_kl):
"""概念性 DPO 损失函数(简化版——直接使用 logits)。"""
# 1. 从 logits 中获取对数概率(当前和参考模型的首选和非首选响应)
policy_logprob_preferred = F.log_softmax(policy_logits_preferred, dim=-1).gather(...) # 提取首选响应中实际标记的对数概率
policy_logprob_dispreferred = F.log_softmax(policy_logits_dispreferred, dim=-1).gather(...) # 提取非首选响应中实际标记的对数概率
ref_policy_logprob_preferred = F.log_softmax(ref_logits_preferred, dim=-1).gather(...) # 同样适用于参考模型
ref_policy_logprob_dispreferred = F.log_softmax(ref_logits_dispreferred, dim=-1).gather(...)
# 2. 计算对数比率(使用对数概率——如前所述)
log_ratio = policy_logprob_preferred - policy_logprob_dispreferred - (ref_policy_logprob_preferred - ref_policy_logprob_dispreferred)
# 3. 偏好概率(Bradley-Terry 模型——隐式奖励信号)
preference_prob = 1 / (1 + np.exp(-beta_kl * log_ratio))
# 4. 二元交叉熵损失(直接优化策略)
dpo_loss = -np.log(preference_prob + 1e-8)
return dpo_loss
3.1 组相对策略优化(GRPO)
GRPO 是 DeepSeek AI 对 PPO 的一种聪明的改进,旨在更加高效,尤其是在复杂的推理任务中。
GRPO —— 更精简、更快速的 PPO
GRPO 就像是 PPO 的精简版表亲。它保留了 PPO 的核心思想,但去掉了独立的价值函数(辅助教练),使其更轻量、更快速。
GRPO 的诀窍:基于组的优势估计(GRAE)
GRPO 的魔法成分在于它如何估计优势。它不是使用辅助教练,而是使用一组由 LLM 生成的相同提示的响应来估计每个响应相对于组内其他响应的“好坏”。
GRPO 训练流程(简化版):
• 生成一组响应:对于每个提示,从 LLM 中生成多个响应的一组。
• 对组进行打分(奖励模型):获取组内所有响应的奖励分数。
• 计算组内相对优势(GRAE —— 组内比较):通过比较每个响应的奖励与组内平均奖励来计算优势。在组内对奖励进行归一化以得到优势。
• 优化策略(使用 GRAE 的 PPO 风格目标函数):使用一个 PPO 风格的目标函数更新 LLM 的策略,但使用这些组内相对优势。
概念性 GRPO 损失函数 —— Python 伪代码
# 注意:这不是实际公式。
# 这是一个高度简化的预期目标版本
def grae_advantages(rewards):
"""概念性组相对优势估计(结果监督)。"""
mean_reward = np.mean(rewards)
std_reward = np.std(rewards)
normalized_rewards = (rewards - mean_reward) / (std_reward + 1e-8)
advantages = normalized_rewards # 对于结果监督,优势 = 归一化奖励
return advantages
def grpo_loss(old_policy_logprobs_group, new_policy_logprobs_group, group_advantages, kl_penalty_coef, clip_epsilon):
"""概念性 GRPO 损失函数(对一组响应取平均)。"""
group_loss = 0
for i in range(len(group_advantages)): # 遍历组内的每个响应
advantage = group_advantages[i]
new_policy_logprob = new_policy_logprobs_group[i]
old_policy_logprob = old_policy_logprobs_group[i]
ratio = np.exp(new_policy_logprob - old_policy_logprob)
clipped_ratio = np.clip(ratio, 1 - clip_epsilon, 1 + clip_epsilon)
surrogate_objective = np.minimum(ratio * advantage, clipped_ratio * advantage)
policy_loss = -surrogate_objective
kl_divergence = new_policy_logprob - old_policy_logprob
kl_penalty = kl_penalty_coef * kl_divergence
group_loss += (policy_loss + kl_penalty) # 累加组内每个响应的损失
return group_loss / len(group_advantages) # 对组内损失取平均
4.1 PPO vs. DPO vs. GRPO
特性 | PPO | DPO | GRPO |
---|---|---|---|
是否需要奖励模型 | 是 | 否 | 否 |
是否需要辅助教练(价值函数) | 是 | 否 | 否 |
训练效率 | 中等 | 高 | 高 |
适用场景 | 通用 | 简单任务 | 复杂推理任务 |