RLHF+DPO微调大型语言模型
本文是关于大型语言模型(LLMs)系列文章的一部分。在上一篇文章中,我讨论了OpenAI和DeepSeek如何使用强化学习创建其最新的高级推理模型。在这里,我将讨论另一种使用强化学习(RL)对LLMs进行基于人类反馈的微调的方法(即RLHF),以及一种更高效的重新表述方法(即DPO)。我将首先回顾这些方法背后的关键思想,然后分享一个带有Python代码的具体示例。
2020年,OpenAI发布了GPT-3,这是一个大型语言模型(LLM),能够通过仅仅“看到”几个例子来执行任意的自然语言处理任务。这包括为模型编写聪明的输入(即提示词)以使其执行所需的任务(例如翻译、问答和完形填空任务)[1]。
尽管GPT-3的性能具有开创性,但它仍然距离我们今天所见的实际应用中的LLMs还很远。换句话说,需要额外的训练才能将GPT-3转化为ChatGPT。
1、InstructGPT
GPT-3发布两年后,OpenAI发表了InstructGPT,这是GPT-3的一个经过微调的版本,更加符合人类的偏好[2]。
这是关键的一步,因为GPT-3的基础模型通常不会生成有价值的用户完成内容(除非用户是一个熟练的提示词编写者)。此外,由于它是从互联网上各种各样的数据中训练出来的,它的回答可能是有毒的和冒犯性的。
另一方面,InstructGPT是一个直观的聊天机器人,生成的帮助和无害的回答[2]。这是通过两轮微调实现的。第一轮是监督微调(SFT),即基于聊天数据(即输入-响应对)。第二轮是来自人类反馈的强化学习(RLHF)。在这里,我们将重点放在后者的方法上。
3、来自人类反馈的强化学习(RLHF)
强化学习(RL)允许模型通过试错来学习。这包括在训练过程中为模型预测分配奖励,并根据这些奖励更新其参数。
来自人类反馈的强化学习(RLHF)是一种大规模地训练模型以符合人类偏好的方式[2]。与人类在训练过程中实时为模型输出分配奖励不同,奖励模型通过监督的方式从人类偏好中训练,并用作代理。
3.1 奖励模型
为了训练奖励模型,对于单个提示,生成了多个GPT-3的响应,然后人类标注员根据详细的有用性和无害性指南对这些响应进行排名[2]。
这是一个重要的细节,因为对于人类来说,**评估响应的相对质量(即对它们进行排名)**比从头开始编写最优响应要容易得多(也更快)。这个过程的结果是高质量和大量训练数据用于奖励模型。
3.2 近端策略优化(PPO)
然后,使用奖励模型通过近端策略优化(PPO)来训练InstructGPT。这是一种高效的强化学习算法,根据奖励值更新模型参数[3]。
PPO结合了两个关键思想,以创建一个比真正的强化学习目标(即找到最大化所有可能轨迹的预期累积奖励的策略(模型))更简单和更稳定的优化。
首先,它使用了一个替代目标,这意味着一个近似于真正目标的目标。其次,它引入了裁剪以确保优化步骤不会太大。PPO目标函数如下所示。
3.3 RLHF的局限性
虽然在RLHF之后,InstructGPT在人类偏好指标上优于GPT-3及其SFT检查点[2],但它有一个关键的局限性。也就是说,最终模型的质量仅限于用于创建奖励模型的训练数据。
这种上限与其他RL方法不同,其他方法理论上可以通过额外的训练不断提高性能[4]。如果我们反思这一点,可能会引起怀疑。
如果RLHF从根本上受到用于创建奖励的训练数据的限制,我们是否可以直接使用这种偏好数据来直接训练LLM? 尽管这种逻辑有些模糊,但答案是可以。
4、直接策略优化(DPO)
直接策略优化(DPO)包括将RLHF重新表述为一个简单的文本分类问题[5]。其作者展示了在KL散度约束下,RLHF的最优策略可以用封闭形式推导出来。
因此,我们可以最小化模型与最优策略之间对优选-不优选响应训练对的对数概率差异。该过程对应的损失函数如下所示[5]。
DPO的主要优点是我们可以在显著简化训练设置的情况下获得与RLHF相同的性能提升。为了展示这一点,让我们逐步介绍一个具体的示例。
5、示例:基于视频标题偏好的Qwen2.5–0.5B微调
LLM在内容创作方面的“品味”往往较差。这就是为什么当有人使用AI撰写博客或其他类型的内容时,很容易被察觉。
在这里,我将展示如何通过微调Qwen2.5–0.5B-Instruct来生成基于我个人偏好的YouTube标题,使用DPO。数据集、模型和示例代码都可以在Hugging Face库和GitHub上免费获取。
5.1 整理偏好数据
这个过程中最重要且耗时的部分是生成偏好数据集。我的生成过程如下:
- 列出114个视频创意
- 使用Qwen2.5–7B-Instruct(通过Together AI的API)为每个创意生成5个标题
- 对于每个创意,创建10个(即5选2)头对头标题配对
- 手动审查所有1140个标题配对并标记哪个更好
我花了大约3小时手动标记所有1140个配对。完成后,我将数据重新格式化为三列:提示、选择和拒绝,以匹配trl库的DPOTrainer期望的格式。
您可以查看完整的数据集这里。用于生成标题配对的代码可以自由获取这里。
5.2 使用DPO进行微调
在准备好偏好数据后,我们可以做简单的事情:微调模型。我们首先导入一些有用的库。
from datasets import load_dataset
from trl import DPOConfig, DPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch
接下来,我们将导入训练数据集(来自步骤1)和基础模型(Qwen2.5–0.5B-Instruct)。
# 加载数据集
dataset = load_dataset("shawhin/youtube-titles-dpo")
# 加载模型和分词器
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token # 设置填充标记
在微调此模型之前,了解其生成标题的能力是有帮助的。因此,让我们使用验证数据集中的提示来生成一个标题。
# helper function
def format_chat_prompt(user_input):
"""
Formats user input into the chat template format with <|im_start|> and
<|im_end|> tags.
Args:
user_input (str): The input text from the user.
Returns:
str: Formatted prompt for the model.
"""
# Format user message
user_prompt = f"<|im_start|>user\n{user_input}<|im_end|>\n"
# Start assistant's turn
assistant_prompt = "<|im_start|>assistant\n"
# Combine prompts
formatted_prompt = user_prompt + assistant_prompt
return formatted_prompt
# Set up text generation pipeline
generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
# Example prompt
prompt = format_chat_prompt(dataset['valid']['prompt'][0][0]['content'])
# Generate output
outputs = generator(prompt, max_length=100, truncation=True,
num_return_sequences=1, temperature=0.7)
print(outputs[0]['generated_text'])
<|im_start|>user
Given the YouTube video idea write an engaging title.
**Video Idea**: intro independent component analysis
**Additional Guidance**:
- Title should be between 30 and 75 characters long
- Only return the title idea, nothing else!<|im_end|>
<|im_start|>assistant
"Unlocking Independent Component Analysis: The Key to Understanding Your Data!"
我们可以看到这个标题不太好。它太长了,包含了一些模糊的信息,比如“理解数据的关键!”。这与我实际用于 ICA 视频的标题形成鲜明对比:“独立成分分析 (ICA) | EEG 分析示例代码”
为了使模型完成更符合我的偏好,让我们对其进行微调。首先,我们需要定义 DPO 的训练参数。在这里,我使用批处理大小为 8 并训练 3 个时期。每个时期都会保存一个检查点,并在最后加载最好的一个。
# define training args
ft_model_name = model_name.split('/')[1].replace("Instruct", "DPO")
training_args = DPOConfig(
output_dir=ft_model_name,
logging_steps=25,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
save_strategy="epoch",
eval_strategy="epoch",
eval_steps=1,
)
接下来,我们可以使用DPOTrainer训练模型。
# train model
trainer = DPOTrainer(
model=model,
args=training_args,
processing_class=tokenizer,
train_dataset=dataset['train'],
eval_dataset=dataset['valid'],
)
trainer.train()
该模型在第 2 轮之后开始过拟合,因此我们将使用该检查点作为最终模型。以下是经过微调的模型针对我们之前看到的相同视频创意得出的结果。
<|im_start|>user
Given the YouTube video idea write an engaging title.
**Video Idea**: intro independent component analysis
**Additional Guidance**:
- Title should be between 30 and 75 characters long
- Only return the title idea, nothing else!<|im_end|>
<|im_start|>assistant
Independent Component Analysis for Beginners
虽然它与我使用的真实模型不同,但与基础模型的生成相比,它仍然有显着的改进。
5.3 评估微调模型
虽然查看单个示例可以让我们对新模型的性能进行“氛围检查”,但这并不是一个强大的评估策略。一种方法是比较基础和微调模型标题生成。
不幸的是,品味和偏好之类的东西很难在标准评估中捕捉到(这就是我们首先使用 DPO 的原因)。此外,我无法使用 Judge LLM 有效地实现自动化(尽管尝试了几次 GPT-4o)。
这就是为什么我再次进行了一些手动数据标记。我的过程如下。
- 从我的初始列表中随机挑选 50 个视频标题创意
- 对于每个创意,分别使用基础模型和微调模型生成标题
- 手动查看所有 50 对并分配偏好标签
- 计算微调模型标题被偏爱的频率
由于我只考虑了 50 对,因此花费的时间明显更少(约 10 分钟)。最终结果是微调标题在 68% 的时间内被偏爱。这些数据的快照如下。
6、局限性
尽管三个微调模型的标题生成有了显著的改进,但仍有少量改进机会。
- Qwen 的 7b 版本生成了偏好数据,而 0.5b 版本经过了微调。未来应使用相同的模型来简化学习任务。
- 偏好数据集中的几个标题对都很糟糕。未来的工作应该尝试删除这些对并观察性能变化。
- 一些标题生成是中文的,可能是因为它在 Qwen 的训练数据中很突出。下次,我将尝试使用其他模型,例如 Llama 或 Gemma。
- 在这里,我使用了 500M 参数模型,因此它可以在我的笔记本电脑上快速运行。但是,更大的模型可能会在这个任务上表现更好。
- DPO 直接应用于指令调整模型。未来的工作应该首先尝试对真实标题示例进行一轮 SFT,然后使用 DPO 进行偏好调整。
7、结束语
LLM 可以开箱即用地执行各种任务。但是,通过快速工程或监督微调来根据人类偏好改进它们的响应可能具有挑战性。
在这里,我们讨论了 RLHF 和 DPO 如何通过根据相对人类偏好(即排名模型完成)对 LLM 进行微调来帮助实现这一点。然后,我们使用 DPO 将 Qwen-0.5B 中的 YouTube 标题创意与我的个人偏好进行匹配。
原文链接:Fine-tuning LLMs on Human Feedback (RLHF + DPO)
汇智网翻译整理,转载请标明出处