在深入探讨PPO算法之前,我们需要先理解大模型背景下强化学习的基本框架。传统强化学习通常被建模为马尔可夫决策过程(MDP),这个数学框架由五个核心要素构成:
状态空间(S):环境可能处于的所有状态集合。在大语言模型中,状态可以理解为当前对话的上下文,即提示词(prompt)与模型已生成内容(rollout)的组合。
动作空间(A):智能体在各状态下可选的动作集合。对语言模型而言,动作就是从词表中选择下一个输出的token。
状态转移概率(P):在状态s采取动作a后转移到状态s'的概率。有趣的是,在大模型中这个转移是确定性的——选定token后上下文就被唯一更新。
奖励函数(R):评估动作好坏的即时反馈。通常来自人工标注的偏好数据或专门训练的奖励模型。
折扣因子(γ):权衡当前与未来奖励的重要性。一般设置为接近1的值(如0.99),因为语言生成需要长期连贯性。
关键理解:与传统RL不同,大模型的状态转移是确定性的,这简化了问题建模。但同时也带来了新挑战——如何设计合适的奖励函数来评估文本质量。
PPO算法在大模型训练中依赖于四个关键组件协同工作:
Actor模型:即我们需要训练的目标语言模型。它负责根据当前状态(上下文)选择下一个token(动作)。在实践中,Actor的参数会不断更新以优化策略。
Critic模型:用于估计状态价值函数V(s)。它评估当前对话上下文的质量,为优势函数计算提供基准。通常与Actor共享大部分网络参数以提高训练效率。
Reference模型:冻结参数的SFT模型,作为行为基准。它通过KL散度约束防止Actor偏离初始安全策略太远,这对保持生成质量至关重要。
Reward模型:专门训练的评分模型。它将"prompt+response"映射为标量分数,反映人类偏好程度。通常只在完整响应末尾提供单一奖励。
PPO的训练过程可以分解为三个关键阶段:
数据收集阶段(Rollout):
奖励计算阶段:
策略优化阶段:
PPO的总目标函数由三个精心设计的项组成:
code复制L = L_clip(θ) - aL_V(φ) + bH(π_θ)
其中θ代表Actor参数,φ代表Critic参数。让我们逐项拆解:
这是PPO最具创新性的部分,其数学表达式为:
code复制L_clip = E[min(r_t(θ)A_t, clip(r_t(θ),1-ε,1+ε)A_t)]
这里有两个关键要素:
概率比r_t(θ):新策略与旧策略选择某动作的概率比。反映策略更新的方向性。
优势函数A_t:评估当前动作相对于平均水平的优劣。正优势表示表现好于预期。
裁剪机制(ε通常取0.2)确保更新幅度受限,防止策略突变。具体来说:
Critic的优化目标是最小化:
code复制L_V = E[(V_φ(s_t) - G_t)^2]
其中G_t是折扣累积回报的估计,通过TD(λ)等方法计算。这项确保Critic能准确评估状态价值,为优势计算提供可靠基准。
熵项鼓励策略保持一定的随机性:
code复制H(π_θ) = -Σ π_θ(a|s)logπ_θ(a|s)
较高的熵意味着更均匀的动作分布,这对探索很有帮助。在实践中,我们通常设置较小的系数b(如0.01)来平衡探索与利用。
准确估计优势函数A_t是PPO成功的关键。常见方法包括:
简单时序差分(TD):
code复制A_t = r_t + γV(s_{t+1}) - V(s_t)
计算简单但方差较大。
广义优势估计(GAE):
code复制A_t^GAE = Σ(γλ)^l δ_{t+l}
其中λ∈[0,1]是权衡参数。GAE通过引入多步回报平滑了估计,在实践中表现更稳定。
工程经验:λ通常取0.9-0.95,γ取0.99。这些超参对训练稳定性影响很大,需要小心调整。
初始化阶段:
Rollout阶段:
python复制def rollout(actor, prompts):
responses = []
old_logprobs = []
for prompt in prompts:
tokens, logprobs = actor.generate(prompt)
responses.append(tokens)
old_logprobs.append(logprobs)
return responses, old_logprobs
奖励计算:
python复制def compute_rewards(prompts, responses):
rm_scores = reward_model(prompts, responses)
ref_logprobs = reference_model(prompts, responses)
kl = old_logprobs - ref_logprobs
return rm_scores - beta*kl
优势估计:
python复制def compute_advantages(rewards, values, gamma=0.99, lam=0.95):
deltas = rewards[:-1] + gamma*values[1:] - values[:-1]
advantages = []
advantage = 0
for delta in reversed(deltas):
advantage = delta + gamma*lam*advantage
advantages.insert(0, advantage)
return advantages
策略优化:
python复制def ppo_update(actor, critic, batch, clip_eps=0.2):
# 计算新策略概率比
new_logprobs = actor.evaluate_actions(batch.states, batch.actions)
ratios = torch.exp(new_logprobs - batch.old_logprobs)
# 裁剪目标函数
surr1 = ratios * batch.advantages
surr2 = torch.clamp(ratios, 1-clip_eps, 1+clip_eps) * batch.advantages
actor_loss = -torch.min(surr1, surr2).mean()
# Critic损失
value_loss = F.mse_loss(critic(batch.states), batch.returns)
# 熵奖励
entropy_loss = -entropy(actor(batch.states)).mean()
# 总损失
total_loss = actor_loss + 0.5*value_loss + 0.01*entropy_loss
total_loss.backward()
| 超参数 | 典型值 | 作用 |
|---|---|---|
| clip_ε | 0.1-0.3 | 控制策略更新幅度 |
| γ | 0.9-0.99 | 未来奖励折扣因子 |
| λ | 0.9-0.95 | GAE平滑系数 |
| β | 0.1-0.2 | KL惩罚权重 |
| 学习率 | 1e-6-1e-5 | 模型参数更新步长 |
| batch_size | 32-256 | 每次更新样本数 |
| epoch | 3-10 | 数据重复利用次数 |
奖励值不稳定:
生成质量下降:
训练不收敛:
内存优化:
训练加速:
稳定性提升:
PPO变体:
多目标优化:
样本效率提升: