ORPO偏好優(yōu)化:性能和DPO一樣好并且更簡單的對齊方法
現(xiàn)在有許多方法可以使大型語言模型(LLM)與人類偏好保持一致。以人類反饋為基礎(chǔ)的強(qiáng)化學(xué)習(xí)(RLHF)是最早的方法之一,并促成了ChatGPT的誕生,但RLHF的成本非常高。與RLHF相比,DPO、IPO和KTO的成本明顯更低,因?yàn)樗鼈儾恍枰?jiǎng)勵(lì)模型。
雖然DPO和IPO的成本較低,但它們?nèi)孕栌?xùn)練兩個(gè)不同的模型。首先是監(jiān)督微調(diào)(SFT)步驟,即訓(xùn)練模型按指令回答問題,然后使用SFT模型作為初始化和參考,以使模型與人類偏好一致。
ORPO是另一種新的LLM對齊方法,這種方法甚至不需要SFT模型。通過ORPO,LLM可以同時(shí)學(xué)習(xí)回答指令和滿足人類偏好。
在本文中,我將解釋ORPO并介紹其相關(guān)的內(nèi)容,最后將展示如何使用消費(fèi)級硬件將Mistral 7B轉(zhuǎn)換為聊天模型。
ORPO:Monolithic Preference Optimization without Reference Model
作者通過展示SFT步驟在對齊流程中并不理想來很好地論證了ORPO的動(dòng)機(jī)。雖然在指令數(shù)據(jù)集上微調(diào)模型確實(shí)使模型適應(yīng)在特定領(lǐng)域回答指令,但生成人類可能拒絕的答案的概率也增加了。
被選中和被拒絕的響應(yīng)可能有很多共同點(diǎn):相同的領(lǐng)域、相同的格式等,因此生成與任務(wù)相關(guān)但不正確的答案的概率增加。而DPO可以降低被拒絕響應(yīng)的概率,同時(shí)增加被選擇響應(yīng)的概率,即在上圖中的曲線之間增大差距。偏好優(yōu)化技術(shù)是在包含以下內(nèi)容的數(shù)據(jù)集上訓(xùn)練的:
提示
選擇的答案
被拒絕的答案
對于STF,它是在與選擇的答案配對的提示上進(jìn)行訓(xùn)練的。用于sft的數(shù)據(jù)集可以與偏好優(yōu)化使用的相同,但不包括"被拒絕"的答案。所以可以直觀地認(rèn)為,應(yīng)該能夠微調(diào)一個(gè)基礎(chǔ)LLM,使其在學(xué)習(xí)如何回答指令的同時(shí),也學(xué)會(huì)懲罰和偏好某些答案。
ORPO就是在這個(gè)理論基礎(chǔ)上建立的,ORPO簡單地通過添加負(fù)對數(shù)似然損失與OR損失(OR代表奇異比)來修改訓(xùn)練損失:
OR損失對被拒絕的答案進(jìn)行弱懲罰,而對選擇的答案進(jìn)行強(qiáng)有力的獎(jiǎng)勵(lì)。這里包含了一個(gè)超參數(shù)lambda用于加權(quán)OR損失。
lambda設(shè)為0.1似乎效果不錯(cuò)。如果設(shè)置為0.5,雖然區(qū)分選擇和拒絕輸出的能力更強(qiáng),但選擇答案的概率也降低了。所以為了對于在拒絕錯(cuò)誤答案比獲取正確答案更為關(guān)鍵的特定應(yīng)用,可能將lambda設(shè)置為0.5會(huì)更好。
通過ORPO的損失,模型在學(xué)習(xí)了SFT期間的內(nèi)容的同時(shí),也學(xué)會(huì)了人類偏好。
但這種方法的一個(gè)缺點(diǎn)是,它可能需要更大的偏好數(shù)據(jù)集。
使用TRL運(yùn)行ORPO
雖然這是今年3月分剛發(fā)布的論文,但是ORPO 已經(jīng)可以在Hugging Face庫上使用了,并且它因?yàn)橹恍薷牧藫p失函數(shù),所以可以很好的與現(xiàn)有的Lora方法集成,這里我們就演示如何將它與GaLora進(jìn)行結(jié)合,訓(xùn)練我們自己的模型。
首先,安裝依賴:
pip install -q -U bitsandbytes
pip install --upgrade -q -U transformers
pip install -q -U peft
pip install -q -U accelerate
pip install -q -U datasets
pip install -q -U git+https://github.com/huggingface/trl.git
然后,導(dǎo)入庫:
import torch, multiprocessing
from datasets import load_dataset
from peft import LoraConfig, prepare_model_for_kbit_training
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from trl import ORPOTrainer, ORPOConfig
還需要運(yùn)行以下代碼,確保如果GPU支持的話則使用FlashAttention和bfloat16:
import os
major_version, minor_version = torch.cuda.get_device_capability()
if major_version >= 8:
os.system("pip install flash-attn")
torch_dtype = torch.bfloat16
attn_implementatinotallow='flash_attention_2'
print("Your GPU is compatible with FlashAttention and bfloat16.")
else:
torch_dtype = torch.float16
attn_implementatinotallow='eager'
print("Your GPU is not compatible with FlashAttention and bfloat16.")
然后,加載數(shù)據(jù)集。這里使用“HuggingFaceH4/ ultrafeedback_binalized”(MIT許可)來訓(xùn)練Zephyr模型。我將一個(gè)聊天模板應(yīng)用到“被選中”和“被拒絕”列上,以對JSON進(jìn)行字符串化。
dataset = load_dataset("HuggingFaceH4/ultrafeedback_binarized", split=["train_prefs","test_prefs"])
def process(row):
row["chosen"] = tokenizer.apply_chat_template(row["chosen"], tokenize=False)
row["rejected"] = tokenizer.apply_chat_template(row["rejected"], tokenize=False)
return row
dataset[0] = dataset[0].map(
process,
num_proc= multiprocessing.cpu_count(),
load_from_cache_file=False,
)
dataset[1] = dataset[1].map(
process,
num_proc= multiprocessing.cpu_count(),
load_from_cache_file=False,
)
剩下就是加載標(biāo)記器,配置并加載模型。
model_name = "mistralai/Mistral-7B-v0.1"
#Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, add_eos_token=True, use_fast=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = 'left' #Necessary for FlashAttention compatibility
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch_dtype,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
model_name, torch_dtype=torch_dtype, quantization_cnotallow=bnb_config, device_map={"": 0}, attn_implementatinotallow=attn_implementation
)
model = prepare_model_for_kbit_training(model)
#Configure the pad token in the model
model.config.pad_token_id = tokenizer.pad_token_id
該模型使用bitsandbytes的NF4數(shù)據(jù)類型(使用BitsAndBytesConfig配置)動(dòng)態(tài)量化。記得設(shè)置“prepare_model_for_kbit_training”,因?yàn)樗С痔荻葯z查點(diǎn)并節(jié)省大量內(nèi)存。
對于LoRA的配置,使用標(biāo)準(zhǔn)超參數(shù)。如果增加“r”可能會(huì)有更好的結(jié)果,但這也會(huì)增加內(nèi)存消耗,所以這里就不進(jìn)行超參數(shù)得調(diào)整了。
peft_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.05,
r=16,
bias="none",
task_type="CAUSAL_LM",
target_modules= ['k_proj', 'q_proj', 'v_proj', 'o_proj', "gate_proj", "down_proj", "up_proj"]
)
還可以在LoraConfig中設(shè)置“use_dora=True”,以使用DoRA訓(xùn)練更好(但更慢)的適配器。
最后就是設(shè)置ORPOConfig并開始訓(xùn)練:
orpo_config = ORPOConfig(
output_dir="./results/",
evaluation_strategy="steps",
do_eval=True,
optim="paged_adamw_8bit",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
per_device_eval_batch_size=2,
log_level="debug",
logging_steps=20,
learning_rate=8e-6,
eval_steps=20,
max_steps=100,
save_steps=20,
save_strategy='epoch',
warmup_ratio=0.1,
lr_scheduler_type="linear",
beta=0.1, #beta is ORPO's lambda
max_length=1024,
)
trainer = ORPOTrainer(
model=model,
train_dataset=dataset[0],
eval_dataset=dataset[1],
peft_cnotallow=peft_config,
args=orpo_config,
tokenizer=tokenizer,
)
trainer.train()
ORPOTrainer與SFTTrainer和DPOTrainer的不同之處在于,它似乎不接受trainingargument作為參數(shù)。所以需要傳遞一個(gè)“ORPOConfig”。這里有個(gè)注意點(diǎn):ORPOConfig中提到的“beta”是論文中描述的“l(fā)ambda”,它衡量OR損失。
以下是在Colab 測試得結(jié)果:
訓(xùn)練和驗(yàn)證損失都減少了。說明模型正在學(xué)習(xí),也就是說這個(gè)方法是有效得。讓我們再看看論文中ORPO的學(xué)習(xí)曲線:
從曲線中可以清楚地看出,ORPO需要數(shù)千個(gè)訓(xùn)練步驟來學(xué)習(xí)如何區(qū)分選擇的響應(yīng)和拒絕的響應(yīng)。為了獲得類似的結(jié)果,應(yīng)該訓(xùn)練ORPO至少2000步,總批大小為64(如論文所述)。這樣看來使用一個(gè)高端消費(fèi)級GPU,例如RTX 4090是可行的,但可能需要幾天的時(shí)間。
總結(jié)
ORPO是一種單步微調(diào)和對準(zhǔn)指令llm的新方法。它不需要任何獎(jiǎng)勵(lì)或SFT模型,并且ORPO比DPO和RLHF更簡單。根據(jù)論文ORPO的性能與DPO相當(dāng)或略好。但是ORPO需要幾千個(gè)訓(xùn)練步驟來學(xué)習(xí)好的和壞的反應(yīng)之間的區(qū)別。
應(yīng)該從現(xiàn)在開始使用ORPO嗎?
如果想要一個(gè)簡單有效的方法,ORPO是可以得。但是想要得到最好的結(jié)果,ORPO還不能完全的得到驗(yàn)證。因?yàn)槟壳斑€沒有一個(gè)偏好優(yōu)化方法的全面比較。但是我們可以從ORPO開始,因?yàn)樗吘贡容^簡單。