当前位置:AIGC资讯 > AIGC > 正文

使用直接偏好优化策略微调Mistral-7b模型

译者 | 朱先忠

审校 | 重楼

引言

通常,经过预训练的大型语言模型(LLM)只能执行下一个标记预测,使其无法回答问题。这就解释了为什么这些基本模型还需要根据成对的指令和答案一步微调,最终才能够充当真正有用人工助理。然而,这个过程仍然可能存在缺陷:微调LLM可能存在偏见的甚至是有毒害性的输出结果。这也正是从人类反馈中强化学习(Reinforcement Learning from Human Feedback简称“RLHF)发挥作用的地方。

具体来说,RLHF能够为LLM提供不同的答案,这些答案将按期待的行为(有益性、毒害性等)进行排序。该模型学习这些候选者中输出最佳答案,从而模仿我们想要灌输的行为。通常这一过程被视为审查模型的一种方式,最近因能够有效提高模型性能而变得流行起来,模型neural-chat-7b-v3-1表现的那样

在本文中,我们将通过使用类似RLHF的技术:直接偏好优化(DPO)通过微调模型OpenHermes-2.5来创建NeuralHermes-2.5。为此,我们将介绍一个偏好数据集,描述DPO算法的工作原理,并将其应用于我们的模型。我们将看到它显著提高开源LLM排行榜上基本模型的性能。

和往常一样,可在GitHubGoogle Colab上获得本文示例工程的有关代码。

偏好数据集

偏好数据集不是标准化的,但它们通常由一组按人类排序的答案组成。这种排序是必不可少的,因为RLHF过程微调LLM以输出首选答案。以下是流行的偏好数据集Anthropic/hh-rlhf一个示例:

作者本人提供图像

容易看出,数据集的结构很简单:对于每一行,都有一个选择的(首选)答案和一个拒绝的答案。RLHF的目标是引导模型输出首选答案。

众所周知,偏好数据集成本高昂且难以制作,因为它们需要收集人类的手动反馈。这种反馈也是主观的,很容易偏向于自信(但错误)的答案或自相矛盾(不同的注释者有不同的价值观)。随着时间的推移,业界已经提出了几种解决方案来解决这些问题,例如用人工智能反馈(RLAIF)取代人类反馈。

另一方面,这些数据集也往往比微调数据集小得多。为了说明这一点,优秀的neural-chat-7b-v3–1模型此模型发布时在Open LLM排行榜网站成为最好的70亿参数规模的LLM)使用518k个样本进行微调(Open Orca/SlimOrca),但RLHF(Intel/Orca_dpo_pars)仅使用12.9k个样本。在这种情况下,作者使用GPT-4/3.5生成答案以创建首选答案,并使用Llama-2-13b-chat生成拒绝答案。这是一种绕过人类反馈,只依赖不同性能水平的模型的聪明方法。

直接偏好优化

虽然RLHF的概念在机器人领域已经使用了很长一段时间,但在OpenAI的论文《从人类偏好微调语言模型》中,它被推广用于LLM。在这篇论文中,作者提出了一个框架,它能够训练奖励模型来近似人类反馈。然后,该奖励模型用于使用近端策略优化(PPO:https://arxiv.org/abs/1707.06347)算法来优化微调模型的策略。

作者本人提供图像

PPO的核心概念围绕着对策略进行较小的增量更新,因为较大的更新可能会导致不稳定或次优的解决方案。根据经验,不幸的是,这种技术仍然不稳定(损失发散),难以复制(大量的超参数,对随机种子敏感),并且计算成本高昂。

也正是直接偏好优化(DPO)发挥作用的地方。DPO通过将任务视为分类问题来简化控制。具体地说,它使用两个模型:经过训练的模型(或策略模型)和一个称为参考模型的副本。在训练过程中,目标是确保训练后的模型比参考模型输出更高的首选答案概率。相反,我们也希望它输出拒绝答案的较低概率。这意味着我们会因为糟糕的答案而惩罚LLM,而因为好的答案而奖励它。

作者本人提供图像

通过使用LLM本身作为奖励模型并采用二进制交叉熵目标,DPO有效地将模型的输出与人类偏好相一致,而不需要大量采样、奖励模型拟合或复杂的超参数调整。这样一来,就能够产生一个更稳定、更高效、计算要求更低的过程。

格式化数据

本文的这个例子中,我们将对优秀OpenHermes-2.5-Mistral-7B模型进行微调,这是一个只经过监督微调的Mistral-7B模型。为此,我们将使用Intel/orca_dpo_paries数据集来调整我们的模型并提高其性能。我们称这种模型为NeuralHermes-2.5-Mistral-7B。

具体来说,实现此操作的第一步安装所需的库,如下所示

pip install -q datasets trl peft bitsandbytes sentencepiece wandb

完成后,我们可以进行库导入。我还使用谷歌Colab中的秘密(secrets)标签来存储我的Hugging Face标志信息

import os
import gc
import torch

import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig
from datasets import load_dataset
from peft import LoraConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training
from trl import DPOTrainer
import bitsandbytes as bnb
from google.colab import userdata
import wandb

# 在Google Colab秘密的标签中定义
hf_token = userdata.get('huggingface')
wb_token = userdata.get('wandb')
wandb.login(key=wb_token)

model_name = "teknium/OpenHermes-2.5-Mistral-7B"
new_model = "NeuralHermes-2.5-Mistral-7B"

OpenHermes-2.5-Mistral-7B使用一个特定的聊天模板,称为ChatML。以下是使用此模板格式化的对话示例:

<|im_start|>system
You are a helpful chatbot assistant.<|im_end|>
<|im_start|>user
Hi<|im_end|>
<|im_start|>assistant
Hi, how can I help you?<|im_end|>

正如您所看到的,ChatML定义了不同的角色(系统、用户、助理),并附加了特殊的标志(<|im_start|>和<|im_end|>)来分隔它们。此外,DPOTrainer还要求具有三列的特定格式:提示(prompt)、选择(chosen)和拒绝(rejected)

我们的数据集包含四列:system(系统question(问题chatgpt和llama2–13b-chat。我们简单地将系统列和问题列连接到提示prompt列。我们还将chatgpt列映射到“已选择(chosen),并将llama2–13b-chat映射到“拒绝(rejected)。为了以可靠的方式格式化数据集,我们将使用分词器的apply_chat_template()函数,该函数已经使用了ChatML。

def chatml_format(example):
 # 格式化系统列
 if len(example['system']) > 0:
 message = {"role": "system", "content": example['system']}
 system = tokenizer.apply_chat_template([message], tokenize=False)
 else:
 system = ""

 # 格式化指令
 message = {"role": "user", "content": example['question']}
 prompt = tokenizer.apply_chat_template([message], tokenize=False, add_generation_prompt=True)

 # 设置所选答案的格式
 chosen = example['chosen'] + "<|im_end|>\n"

 # 格式化拒绝的答案
 rejected = example['rejected'] + "<|im_end|>\n"

 return {
 "prompt": system + prompt,
 "chosen": chosen,
 "rejected": rejected,
 }

# 加载数据集
dataset = load_dataset("Intel/orca_dpo_pairs")['train']

# 保存列数据
original_columns = dataset.column_names

# 分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"

# 格式化数据集
dataset = dataset.map(
 chatml_format,
 remove_columns=original_columns
)

现在,让我们打印一个格式化数据集的示例,以确认一切正常:

{'prompt': '<|im_start|>system\nYou are an AI assistant. You will be given a task. You must generate a detailed and long answer.<|im_end|>\n<|im_start|>user\nGenerate an approximately fifteen-word sentence that describes all this data: Midsummer House eatType restaurant; Midsummer House food Chinese; Midsummer House priceRange moderate; Midsummer House customer rating 3 out of 5; Midsummer House near All Bar One<|im_end|>\n<|im_start|>assistant\n',
'chosen': 'Midsummer House is a moderately priced Chinese restaurant with a 3/5 customer rating, located near All Bar One.<|im_end|>\n',
'rejected': ' Sure! Here\'s a sentence that describes all the data you provided:\n\n"Midsummer House is a moderately priced Chinese restaurant with a customer rating of 3 out of 5, located near All Bar One, offering a variety of delicious dishes."<|im_end|>\n'}

我们可以看到,该提示结合了系统和用户指令。由于add_generation_prompt=True参数的作用你会注意到其中还附加了助答案的开头部分。如果您想跳过这一步,可以直接将预处理的数据集用作mlabonne/chatml_dpo_pairs

使用DPO训练模型

接下来,我们定义LoRA配置来训练模型。正如英特尔的博客文章中所描述的,我们将秩值设置为等于lora_lfa,这是不寻常的(按一般经验都取2*r)。我们还要考虑所有带有适配器的线性模块。

# LoRA配置
peft_config = LoraConfig(
 r=16,
 lora_alpha=16,
 lora_dropout=0.05,
 bias="none",
 task_type="CAUSAL_LM",
 target_modules=['k_proj', 'gate_proj', 'v_proj', 'up_proj', 'q_proj', 'o_proj', 'down_proj']
)

现在我们准备加载我们想要使用DPO进行微调的模型。在这种情况下,需要两个模型:要微调的模型和参考模型。这主要是为了可读性,因为如果没有提供参考模型,DPOTrainer对象会自动创建参考模型。

# 要微调的模型
model = AutoModelForCausalLM.from_pretrained(
 model_name,
 torch_dtype=torch.float16,
 load_in_4bit=True
)
model.config.use_cache = False

# 参考模型
ref_model = AutoModelForCausalLM.from_pretrained(
 model_name,
 torch_dtype=torch.float16,
 load_in_4bit=True
)

最后一步包括向TrainingArguments和DPOTrainer提供所有超参数:

  • 其中,beta参数对DPO来说是唯一的,因为它控制着与初始策略的偏差(0.1是它的典型值)。
  • 与英特尔博客文章中描述的值相比,我们降低了学习率(从5e-4到5e-5)和步数(从1000到200)。我在运行几次后手动优化了这些值,以稳定训练并取得最佳效果。

我们现在可以开始训练模型了。请注意,它需要一个A100 GPU,大约需要1个小时才能完成训练。

# 训练参数
training_args = TrainingArguments(
 per_device_train_batch_size=4,
 gradient_accumulation_steps=4,
 gradient_checkpointing=True,
 learning_rate=5e-5,
 lr_scheduler_type="cosine",
 max_steps=200,
 save_strategy="no",
 logging_steps=1,
 output_dir=new_model,
 optim="paged_adamw_32bit",
 warmup_steps=100,
 bf16=True,
 report_to="wandb",
)

# 创建DPO训练器
dpo_trainer = DPOTrainer(
 model,
 ref_model,
 args=training_args,
 train_dataset=dataset,
 tokenizer=tokenizer,
 peft_cnotallow=peft_config,
 beta=0.1,
 max_prompt_length=1024,
 max_length=1536,
)

# 使用DPO微调模型
dpo_trainer.train()

我们的模型现在进行了微调。您可以在地址https://wandb.ai/mlabonne/NeuralHermes-2-5-Mistral-7B/runs/axe71gr0?workspace=user-mlabonne查看有关项目权重和偏差。以下是一些需要分析的有趣指标:

作者本人提供图像

有趣的是,尽管使用了100个热身步骤,但训练损失很快降至零(在50步之前)。与此同时,其他指标也在不断发展。

上图中,train/rewards/chosentrain/rewards/rejected图对应于训练模型和参考模型输出的对数概率之间的平均差。随着时间的推移,随着我们训练的模型学习到首选答案,它们会出现分歧,这是有道理的。另外,train/rewards/margins图也显示了这两个图之间的差异。最后,train/reward/accuracies图显示了选择首选答案的频率。经过训练的模型很快就达到了完美的准确度分数,这是一个好迹象,但也可能意味着首选答案和拒绝答案之间的差异太明显。

现在已经训练结束那么我们可以将适配器与原始模型合并到一起了。接下来,我们保存合并后的模型和分词器,然后将其推送到Hugging Face中心。

#保存所有工作
dpo_trainer.model.save_pretrained("final_checkpoint")
tokenizer.save_pretrained("final_checkpoint")

# 刷新内存
del dpo_trainer, model, ref_model
gc.collect()
torch.cuda.empty_cache()

# 在FP16(而不是NF4)中重新加载模型
base_model = AutoModelForCausalLM.from_pretrained(
 model_name,
 return_dict=True,
 torch_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 将基本模型与适配器合并
model = PeftModel.from_pretrained(base_model, "final_checkpoint")
model = model.merge_and_unload()

# 保存模型和分词器
model.save_pretrained(new_model)
tokenizer.save_pretrained(new_model)

# 然后将其推送到Hugging Face中心
model.push_to_hub(new_model, use_temp_dir=False, token=hf_token)
tokenizer.push_to_hub(new_model, use_temp_dir=False, token=hf_token)

现在,让我们看看我们的模型在实际测试中的表现。我们将格式化提示以提出一个基本问题:“What is a Large Language Model?什么是大型语言模型?

#格式化提示
message = [
 {"role": "system", "content": "You are a helpful assistant chatbot."},
 {"role": "user", "content": "What is a Large Language Model?"}
]
tokenizer = AutoTokenizer.from_pretrained(new_model)
prompt = tokenizer.apply_chat_template(message, add_generation_prompt=True, tokenize=False)

# 创建管道
pipeline = transformers.pipeline(
 "text-generation",
 model=new_model,
 tokenizer=tokenizer
)

# 生成文本
sequences = pipeline(
 prompt,
 do_sample=True,
 temperature=0.7,
 top_p=0.9,
 num_return_sequences=1,
 max_length=200,
)
print(sequences[0]['generated_text'])

以下是模型给出的答案:

A large language model is a type of artificial intelligence (AI) system that has been trained on vast amounts of text data. These models are designed to understand and generate human language, allowing them to perform various natural language processing tasks, such as text generation, language translation, and question answering. Large language models typically use deep learning techniques, like recurrent neural networks (RNNs) or transformers, to learn patterns and relationships in the data, enabling them to generate coherent and contextually relevant responses. The size of these models, in terms of the number of parameters and the volume of data they are trained on, plays a significant role in their ability to comprehend and produce complex language structures.

对应的中文意思是:“大型语言模型是一种基于大量文本数据进行训练的人工智能系统。这些模型旨在理解和生成人类语言,使其能够执行各种自然语言处理任务,如文本生成、语言翻译和问答。大型语言模型通常使用深度学习技术,如递归神经网络(RNN)或转换器,来学习数据中的模式和关系,使其能够生成连贯且与上下文相关的响应。这些模型的大小,就参数的数量和训练的数据量而言,对它们理解和产生复杂语言结构的能力起着重要作用。”

一切似乎都在起作用,我们现在可以评估合并后的模型了。由于这是一个通用模型,我们可以利用lm评估工具对其进行评估。由于该过程相当耗费资源,我们也可以直接将其提交到Open LLM排行榜网站上进行评估。这个过程需要几天时间,但以下是与其他OpenHermes模型相比的结果:

作者本人提供图像

与原始模型相比NeuralHermes-2–5-Mistral-7B模型的平均得分提高了6.7分(尤其是在GSM8K上)。这是一个出乎意料的大改进,它展示了直接偏好优化的力量。

结论

在本文中,我们使用DPO对经过监督的微调模型进行了微调,并创建了我们自己的NeuralHermes-2.5模型。通过利用一个高质量的偏好数据集,我们成功开发了一个样本高效的微调方案对Open LLM排行榜产生了重大改进。如果你想尝试一下,你可以找到这个模型的量化变体,或者直接使用Hugging Face Space来进行试验

最后请注意,我们的微调方案仍然可以通过不同的方式进行改进。例如,其中使用的偏好数据集仍然很原始,可以通过更多的过滤和使用不同的模型来改进。此外,许多超参数仍然可以进行调整以获得更好的结果。特别地,仍然可以降低学习率以便在更多步骤上训练模型并注入更多偏好数据。

参考资料

  • Fine-tune Llama 2 with DPO by Kashif Rasul, Younes Belkada, and Leandro von Werra
  • Supervised Fine-Tuning and Direct Preference Optimization on Intel Gaudi2 by Kaokao Lv, Wenxin Zhang, and Haihao Shen
  • llama2-fine-tune by mzbac

译者介绍

朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。

原文标题:Fine-tune a Mistral-7b model with Direct Preference Optimization,作者:Maxime Labonne

更新时间 2024-03-05