专栏名称: Datawhale
一个专注于AI领域的开源组织,汇聚了众多顶尖院校和知名企业的优秀学习者,聚集了一群有开源精神和探索精神的团队成员。愿景-for the learner,和学习者一起成长。
目录
相关文章推荐
51好读  ›  专栏  ›  Datawhale

零基础入门:DeepSeek微调教程来了!

Datawhale  · 公众号  ·  · 2025-02-24 22:46

正文

Datawhale干货

作者:吴锦凤,Datawhale优秀学习者

开门见山,直接给大家展示微调前后的效果。
微调前:
微调后:
在此处可以看到很明显大模型进行微调后口吻已经发生了更改。据笔者使用下来的记录表示,微调后的大模型思考时间更加短暂。
接下来,让我们一起逐步完成微调实践,共同优化模型性能!

一、什么是大模型微调?

微调就像给一个 “学霸” 补课,让它从 “通才” 变成某个领域的 “专家”
此处以本文进行微调的医学数据进行举例: 假设你有一个很聪明的朋友,他读过全世界的书(相当于大模型的 预训练 阶段),能和你聊历史、科学、文学等各种话题。 但如果你需要他帮你 看医学报告 段) ,能和你聊历史、科学、文学等各种话题。 但如果你需要他帮你看医学报告,虽然他懂一些基础知识,但可能不够专业。这时候,你给他一堆医学书籍和病例,让他专门学习这方面的知识(这就是微调),他就会变得更擅长医疗领域的问题。

📖 故事解释:

想象你有一个会画小猫的机器人🤖(这就是预训练模型)。现在你想让它学会画戴帽子的小猫🎩🐱。不需要从头教它画画,只需要给它看很多"戴帽子小猫"的图片,然后说:"保持原来的画画能力,但要学会加帽子哦!" 这就是微调!

📖 生活案例解释:

案例1:智能音箱调方言
  • 基础版音箱只会普通话(预训练模型)

  • 给它听 100 句四川话(微调数据)

  • 现在能听懂"摆龙门阵"(方言理解能力↑)

案例2:相机滤镜原理
  • 原始相机拍所有场景(通用模型)

  • 加载“美食滤镜”参数(微调后的模型)

  • 拍食物时自动增强饱和度(专业能力强化)

加强版解释:乐高城堡改造成儿童医院

第一步:原有结构 —— 通用乐高城堡

[通用城堡]
▸ 比喻:就像网购的"标准款城堡积木套装",有城墙、塔楼、尖顶,能当普通房子用。
▸ 对应技术:预训练模型(比如 ChatGPT),已经学会通用语言能力,但不够专业。

第二步:局部改造 —— 低成本改装

① 拆尖顶 → 改圆顶
[尖顶改圆顶]
操作 :把塔顶的尖积木换成圆积木,更温和可爱。
技术含义 :微调模型顶层参数(比如修改分类头),让输出风格更适合儿童对话。
② 加装旋转门 [旋转门]
操作 :在门口插入一个可旋转的积木模块,不破坏原有门结构。
技术含义 :插入适配器模块(Adapter),让模型新增儿科医学术语理解能力,且不干扰原有知识。
③ 涂装医院标志
[医院标志]
操作 :在城堡外墙贴上"十字符号"和卡通动物贴纸。
▸ 技术含义:特征空间偏移(Feature Shift),调整模型内部表示,让它更关注医疗相关词汇和童趣表达。

第三步:新功能 —— 变身儿童医院

[儿童医院]
成果 :改装后的城堡能接待小患者,有玩具区、温 和的医生 (圆顶),还有专用医疗设备(旋转门)。
▸ 技术含义:通过轻量改造,通用模型变成"儿科医疗问答机器人",专精儿童健康咨询。

二、当前尝试过的硬件配置

显卡:NVIDIA GeForce RTX 4060
CPU:Intel Core i7-13700H
内存: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 torchimport matplotlib.pyplot as pltfrom transformers import ( AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, TrainerCallback)from peft import LoraConfig, get_peft_modelfrom datasets import load_datasetimport os
# 配置路径(根据实际路径修改)model_path = r"你的模型路径" # 模型路径data_path = r"你的数据集路径" # 数据集路径output_path = r"你的保存微调后的模型路径" # 微调后模型保存路径
# 强制使用GPUassert torch.cuda.is_available(), //"必须使用GPU进行训练!"device = torch.device("cuda")
# 自定义回调记录Lossclass 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']}", 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, # 累计梯度相当于batch_size=8 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, # 强制使用CUDA dataloader_pin_memory=False, # 加速数据加载 remove_unused_columns=False # 防止删除未使用的列)
def main(): # 创建输出目录 os.makedirs(output_path, exist_ok=True)
# 加载tokenizer tokenizer = AutoTokenizer.from_pretrained(model_path) tokenizer.pad_token = tokenizer.eos_token
# 加载模型到GPU model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16, device_map={"": device} # 强制使用指定GPU ) 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) # 使用input_ids作为labels } return batch
# 创建Trainer 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}")
# 绘制训练集损失Loss曲线 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 torchimport matplotlib.pyplot as pltfrom transformers import ( # HuggingFace Transformer模型工具 AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, TrainerCallback)from peft import LoraConfig, get_peft_model # 参数高效微调库from datasets import load_dataset # 数据集加载工具import os # 系统路径操作

有关类库介绍:

1. torch (PyTorch 库的核心模块)

  • 功能 :深度学习框架,提供张量计算和神经网络构建功能。

  • 代码中的作用

    • 管理GPU设备 ( torch.cuda.is_available() 检查GPU可用性)

    • 定义模型训练时的张量操作

    • 控制混合精度训练 ( torch.float16 )


2. matplotlib.pyplot (Matplotlib 绘图库)

  • 功能 :数据可视化工具库。

  • 代码中的作用

    • 绘制训练损失曲线 ( plt.plot(losses) )

    • 生成并保存训练过程的Loss变化图 ( loss_curve.png )


3. transformers (HuggingFace Transformers 库)

  • 核心组件

    • AutoTokenizer :自动加载预训练模型对应的分词器

      • 用于将文本转换为模型可理解的 token ID 序列

    • AutoModelForCausalLM :自动加载因果语言模型(如GPT系列)

      • 提供基础的大语言模型结构

    • TrainingArguments :定义训练超参数

      • 控制批次大小、学习率、日志频率等

    • Trainer :封装训练流程的类

      • 自动处理训练循环、梯度下降、日志记录等

    • TrainerCallback :训练回调基类

      • 用于实现自定义训练监控逻辑(如示例中的损失记录)


4. peft (Parameter-Efficient Fine-Tuning)

  • 功能 :实现参数高效微调方法的库。

  • 核心组件

    • LoraConfig :LoRA(Low-Rank Adaptation)的配置类

      • 定义秩( r )、目标模块( target_modules )等关键参数

    • get_peft_model :将基础模型转换为 PEFT 模型

      • 仅需训练原模型约 0.1% 的参数即可实现有效微调

  • 代码中的作用

    • 对 LLaMA 等大模型进行轻量化微调

    • 显存占用量减少约 60-70%,适合消费级 GPU


5. datasets (HuggingFace Datasets 库)

  • 功能 :高效数据集加载与处理工具。

  • 核心方法

    • load_dataset :加载多种格式的数据

      • 支持 JSON/CSV/Parquet 等格式(示例中使用 JSON)

    • map :数据预处理流水线

      • 应用自定义的格式化函数 ( format_example )

  • 代码中的作用

    • 从本地文件加载医疗问答数据集

    • 将原始数据转换为模型需要的输入格式


6. os (操作系统接口)

  • 功能 :提供操作系统相关功能。

  • 代码中的作用

    • 创建输出目录 ( os.makedirs )

    • 处理文件路径相关操作

    • 确保模型保存路径的有效性

2. 配置路径和硬件检查

功能总结 :配置模型/数据路径, 强制检查GPU可用性
# 配置路径(根据实际路径修改)model_path = r"你的模型路径" # 预训练模型存放路径data_path = r"你的数据集路径" # 训练数据路径(JSON格式)output_path = r"你的保存微调后的模型路径" # 微调后模型保存位置
# 强制使用GPU(确保CUDA可用)assert torch.cuda.is_available(), "必须使用GPU进行训练!"device = torch.device("cuda") # 指定使用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): # 从JSON文件加载数据集(仅取前1500条) 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']}", # 添加结束符 padding="max_length", # 填充至最大长度 truncation=True, # 超长截断 max_length=512, # 最大序列长度 return_tensors="pt" # 返回PyTorch张量 ) # 返回处理后的输入(移除batch维度) 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.拼接指令和答案

  • 作用 :将问题( Question )和详细分析( Complex_CoT )拼接成一个指令。

  • 示例

    • 输入: Question="发烧怎么办?" , Complex_CoT="可能是感冒引起的。"

    • 输出: "诊断问题:发烧怎么办?\n详细分析:可能是感冒引起的。"

  • 类比 :就像把问题和分析写在一张纸上。

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']}", # 添加结束符 padding="max_length", # 填充至最大长度 truncation=True, # 超长截断 max_length=512, # 最大序列长度 return_tensors="pt" # 返回PyTorch张量)

3.返回处理后的输入

  • 作用 :返回处理后的输入数据,并移除多余的维度。

  • 参数说明

    • input_ids :文本对应的 token ID 序列。

    • attention_mask :标记哪些位置是有效 token(1 表示有效,0 表示填充)。

  • 类比 :就像把翻译好的数字整理成一张表格。

return {"input_ids": inputs["input_ids"].squeeze(0), "attention_mask": inputs["attention_mask"].squeeze(0)}

4.应用格式化函数

  • 作用 :对整个数据集应用格式化函数,并移除原始列。

  • 参数说明

    • format_example :格式化函数。

    • remove_columns=dataset.column_names :移除原始列(如 Question Complex_CoT 等)。

  • 类比 :就像把整本书的每一页都翻译成机器能懂的格式。

return dataset.map(format_example, remove_columns=dataset.column_names)

5. LoRA微调配置

功能总结 :配置LoRA参数,指定要适配的模型模块。
peft_config = LoraConfig(






请到「今天看啥」查看全文