优化Mistral-7B模型: 直接偏好微调法

2024年01月02日 由 daydream 发表 704 0

预训练的大型语言模型(LLM)只能执行下一个词预测,这使得它们无法回答问题。这就是为什么这些基础模型随后需要在指令和答案对上进行微调,以便充当有用的助手。然而,这个过程仍然可能是有缺陷的:微调后的LLM可能存在偏见、有害、有毒等问题。这就是来自人类反馈的强化学习 (RLHF) 发挥作用的地方。


RLHF会提供不同的答案给LLM,这些答案根据期望的行为(有益性、有害性等)进行排名。模型学会输出这些候选答案中最好的一个,从而模仿我们想要灌输的行为。这个过程通常被视为审查模型的一种方式,最近因提高性能而变得流行,正如neural-chat-7b-v3-1所展示的。


微信截图_20240102104315


在本文中,我们将通过使用类RLHF技术:直接偏好优化(DPO),对OpenHermes-2.5进行微调,创造NeuralHermes-2.5。为此,我们将介绍一个偏好数据集,描述DPO算法是如何工作的,并将其应用到我们的模型中。我们将看到它显著地提高了基础模型在Open LLM排行榜上的性能。


偏好数据集


偏好数据集没有标准化,但它们通常由人类排名的一系列答案组成。这个排名至关重要,因为RLHF过程将LLM微调为输出首选答案。这里是一个Anthropic/hh-rlhf流行偏好数据集的例子:


微信截图_20240102104326


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


偏好数据集是非常昂贵且难以制作的,因为需要从人类那里收集手动反馈。这些反馈也是主观的,并且可能很容易偏向于自信(但错误)的答案,或者自我矛盾(不同的标注者有不同的价值观)。随着时间的推移,已经提出了几种解决这些问题的方案,例如用AI反馈代替人类反馈(RLAIF)。


这些数据集也往往比微调数据集小得多。举例说明,优秀的neural-chat-7b-v3-1(发布时是Open LLM排行榜上最好的7B LLM)使用了51.8万样本进行微调(Open-Orca/SlimOrca),但只用了1.29万样本进行RLHF(Intel/orca_dpo_pairs)。在这种情况下,作者使用GPT-4/3.5生成答案来创建首选答案,并用Llama 2 13b聊天来创建拒绝反应。这是一种巧妙的绕过人类反馈,只依赖不同性能水平的模型的方式。


 直接偏好优化


尽管RLHF的概念在机器人技术中已经使用了很长时间,但直到OpenAI的论文《根据人类偏好微调语言模型》中才为LLMs普及。在这篇文章中,作者们提出了一个框架,其中一个奖励模型被训练来近似人类反馈。然后使用近距离策略优化(PPO)算法优化微调模型的策略。


微信截图_20240102104333


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


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


微信截图_20240102104339


通过使用LLM自身作为奖励模型,并采用二元交叉熵目标,DPO有效地使模型的输出与人类偏好一致,无需进行大量样本抽样、奖励模型拟合或复杂的超参数调整。它使过程更稳定、更高效、计算要求更低。


 数据格式化


在这个例子中,我们将微调一个仅经过监督微调的出色模型OpenHermes-2.5-Mistral-7B。为此,我们将使用Intel/orca_dpo_pairs数据集来对齐我们的模型并改善其性能。我们称这个新模型为NeuralHermes-2.5-Mistral-7B。


第一步是安装所需的库,如下所示。

pip install -q datasets trl peft bitsandbytes sentencepiece wandb


完成后,我们可以导入库。在Google 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

# Defined in the secrets tab in 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|>


我们的数据集包含四个列:system(系统),question(问题),chatgpt,和llama2-13b-chat。我们将简单地把system列和question列连接起来到prompt列。我们还会将chatgpt列映射成“chosen”,将llama2-13b-chat列映射成“rejected”。为了以可靠的方式格式化数据集,我们将使用tokenizer的功能,该功能已经使用了ChatML。

apply_chat_template()

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

# Format instruction
message = {"role": "user", "content": example['question']}
prompt = tokenizer.apply_chat_template([message], tokenize=False, add_generation_prompt=True)

# Format chosen answer
chosen = example['chosen'] + "<|im_end|>\n"

# Format rejected answer
rejected = example['rejected'] + "<|im_end|>\n"

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

# Load dataset
dataset = load_dataset("Intel/orca_dpo_pairs")['train']

# Save columns
original_columns = dataset.column_names

# Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"

# Format dataset
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'}


我们可以看到prompt结合了系统和用户指令。由于参数的原因,它还添加了助理答案的开头。如果你想跳过这一步,你可以直接使用预处理过的数据集,即mlabonne/chatml_dpo_pairs。add_generation_prompt=True


 与DPO一起训练模型


接下来,我们定义LoRA配置以训练模型。如Intel的博客文章所描述,我们将rank值设置为等同于,这是不寻常的(通常是作为经验法则的2倍)。我们还针对所有的线性模块使用adapters。lora_alphar

# LoRA configuration
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 to fine-tune
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
load_in_4bit=True
)
model.config.use_cache = False

# Reference model
ref_model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
load_in_4bit=True
)


最后一步包括提供所有的超参数TrainingArgumentsDPOTrainer


  • 其中,beta参数是DPO独有的,因为它控制了与初始策略的偏离(0.1是一个典型的值)。
  • 与Intel的博客文章中描述的值相比,我们降低了学习率(从5e-4降到5e-5)和步骤数(从1000降到200)。在几次运行后手动优化了这些值,以稳定训练并达到最佳结果。


我们现在可以开始训练模型。注意,这需要使用A100 GPU,并且训练完成需要1小时左右的时间。

Training arguments
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",
)

# Create DPO trainer
dpo_trainer = DPOTrainer(
model,
ref_model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
peft_config=peft_config,
beta=0.1,
max_prompt_length=1024,
max_length=1536,
)

# Fine-tune model with DPO
dpo_trainer.train()


我们的模型现在已经微调完成。以下是一些有趣的指标用于分析:


微信截图_20240102104352


有趣的是,尽管有100个预热步骤,训练损失很快降到零(在50步之前)。同时,其他指标继续演变。


train/rewards/chosen和train/rewards/rejected图表对应于由训练模型和参考模型输出的对数概率之间的平均差异。随着时间的推移,它们分离是有道理的,因为我们训练的模型学习了首选答案。train/rewards/margins图表还显示了这两个图表之间的差异。最后,train/reward/accuracies图表显示了选择首选答案的频率。训练模型很快就达到了完美的准确率,这是一个好兆头,但也可能意味着首选答案和被拒绝答案之间的差异太明显。


现在它已经训练完毕,我们可以将适配器与原始模型合并。接下来,我们保存合并的模型和tokenizer之前将其推送到Hugging Face Hub。

ave artifacts
dpo_trainer.model.save_pretrained("final_checkpoint")
tokenizer.save_pretrained("final_checkpoint")

# Flush memory
del dpo_trainer, model, ref_model
gc.collect()
torch.cuda.empty_cache()

# Reload model in FP16 (instead of NF4)
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
return_dict=True,
torch_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Merge base model with the adapter
model = PeftModel.from_pretrained(base_model, "final_checkpoint")
model = model.merge_and_unload()

# Save model and tokenizer
model.save_pretrained(new_model)
tokenizer.save_pretrained(new_model)

# Push them to the HF Hub
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)

让我们看看我们的模型在真实测试中的表现。我们将格式化prompt来询问一个基本问题:“什么是大型语言模型?”

ormat prompt
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)

# Create pipeline
pipeline = transformers.pipeline(
"text-generation",
model=new_model,
tokenizer=tokenizer
)

# Generate text
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.


现在可以评估合并模型。由于这是一个通用模型,我们可以利用lm-evaluation-harness来评估它。由于这个过程相当占用资源,我们也可以直接将其提交给Open LLM Leaderboard进行评估。花了几天时间,但这里是与其他OpenHermes模型相比的结果:


微信截图_20240102153229


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


结论


在这篇文章中,我们使用DPO微调了一个已经过监督微调的模型,并创建了我们自己的NeuralHermes-2.5模型。通过利用高质量的偏好数据集,我们创建了一个样本效率高的微调流程,在Open LLM Leaderboard上产生了显著的改进。如果你想尝试一下,你可以找到这个模型的量化变体。


请注意,我们的微调流程仍然可以通过不同的方式改进。例如,偏好数据集仍然相对原始,可以通过更多的筛选和使用不同的模型进行改进。此外,仍有许多超参数可以调整以获得更好的结果。特别是,学习率仍然可以降低,以便在更多的步骤上训练模型并注入更多的偏好数据。

文章来源:https://towardsdatascience.com/fine-tune-a-mistral-7b-model-with-direct-preference-optimization-708042745aac
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消