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

预训练的大型语言模型(大语言模型,LLMs)本质上只能预测接下来的单词或符号(Token),这限制了它们直接回答问题的能力。为了让这些模型成为更实用的助手,开发者会通过训练模型理解一系列的问题和答案来"微调"它们。不过,这种微调有时会让模型表现出偏见,或者产生有害的内容。为了解决这些问题,研究者们引入了一种称为"从人类反馈中学习的强化学习"(RLHF)的方法。

通过RLHF,我们可以向模型展示多种可能的回答,并根据我们想要的行为(比如有帮助性、避免有毒内容等)对这些回答进行排序。模型通过学习选择最佳答案,从而学会我们希望它表现出的行为。虽然这种方法有时被认为是对模型的"审查",但它最近被证明是一种有效提升模型表现的手段,比如在neural-chat-7b-v3--1的案例中。

本文将介绍我们如何通过一个类似于RLHF的技术------直接偏好优化(DPO)------对OpenHermes-2.5进行微调,从而创造出NeuralHermes-2.5。我们会首先介绍一个偏好数据集,然后解释DPO算法是如何工作的,最后展示这种方法如何显著提升我们模型在Open LLM Leaderboard上的表现。

如同往常,您可以在GitHubGoogle Colab找到相关的代码。

🥇 偏好数据集

偏好数据集并没有统一的标准,它们通常是由人工排名的答案集合。这种排名至关重要,因为通过强化学习从人类反馈中优化(RLHF)过程来微调大语言模型(LLMs),以产生首选答案。以下是一个流行的偏好数据集Anthropic/hh-rlhf的示例:

这个数据集的结构非常直观:每一行有一个被选定的(首选)答案和一个被拒绝的答案。RLHF的目标是指导模型输出首选答案。

偏好数据集的制作成本高昂,难度大,因为它们需要收集人类的手动反馈。这些反馈还可能带有主观性,容易偏向于错误但自信的答案,或者存在内部矛盾(不同评注者有不同的价值观)。为了解决这些问题,人们提出了几种方法,例如使用AI反馈(基于AI的强化学习,RLAIF)来代替人类反馈。

与细调数据集相比,这些数据集的规模通常较小。例如,当nerual-chat-7b-v3-1(发布时在Open LLM Leaderboard上表现最佳的7亿参数LLM)使用了51.8万个样本进行细调(Open-Orca/SlimOrca),但仅用了1.29万个样本进行RLHF(Intel/orca_dpo_pairs)。在这种情况下,作者利用GPT-4/3.5生成的答案作为首选答案,并使用Llama 2 13b 聊天模型生成的回答作为被拒绝的反馈。这种方法巧妙地避免了依赖人类反馈,转而只依靠不同性能水平的模型。

🎓 直接偏好优化

尽管在机器人领域,通过人类反馈优化的强化学习(RLHF)已有长时间的应用,但直到 OpenAI 发表了《通过人类偏好微调语言模型》这篇论文,这一概念才在大语言模型(LLM)中得到广泛推广。该论文提出了一个以人类反馈为基础训练奖励模型的框架,进而利用近端策略优化(PPO)算法来提升模型策略的效果。

PPO 的核心理念是通过较小的步骤逐步更新策略,避免因大幅更新而导致的不稳定性或找到不是最优的解决方案。然而,实践表明,这种方法往往仍然存在不稳定性(即模型的误差会突然增大),难以复现(因为涉及众多的调整参数和对初始化值敏感),同时也需要较大的计算资源。

在这种背景下,直接偏好优化(DPO)提供了一种新的解决思路。DPO 将任务简化为一个分类问题,它运用了两个模型:一个是已训练的模型(或称为策略模型)和另一个作为参照的复制模型。训练的目标是使得训练模型对于更受青睐的答案给出更高的概率值,对于不被接受的答案则给出更低的概率值。这样,我们实际上是在对模型给出不佳答案进行惩罚,而对给出好答案的行为给予奖励。

通过利用大语言模型本身作为奖励机制,并采取二元交叉熵的目标函数,DPO 有效地将模型输出与人类的偏好相匹配,无需进行大量抽样、复杂的奖励模型拟合或调整众多参数。这种方法更稳定、效率更高,而且在计算上也更加经济。

💾 格式化数据

在本例中,我们将对一款出色的模型 OpenHermes-2.5-Mistral-7B 进行调优,这是一个只经过监督学习调优的 Mistral-7b 模型。为此,我们计划利用 Intel/orca_dpo_pairs 数据集来优化我们的模型并提升其表现。我们将这个经过调优的新模型命名为 NeuralHermes-2.5-Mistral-7B。

首先,我们需要安装一些必要的软件库:

css 复制代码
pip install -q datasets trl peft bitsandbytes sentencepiece wandb

安装完成后,我们继续导入这些库。同时,我也利用了 Google Colab 的密钥管理功能,安全地存储了我的 Hugging Face 和 WandB 访问令牌。

python 复制代码
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。下面是一个采用此模板格式的对话示例:

sql 复制代码
<|im_start|>system  
你是一个有帮助的聊天机器人助手。<|im_end|>  
<|im_start|>user  
嗨<|im_end|>  
<|im_start|>assistant  
嗨,我能帮你什么?<|im_end|>

可以看到,ChatML 定义了不同角色(系统、用户、助手)并通过特殊符号分隔它们。此外,DPOTrainer 还规定了一种特定的格式,包含三个部分:提示、选定回答和拒绝回答。

我们的数据集有四个字段:系统、问题、chatgpt 和 llama2--13b-chat。我们将系统和问题字段合并为提示字段。将 chatgpt 字段映射为"选定回答"(chosen),将 llama2--13b-chat 字段映射为"拒绝回答"(rejected)。为了确保数据集的格式正确无误,我们会使用 tokenizer 的 apply_chat_template() 函数,它已经内置了 ChatML 格式。

ini 复制代码
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'] + "\n"

    # 格式化拒绝回答
    rejected = example['rejected'] + "\n"

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

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

# 保存原始列名
original_columns = dataset.column_names

# Tokenizer
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
)

我们输出一条格式化后的数据集样本,以确保一切按预期进行:

vbnet 复制代码
{'prompt': 'system\n你是一个 AI 助手。你将获得一个任务。你必须生成一个详细且长的答案。\nuser\n生成一个大约十五个词的句子,描述以下所有数据:Midsummer House eatType 餐厅;Midsummer House food 中国菜;Midsummer House priceRange 中等;Midsummer House customer rating 3/5;Midsummer House near All Bar One\nassistant\n',
'chosen': 'Midsummer House 是一个中等价位的中国菜餐厅,顾客评分为3/5,位于 All Bar One 附近。\n',
'rejected': ' 当然!这里有一个句子描述了你提供的所有数据:\n\n"Midsummer House 是一个提供多种美味佳肴的中等价位中国菜餐厅,顾客评分为3/5,位于 All Bar One 附近。"\n'}

通过这种方式,提示部分结合了系统和用户指令。利用 add_generation_prompt=True 参数,它还附加了助手答案的开头。如果您希望跳过这一步骤,可以直接使用预处理过的数据集 mlabonne/chatml_dpo_pairs

⚙️ 利用 DPO 进行模型训练

我们接下来定义了 LoRA 配置来训练模型。根据 Intel 的博客文章所述,我们将 rank 值设置与 lora_alpha 相等,这种设置相对不常见(一般规则是 2 * r)。此外,我们还会针对所有带适配器的线性模块进行操作。

LoRA 配置

ini 复制代码
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 会自动创建一个。

待微调的模型

ini 复制代码
pythonCopy code
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:

特别地,DPO 独有的 beta 参数控制了模型与初始策略之间的偏离程度(典型值为 0.1)。 与 Intel 的博客文章所描述的相比,我们降低了学习率(从 5e-4 降到 5e-5)和步数(从 1000 降到 200),这是基于几次实验后对这些值的优化,目的是为了稳定训练过程并获得最佳效果。 现在,我们可以开始模型的训练过程了。需要注意的是,这个过程需要使用 A100 GPU,并且大约需要 1 小时才能完成。

训练参数

ini 复制代码
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 训练器

ini 复制代码
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,
)

使用 DPO 微调模型

scss 复制代码
dpo_trainer.train()

现在,我们的模型已经完成了微调。你可以通过以下链接在 Weights & Biases 查看项目详情。以下是一些值得分析的有趣指标:

有趣的是,尽管设定了 100 步的预热期,训练损失却在不到 50 步的时候迅速降为零。同时,其他指标仍在变化中。

train/rewards/chosen 和 train/rewards/rejected 图表展示了训练模型与参考模型输出的对数概率平均差值。随着训练的进行,这两个指标的分歧增大,因为我们的模型正在学习辨识更优的答案。train/rewards/margins 图表则展示了这两个差值的变化情况。最终,train/reward/accuracies 图表显示了模型选择最优答案的频率。训练模型很快就达到了完美的准确率,这虽是个好迹象,但也可能意味着模型区分首选答案和被拒绝答案的能力过于明显。

完成训练后,我们可以将适配器与原始模型合并,然后保存合并后的模型及其 tokenizer,并将它们推送到 Hugging Face Hub。

保存和推送

ini 复制代码
pythonCopy code
# 保存模型和 tokenizer
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").merge_and_unload()
model.save_pretrained(new_model)
tokenizer.save_pretrained(new_model)

# 推送到 Hugging Face 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)

现在,让我们通过一个简单的问题来测试我们的模型性能:"什么是大语言模型(Large Language Model)?"

测试模型

ini 复制代码
pythonCopy code
# 构建测试提示
message = [
    {"role": "system", "content": "你是一个有用的助理聊天机器人。"},
    {"role": "user", "content": "什么是大语言模型?"}
]
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'])

模型给出的答案:

大语言模型是一种经过大量文本数据训练的人工智能系统。这些模型旨在理解和生成人类语言,使它们能够执行各种自然语言处理任务,如文本生成、语言翻译和问题回答。大语言模型通常使用深度学习技术,如循环神经网络(RNN)或 Transformer,来学习数据中的模式和关系,使它们能够产生连贯且与上下文相关的响应。这些模型的大小,无论是参数数量还是训练数据的量,都在它们理解和产生复杂语言结构的能力中发挥着重要作用。 一切正常,我们现在可以对合并后的模型进行评估了。由于这是一个通用模型,我们可以使用 lm-evaluation-harness 来评估它。鉴于这个过程相当消耗资源,我们也可以直接将其提交到 Open LLM Leaderboard 进行评估。几天后,我们得到了与其他 OpenHermes 模型相比的结果:

与原模型相比,NeuralHermes-2--5-Mistral-7B 模型在平均得分上提高了 6.7 分(尤其是在 GSM8K 任务上)。这一意外的大幅提升展示了直接偏好优化(DPO)的强大能力。

结论

在这篇文章中,我们进一步精细调整了一个已经经过监督学习微调的模型,创造了NeuralHermes-2.5模型。通过利用一个高品质的偏好数据集,我们设计出了一个高效的微调流程,这个流程显著提升了我们在Open LLM Leaderboard上的表现。如果你想尝试,可以找到这个模型的量化版本,或者在Hugging Face Space上使用它。

需要注意的是,我们的微调流程还有进一步优化的空间。比如说,目前使用的偏好数据集还比较初级,如果进行更细致的筛选和采用不同模型进行处理,可以得到更好的结果。此外,还有很多超参数可以调整,以期获得更佳表现。特别是,进一步降低学习速率,可以让模型在更多迭代步骤中学习,并吸收更多的偏好数据。

参考文献

在这个过程中,我们展示了通过精细的策略和创新的技术手段,如何有效地提升模型性能。对于感兴趣的读者,这些文献提供了进一步探索的窗口。

来源:towardsdatascience.com/fine-tune-a...

相关推荐
AIGC大时代21 分钟前
如何使用ChatGPT辅助文献综述,以及如何进行优化?一篇说清楚
人工智能·深度学习·chatgpt·prompt·aigc
吕小明么15 小时前
OpenAI o3 “震撼” 发布后回归技术本身的审视与进一步思考
人工智能·深度学习·算法·aigc·agi
聆思科技AI芯片1 天前
实操给桌面机器人加上超拟人音色
人工智能·机器人·大模型·aigc·多模态·智能音箱·语音交互
minos.cpp1 天前
Mac上Stable Diffusion的环境搭建(还算比较简单)
macos·ai作画·stable diffusion·aigc
AI小欧同学1 天前
【AIGC-ChatGPT进阶副业提示词】育儿锦囊:化解日常育儿难题的实用指南
chatgpt·aigc
剑盾云安全专家1 天前
AI加持,如何让PPT像开挂一键生成?
人工智能·aigc·powerpoint·软件
合合技术团队2 天前
高效准确的PDF解析工具,赋能企业非结构化数据治理
人工智能·科技·pdf·aigc·文档
程序员小灰2 天前
OpenAI正式发布o3:通往AGI的路上,已经没有了任何阻碍
人工智能·aigc·openai
程序边界2 天前
AIGC时代:如何打造卓越的技术文档?
aigc
爱研究的小牛2 天前
DeepFaceLab技术浅析(六):后处理过程
人工智能·深度学习·机器学习·aigc