监督微调(SFT)
在 Llama 2 的监督微调(Supervised Fine-Tuning, SFT)阶段,研究团队对模型进行了进一步的调优,以提升其生成对话和其他任务的表现。以下是 SFT 过程的详细讲解:
1. 数据收集与准备
为了开始 SFT 过程,研究团队首先从公开的指令微调数据集中挑选了高质量的数据,用于模型的初步调优。这些数据来自多个第三方来源,但团队发现很多数据集缺乏多样性,质量也有所欠缺,特别是在对话风格的指令对齐方面。因此,团队专注于收集数千个高质量的 SFT 数据,这些数据由供应商基于注释制作。例如,团队积累了27,540条注释 。
2. 质量优先原则
尽管有很多第三方数据可供使用,团队发现这些数据的质量和多样性不足,因此决定通过供应商和专业标注人员生成小规模但高质量的数据集。通过少量高质量的指令微调数据,研究发现模型的性能得到了显著提高。这与 Zhou 等人(2023)的发现一致,说明干净、优质的指令微调数据集可以实现高水平的模型性能 。
3. SFT 的细节
在监督微调过程中,团队使用了以下超参数:
- 学习率调度:使用余弦学习率调度,初始学习率为 2 × 1 0 − 5 2 \times 10^{-5} 2×10−5,权重衰减为 0.1。
- 批次大小:每个批次包含 64 个样本,序列长度为 4096 个 token。
- 目标:每个训练样本包括一个提示(prompt)和一个回答(answer)。为了确保填充所有序列长度,团队将训练集中的所有提示和答案串联起来。一个特殊的 token 用于区分提示和答案部分。
- 损失函数:训练时,团队只对答案部分的 token 进行反向传播,忽略提示部分的损失,确保模型只学习如何生成正确的答案 。
微调(Fine-tuning):基于人类反馈的强化学习(RLHF)
人类偏好数据收集
在 Llama 2 的强化学习微调阶段,模型被进一步优化,以便更好地符合人类的偏好和指令。整个 RLHF 过程首先依赖于人类偏好数据收集,即通过人类标注者从模型生成的多个候选回答中挑选出最符合偏好的答案。具体步骤如下:
- 数据来源:团队从多个开源数据集和内部数据源收集了偏好数据。表 6 列出了这些数据源的统计信息,包括对话的轮数、每个示例中的 token 数量等。这些偏好数据用于训练奖励模型,帮助 Llama 2 生成符合人类偏好的回答。
- 数据格式:每个数据示例由一个提示(prompt)和多个模型生成的候选答案组成。人类标注者根据每个回答的质量,选择最佳的候选答案 。
奖励模型的构建
奖励模型(Reward Model)的构建在整个**基于人类反馈的强化学习(RLHF)**过程中至关重要。奖励模型的主要任务是根据人类反馈的偏好对模型生成的答案进行评分,用以优化模型的行为,确保其生成结果更符合人类期望,尤其在帮助性和安全性方面。
1.输入与输出
奖励模型接收模型的提示(prompt)和相应的模型生成的答案作为输入,输出一个标量分数,用以表示生成结果的质量。在 Llama 2 的训练过程中,模型生成的回答主要从两个维度进行评分:
- 帮助性(Helpfulness):评估模型生成的答案是否对用户有帮助,尤其是在问题解决、信息提供等方面。
- 安全性(Safety):评估生成的内容是否符合安全规范,是否避免了有害、冒犯或不当的信息。
为了更好地在这两个维度上进行评分,Llama 2 分别训练了两个独立的奖励模型:一个针对帮助性,另一个针对安全性。每个奖励模型都初始化自预训练的对话模型(chat model)检查点,这样可以确保奖励模型已经具备了从预训练中学到的丰富知识基础 。
2.训练目标
奖励模型的训练目标是将人类的偏好数据转换为模型可以学习的信号。在训练过程中,研究人员使用二元排序损失函数(binary ranking loss)来训练奖励模型:
L ranking = − log ( σ ( r θ ( x , y c ) − r θ ( x , y r ) ) ) L_{\text{ranking}} = -\log(\sigma(r_{\theta}(x, y_c) - r_{\theta}(x, y_r))) Lranking=−log(σ(rθ(x,yc)−rθ(x,yr)))
r θ ( x , y ) r_{\theta}(x, y) rθ(x,y) 表示给定提示 x x x 和生成结果 y y y 的评分(reward),由具有权重 θ \theta θ 的模型计算。
y c y_c yc 是人类标注者选择的最佳答案,而 y r y_r yr 是被拒绝的答案。
该损失函数旨在确保被人类标注者选择的答案 y c y_c yc 获得比被拒绝的答案 y r y_r yr 更高的评分。通过这种方式,模型可以逐步学习人类的偏好。
3.帮助性和安全性模型的优化
尽管帮助性和安全性在某些情况下会出现权衡,即提升安全性可能导致帮助性下降,团队通过分别训练这两个奖励模型来应对这一挑战。通过这种方法,两个模型能够专注于各自的优化目标,避免单一模型难以在帮助性和安全性之间平衡的困境 。
- 帮助性奖励模型(Helpfulness Reward Model):主要通过对人类反馈数据进行优化,专注于为有帮助的生成内容打高分。
- 安全性奖励模型(Safety Reward Model):通过专门的数据集(如有害内容或潜在风险内容)进行训练,确保模型生成的内容符合安全规范。
4.训练数据与评价标准
奖励模型的训练数据来自多个开源数据集和内部收集的偏好数据。表格 6 显示了训练数据的详细信息,包括对话轮次、每个示例的 token 数量等 。
在模型的评价过程中,团队使用了内部的 Meta Helpfulness 和 Meta Safety 数据集来评估奖励模型的表现。表格 7 列出了不同模型在帮助性和安全性上的表现,Llama 2 的奖励模型在这些内部测试集上表现优异 。
5.损失函数的扩展
为了进一步优化帮助性奖励模型,研究人员在二元排序损失函数的基础上引入了边距(margin)机制。边距用于区分不同强度的人类偏好,即当两个生成结果的差异较大时,给予模型更大的边距,使模型学会为质量更高的生成内容打更高的分数 。
奖励模型构建代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
# 定义奖励模型类
class RewardModel(nn.Module):
def __init__(self, hidden_size):
super(RewardModel, self).__init__()
# 这里假设奖励模型使用基于预训练模型的隐藏层表示,后面加一个线性层输出奖励值
self.hidden_size = hidden_size
# 用于奖励分数的线性投影层
self.reward_head = nn.Linear(hidden_size, 1)
def forward(self, hidden_states):
"""
输入: 预训练模型的隐藏层表示
输出: 奖励分数
"""
# 奖励分数输出
reward = self.reward_head(hidden_states)
# 将输出拉平,得到一个标量表示的奖励分数
return reward.squeeze(-1)
# 定义二元排序损失函数
def binary_ranking_loss(preferred_reward, rejected_reward, margin=0.0):
"""
假设 preferred_reward 是人类标注者选择的最佳回答的奖励分数,
rejected_reward 是被拒绝回答的奖励分数。
"""
# sigmoid 形式的排序损失函数
loss = -F.logsigmoid(preferred_reward - rejected_reward + margin)
return loss.mean()
# 模拟一个训练流程
def train_reward_model(pretrained_model_hidden_states, preferred_hidden_states, rejected_hidden_states, model, optimizer):
"""
pretrained_model_hidden_states: 模型生成的隐藏层表示
preferred_hidden_states: 被人类标注者选中的生成结果的隐藏层表示
rejected_hidden_states: 被人类标注者拒绝的生成结果的隐藏层表示
model: 奖励模型
optimizer: 优化器
"""
model.train()
# 前向传播计算奖励分数
preferred_reward = model(preferred_hidden_states)
rejected_reward = model(rejected_hidden_states)
# 计算损失
loss = binary_ranking_loss(preferred_reward, rejected_reward)
# 反向传播并更新权重
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
# 模拟奖励模型的使用和训练
if __name__ == "__main__":
# 假设隐藏层表示的维度为768
hidden_size = 768
batch_size = 8
# 初始化奖励模型和优化器
reward_model = RewardModel(hidden_size)
optimizer = torch.optim.Adam(reward_model.parameters(), lr=1e-5)
# 模拟预训练模型生成的隐藏层表示
preferred_hidden_states = torch.randn(batch_size, hidden_size)
rejected_hidden_states = torch.randn(batch_size, hidden_size)
# 训练奖励模型
loss = train_reward_model(None, preferred_hidden_states, rejected_hidden_states, reward_model, optimizer)
print(f"Training loss: {
loss}")
代码详细解释:
- RewardModel 类:
这个类实现了一个简单的奖励模型。模型的输入是从预训练的对话模型中得到的隐藏层表示(通常是来自生成的序列)。模型的输出是一个标量值,用于表示模型生成结果的奖励分数。reward_head
是一个简单的线性层,将隐藏层表示转换为一个奖励分数。 - binary_ranking_loss 函数:
这是一个自定义的损失函数,使用二元排序损失函数(Binary Ranking Loss)来优化奖励模型。它的目的是使得被人类标注者选中的生成结果(preferred_reward
)比被拒绝的生成结果(rejected_reward
)有更高的奖励分数。 - train_reward_model 函数:
该函数展示了如何训练奖励模型:- 输入:预训练模型的隐藏层表示、由人类标注的首选生成结果(
preferred_hidden_states
)和被拒绝的生成结果(rejected_hidden_states
)。 - 输出:模型计算的奖励分数。
- 训练步骤:使用优化器进行反向传播,并更新奖励模型的参数。
- 输入:预训练模型的隐藏层表示、由人类标注的首选生成结果(
- 主训练流程:
在__main__
函数中,我们初始化了奖励模型和优化器,并用随机生成的隐藏层表示来模拟训练过程。通过调用train_reward_model
函数,计算损失并更新模型参数。
关键概念:
- 隐藏层表示(hidden states):从预训练模型中获取的中间表示,通常是用于生成答案的某些 token 的向量表示。
- 二元排序损失函数(Binary Ranking Loss):通过将正确生成的结果奖励分数与被拒绝的结果奖励分数进行比较,模型学习到更符合人类偏好的生成模式。
强化学习算法
在 RLHF 阶段,Llama 2 使用了两种主要的强化学习算法来微调模型:
Proximal Policy Optimization (PPO)
在 Llama 2 的强化学习微调中,使用了 Proximal Policy Optimization (PPO) 算法来优化模型的策略,使得生成的答案更符合人类偏好。以下是对该过程的详细解释:
PPO 的作用
PPO 是一种强化学习算法,旨在通过梯度更新来优化策略。它与传统的策略梯度方法相比,更加稳定,避免了策略更新过程中较大的波动。Llama 2 使用了 PPO 来进一步提高其在帮助性(Helpfulness)和安全性(Safety)两个维度上的表现。
1. 目标函数的优化
在 Llama 2 的强化学习过程中,目标函数可以简化为如下形式:
arg max π E p ∼ D , g ∼ π [ R ( g ∣ p ) ] \arg\max_{\pi} \mathbb{E}_{p \sim D, g \sim \pi} [R(g \mid p)] argπmaxEp∼D,g∼π[R(g∣p)]
其中:
- π \pi π 是策略,即模型生成答案的过程。
- p p p 是提示(prompt),从数据集中采样。
- g g g 是生成的答案(generation),由策略生成。
- R ( g ∣ p ) R(g \mid p) R(g∣p) 是奖励函数,表示根据人类偏好对生成的答案进行评分。
Llama 2 通过 PPO 迭代地改进其策略,确保生成的答案越来越符合奖励模型的评分标准。
2. 奖励函数与 KL 惩罚项
在优化过程中,使用的最终奖励函数如下:
R ( g ∣ p ) = R c ( g ∣ p ) − β D KL ( π θ ( g ∣ p ) ∥ π 0 ( g ∣ p ) ) R(g \mid p) = R_c(g \mid p) - \beta D_{\text{KL}}(\pi_{\theta}(g \mid p) \parallel \pi_0(g \mid p)) R(g∣p)=Rc(g∣p)−βDKL(πθ(g∣p)∥π0(g∣p))
其中:
- R c ( g ∣ p ) R_c(g \mid p) Rc(g∣p) 是组合的奖励函数,结合了帮助性和安全性两个方面的评分。
- D KL D_{\text{KL}} DKL 是 KL 散度,用来衡量新策略和原始策略之间的偏差。
- β \beta β 是用于平衡奖励和 KL 惩罚的系数。为了训练的稳定性,控制策略过度偏离原策略,同时避免“奖励黑客”问题,即模型生成高奖励但低人类评分的答案。
在 Llama 2 中,帮助性和安全性是分开考虑的,特别是在处理潜在不安全提示时,优先优化安全性模型的分数。
3.PPO 训练过程
Llama 2 的 PPO 训练使用了以下超参数:
批次大小:512
迷你批次大小:64
PPO 剪裁阈值:0.2
学习率: 1 0 − 6 10^{-6} 10−6
AdamW 优化器参数: β 1 = 0.9 \beta_1 = 0.9 β1=0.9, β 2 = 0.95 \beta_2 = 0.95 β2=0.95, ϵ = 1 0 − 5 \epsilon = 10^{-5} ϵ=10−5
KL 惩罚系数 β \beta β:对于 7B 和 13B 模型设为 0.01,对于 34B 和 70B 模型设为 0.005。
每次 PPO 迭代都会根据最新的策略生成一批新的回答,然后根据奖励模型的评分对这些回答进行优化。为了提高训练效率,Llama 2 使用了 FSDP 技术(Fully Sharded Data Parallel),以支持更大的批次和快速更新。
PPO算法简化代码:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
class PolicyNetwork(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dim=128):
super(PolicyNetwork, self).__init__()
self.fc1 = nn.Linear(state_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.action_head = nn.Linear(hidden_dim, action_dim)
self.value_head = nn.Linear(hidden_dim, 1)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
action_logits = self.action_head(x)
state_value = self.value_head(x)
return action_logits, state_value
class PPOAgent:
def __init__(self, state_dim, action_dim, hidden_dim=128, clip_ratio=0.2, lr=3e-4):
self.policy_net = PolicyNetwork(state_dim, action_dim, hidden_dim)
self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr)
self.clip_ratio = clip_ratio
def select_action(self, state):
state = torch.from_numpy(state).float()
action_logits, _ = self.policy_net(state)
action_probs = torch.softmax(action_logits, dim=-1)
action_dist = torch.distributions.Categorical(action_probs)
action = action_dist.sample()
return action.item(), action_dist.log_prob(action)
def compute_advantages(self, rewards, values, next_values, gamma=0.99, lam=0.95):
# GAE-Lambda for advantage estimation
deltas = rewards + gamma * next_values - values
advantages = []
advantage = 0
for delta in reversed(deltas):
advantage = delta + gamma * lam * advantage
advantages.insert(0, advantage)
return torch.tensor(advantages)
def ppo_update(self, states, actions, log_probs_old, returns, advantages, epochs=10, batch_size=64):
for _ in range(epochs):
indices = np.random.permutation(len(states))
for i in range(0, len(states), batch_size):
batch_indices = indices[i:i + batch_size]
batch_states = states[batch_indices]
batch_actions = actions[batch_indices]
batch_log_probs_old = log_probs_old[batch_indices]
batch_returns = returns[batch_indices]
batch_advantages = advantages[batch_indices]
# Get new action logits and values
action_logits, values = self.policy_net(batch_states)
action_probs = torch.softmax(action_logits, dim=-1)
action_dist = torch.distributions.Categorical(action_probs)
log_probs_new = action_dist.log_prob(batch_actions)
# Ratio between new and old policy
ratio = torch.exp(log_probs_new - batch_log_probs_old)
# Policy loss with clipping
surr1 = ratio * batch_advantages
surr2 = torch.clamp(ratio, 1.0 - self.clip_ratio, 1.0 + self.clip_ratio) * batch_advantages
policy_loss = -torch.min(surr1, surr2).mean()
# Value loss
value_loss = nn.MSELoss()(values.squeeze(), batch_returns)
# Total loss
loss = policy_loss + 0.5 * value_loss
# Backpropagation
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 模拟环境状态、动作、奖励、下一状态等
def simulate_environment(state_dim, action_dim, max_steps=100):
agent = PPOAgent(state_dim, action_dim)
states = []
actions = []
rewards = []
log_probs = []
values = []
next_values = []
state = np.random.rand(state_dim)
for _ in range(max_steps):
action, log_prob = agent.select_action(state)
next_state = np.random.rand(state_dim)
reward = np.random.rand() # 假设随机奖励
# 获取状态价值
_, value = agent.policy_net(torch.from_numpy(state).float())
_, next_value = agent.policy_net(torch.from_numpy(next_state).float())
# 存储每一步的信息
states.append(state)
actions.append(action)
rewards.append(reward)
log_probs.append(log_prob)
values.append(value.item())
next_values.append(next_value.item())
state = next_state
# 转换为 torch tensor
states = torch.tensor(states, dtype=torch.float32)
actions = torch.tensor(actions, dtype=torch.int64)
log_probs = torch.stack(log_probs)
rewards = torch.tensor(rewards, dtype=torch.float32)
values = torch.tensor(values, dtype=torch.float32)
next_values = torch.tensor(next_values, dtype=torch.float32)
# 计算 advantage 和 returns
advantages = agent.compute_advantages(rewards, values, next_values)
returns = rewards + 0.99 * next_values
# 执行 PPO 更新
agent.ppo_update(states, actions, log_probs, returns, advantages)
# 运行 PPO 训练模拟
simulate_environment(state_dim=4, action_dim=2)
代码讲解:
- PolicyNetwork:
- 这是一个神经网络,用于输出动作策略(即生成动作的概率分布)和状态价值(即该状态的预期回报)。
- 网络有两部分输出:
action_head
用于生成动作的概率分布,value_head
用于估计状态的价值。
- PPOAgent:
- 这是 PPO 算法的核心部分,包含策略网络和用于更新策略的函数。
- select_action:根据当前状态,生成动作及其对应的 log 概率。
- compute_advantages:使用 GAE-Lambda 估计优势函数,用于评估当前策略的表现相对于基准策略的好坏。
- ppo_update:通过 PPO 算法更新策略和状态值函数。该函数使用剪裁比率的方式确保更新的稳定性。
- simulate_environment:
- 这是一个环境模拟函数,创建了一个简化的环境,其中每一步的状态、动作和奖励都是随机生成的。
- 该函数模拟了一个完整的训练过程:在每个时间步生成动作,存储对应的状态、奖励、log 概率和状态价值,最后计算优势并执行 PPO 更新。
- PPO 算法关键点:
- 策略更新:通过计算新旧策略之间的比值
ratio
,使用剪裁机制确保策略不会在更新过程中偏离得太远。 - 值函数更新:优化状态价值函数,使其更好地估计当前状态的预期回报。
- 优势函数:通过 GAE-Lambda 算法估计每个状态的优势,用于更新策略。
- 策略更新:通过计算新旧策略之间的比值
PPO如何结合奖励模型使用
1. 初始策略生成
Llama 2 的模型首先基于给定的提示(Prompt)生成多个候选答案。这个过程是通过现有的预训练模型或之前的策略模型完成的,生成的文本序列作为策略(Policy)的一部分。
在策略生成时,模型的任务是根据当前的策略(即当前的文本生成模型)为给定的提示生成一系列可能的输出。这个生成过程可以用分布 π θ ( g ∣ p ) \pi_{\theta}(g \mid p) πθ(g∣p) 来描述,其中:
- g g g 是生成的答案(即模型生成的文本)
- p p p 是提示(Prompt),模型根据它来生成答案。
- π θ \pi_{\theta} πθ 是当前策略,参数 θ \theta θ 是模型的权重。
2.奖励模型的评分
生成答案后,Llama 2 使用奖励模型来对这些生成的候选答案进行评分。奖励模型的目标是根据人类反馈学习到的偏好,对生成的文本进行评估。
奖励模型输入:生成的文本序列 g g g 以及提示 p p p。
奖励模型输出:为生成的文本打分,得到一个奖励分数 R ( g ∣ p ) R(g \mid p) R(g∣p)。奖励模型通过对文本的质量进行评分,结合多个维度,比如生成的文本是否对用户有帮助(Helpfulness)或是否安全(Safety)。
Llama 2 通常会训练多个奖励模型,分别用于优化不同的目标,如帮助性和安全性。这些奖励分数为后续的策略优化提供了反馈信号。
3.PPO策略优化
PPO 使用奖励模型的评分来调整模型的生成策略。在 PPO 中,模型的策略更新基于以下公式:
R ( g ∣ p ) = R c ( g ∣ p ) − β D KL ( π θ ( g ∣ p ) ∥ π 0 ( g ∣ p ) ) R(g \mid p) = R_c(g \mid p) - \beta D_{\text{KL}}(\pi_{\theta}(g \mid p) \parallel \pi_0(g \mid p)) R(g∣p)=Rc(g∣p)−βDKL(πθ(g∣p)∥π0(g∣p))
奖励信号 R ( g ∣ p ) R(g \mid p) R(g∣p):这是模型获得的总奖励,其中 R c ( g ∣ p ) R_c(g \mid p) Rc(g∣p) 是奖励模型给出的帮助性或安全性的评分。
KL 惩罚项 D KL D_{\text{KL}} DKL:为了防止策略偏离预训练模型太远,PPO 引入了 KL 散度作为惩罚项,控制策略的更新幅度。过大的偏差可能导致生成结果偏离模型的语言流畅性或人类期望。具体来说, D KL ( π θ ∥ π 0 ) D_{\text{KL}}(\pi_{\theta} \parallel \pi_0) DKL(πθ∥π0) 用来衡量新策略 π θ \pi_{\theta} πθ 和原始策略 π 0 \pi_0 π0 之间的差异。
4.PPO 的损失函数
PPO 的损失函数通过结合策略的优势(Advantage)以及 KL 惩罚项,确保模型策略的更新既能获得更高的奖励,又不会偏离之前的策略。PPO 的目标是优化以下损失函数:
L ( θ ) = E [ min ( π θ old ( a ∣ s ) π θ ( a ∣ s ) A π θ old , , clip ( π θ old ( a ∣ s ) π θ ( a ∣ s ) , 1 − ϵ , 1 + ϵ ) A π θ old ) ] L(\theta) = \mathbb{E} \left[ \min \left( \frac{\pi_{\theta_{\text{old}}}(a \mid s)}{\pi_{\theta}(a \mid s)} A_{\pi_{\theta_{\text{old}}},} , \text{clip} \left( \frac{\pi_{\theta_{\text{old}}}(a \mid s)}{\pi_{\theta}(a \mid s)}, 1 - \epsilon, 1 + \epsilon \right) A_{\pi_{\theta_{\text{old}}}} \right) \right] L(θ)=E[min(πθ(a∣s)πθold(a∣s)Aπθold,,clip(πθ(a∣s)πθold(a∣s),1−ϵ,1+ϵ)Aπθold)]
其中:
- A π θ old A_{\pi_{\theta_{\text{old}}}} Aπθold 是优势函数,衡量新策略相对于旧策略的改进程度。
- 策略比率 π θ ( a ∣ s ) π θ old ( a ∣ s ) \frac{\pi_{\theta}(a \mid s)}{\pi_{\theta_{\text{old}}}(a \mid s)} πθold(a∣s)πθ(a∣s) 用来衡量新策略与旧策略在生成文本时的变化程度。
- ϵ \epsilon ϵ 是剪裁阈值,防止策略比率变化过大。
5.结合奖励模型的 PPO 更新
- 生成的答案评分:使用奖励模型对每个生成的答案 g 打分,得到一个奖励值。
- PPO 策略更新:PPO 使用这些奖励分数,通过策略更新优化模型的生成策略。每次更新时,PPO 会通过剪裁比率来控制策略的变化幅度,并通过 KL 散度惩罚确保生成策略不会偏离原始模型太远。
在训练过程中,模型会生成大量的候选答案,PPO 会根据奖励模型的评分进行反向传播,调整策略参数,使得模型在未来生成的答案更加符合奖励模型的高分评判标准。
注意:上述中生成策略的是一个神经网络
在 RLHF 中,Llama 2 的生成策略是一个预训练好的语言模型,这个模型是基于 Transformer 架构的。以下是生成策略神经网络的基本特点:
- Transformer 架构:
- Llama 2 使用了 Transformer 模型,它由多层的自注意力机制和前馈神经网络组成。每一层 Transformer 块的输入是上一步生成的文本的表示,它通过注意力机制捕捉上下文信息并生成下一个 token。
- 这种模型能够处理自然语言中的序列数据,特别擅长处理长文本的生成和理解任务。
- 输入和输出:
- 输入:生成策略的输入是提示(prompt)或者先前生成的文本序列。
- 输出:模型通过神经网络输出下一个 token 的概率分布,然后根据概率分布选择下一个生成的词(即策略的输出)。这个生成过程是逐步进行的,模型会基于之前生成的结果继续生成下一个 token,直到达到指定的结束条件(如句子结束或最大长度)。
- 神经网络的参数更新:
- 生成策略(即 Transformer 语言模型)的参数 θ\thetaθ 是通过**PPO(Proximal Policy Optimization)**来更新的。在 PPO 中,模型的输出是生成的文本序列,而奖励模型为这些生成的文本评分。PPO 使用这些评分信号,通过反向传播来调整生成策略的神经网络参数,使其能够在未来生成更高评分的文本。
PPO 如何更新生成策略神经网络
- 生成文本序列:模型根据提示或之前的文本序列生成新文本。生成的每个词都基于策略网络(神经网络)的输出概率分布,从中采样。
- 获得奖励信号:生成文本后,奖励模型会为这些生成的结果打分,评分依据是生成内容的帮助性、安全性等方面。这个评分作为强化学习中的奖励信号。
- 计算优势函数:PPO 使用奖励信号和状态价值来计算优势函数,这表示当前策略相较于之前策略的改进程度。
- 策略更新:通过 PPO 的更新机制,神经网络的参数会进行调整,以便在未来生成符合奖励模型高分标准的文本。PPO 会控制策略更新的幅度,确保生成策略逐步优化,防止过度调整。
拒绝采样(Rejection Sampling)
在 Llama 2 的训练过程中,拒绝采样(Rejection Sampling) 是一种用来选择最佳模型输出的强化学习方法,用于提升模型生成的质量。相比于 PPO(Proximal Policy Optimization),拒绝采样着重于从模型生成的多个候选答案中选择最优质的答案。以下是拒绝采样在 Llama 2 中的详细工作流程:
1. 拒绝采样的基本过程
拒绝采样的核心思想是在每次生成时,模型会为每个提示(Prompt)生成多个候选答案,然后通过奖励模型对每个候选答案进行评分,选择评分最高的答案进行进一步的微调。这个方法有助于最大限度地利用奖励模型提供的反馈信号,从而逐步提高模型生成的质量。
步骤:
- 对于每个提示 p p p,模型根据当前策略 π θ \pi_{\theta} πθ 生成多个候选答案 g 1 , g 2 , … , g K g_1, g_2, \ldots, g_K g1,g2,…,gK。
- 奖励模型对每个候选答案 g k g_k gk 进行评分,获得对应的奖励分数 R ( g k ∣ p ) R(g_k \mid p) R(gk∣p)。
- 从这些候选答案中选择奖励分数最高的答案作为最优答案,之后对模型进行更新和微调。
这种方法的关键优势在于,它不需要一次就生成最优答案,而是通过探索多种可能性,最大化选择最优答案的机会。
2. 与 PPO 的区别
Llama 2 同时使用了 PPO 和拒绝采样来优化生成策略。这两种强化学习算法的主要区别在于:
- 广度:在拒绝采样中,模型为每个提示生成多个候选答案并选择最优的进行微调,而 PPO 只生成一个答案并立即使用该结果进行策略更新。
- 深度:在 PPO 中,每一步生成的答案是基于上一步更新后的策略生成的,策略更新是逐步进行的。而在拒绝采样中,所有候选答案是在模型当前策略下生成的,选择最优答案后再统一进行策略更新。
因此,拒绝采样更多地强调在单次生成中进行广泛的探索,通过多个候选答案寻找最优解,而 PPO 强调的是深度优化,每一步生成结果都基于上一步的策略更新。
3. 拒绝采样在 Llama 2 中的应用
在 Llama 2 的早期版本(如 RLHF V1 到 V3)中,模型主要依赖拒绝采样来选择最优答案,并进行后续的微调。在后续版本中(如 RLHF V4 之后),Llama 2 将拒绝采样与 PPO 结合使用,先通过拒绝采样选择最优答案,然后使用 PPO 进行进一步的优化。
在每个迭代过程中,拒绝采样会对每个提示生成多个候选答案,使用奖励模型打分并选择最佳的答案进行训练。图 7 中展示了在不同样本数量 NNN 下,最大奖励和中位奖励之间的差距。随着生成的样本数量增多,模型可以通过探索更多的候选答案找到最优解,从而提升模型的整体表现 。
4. 温度调节与样本选择
在拒绝采样过程中,温度参数对于生成样本的多样性起到了关键作用。较高的温度可以增加生成的多样性,探索更多的可能性,而较低的温度则使模型生成的结果更加集中。在 Llama 2 中,随着 RLHF 迭代的进行,温度参数也会根据不同阶段的需要进行调整,以便在探索和利用之间找到平衡。
在图 8 中,展示了在不同温度下,样本数量对奖励最大化的影响。通过调整温度,模型可以更有效地在多样性和奖励分数之间取得平衡 。