简介

依据人类反响的强化学习 (Reinforcement Learning from Human Feedback,RLHF) 事实上已成为 GPT-4 或 Claude 等 LLM 操练的毕竟一步,它可以保证言语模型的输出符合人类在闲聊或安全性等方面的希望。然而,它也给 NLP 引入了一些 RL 相关的杂乱性: 既要构建一个好的奖励函数,并操练一个模型用以估量每个状态的价值 (value) ; 又要留心毕竟生成的 LLM 不能与原始模型相差太远,假设太远的话会使得模型容易产生乱码而非有意义的文本。该进程非常杂乱,涉及到许多杂乱的组件,而这些组件本身在操练进程中又是动态变化的,因而把它们料理好并不容易。

Rafailov、Sharma、Mitchell 等人最近宣告了一篇论文 Direct Preference Optimization,论文提出将现有方法运用的依据强化学习的政策转换为可以通过简略的二元交叉熵丢掉直接优化的政策,这一做法大大简化了 LLM 的提纯进程。

本文介绍了直接偏好优化 (Direct Preference Optimization,DPO) 法,该方法现已集成至 TRL 库 中。一同,我们还展示了如安在 stack-exchange preference 数据集上微调最新的 Llama v2 7B 模型, stack-exchange preference 数据会合包含了各个 stack-exchange 门户上的各种问题及其排序后的答复。

DPO 与 PPO

在通过 RL 优化人类衍生偏好时,一直以来的传统做法是运用一个辅助奖励模型来微调政策模型,以通过 RL 机制最大化政策模型所能取得的奖励。直观上,我们运用奖励模型向待优化模型供给反响,以促使它多生成高奖励输出,少生成低奖励输出。一同,我们运用冻住的参看模型来保证输出偏差不会太大,且持续坚持输出的多样性。这一般需求在政策函数设计时,除了奖励最大化政策外再添加一个相关于参看模型的 KL 赏罚项,这样做有助于避免模型学习做弊或钻营奖励模型。

DPO 绕过了建模奖励函数这一步,这源于一个要害洞见: 从奖励函数到最优 RL 战略的分析映射。这个映射直观地度量了给定奖励函数与给定偏好数据的匹配程度。有了它,作者就可与将依据奖励和参看模型的 RL 丢掉直接转换为仅依据参看模型的丢掉,然后直接在偏好数据上优化言语模型!因而,DPO 从寻觅最小化 RLHF 丢掉的最佳方案开始,通过改动参量的方法推导出一个 仅需 参看模型的丢掉!

有了它,我们可以直接优化该似然政策,而不需求奖励模型或繁琐的强化学习优化进程。

怎么运用 TRL 进行操练

如前所述,一个典型的 RLHF 流水线一般包含以下几个环节:

  1. 有监督微调 (supervised fine-tuning,SFT)
  2. 用偏好标签标明数据
  3. 依据偏好数据操练奖励模型
  4. RL 优化

TRL 库包含了一切这些环节所需的东西程序。而 DPO 操练直接消除了奖励建模和 RL 这两个环节 (环节 3 和 4),直接依据标明好的偏好数据优化 DPO 政策。

运用 DPO,我们依然需求执行环节 1,但我们仅需在 TRL 中向 DPOTrainer 供给环节 2 准备好的偏好数据,而不再需求环节 3 和 4。标明好的偏好数据需求遵照特定的格式,它是一个含有以下 3 个键的字典:

  • prompt : 即推理时输入给模型的提示
  • chosen : 即针对给定提示的较优答复
  • rejected : 即针对给定提示的较劣答复或非给定提示的答复

例如,关于 stack-exchange preference 数据集,我们可以通过以下东西函数将数据会合的样本映射至上述字典格式并删去一切原始列:

def return_prompt_and_responses(samples) -> Dict[str, str, str]:
    return {
        "prompt": [
            "Question: " + question + "\n\nAnswer: "
            for question in samples["question"]
        ],
        "chosen": samples["response_j"], # rated better than k
        "rejected": samples["response_k"], # rated worse than j
    }
dataset = load_dataset(
    "lvwerra/stack-exchange-paired",
    split="train",
    data_dir="data/rl"
)
original_columns = dataset.column_names
dataset.map(
    return_prompt_and_responses,
    batched=True,
    remove_columns=original_columns
)

一旦有了排序数据集,DPO 丢掉其实本质上便是一种有监督丢掉,其经由参看模型取得隐式奖励。因而,从上层来看,DPOTrainer 需求我们输入待优化的基础模型以及参看模型:

dpo_trainer = DPOTrainer(
    model, # 经 SFT 的基础模型
    model_ref, # 一般为经 SFT 的基础模型的一个仿制
    beta=0.1, # DPO 的温度超参
    train_dataset=dataset, # 上文准备好的数据集
    tokenizer=tokenizer, # 分词
    args=training_args, # 操练参数,如: batch size, 学习率等
)

其间,超参 beta 是 DPO 丢掉的温度,一般在 0.10.5 之间。它控制了我们对参看模型的重视程度,beta 越小,我们就越疏忽参看模型。对操练器初始化后,我们就可以简略调用以下方法,运用给定的 training_args 在给定数据集上进行操练了:

dpo_trainer.train()

依据 Llama v2 进行试验

在 TRL 中完成 DPO 操练器的优点是,人们可以运用 TRL 及其依靠库 (如 Peft 和 Accelerate) 中已有的 LLM 相关功用。有了这些库,我们甚至可以运用 bitsandbytes 库供给的 QLoRA 技术 来操练 Llama v2 模型。

有监督微调

如上文所述,我们先用 TRL 的 SFTTrainer 在 SFT 数据子集上运用 QLoRA 对 7B Llama v2 模型进行有监督微调:

# load the base model in 4-bit quantization
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
base_model = AutoModelForCausalLM.from_pretrained(
    script_args.model_name, # "meta-llama/Llama-2-7b-hf"
    quantization_config=bnb_config,
    device_map={"": 0},
    trust_remote_code=True,
    use_auth_token=True,
)
base_model.config.use_cache = False
# add LoRA layers on top of the quantized base model
peft_config = LoraConfig(
    r=script_args.lora_r,
    lora_alpha=script_args.lora_alpha,
    lora_dropout=script_args.lora_dropout,
    target_modules=["q_proj", "v_proj"],
    bias="none",
    task_type="CAUSAL_LM",
)
...
trainer = SFTTrainer(
    model=base_model,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    peft_config=peft_config,
    packing=True,
    max_seq_length=None,
    tokenizer=tokenizer,
    args=training_args, # HF Trainer arguments
)
trainer.train()

DPO 操练

SFT 结束后,我们保存好生成的模型。接着,我们持续进行 DPO 操练,我们把 SFT 生成的模型作为 DPO 的基础模型和参看模型,并在上文生成的 stack-exchange preference 数据上,以 DPO 为政策函数操练模型。我们选择对模型进行 LoRa 微调,因而我们运用 Peft 的 AutoPeftModelForCausalLM 函数加载模型:

model = AutoPeftModelForCausalLM.from_pretrained(
    script_args.model_name_or_path, # location of saved SFT model
    low_cpu_mem_usage=True,
    torch_dtype=torch.float16,
    load_in_4bit=True,
    is_trainable=True,
)
model_ref = AutoPeftModelForCausalLM.from_pretrained(
    script_args.model_name_or_path, # same model as the main one
    low_cpu_mem_usage=True,
    torch_dtype=torch.float16,
    load_in_4bit=True,
)
...
dpo_trainer = DPOTrainer(
    model,
    model_ref,
    args=training_args,
    beta=script_args.beta,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    tokenizer=tokenizer,
    peft_config=peft_config,
)
dpo_trainer.train()
dpo_trainer.save_model()

可以看出,我们以 4 比特的方法加载模型,然后通过 peft_config 参数选择 QLora 方法对其进行操练。操练器还会用点评数据集点评操练进度,并陈说一些要害方针,例如可以选择通过 WandB 记载并显现隐式奖励。毕竟,我们可以将操练好的模型推送到 HuggingFace Hub。

总结

SFT 和 DPO 操练脚本的无缺源代码可在该目录 examples/stack_llama_2 处找到,训好的已兼并模型也已上传至 HF Hub (见 此处)。

你可以在 这儿 找到我们的模型在操练进程的 WandB 日志,其间包含了 DPOTrainer 在操练和点评期间记载下来的以下奖励方针:

  • rewards/chosen (较优答复的奖励) : 针对较优答复,战略模型与参看模型的对数概率二者之差的均值,按 beta 缩放。
  • rewards/rejected (较劣答复的奖励) : 针对较劣答复,战略模型与参看模型的对数概率二者之差的均值,按 beta 缩放。
  • rewards/accuracy (奖励准确率) : 较优答复的奖励大于相应较劣答复的奖励的频率的均值
  • rewards/margins (奖励余裕值) : 较优答复的奖励与相应较劣答复的奖励二者之差的均值。

直观上讲,在操练进程中,我们希望余裕值添加并且准确率到达 1.0,换句话说,较优答复的奖励高于较劣答复的奖励 (或余裕值大于零)。随后,我们还可以在点评数据集上核算这些方针。

我们希望我们代码的发布可以下降读者的入门门槛,让我们可以在自己的数据集上测验这种大言语模型对齐方法,我们刻不容缓地想看到你会用它做哪些工作!假设你想试试我们操练出来的模型,可以玩玩这个 space: trl-lib/stack-llama。


英文原文: hf.co/blog/dpo-tr…

原文作者: Kashif Rasul, Younes Belkada, Leandro von Werra

译者: Matrix Yao (姚伟峰),英特尔深度学习工程师,作业方向为 transformer-family 模型在各模态数据上的使用及大规模模型的操练推理。

审校/排版: zhongdongy (阿东)