譯者 | 朱先忠
審校 | 重樓
引言
通常,經過預訓練的大型語言模型(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排行榜上基本模型的性能。
和往常一樣,您可在GitHub和Google 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/chosen和train/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