在此处可以看到很明显大模型进行微调后口吻已经发生了更改。据笔者使用下来的记录表示,微调后的大模型思考时间更加短暂。
接下来,让我们一起逐步完成微调实践,共同优化模型性能!
一、什么是大模型微调?
微调就像给一个
“学霸”
补课,让它从
“通才”
变成某个领域的
“专家”
。
此处以本文进行微调的医学数据进行举例: 假设你有一个很聪明的朋友,他读过全世界的书(相当于大模型的
预训练
阶段),能和你聊历史、科学、文学等各种话题。 但如果你需要他帮你
看医学报告段)
,能和你聊历史、科学、文学等各种话题。 但如果你需要他帮你看医学报告,虽然他懂一些基础知识,但可能不够专业。这时候,你给他一堆医学书籍和病例,让他专门学习这方面的知识(这就是微调),他就会变得更擅长医疗领域的问题。
📖 故事解释:
想象你有一个会画小猫的机器人🤖(这就是预训练模型)。现在你想让它学会画戴帽子的小猫🎩🐱。不需要从头教它画画,只需要给它看很多"戴帽子小猫"的图片,然后说:"保持原来的画画能力,但要学会加帽子哦!" 这就是微调!
📖 生活案例解释:
-
基础版音箱只会普通话(预训练模型)
-
给它听 100 句四川话(微调数据)
-
现在能听懂"摆龙门阵"(方言理解能力↑)
-
原始相机拍所有场景(通用模型)
-
加载“美食滤镜”参数(微调后的模型)
-
拍食物时自动增强饱和度(专业能力强化)
加强版解释:乐高城堡改造成儿童医院
第一步:原有结构 —— 通用乐高城堡
▸ 比喻:就像网购的"标准款城堡积木套装",有城墙、塔楼、尖顶,能当普通房子用。
▸ 对应技术:预训练模型(比如 ChatGPT),已经学会通用语言能力,但不够专业。
第二步:局部改造 —— 低成本改装
▸
操作
:把塔顶的尖积木换成圆积木,更温和可爱。
▸
技术含义
:微调模型顶层参数(比如修改分类头),让输出风格更适合儿童对话。
▸
操作
:在门口插入一个可旋转的积木模块,不破坏原有门结构。
▸
技术含义
:插入适配器模块(Adapter),让模型新增儿科医学术语理解能力,且不干扰原有知识。
▸
操作
:在城堡外墙贴上"十字符号"和卡通动物贴纸。
▸ 技术含义:特征空间偏移(Feature Shift),调整模型内部表示,让它更关注医疗相关词汇和童趣表达。
第三步:新功能 —— 变身儿童医院
▸
成果
:改装后的城堡能接待小患者,有玩具区、温
和的医生
(圆顶),还有专用医疗设备(旋转门)。
▸ 技术含义:通过轻量改造,通用模型变成"儿科医疗问答机器人",专精儿童健康咨询。
二、当前尝试过的硬件配置
显卡:NVIDIA GeForce RTX 4060
内存:16 G(因为家庭电脑所以日常状态是 8.8/15.7 GB)
三、微调工作
(1) 数据集准备
本文数据集来源,魔搭社区的 medical-o1-reasoning-SFT。
在 DeepSeek 的蒸馏模型微调过程中,数据集中引入 Complex_CoT(复杂思维链)是关键设计差异。若仅使用基础问答对进行训练,模型将难以充分习得深度推理能力,导致最终性能显著低于预期水平。这一特性与常规大模型微调的数据要求存在本质区别。
(2) 模型微调代码(此处是无框架纯手搓)——直接上了,后面会有细节讲解
需要引入的库:
pip install torch transformers peft datasets matplotlib accelerate safetensors
import torch
import matplotlib.pyplot as plt
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
TrainingArguments,
Trainer,
TrainerCallback
)
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
import os
# 配置路径(根据实际路径修改)
model_path = r"你的模型路径"
data_path = r"你的数据集路径"
output_path = r"你的保存微调后的模型路径"
# 强制使用GPU
assert torch.cuda.is_available(), //"必须使用GPU进行训练!"
device = torch.device("cuda")
# 自定义回调记录Loss
class LossCallback(TrainerCallback):
def __init__(self):
self.losses = []
def on_log(self, args, state, control, logs=None, **kwargs):
if "loss" in logs:
self.losses.append(logs["loss"])
# 数据预处理函数
def process_data(tokenizer):
dataset = load_dataset("json", data_files=data_path, split="train[:1500]")
def format_example(example):
instruction = f"诊断问题:{example['Question']}\n详细分析:{example['Complex_CoT']}"
inputs = tokenizer(
f"{instruction}\n### 答案:\n{example['Response']}<|endoftext|>",
padding="max_length",
truncation=True,
max_length=512,
return_tensors="pt"
)
return {"input_ids": inputs["input_ids"].squeeze(0), "attention_mask": inputs["attention_mask"].squeeze(0)}
return dataset.map(format_example, remove_columns=dataset.column_names)
# LoRA配置
peft_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# 训练参数配置
training_args = TrainingArguments(
output_dir=output_path,
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=3e-4,
fp16=True,
logging_steps=20,
save_strategy="no",
report_to="none",
optim="adamw_torch",
no_cuda=False,
dataloader_pin_memory=False,
remove_unused_columns=False
)
def
main():
os.makedirs(output_path, exist_ok=True)
tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float16,
device_map={"": device}
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
dataset = process_data(tokenizer)
loss_callback = LossCallback()
def data_collator(data):
batch = {
"input_ids": torch.stack([torch.tensor(d["input_ids"]) for d in data]).to(device),
"attention_mask": torch.stack([torch.tensor(d["attention_mask"]) for d in data]).to(device),
"labels": torch.stack([torch.tensor(d["input_ids"]) for d in data]).to(device)
}
return batch
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
data_collator=data_collator,
callbacks=[loss_callback]
)
print("开始训练...")
trainer.train()
trainer.model.save_pretrained(output_path)
print(f"模型已保存至:{output_path}")
plt.figure(figsize=(10, 6))
plt.plot(loss_callback.losses)
plt.title("Training Loss Curve")
plt.xlabel("Steps")
plt.ylabel("Loss")
plt.savefig(os.path.join(output_path, "loss_curve.png"))
print("Loss曲线已保存")
if __name__ == "__main__":
main()
(3) 代码详细讲解
1. 导入必要的库和模块
功能总结
:导入项目依赖的第三方库,包括 PyTorch 基础库、HuggingFace 工具库、可视化库等。
import torch
import matplotlib.pyplot as plt
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
TrainingArguments,
Trainer,
TrainerCallback
)
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
import os
有关类库介绍:
1.
torch
(PyTorch 库的核心模块)
2.
matplotlib.pyplot
(Matplotlib 绘图库)
3.
transformers
(HuggingFace Transformers 库)
-
核心组件
:
-
AutoTokenizer
:自动加载预训练模型对应的分词器
-
AutoModelForCausalLM
:自动加载因果语言模型(如GPT系列)
-
TrainingArguments
:定义训练超参数
-
Trainer
:封装训练流程的类
-
TrainerCallback
:训练回调基类
4.
peft
(Parameter-Efficient Fine-Tuning)
-
功能
:实现参数高效微调方法的库。
-
核心组件
:
-
代码中的作用
:
5.
datasets
(HuggingFace Datasets 库)
-
功能
:高效数据集加载与处理工具。
-
核心方法
:
-
load_dataset
:加载多种格式的数据
-
map
:数据预处理流水线
-
代码中的作用
:
-
从本地文件加载医疗问答数据集
-
将原始数据转换为模型需要的输入格式
6.
os
(操作系统接口)
-
功能
:提供操作系统相关功能。
-
代码中的作用
:
-
创建输出目录 (
os.makedirs
)
-
处理文件路径相关操作
-
确保模型保存路径的有效性
2. 配置路径和硬件检查
功能总结
:配置模型/数据路径,
强制检查GPU可用性
# 配置路径(根据实际路径修改)
model_path = r"你的模型路径"
data_path = r"你的数据集路径"
output_path = r"你的保存微调后的模型路径"
# 强制使用GPU(确保CUDA可用)
assert torch.cuda.is_available(), "必须使用GPU进行训练!"
device = torch.device("cuda")
3.
自定义训练回调类
功能总结
:实现自定义回调,
在模型训练过程中,实时记录损失值(Loss)的变化
。损失值是用来衡量模型预测结果与真实结果之间的差距的,损失值越小,说明模型的表现越好。
class
LossCallback(TrainerCallback):
def __init__(self):
self.losses = []
def on_log(self, args, state, control, logs=None, **kwargs):
if "loss" in logs:
self.losses.append(logs["loss"])
4. 数据预处理函数
功能总结
:加载并格式化训练数据,
将原始数据集转换为模型可以理解的格式
。
def process_data(tokenizer):
dataset = load_dataset("json", data_files=data_path, split="train[:1500]")
def format_example(example):
instruction = f"诊断问题:{example['Question']}\n详细分析:{example['Complex_CoT']}"
inputs = tokenizer(
f"{instruction}\n### 答案:\n{example['Response']}<|endoftext|>",
padding="max_length",
truncation=True,
max_length=512,
return_tensors="pt"
)
return {"input_ids": inputs["input_ids"].squeeze(0),
"attention_mask": inputs["attention_mask"].squeeze(0)}
return dataset.map(format_example, remove_columns=dataset.column_names)
关键代码
1.拼接指令和答案
instruction = f"诊断问题:{example['Question'
]}\n详细分析:{example['Complex_CoT']}"
2.使用分词器处理文本
-
作用
:将拼接后的文本转换为模型可以理解的格式。
-
参数说明
:
-
padding="max_length"
:将文本填充到固定长度(512)。
-
truncation=True
:如果文本超过 512 个 token,就截断。
-
max_length=512
:最大长度为 512。
-
return_tensors="pt"
:返回 PyTorch 张量。
-
示例
:
-
输入:
"诊断问题:发烧怎么办?
\n详细分析:可能是感冒引起的。\n### 答案:\n多喝水,休息。"
-
输出:
input_ids=[101, 234, 345, ..., 102]
,
attention_mask=[1, 1, 1, ..., 1]
-
类比
:就像把文字翻译成机器能懂的数字。
inputs = tokenizer(
f"{instruction}\n### 答案:\n{example['Response']}<|endoftext|>",
padding="max_length",
truncation=True,
max_length=512,
return_tensors="pt"
)
3.返回处理后的输入
-
作用
:返回处理后的输入数据,并移除多余的维度。
-
参数说明
:
-
类比
:就像把翻译好的数字整理成一张表格。
return {"input_ids": inputs["input_ids"].squeeze(0),
"attention_mask": inputs["attention_mask"].squeeze(0)}
4.应用格式化函数
return dataset.map(format_example, remove_columns=dataset.column_names)
5. LoRA微调配置
功能总结
:配置LoRA参数,指定要适配的模型模块。
peft_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
1.
r=16
:LoRA 的秩
"相当于给AI的‘学习笔记’设置 16 页的篇幅限制"
2.
lora_alpha=32
:缩放系数
-
作用
:控制低秩矩阵对原始模型的影响强度。
-
解释
:
-
影响
:
-
比喻:
就像是,音量旋钮的大小决定了声音的响亮程度。如果旋钮转得太大,声音可能会震耳欲聋,甚至让人难以忍受;如果旋钮转得太小,声音又可能太小,听不清楚。
过大的 lora_alpha 可能会导致模型的训练变得不稳定,就像声音太大可能会让人感到不适一样。可能会导致过拟合,因为模型对训练数据的
细节调整过于敏感。
较小的 lora_alpha 会导致模型在
训练过
程中会更保守地调整权重
,训练过程更稳定
,但适应新任务的速度可能会较慢。
3.
target_modules=["q_proj", "v_proj"]
:目标模块
-
作用
:指定需要插入低秩矩阵的模型模块。
-
解释
:
-
影响
:
4.
lora_dropout=0.05
:Dropout 率
5.
bias="none"
:偏置参数
6.
task_type="CAUSAL_LM"
:任务类型
训练参数配置
training_args = TrainingArguments(
output_dir=output_path,
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
num_train_epochs=3,
learning_rate=3e-4,
fp16=True,
logging_steps=20,
save_strategy="no",
report_to="none",
optim="adamw_torch",
no_cuda=False,
dataloader_pin_memory=False,
remove_unused_columns=False
)
1.
output_dir=output_path
:输出目录
2.
per_device_train_batch_size=2
:单 GPU 批次大小
-
作用
:设置每个 GPU 上的训练批次大小。
-
解释
:
-
示例
:
3.
gradient_accumulation_steps=4
:梯度累积步数
4.
num_train_epochs=3
:训练轮次
-
作用
:设置模型在整个数据集上训练的轮次。
-
解释
:
-
示例
:
5.
learning_rate=3e-4
:初始学习率
6.
fp16=True
:混合精度训练
-
作用
:启用混合精度训练,节省显存并加速训练。
-
解释
:
-
示例
:
7.
logging_steps=20
:日志记录频率
-
作用
:设置每隔多少步记录一次日志。
-
解释
:
-
示例
:
8.
save_strategy="no"
:保存策略
-
作用
:设置是否保存中间检查点。
-
解释
:
-
示例
: