Datawhale 02月25日
零基础入门:DeepSeek微调教程来了!
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入浅出地介绍了大模型微调的概念、原理和实践步骤。通过类比“学霸补课”和“乐高城堡改造”等生动形象的例子,解释了微调如何使通用模型转变为特定领域的专家。文章详细阐述了微调前后的效果对比,展示了口吻和思考时间上的显著改进。同时,提供了硬件配置建议、数据集准备方法以及无框架纯手搓的代码示例,并对关键代码进行了详细解读,旨在帮助读者掌握大模型微调的核心技术。

📖 **大模型微调的核心概念:** 微调类似于给“学霸”补课,使其从“通才”转变为特定领域的“专家”。通过提供特定领域的知识,例如医学书籍和病例,使模型更擅长解决该领域的问题,从而优化模型性能。

🤖 **乐高城堡改造类比:** 将通用模型比作乐高城堡,微调过程则是在原有结构上进行局部改造,例如拆尖顶改圆顶、加装旋转门、涂装医院标志等。这些改造对应于修改模型顶层参数、插入适配器模块以及调整特征空间,最终使城堡变身为儿童医院,专精儿童健康咨询。

💽 **硬件配置与数据集准备:** 实践中使用的硬件配置包括 NVIDIA GeForce RTX 4060 显卡、Intel Core i7-13700H CPU 和 16GB 内存。数据集来源于魔搭社区的 medical-o1-reasoning-SFT,强调了数据集引入 Complex_CoT(复杂思维链)的重要性,以提升模型的深度推理能力。

💻 **微调代码与关键步骤:** 详细介绍了微调代码的实现,包括导入必要的库、配置路径和硬件检查、自定义训练回调类、数据预处理函数以及 LoRA 微调配置。代码中使用了 Hugging Face 的 transformers 和 datasets 库,以及 PEFT 库进行参数高效微调。

原创 吴锦凤 2025-02-24 22:47 浙江

保姆级教程。

 Datawhale干货 

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

开门见山,直接给大家展示微调前后的效果。

微调前:

微调后:

在此处可以看到很明显大模型进行微调后口吻已经发生了更改。据笔者使用下来的记录表示,微调后的大模型思考时间更加短暂。

接下来,让我们一起逐步完成微调实践,共同优化模型性能!

一、什么是大模型微调?

微调就像给一个“学霸”补课,让它从“通才”变成某个领域的“专家”

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

? 故事解释:

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

? 生活案例解释:

案例1:智能音箱调方言

案例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']}<|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, # 累计梯度相当于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 库的核心模块)


2. matplotlib.pyplot (Matplotlib 绘图库)


3. transformers (HuggingFace Transformers 库)


4. peft (Parameter-Efficient Fine-Tuning)


5. datasets (HuggingFace Datasets 库)


6. os (操作系统接口)

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']}<|endoftext|>", # 添加结束符 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.拼接指令和答案

instruction = f"诊断问题:{example['Question']}\n详细分析:{example['Complex_CoT']}"


2.使用分词器处理文本

inputs = tokenizer( f"{instruction}\n### 答案:\n{example['Response']}<|endoftext|>", # 添加结束符 padding="max_length", # 填充至最大长度 truncation=True, # 超长截断 max_length=512, # 最大序列长度 return_tensors="pt" # 返回PyTorch张量)


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秩(矩阵分解维度) lora_alpha=32, # 缩放系数(控制适配器影响强度) target_modules=["q_proj", "v_proj"], # 要适配的注意力模块(查询/值投影) lora_dropout=0.05, # 防止过拟合的Dropout率 bias="none", # 不训练偏置参数 task_type="CAUSAL_LM" # 任务类型(因果语言模型))

1. r=16:LoRA 的秩

"相当于给AI的‘学习笔记’设置 16 页的篇幅限制"

→ 页数少(r小):学得快但可能漏细节

→ 页数多(r大):学得细但速度慢


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, # 单GPU批次大小(显存优化) gradient_accumulation_steps=4, # 梯度累积步数(等效batch_size=8) num_train_epochs=3, # 训练轮次 learning_rate=3e-4, # 初始学习率 fp16=True, # 启用混合精度训练(节省显存) logging_steps=20, # 每隔20步记录日志 save_strategy="no", # 不保存中间检查点 report_to="none", # 禁用第三方报告(如W&B) optim="adamw_torch", # 优化器类型 no_cuda=False, # 强制使用CUDA dataloader_pin_memory=False, # 禁用锁页内存(加速数据加载) remove_unused_columns=False # 保留未使用的列(避免数据错误))

1. output_dir=output_path:输出目录


2. per_device_train_batch_size=2:单 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":保存策略


9. report_to="none":禁用第三方报告


10. optim="adamw_torch":优化器类型


11. no_cuda=False:强制使用 CUDA


12. dataloader_pin_memory=False:禁用锁页内存


13. 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 # 使用EOS作为填充符
# 加载预训练模型(半精度+指定GPU) model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16, # 半精度加载(节省显存) device_map={"": device} # 指定使用的GPU设备 ) # 应用LoRA适配器 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) # 标签=输入(因果LM任务) } 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}")
# 绘制损失曲线 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")) # 保存为PNG print("Loss曲线已保存")
if __name__ == "__main__": main()

关键代码:

1. 加载 Tokenizer 并设置填充符
tokenizer = AutoTokenizer.from_pretrained(model_path)tokenizer.pad_token = tokenizer.eos_token  # 使用EOS作为填充符
2.加载预训练模型
model = AutoModelForCausalLM.from_pretrained(    model_path,    torch_dtype=torch.float16,       # 半精度加载(节省显存)    device_map={"": device}          # 指定使用的GPU设备)
3.数据整理函数
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)  # 标签=输入(因果LM任务)    }    return batch
4.初始化 Trainer
trainer = Trainer(    model=model,    args=training_args,    train_dataset=dataset,    data_collator=data_collator,  # 自定义数据整理    callbacks=[loss_callback]     # 添加回调)

四、完结感言

非常感谢 Deepseek 官网满血版在本章的代码修改、资料收集以及文章润色方面提供的宝贵帮助!

本章的微调部分目前还较为基础,导致损失函数的收敛效果不够理想,仍有较大的优化空间。例如,数据集构建可以更加精细化,代码结构也有待进一步优化和调整。我们非常期待各位小伙伴的宝贵建议和指正,让我们共同进步,一起在 AI 学习的道路上探索更多乐趣!

一起“点赞三连

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

大模型微调 LoRA Hugging Face AI 深度学习
相关文章