思維鏈(Chain-of-Thought, CoT)是一種通過模擬人類逐步推理
過程來提升大型語言模型(LLM)復雜任務表現的技術。其核心思想是讓模型在生成最終答案前,先輸出中間推理步驟
,從而增強邏輯性和可解釋性。
1. 基礎示例:數學問題求解
- 問題: “羅杰有5個網球,他又買了兩盒網球,每盒有3個網球。他現在有多少網球?”
- 傳統Prompting(直接輸出答案):模型可能直接輸出錯誤答案(如"27"),因為缺乏中間計算步驟
CoT Prompting(分步推理):
問:羅杰有5個網球,他又買了兩盒網球,每盒有3個網球。他現在有多少網球?
答:羅杰一開始有5個網球,2盒3個網球,一共是2*3=6個網球,5+6=11。答案是11。
2.復雜推理:符號操作任務
問題
“將單詞 ‘hello’ 的最后一個字母與 ‘world’ 的最后一個字母拼接起來。”
CoT Prompting:
1. 'hello'的最后一個字母是'o';
2. 'world'的最后一個字母是'd';
3. 拼接'o'和'd'得到'od'。
最終答案:od
3. 零樣本思維鏈(Zero-shot CoT)
無需示例,僅通過指令觸發推理:
問題
“若A=B+2,B=3*C,C=5,求A的值。”
指令
“讓我們一步一步思考:”
模型自動生成:
1. 計算C=5;
2. 計算B=3*5=15;
3. 計算A=15+2=17。
答案:17
代碼示例:
準備數據
import json
import os
from typing import List, Dict, Any# 創建CoT訓練數據集
def create_cot_dataset():"""創建包含思維鏈(CoT)示例的數據集"""cot_examples = [{"instruction": "解決以下數學問題,請一步一步思考。","input": "羅杰有5個網球,他又買了兩盒網球,每盒有3個網球。他現在有多少網球?","output": "讓我一步一步思考:\n1. 羅杰一開始有5個網球\n2. 他買了兩盒網球,每盒有3個網球\n3. 兩盒網球總共有2×3=6個網球\n4. 羅杰現在總共有5+6=11個網球\n因此,羅杰現在有11個網球。"},{"instruction": "解決以下數學問題,請一步一步思考。","input": "一個商店以每個15元的價格賣了8個蘋果,又以每個10元的價格賣了12個橘子。商店總共賺了多少錢?","output": "讓我一步一步思考:\n1. 蘋果的收入:8個 × 15元 = 120元\n2. 橘子的收入:12個 × 10元 = 120元\n3. 總收入 = 蘋果收入 + 橘子收入 = 120元 + 120元 = 240元\n因此,商店總共賺了240元。"},{"instruction": "解決以下邏輯問題,請一步一步思考。","input": "如果所有的A都是B,所有的B都是C,那么所有的A都是C嗎?","output": "讓我一步一步思考:\n1. 已知條件:所有的A都是B,所有的B都是C\n2. 對于任何一個A,它一定是B(根據條件1)\n3. 由于它是B,而所有的B都是C(根據條件2),所以這個A也一定是C\n4. 因此,所有的A都是C\n所以,結論\"所有的A都是C\"是正確的。",},{"instruction": "解決以下符號操作問題,請一步一步思考。","input": "將單詞'hello'的最后一個字母與'world'的最后一個字母拼接起來。","output": "讓我一步一步思考:\n1. 'hello'的最后一個字母是'o'\n2. 'world'的最后一個字母是'd'\n3. 將這兩個字母拼接起來,得到'od'\n因此,答案是'od'。"},{"instruction": "解決以下代數問題,請一步一步思考。","input": "若A=B+2,B=3*C,C=5,求A的值。","output": "讓我一步一步思考:\n1. 已知C=5\n2. 計算B:B=3*C=3*5=15\n3. 計算A:A=B+2=15+2=17\n因此,A的值是17。"}]# 添加更多復雜的CoT示例more_examples = [{"instruction": "解決以下概率問題,請一步一步思考。","input": "一個袋子里有3個紅球和5個藍球。如果隨機抽取兩個球,求抽到的兩個球都是紅球的概率。","output": "讓我一步一步思考:\n1. 袋子里總共有3+5=8個球\n2. 第一次抽取紅球的概率是3/8\n3. 在第一次抽到紅球的情況下,第二次抽取紅球的概率是2/7(因為還剩2個紅球和5個藍球)\n4. 兩次都抽到紅球的概率是(3/8)×(2/7)=6/56=3/28\n因此,抽到的兩個球都是紅球的概率是3/28。"},{"instruction": "解決以下物理問題,請一步一步思考。","input": "一輛汽車以60千米/小時的速度行駛了2小時,然后以80千米/小時的速度行駛了1小時。求汽車的平均速度。","output": "讓我一步一步思考:\n1. 第一階段:速度60千米/小時,時間2小時,距離=60×2=120千米\n2. 第二階段:速度80千米/小時,時間1小時,距離=80×1=80千米\n3. 總距離=120+80=200千米\n4. 總時間=2+1=3小時\n5. 平均速度=總距離/總時間=200/3≈66.67千米/小時\n因此,汽車的平均速度約為66.67千米/小時。"}]cot_examples.extend(more_examples)# 保存數據集os.makedirs("d:\\Trae\\develop\\try\\data", exist_ok=True)with open("d:\\Trae\\develop\\try\\data\\cot_dataset.json", "w", encoding="utf-8") as f:json.dump(cot_examples, f, ensure_ascii=False, indent=2)print(f"已創建CoT數據集,包含{len(cot_examples)}個示例")return cot_examplesif __name__ == "__main__":create_cot_dataset()
lora微調
import os
import json
import torch
from transformers import (AutoModelForCausalLM,AutoTokenizer,Trainer,TrainingArguments
)
from datasets import Dataset
from peft import (LoraConfig,get_peft_model,prepare_model_for_kbit_training,TaskType
)def load_cot_dataset(file_path: str):"""加載CoT數據集"""with open(file_path, "r", encoding="utf-8") as f:data = json.load(f)return datadef prepare_dataset_for_lora(examples, tokenizer, max_length: int = 512):"""準備用于LoRA訓練的數據集"""formatted_examples = []for example in examples:# 格式化為指令微調格式formatted_text = f"指令: {example['instruction']}\n問題: {example['input']}\n回答: {example['output']}"formatted_examples.append({"text": formatted_text})# 創建數據集dataset = Dataset.from_list(formatted_examples)# 對數據集進行分詞處理def tokenize_function(examples):return tokenizer(examples["text"],padding="max_length",truncation=True,max_length=max_length,return_tensors="pt")tokenized_dataset = dataset.map(tokenize_function,batched=True,remove_columns=["text"])# 劃分訓練集和驗證集tokenized_dataset = tokenized_dataset.train_test_split(test_size=0.1)return tokenized_datasetdef train_cot_with_lora():"""使用LoRA方法微調模型以學習CoT推理"""# 加載預訓練模型和分詞器model_name = "THUDM/chatglm3-6b" # 可以替換為其他中文模型print(f"加載模型: {model_name}")tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)model = AutoModelForCausalLM.from_pretrained(model_name,trust_remote_code=True,torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,load_in_8bit=True if torch.cuda.is_available() else False)# 準備模型進行LoRA微調if torch.cuda.is_available():model = prepare_model_for_kbit_training(model)# 配置LoRAlora_config = LoraConfig(task_type=TaskType.CAUSAL_LM,r=8, # LoRA的秩lora_alpha=32,lora_dropout=0.1,target_modules=["query_key_value"], # 根據模型架構調整bias="none",)# 應用LoRA配置model = get_peft_model(model, lora_config)print(f"可訓練參數數量: {model.print_trainable_parameters()}")# 加載CoT數據集cot_examples = load_cot_dataset("d:\\Trae\\develop\\try\\data\\cot_dataset.json")print(f"加載了{len(cot_examples)}個CoT示例")# 準備訓練數據集tokenized_dataset = prepare_dataset_for_lora(cot_examples, tokenizer)# 設置訓練參數training_args = TrainingArguments(output_dir="d:\\Trae\\develop\\try\\cot_lora_model",overwrite_output_dir=True,num_train_epochs=3,per_device_train_batch_size=4, # 使用LoRA可以用更大的批量per_device_eval_batch_size=4,gradient_accumulation_steps=4,evaluation_strategy="steps",eval_steps=50,save_strategy="steps",save_steps=50,save_total_limit=3,learning_rate=1e-4,weight_decay=0.01,warmup_steps=50,logging_dir="d:\\Trae\\develop\\try\\logs",logging_steps=10,fp16=torch.cuda.is_available(),report_to="none",)# 創建訓練器trainer = Trainer(model=model,args=training_args,train_dataset=tokenized_dataset["train"],eval_dataset=tokenized_dataset["test"],)# 開始訓練print("開始LoRA訓練...")trainer.train()# 保存模型model.save_pretrained("d:\\Trae\\develop\\try\\cot_lora_model\\final")tokenizer.save_pretrained("d:\\Trae\\develop\\try\\cot_lora_model\\final")print("LoRA訓練完成,模型已保存")if __name__ == "__main__":train_cot_with_lora()
推理
import torch
from transformers import AutoModelForCausalLM, AutoTokenizerdef load_cot_model(model_path: str):"""加載訓練好的CoT模型"""tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)model = AutoModelForCausalLM.from_pretrained(model_path,trust_remote_code=True,torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)return model, tokenizerdef generate_cot_response(model, tokenizer, instruction: str, problem: str, max_length: int = 1024):"""使用CoT模型生成包含推理步驟的回答"""# 構建輸入提示prompt = f"指令: {instruction}\n問題: {problem}\n回答:"# 生成回答inputs = tokenizer(prompt, return_tensors="pt").to(model.device)outputs = model.generate(inputs.input_ids,max_length=max_length,temperature=0.7,top_p=0.9,do_sample=True,num_return_sequences=1)# 解碼回答response = tokenizer.decode(outputs[0], skip_special_tokens=True)# 提取回答部分response = response.split("回答:")[1].strip() if "回答:" in response else responsereturn responsedef test_cot_model():"""測試CoT模型的推理能力"""# 加載模型model_path = "d:\\Trae\\develop\\try\\cot_model\\final"model, tokenizer = load_cot_model(model_path)model.to("cuda" if torch.cuda.is_available() else "cpu")model.eval()# 測試問題test_problems = [{"instruction": "解決以下數學問題,請一步一步思考。","problem": "小明有12個蘋果,他給了小紅3個,又給了小李2個,然后自己吃了1個。小明還剩下多少個蘋果?"},{"instruction": "解決以下邏輯問題,請一步一步思考。","problem": "如果今天不是周一,那么明天不是周二。今天是周三,那么明天是周幾?"},{"instruction": "解決以下代數問題,請一步一步思考。","problem": "若x+y=10且xy=21,求x2+y2的值。"}]# 對每個問題生成回答for i, test in enumerate(test_problems):print(f"\n測試 {i+1}:")print(f"指令: {test['instruction']}")print(f"問題: {test['problem']}")response = generate_cot_response(model, tokenizer, test['instruction'], test['problem'])print(f"回答:\n{response}")print("-" * 50)if __name__ == "__main__":test_cot_model()