目錄
- 一、前言
- 二、Prompt-tuning實戰
- 2.1、下載模型到本地
- 2.2、加載模型與數據集
- 2.3、處理數據
- 2.4、Prompt-tuning微調
- 2.5、訓練參數配置
- 2.6、開始訓練
- 三、模型評估
- 四、完整訓練代碼
一、前言
Prompt-tuning
通過修改輸入文本的提示(Prompt
)來引導模型生成符合特定任務或情境的輸出,而無需對模型的全量參數進行微調。
Prompt-Tuning
高效微調只會訓練新增的Prompt的表示層,模型的其余參數全部固定,其核心在于將下游任務轉化為預訓練任務
新增的 Prompt
內容可以分為 Hard Prompt
和 Soft Prompt
兩類:
Soft prompt
通常指的是一種較為寬泛或模糊的提示,允許模型在生成結果時有更大的自由度,通常用于啟發模型進行創造性的生成;Hard prompt
是一種更為具體和明確的提示,要求模型按照給定的信息生成精確的結果,通常用于需要模型提供準確答案的任務;
Soft Prompt 在 peft 中一般是隨機初始化prompt的文本內容,而 Hard prompt 則一般需要設置具體的提示文本內容;
對于不同任務的Prompt
的構建示例如下:
例如,假設我們有興趣將英語句子翻譯成德語。我們可以通過各種不同的方式詢問模型,如下圖所示。
1)“Translate the English sentence ‘{english_sentence}’ into German: {german_translation}”
2)“English: ‘{english sentence}’ | German: {german translation}”
3)“From English to German:‘{english_sentence}’-> {german_translation}”
上面說明的這個概念被稱為硬提示調整
軟提示調整(soft prompt tuning)將輸入標記的嵌入與可訓練張量連接起來,該張量可以通過反向傳播進行優化,以提高目標任務的建模性能。
例如下方偽代碼:
# 定義可訓練的軟提示參數
# 假設我們有 num_tokens 個軟提示 token,每個 token 的維度為 embed_dim
soft_prompt = torch.nn.Parameter(torch.rand(num_tokens, embed_dim) # 隨機初始化軟提示向量
)# 定義一個函數,用于將軟提示與原始輸入拼接
def input_with_softprompt(x, soft_prompt):# 假設 x 的維度為 (batch_size, seq_len, embed_dim)# soft_prompt 的維度為 (num_tokens, embed_dim)# 將 soft_prompt 在序列維度上與 x 拼接# 拼接后的張量維度為 (batch_size, num_tokens + seq_len, embed_dim)x = concatenate([soft_prompt, x], dim=seq_len)return x# 將包含軟提示的輸入傳入模型
output = model(input_with_softprompt(x, soft_prompt))
- 軟提示參數:
使用 torch.nn.Parameter
將隨機初始化的向量注冊為可訓練參數。這意味著在訓練過程中,soft_prompt
中的參數會隨梯度更新而優化。
- 拼接輸入:
函數 input_with_softprompt
接收原始輸入 x
(通常是嵌入后的 token
序列)和 soft_prompt
張量。通過 concatenate
(偽代碼中使用此函數代指張量拼接操作),將軟提示向量沿著序列長度維度與輸入拼接在一起。
- 傳遞給模型:
將包含軟提示的輸入張量傳給模型,以引導模型在執行特定任務(如分類、生成、QA 等)時更好地利用這些可訓練的軟提示向量。
二、Prompt-tuning實戰
預訓練模型與分詞模型——Qwen/Qwen2.5-0.5B-Instruct
數據集——lyuricky/alpaca_data_zh_51k
2.1、下載模型到本地
# 下載數據集
dataset_file = load_dataset("lyuricky/alpaca_data_zh_51k", split="train", cache_dir="./data/alpaca_data")
ds = load_dataset("./data/alpaca_data", split="train")# 下載分詞模型
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
# Save the tokenizer to a local directory
tokenizer.save_pretrained("./local_tokenizer_model")#下載與訓練模型
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="Qwen/Qwen2.5-0.5B-Instruct", # 下載模型的路徑torch_dtype="auto",low_cpu_mem_usage=True,cache_dir="./local_model_cache" # 指定本地緩存目錄
)
2.2、加載模型與數據集
#加載分詞模型
tokenizer_model = AutoTokenizer.from_pretrained("../local_tokenizer_model")# 加載數據集
ds = load_dataset("../data/alpaca_data", split="train[:10%]")# 記載模型
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="../local_llm_model/models--Qwen--Qwen2.5-0.5B-Instruct/snapshots/7ae557604adf67be50417f59c2c2f167def9a775",torch_dtype="auto",device_map="cuda:0")
2.3、處理數據
"""
并將其轉換成適合用于模型訓練的輸入格式。具體來說,
它將原始的輸入數據(如用戶指令、用戶輸入、助手輸出等)轉換為模型所需的格式,
包括 input_ids、attention_mask 和 labels。
"""
def process_func(example, tokenizer=tokenizer_model):MAX_LENGTH = 256input_ids, attention_mask, labels = [], [], []instruction = tokenizer("\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: ")if example["output"] is not None:response = tokenizer(example["output"] + tokenizer.eos_token)else:returninput_ids = instruction["input_ids"] + response["input_ids"]attention_mask = instruction["attention_mask"] + response["attention_mask"]labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]if len(input_ids) > MAX_LENGTH:input_ids = input_ids[:MAX_LENGTH]attention_mask = attention_mask[:MAX_LENGTH]labels = labels[:MAX_LENGTH]return {"input_ids": input_ids,"attention_mask": attention_mask,"labels": labels}# 分詞
tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)
2.4、Prompt-tuning微調
soft Prompt
# Soft Prompt
config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10) # soft_prompt會隨機初始化
Hard Prompt
# Hard Prompt
prompt = "下面是一段人與機器人的對話。"
config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM, prompt_tuning_init=PromptTuningInit.TEXT,prompt_tuning_init_text=prompt,num_virtual_tokens=len(tokenizer_model(prompt)["input_ids"]),tokenizer_name_or_path="../local_tokenizer_model")
加載peft配置
peft_model = get_peft_model(model, config)print(peft_model.print_trainable_parameters())
可以看到要訓練的模型相比較原來的全量模型要少很多
2.5、訓練參數配置
# 配置模型參數
args = TrainingArguments(output_dir="chatbot", # 訓練模型的輸出目錄per_device_train_batch_size=1,gradient_accumulation_steps=4,logging_steps=10,num_train_epochs=1,
)
2.6、開始訓練
# 創建訓練器
trainer = Trainer(args=args,model=model,train_dataset=tokenized_ds,data_collator=DataCollatorForSeq2Seq(tokenizer_model, padding=True )
)
# 開始訓練
trainer.train()
可以看到 ,損失有所下降
三、模型評估
# 模型推理
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, pipelinemodel = AutoModelForCausalLM.from_pretrained("../local_llm_model/models--Qwen--Qwen2.5-0.5B-Instruct/snapshots/7ae557604adf67be50417f59c2c2f167def9a775", low_cpu_mem_usage=True)
peft_model = PeftModel.from_pretrained(model=model, model_id="./chatbot/checkpoint-643")
peft_model = peft_model.cuda()#加載分詞模型
tokenizer_model = AutoTokenizer.from_pretrained("../local_tokenizer_model")
ipt = tokenizer_model("Human: {}\n{}".format("我們如何在日常生活中減少用水?", "").strip() + "\n\nAssistant: ", return_tensors="pt").to(peft_model.device)
print(tokenizer_model.decode(peft_model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True))print("-----------------")
#預訓練的管道流
# 構建prompt
ipt = "Human: {}\n{}".format("我們如何在日常生活中減少用水?", "").strip() + "\n\nAssistant: "
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer_model)
output = pipe(ipt, max_length=256, do_sample=True, truncation=True)
print(output)
訓練了一輪,感覺效果不大,可以增加訓練輪數試試
四、完整訓練代碼
from datasets import load_dataset
from peft import PromptTuningConfig, TaskType, PromptTuningInit, get_peft_model, PeftModel
from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModelForCausalLM, TrainingArguments, \DataCollatorForSeq2Seq, Trainer# 下載數據集
# dataset_file = load_dataset("lyuricky/alpaca_data_zh_51k", split="train", cache_dir="./data/alpaca_data")
# ds = load_dataset("./data/alpaca_data", split="train")
# print(ds[0])# 下載分詞模型
# tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
# Save the tokenizer to a local directory
# tokenizer.save_pretrained("./local_tokenizer_model")#下載與訓練模型
# model = AutoModelForCausalLM.from_pretrained(
# pretrained_model_name_or_path="Qwen/Qwen2.5-0.5B-Instruct", # 下載模型的路徑
# torch_dtype="auto",
# low_cpu_mem_usage=True,
# cache_dir="./local_model_cache" # 指定本地緩存目錄
# )#加載分詞模型
tokenizer_model = AutoTokenizer.from_pretrained("../local_tokenizer_model")# 加載數據集
ds = load_dataset("../data/alpaca_data", split="train[:10%]")# 記載模型
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path="../local_llm_model/models--Qwen--Qwen2.5-0.5B-Instruct/snapshots/7ae557604adf67be50417f59c2c2f167def9a775",torch_dtype="auto",device_map="cuda:0")# 處理數據
"""
并將其轉換成適合用于模型訓練的輸入格式。具體來說,
它將原始的輸入數據(如用戶指令、用戶輸入、助手輸出等)轉換為模型所需的格式,
包括 input_ids、attention_mask 和 labels。
"""
def process_func(example, tokenizer=tokenizer_model):MAX_LENGTH = 256input_ids, attention_mask, labels = [], [], []instruction = tokenizer("\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: ")if example["output"] is not None:response = tokenizer(example["output"] + tokenizer.eos_token)else:returninput_ids = instruction["input_ids"] + response["input_ids"]attention_mask = instruction["attention_mask"] + response["attention_mask"]labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]if len(input_ids) > MAX_LENGTH:input_ids = input_ids[:MAX_LENGTH]attention_mask = attention_mask[:MAX_LENGTH]labels = labels[:MAX_LENGTH]return {"input_ids": input_ids,"attention_mask": attention_mask,"labels": labels}# 分詞
tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)prompt = "下面是一段人與機器人的對話。"# prompt-tuning
# Soft Prompt
# config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM, num_virtual_tokens=10) # soft_prompt會隨機初始化
# Hard Prompt
config = PromptTuningConfig(task_type=TaskType.CAUSAL_LM, prompt_tuning_init=PromptTuningInit.TEXT,prompt_tuning_init_text=prompt,num_virtual_tokens=len(tokenizer_model(prompt)["input_ids"]),tokenizer_name_or_path="../local_tokenizer_model")peft_model = get_peft_model(model, config)print(peft_model.print_trainable_parameters())# 訓練參數args = TrainingArguments(output_dir="./chatbot",per_device_train_batch_size=1,gradient_accumulation_steps=8,logging_steps=10,num_train_epochs=1
)# 創建訓練器
trainer = Trainer(model=peft_model, args=args, train_dataset=tokenized_ds,data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer_model, padding=True))# 開始訓練
trainer.train()