# GLM4-9B-chat Lora 微調.
?
介紹如何基于 llama-factory 框架,對 glm-4-9b-chat 模型進行 Lora 微調。Lora 是一種高效微調方法,深入了解其原理可參見博客:[知乎|深入淺出 Lora](https://zhuanlan.zhihu.com/p/650197598)。
## 環境配置
?
在完成基本環境配置和本地模型部署的情況下,你還需要安裝一些第三方庫,可以使用以下命令:
```
python -m pip install --upgrade pip
# 更換 pypi 源加速庫的安裝
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 安裝 LLaMA-Factory
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"
#上面這步操作會完成torch、transformers、datasets等相關依賴包的安裝
```
?
## 二、模型下載
?
使用 `modelscope` 中的`snapshot_download`函數下載模型,第一個參數為模型名稱,參數`cache_dir`為模型的下載路徑。
在 `/root/autodl-tmp` 路徑下新建 `download.py` 文件并在其中輸入以下內容,粘貼代碼后記得保存文件,如下圖所示。并運行 `python /root/autodl-tmp/download.py`執行下載,模型大小為 14 GB,下載模型大概需要 10~20 分鐘
```
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/glm-4-9b-chat', cache_dir='/root/autodl-tmp', revision='master')
```
?
## 三、指令集構建 —— Alpaca 格式
?
LLaMA-Factory 支持 alpaca 格式和 sharegpt 格式的數據集,本次微調我們使用 alpaca 格式
### 指令監督微調數據格式說明
?
在指令監督微調時,`instruction` 列對應的內容會與 `input` 列對應的內容拼接后作為人類指令,即人類指令為 `instruction\ninput`。而 `output` 列對應的內容為模型回答。
如果指定,`system` 列對應的內容將被作為系統提示詞。
`history` 列是由多個字符串二元組構成的列表,分別代表歷史消息中每輪對話的指令和回答。注意在指令監督微調時,歷史消息中的回答內容**也會被用于模型學習**。
```
[
{
"instruction": "人類指令(必填)",
"input": "人類輸入(選填)",
"output": "模型回答(必填)",
"system": "系統提示詞(選填)",
"history": [
["第一輪指令(選填)", "第一輪回答(選填)"],
["第二輪指令(選填)", "第二輪回答(選填)"]
]
}
]
```
?
### 單輪對話數據的格式轉換
?
使用以下程序將[數據集](https://github.com/SmartFlowAI/EmoLLM/blob/main/datasets)轉換成 alpaca 格式
```
import json
import re
# 選擇要格式轉換的數據集
file_name = "single_turn_dataset_1.json"
#file_name = "single_turn_dataset_2.json"
system_prompt = "如果要添加系統提示詞,請放在這里"
with open(f'../{file_name}', 'rt', encoding='utf-8') as file:
data = json.load(file)
converted_data = [{"instruction": item["prompt"],
"input": "",
"output": item["completion"],
"system": system_prompt
} for item in data]
for i in range(len(converted_data)):
# 數據清洗-去掉特殊符號
if "🐳" in converted_data[i]["output"]:
converted_data[i]["output"] = converted_data[i]["output"].replace("🐳", "")
# 數據清洗-去掉“你好,我是紅燒肉”,會影響大模型的自我認知
if '好,我是' in converted_data[i]["output"]:
converted_data[i]["output"] = converted_data[i]["output"].strip()
intro_pattern = r"^[^\n]+\n"
converted_data[i]["output"] = re.sub(intro_pattern, "", converted_data[i]["output"]).strip()
with open(f'./processed/{file_name}', 'w', encoding='utf-8') as f:
json.dump(converted_data, f, ensure_ascii=False, indent=4)
print(f'./processed/{file_name} Done')
```
?
### 多輪對話數據的格式轉換
?
使用以下程序將[數據集](https://github.com/SmartFlowAI/EmoLLM/blob/main/datasets)轉換成 alpaca 格式
```
from tqdm import tqdm
import json
# 選擇要格式轉換的數據集
file_name = "data.json"
#file_name = "data_pro.json"
#file_name = "multi_turn_dataset_1.json"
#file_name = "multi_turn_dataset_2.json"
#file_name = "aiwei.json"
system_prompt = "如果要添加系統提示詞,請放在這里"
with open(f'../{file_name}', 'rt', encoding='utf-8') as file:
data = json.load(file)
# 遍歷原始數據,進行格式轉換
# 轉換后的數據格式
converted_data = []
for item in tqdm(data):
conversation = item['conversation']
history = [(c['input'], c['output']) for c in conversation[:-1]]
last_item = conversation[-1]
converted_data.append({
"instruction": last_item['input'],
"input": "",
"output": last_item['output'],
"system": system_prompt,
"history": history
})
# 將轉換后的數據轉換為JSON格式
converted_json = json.dumps(converted_data, ensure_ascii=False, indent=4)
with open(f'./processed/{file_name}', 'w', encoding='utf-8') as f:
json.dump(converted_data, f, ensure_ascii=False, indent=4)
```
?
### 角色扮演數據的格式轉換
?
代碼同上,根據原數據集是單輪對話還是多輪對話來選擇。注意設置各個角色的“system_prompt”。
### 數據集合并
?
為了方便處理(不想在LLaMA-Factory中添加太多的數據集),這里將所有已經處理好的 alpaca 格式的數據集(每一個數據集文件都是一個json字符串)合并成一個文件(一個大的json字符串),合并代碼如下:
```
import json
# 初始化一個空列表來存儲所有數據
merged_data = []
file_list = [
"single_turn_dataset_1.json",
"single_turn_dataset_2.json",
"self_cognition_EmoLLM.json",
"ruozhiba_raw.json",
"data.json",
"data_pro.json",
"multi_turn_dataset_1.json",
"multi_turn_dataset_2.json",
"aiwei.json",
"tiangou.json",
"SoulStar_data.json",
"mother_v2.json",
"scientist.json"
]
# 遍歷所有文件并讀取數據
for filename in file_list:
with open(f"./processed/{filename}", 'r', encoding='utf-8') as file:
data = json.load(file)
merged_data.extend(data)
# 將合并后的數據寫入新的 JSON 文件
with open('emo_glm4_merged_data.json', 'w', encoding='utf-8') as output_file:
json.dump(merged_data, output_file, ensure_ascii=False, indent=4)
print("合并完成,已保存到 emo_glm4_merged_data.json 文件中。")
```
?
### 將數據集配置到LLaMA-Factory 中
?
修改 LLaMa-Factory 目錄中的 data/dataset_info.json 文件,在其中添加:
```
"emo_merged": {
"file_name": "emo_glm4_merged_data.json文件的絕對路徑",
}
```
?
## 四、微調模型
?
在 LLaMA-Factory 目錄中新建配置文件 emo_glm4_lora_sft.yaml :
```
### model
model_name_or_path: glm-4-9b-chat模型地址的絕對路徑
### method
stage: sft
do_train: true
finetuning_type: lora
lora_target: all
### dataset
# dataset 要和 data/dataset_info.json 中添加的信息保持一致
dataset: emo_merged
template: glm4
cutoff_len: 2048
max_samples: 1000
overwrite_cache: true
preprocessing_num_workers: 16
### output
# output_dir是模型訓練過程中的checkpoint,訓練日志等的保存目錄
output_dir: saves/emo-glm4-epoch10/lora/sft
logging_steps: 10
#save_steps: 500
plot_loss: true
overwrite_output_dir: true
save_strategy: epoch
### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-4
num_train_epochs: 10.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
fp16: true
### eval
do_eval: false
val_size: 0.1
per_device_eval_batch_size: 1
eval_strategy: steps
eval_steps: 10
```
?
執行以下命令開始微調:
```
cd LLaMA-Factory
llamafactory-cli train glm4_emo_lora_sft.yaml
```
?
訓練完成后,在 LLaMA-Factory 目錄中新建配置文件 emo_glm4_lora_sft_export.yaml:
```
### model
model_name_or_path: glm-4-9b-chat模型地址的絕對路徑
# 剛才emo_glm4_lora_sft.yaml文件中的 output_dir
adapter_name_or_path: saves/emo-glm4-epoch10/lora/sft
template: glm4
finetuning_type: lora
### export
export_dir: models/EmoLLM-glm-4-9b-chat
export_size: 2
export_device: cpu
export_legacy_format: false
```
?
## 五、合并模型
?
執行以下命令開始合并模型:
```
cd LLaMA-Factory
llamafactory-cli export emo_glm4_lora_sft_export.yaml
```
?
在 models/EmoLLM-glm-4-9b-chat 目錄中就可以獲得經過Lora微調后的完整模型。?