思维链技术(Chain-of-Thought, CoT)
思维链(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_examples
if __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 data
def 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_dataset
def 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)
# 配置LoRA
lora_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, AutoTokenizer
def 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, tokenizer
def 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 response
return response
def 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,求x²+y²的值。"
}
]
# 对每个问题生成回答
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()