一. 前言介绍
本文内容:
- 模型加载与预处理:详细讲解如何加载预训练模型、分词器,并处理输入数据集。
- LoRA配置:介绍如何使用LoRA技术配置模型,并高效进行微调,节省计算资源。
- 训练过程:展示了如何配置训练参数,使用SFTTrainer进行训练,并通过WandB记录训练日志。
- 模型保存与评估:如何保存微调后的模型,以及如何通过合适的评估集对模型进行验证。
- 模型合并:展示了如何通过加权平均的方式合并多个模型权重,得到一个更强大的模型。
1.1 项目背景
本文档描述了如何在MAC笔记本上对DeepSeek-R1-Distill-Llama-1.5BQwen架构 进行高效微调,使用** transformers进行数据处理,并结合LoRA技术进行模型微调,使用WandB监控训练过程,ModelScope下载模型。(训练数据量大约2w条左右)
- 由于为MAC笔记本本地训练 无显卡支持 故而放弃(DeepSeek-R1-Distill-Qwen-7B Q wen)
下载的服务信息如下:
安装服务 | 版本名称 | 作用 |
Unsloth | 用于数据处理和模型微调。 | |
Transformers | Hugging Face 提供的模型库,用于加载和微调 DeepSeek-R1。 | |
WandB | 用于训练过程的实时监控和可视化。 | |
LoRA | 用于微调的低秩适应技术。 | |
ModelScope | 用于下载 DeepSeek-R1-8b 模型。 | |
python3.11 | Python 3.11 | 用于执行 Python 脚本和训练任务。 |
1.2 LoRA和 QLoRA 简介
以下是 LoRA 和 QLoRA 的区别表格:
特性 | LoRA (Low-Rank Adaptation) | QLoRA (Quantized LoRA) |
核心原理 | 通过低秩矩阵分解减少需要调整的参数量 | 在 LoRA 的基础上结合量化技术,进一步减少存储和计算需求 |
主要优点 | 降低训练时需要调整的参数数量,提高微调效率 | 除了低秩矩阵,还通过量化减少内存占用,适用于资源有限的环境 |
存储需求 | 较低,但不如 QLoRA 节省内存 | 显著减少内存使用,适合在内存受限的设备上使用 |
计算效率 | 提高训练效率,减少计算资源消耗 | 量化后的低精度计算进一步提高了计算效率,降低了开销 |
适用场景 | 计算资源有限但不需要极限压缩的场景 | 内存和计算资源极其有限的环境,特别是在边缘设备上使用 |
适用硬件 | 适用于大多数硬件设备,尤其是高性能计算环境 | 特别适合内存有限的硬件,如边缘设备、低内存服务器等 |
1.3 LLaMA 架构和 Qwen 架构
特性 | LLaMA 架构 | Qwen 架构 |
开发者 | Meta(Facebook) | 深度求索(DeepSeek) |
设计目标 | 高效、轻量化 | 中文优化、多语言支持 |
参数量 | 7B、13B、33B、65B 等 | 7B、14B 等 |
开源情况 | 开源 | 部分开源或未完全公开 |
适用场景 | 资源有限的环境 | 中文任务、多语言任务 |
LLaMA 架构
- 全称:Large Language Model Meta AI(LLaMA)
- 开发者:由 Meta(原 Facebook)开发。
- 特点:
高效性:LLaMA 旨在以较少的参数量实现高性能,专注于优化计算效率。
轻量化:模型参数量相对较小(如 7B、13B、33B、65B),但通过高质量数据和训练方法,性能接近甚至超越更大的模型。
开源:Meta 发布了 LLaMA 的权重和代码,供研究社区使用。
- 应用场景:
适合资源有限的环境,如本地部署或移动设备。
适用于各种 NLP 任务,尤其是在生成、问答、文本分类等任务中,具有较好的性能和效率。
Qwen 架构
- 开发者:由中国的深度求索(DeepSeek)团队开发。
- 特点:
定制化设计:Qwen 可能是针对中文或特定任务优化的架构,具体细节未完全公开。
多语言支持:Qwen 系列模型通常对中文有较好的支持,同时在英文和多语言任务上也有不错的表现。
参数量灵活:Qwen 系列包括不同规模的模型(如 7B、14B 等),适合不同场景。
- 应用场景:
Qwen 适用于文本生成、自动化内容创作、对话系统、语音合成等任务。
二. 环境准备
2.1 Unsloth 安装(显卡版本-暂时不用)
- Unsloth 是一个用于数据处理和模型微调的工具。您可以通过以下命令安装:
- MAC不试用,需要显卡
##官网:https://github.com/unslothai/unsloth #01 创建项目,并设置python虚拟环境,python3.11版本 #02 安装 unsloth(cpu版本) brew install llvm(Homebrew clang version 19.1.7) echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc source ~/.zshrc pip install torch pip install numpy pip install"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git" #03 版本检查 python -c "import torch; print(torch.__version__)" 2.6.0 #04 引用 from unsloth import FastLanguageModel
安装完成后,您可以使用 Unsloth 进行数据的预处理、加载和微调模型。
- 暂时不使用
#01 linux 服务建议使用docker #02 拉取镜像 docker pull modelscope-registry.us-west-1.cr.aliyuncs.com/modelscope-repo/modelscope:ubuntu22.04-py310-torch2.3.1-1.22.2 #03 启动
2.2 创建Python项目
复制#01 环境是python3.11 #02 项目目录 Unsloth-DeepSeek-R1-8b/ ├── data/ # 存放训练数据、验证数据等 │ ├── raw/ # 原始数据 │ └── processed/ # 预处理后的数据 │ ├── models/ # 存放模型文件 │ ├── checkpoints/ # 存储训练过程中的模型检查点 │ └── final_model/ # 存储最终微调后的模型 │ ├── scripts/ # 存放训练脚本、数据处理脚本等 │ ├── train.py # 训练脚本 │ ├── data_preprocessing.py# 数据预处理脚本 │ └── evaluate.py # 评估脚本 │ ├── logs/ # 存放训练日志文件 │ └── training_logs.txt # 训练过程中的日志 │ ├── wandb/ # 存放 wandb 相关的配置和记录 │ └── wandb_config.py # wandb 配置文件 │ ├── environment/ # 环境配置文件 │ ├── requirements.txt # 项目的 Python 依赖 │ └── environment.yml # 如果使用 Conda,可以创建一个环境配置文件 │ ├── main.py # 主运行文件,启动训练或其他任务 └── README.md # 项目的描述文件,包含如何使用和运行的说明 #03 创建目录 # 创建子目录 mkdir -p data/raw mkdir -p data/processed mkdir -p models/checkpoints mkdir -p models/final_model mkdir -p scripts mkdir -p logs mkdir -p wandb mkdir -p environment # 创建文件 touch scripts/train.py touch scripts/data_preprocessing.py touch scripts/evaluate.py touch logs/training_logs.txt touch wandb/wandb_config.py touch environment/requirements.txt touch environment/environment.yml touch main.py touch README.md
2.3 python 依赖库
复制#03 安装即可 pip install torch==2.6.0 transformers datasets #03 更新证书(后续如果有pip网站使用https 会验证该证书) /Applications/Python\ 3.11/Install\ Certificates.command
2.2 LoRA peft 安装
LoRA 和 PEFT 的安装:
- LoRA 和 PEFT 是用于高效微调的技术。如果你想在 Mac 上使用这些技术来微调 DeepSeek 模型,你需要安装相关的依赖项。
- PEFT 包含了 LoRA 的实现,并且它使得你能够通过修改模型的一部分参数来进行高效微调,从而不需要调整整个模型的权重。
#01 安装 peft pip install peft
2.3 WandB 设置
WandB 是一个用于训练过程实时监控和可视化的工具。您可以通过以下步骤设置 WandB:
- 注册并登录 WandB官网。
- 获取您的 API 密钥并配置环境变量:
#01 aipkey (本人谷歌邮箱) #02 命令 pip install wandb wandb login #02 运行文件 import wandb # 导入 wandb 库,用于跟踪和可视化实验 import random # 导入 random 库,用于生成随机数 # 开始一个新的 wandb 运行来跟踪当前脚本 wandb.init( # 设置 wandb 项目,所有与该运行相关的数据将被记录到这个项目中 project="my-awesome-project", # 项目名称,你可以在 wandb 仪表盘中看到这个项目 # 追踪超参数和运行的元数据 config={ "learning_rate": 0.02, # 设置学习率 "architecture": "CNN", # 模型架构(这里是卷积神经网络) "dataset": "CIFAR-100", # 使用的数据集(这里是 CIFAR-100 数据集) "epochs": 10, # 训练的轮数 } ) # 模拟训练过程 epochs = 10# 总训练轮数 offset = random.random() / 5# 生成一个小的随机偏移量,用于模拟训练过程中一些不确定性 # 开始训练循环,模拟 2 到 10 轮的训练过程 for epoch inrange(2, epochs): # 从第二轮开始,到第 10 轮结束 # 模拟准确率的变化,随着 epoch 的增加,准确率逐渐提升 acc = 1 - 2 ** -epoch - random.random() / epoch - offset # 模拟损失的变化,随着 epoch 的增加,损失逐渐减少 loss = 2 ** -epoch + random.random() / epoch + offset # 使用 wandb 记录每一轮的准确率(acc)和损失值(loss) wandb.log({"acc": acc, "loss": loss}) # [可选] 结束 wandb 运行,确保数据被正确上传并完成记录 wandb.finish()
2.4 modelscope pull 模型
复制#01 安装modelscope pip install modelscope #02 下载模型文件 mkdir -p ./models/DeepSeek-R1-Distill-Llama-8B mkdir -p ./models/DeepSeek-R1-Distill-Qwen-1.5B mkdir -p ./models/DeepSeek-R1-Distill-Qwen-7B modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Llama-8B --local_dir ./models/DeepSeek-R1-Distill-Llama-8B modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B --local_dir ./models/DeepSeek-R1-Distill-Qwen-1.5B modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --local_dir ./models/DeepSeek-R1-Distill-Qwen-7B modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Llama-8B --local_dir ./DeepSeek-R1-Distill-Llama-8B
2.5 测试模型使用
复制""" 训练前询问问题: 皮质醇增多症患者在血浆ACTH明显升高且大剂量地塞米松抑制试验阳性的情况下,应考虑哪种疾病? 训练后再次询问: scripts/test_inference.py """ import os from transformers import AutoModelForCausalLM, AutoTokenizer import torch # 获取当前脚本的路径 current_dir = os.path.dirname(__file__) # 拼接模型和分词器路径 model_dir = os.path.join(current_dir, '..', 'models', 'DeepSeek-R1-Distill-Qwen-1.5B') # 打印路径确认 print(f"Model path: {model_dir}") # 确保模型和分词器的路径存在 ifnot os.path.exists(model_dir): raise ValueError(f"Model directory does not exist at {model_dir}") else: print("Model directory exists, proceeding with loading.") # 加载模型和分词器 print("Loading model and tokenizer...") model = AutoModelForCausalLM.from_pretrained(model_dir) tokenizer = AutoTokenizer.from_pretrained(model_dir) # 打印模型和分词器的配置信息 print(f"Model config: {model.config}") print(f"Tokenizer config: {tokenizer}") # 输入中文文本 input_text = "皮质醇增多症患者在血浆ACTH明显升高且大剂量地塞米松抑制试验阳性的情况下,应考虑哪种疾病?" print(f"User input: {input_text}") # 结构化的 prompt prompt_style_chat = f"""请写出一个恰当的回答来完成当前对话任务。 ### Instruction: 你是一名助人为乐的助手。 ### Question: {input_text} ### Response: <think>""" # 使用分词器处理输入文本 inputs = tokenizer(prompt_style_chat, return_tensors="pt", padding=True, truncation=True, max_length=512) # 打印 tokenized 输入 print(f"Tokenized input: {inputs}") # 打印输入形状 print(f"Input shape: {inputs['input_ids'].shape}") # 打印模型的最大长度 print(f"Model max length: {model.config.max_position_embeddings}") # 将模型移至正确的设备(使用 GPU 如果可用) device = "cuda"if torch.cuda.is_available() else"cpu" model.to(device) # 打印设备信息 print(f"Using device: {device}") # 打印分词器的 pad_token_id pad_token_id = tokenizer.pad_token_id if tokenizer.pad_token_id isnotNoneelse model.config.pad_token_id print(f"Using pad_token_id: {pad_token_id}") # 生成模型输出 print("Generating response...") # 使用 max_new_tokens 来控制生成长度 with torch.no_grad(): # 禁用梯度计算,节省内存 try: print("Calling model.generate()...") outputs = model.generate( inputs['input_ids'].to(device), attention_mask=inputs['attention_mask'].to(device), max_new_tokens=1200, # 设置最大生成的 token 数量 temperature=1.0, top_p=0.9, pad_token_id=pad_token_id ) print("Model.generate() completed.") except Exception as e: print(f"Error generating response: {e}") raise # 打印生成的输出 ID 和它们的形状 print(f"Generated output IDs: {outputs}") print(f"Shape of generated output: {outputs.shape}") # 解码生成的输出文本 try: response = tokenizer.decode(outputs[0], skip_special_tokens=True) print(f"Generated response: {response}") except Exception as e: print(f"Error decoding output: {e}")
- 问题回答
User input:皮质醇增多症患者在血浆ACTH明显升高且大剂量地塞米松抑制试验阳性的情况下,应考虑哪种疾病? Tokenized input: {'input_ids':tensor([[151646, 14880, 112672, 46944, 112449, 111423, 36407, 60548, 67949, 105051, 88802, 3407, 14374, 29051, 510, 56568, 110124, 99262, 103247, 99350, 9370, 110498, 3407, 14374, 15846, 510, 99888, 99178, 103032, 107284, 99769, 101924, 18493, 99389, 101498, 6823, 39, 100687, 109061, 100136, 26288, 114786, 29490, 101202, 72261, 100180, 106555, 102360, 112758, 104248, 3837, 50511, 101118, 113195, 101160, 26850, 14374, 5949, 510, 151648]]), 'attention_mask':tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])} Input shape:torch.Size([1,60]) Model max length:131072 Using device:cpu Using pad_token_id:151643 Generatingresponse... Callingmodel.generate()... Model.generate()completed. Generated response:请写出一个恰当的回答来完成当前对话任务。 ### Instruction: 你是一名助人为乐的助手。 ### Question: 皮质醇增多症患者在血浆ACTH明显升高且大剂量地塞米松抑制试验阳性的情况下,应考虑哪种疾病? ### Response: <think> 好的,我现在需要仔细分析这个问题并给出一个合适的回答。首先,问题描述的是皮质醇增多症(PHT)患者在血浆ACTH明显升高且大剂量地塞米松抑制试验(SSDS)显示阳性的情况下,应考虑哪种疾病。 首先,我记得皮质醇增多症是由于皮质醇分泌异常导致,通常由代谢紊乱或神经退行性疾病引起,比如皮质醇过激释放症、皮质醇过激释放性代谢综合征等。通常,患者可能表现出皮质醇水平升高,血浆ACTH显著升高,这符合题意的第一个条件。 接下来,第二个条件是SSDS试验阳性。SSDS试验主要用于检测皮质醇释放的细胞因子,比如PD-L1,这些因子在疾病早期有显著的表观变化。皮质醇增多症患者的皮质醇释放确实受阻,导致细胞因子释放减少,这在SSDS中会被检测出来,所以这种情况属于皮质醇增多症。 综合这两个条件,患者的血浆ACTH升高和SSDS阳性,符合皮质醇增多症的特征。因此,这种情况下应考虑的是皮质醇增多症。 我需要确保我没有遗漏其他可能导致SSDS试验阳性的情况。比如,是否有一些其他类型的疾病,比如胰岛素素合成障碍或胰岛素缺乏,也会影响皮质醇释放?不过,这些更可能是胰岛素素合成障碍,而不是直接由皮质醇释放受阻引起的。皮质醇增多症通常是由于皮质醇释放异常,因此SSDS阳性更直接与皮质醇释放受阻相关。 此外,ACTH升高可能与皮质醇增多症不同,而更可能是由于激素分泌过量或其他激素调节问题。因此,ACTH升高的信号应该更多指向皮质醇增多症。 综上所述,这种情况下应该考虑的疾病是皮质醇增多症。 </think> 应考虑皮质醇增多症(PantoprazolidonePhenomenon)。 因为: 1.血浆ACTH显著升高,符合皮质醇增多症的特征。 2.SSDS试验阳性,表明皮质醇释放受阻,属于皮质醇增多症的表现。
三. 训练数据数据
3.1 准备数据集
复制#01 我们使用COT格式 医学领域 medical-o1-reasoning-SFT 数据集 https://huggingface.co/datasets/FreedomIntelligence/medical-o1-reasoning-SFT #02 b本地导入方式() from datasets import load_dataset ds = load_dataset("FreedomIntelligence/medical-o1-reasoning-SFT", "zh")
- Hugging face 数据集
- modelscope
#01 使用modelscope 数据集 官网地址 https://www.modelscope.cn/datasets/YIRONGCHEN/PsyDTCorpus/files #02 下载完整数据集repo modelscope download --dataset YIRONGCHEN/PsyDTCorpus --local_dir ./dir #03 下载单个文件到指定本地文件夹(以下载README.md到当前路径下“dir”目录为例) modelscope download --dataset YIRONGCHEN/PsyDTCorpus README.md --local_dir ./dir
3.2 数据清洗
复制#01 用于对medical-o1-reasoning-SFT数据集进行修改,Complex_CoT列和Response列进行拼接,并加上文本结束标记: defformatting_prompts_func(examples, EOS_TOKEN): """ 格式化数据集中的每个示例,使其符合训练的要求。 Args: examples (dict): 数据集中的输入示例 EOS_TOKEN (str): 结束符 Returns: dict: 格式化后的文本数据 """ train_prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request. Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response. ### Instruction: You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. Please answer the following medical question. ### Question: {} ### Response: <think> {} </think> {}""" inputs = examples["Question"] cots = examples["Complex_CoT"] outputs = examples["Response"] texts = [] forinput, cot, output inzip(inputs, cots, outputs): text = train_prompt_style.format(input, cot, output) + EOS_TOKEN texts.append(text) return {"text": texts} """ 问题({}) 被嵌套到 ### Question: 下面,替换掉 {}。 推理过程({}) 被嵌套到 <think></think> 标签内,替换掉第二个 {}。 答案({}) 被嵌套到模板的最后,替换掉第三个 {}。 具体替换流程: {} 第一个位置将会被每个样本中的问题(examples["Question"])替换。 {} 第二个位置将会被每个样本中的推理过程(examples["Complex_CoT"])替换。 {} 第三个位置将会被每个样本中的答案(examples["Response"])替换。 例如,如果输入数据如下: 问题(Question): "What is the cause of fever?" 推理过程(Complex_CoT): "Fever is usually caused by an infection or inflammation. We need to identify the source." 答案(Response): "The most common causes of fever are bacterial or viral infections." """
- 原数据格式
{ "Question": [ "What is the cause of headache?", "How do you treat a cold?" ], "Complex_CoT": [ "The causes of headaches are numerous, including tension, dehydration, or sinus issues.", "Treating a cold typically involves rest, fluids, and over-the-counter medications for symptoms." ], "Response": [ "A headache can be caused by stress, lack of sleep, or a sinus infection.", "For a cold, hydration and rest are key. Medications like ibuprofen can help with symptoms." ] }
- 格式化后数据
{ "text": [ """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request. Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response. ### Instruction: You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. Please answer the following medical question. ### Question: What is the cause of headache? ### Response: <think> The causes of headaches are numerous, including tension, dehydration, or sinus issues. </think> A headache can be caused by stress, lack of sleep, or a sinus infection. <|endoftext|> """, """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request. Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response. ### Instruction: You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. Please answer the following medical question. ### Question: How do you treat a cold? ### Response: <think> Treating a cold typically involves rest, fluids, and over-the-counter medications for symptoms. </think> For a cold, hydration and rest are key. Medications like ibuprofen can help with symptoms. <|endoftext|> """ ] }
3.3 训练数据
- setup_wandb: 配置并登录到 wandb 进行实验跟踪和日志记录。
- set_paths: 设置根目录、模型路径、数据集路径和保存微调模型的路径。
- load_model_and_tokenizer: 加载预训练模型和分词器,获取结束符。
- formatting_prompts_func: 格式化数据集中的问题和回答,以便训练。
- setup_lora: 配置并应用LoRA(低秩适配器)到模型。
- load_dataset_func: 加载数据集并进行切分,返回训练集和评估集。
- setup_training_args: 设置训练参数,包括学习率、批处理大小、训练周期等。
- train_model: 使用 SFTTrainer 进行模型训练。
- save_model: 保存训练好的模型到指定路径。
import os import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments from datasets import load_dataset from peft import get_peft_model, LoraConfig from trl import SFTTrainer # 使用 SFTTrainer import wandb from config import setting # 设置环境变量,禁用tokenizer的并行化 os.environ["TOKENIZERS_PARALLELISM"] = "false" # 登录wandb defsetup_wandb(): """ 登录到wandb以便记录训练过程中的日志和指标。 """ wandb.login() # 设置路径 defset_paths(): """ 设置项目根目录、模型路径、数据集路径和最终模型保存路径。 Returns: model_dir (str): 模型文件路径 dataset_path (str): 数据集路径 final_model_dir (str): 微调后模型的保存路径 """ root_dir = setting.root_dir # 项目根路径 model_dir = os.path.join(root_dir, 'models', 'DeepSeek-R1-Distill-Qwen-1.5B') # 模型文件路径 dataset_path = os.path.join(root_dir, 'data', 'medical-o1-reasoning-SFT') # 数据集路径 final_model_dir = os.path.join(root_dir, 'models', 'final_model') # 高效微调后模型保存路径 print(f'设置模型路径:{model_dir} | 数据集位置:{dataset_path}') return model_dir, dataset_path, final_model_dir # 加载模型和分词器 defload_model_and_tokenizer(model_dir): """ 加载预训练模型和对应的分词器,并获取结束符(EOS_TOKEN)。 Args: model_dir (str): 模型的文件路径 Returns: model (AutoModelForCausalLM): 加载的模型 tokenizer (AutoTokenizer): 加载的分词器 EOS_TOKEN (str): 模型的结束符(如果没有,使用默认值) """ print("加载分词器:Loading model and tokenizer...") model = AutoModelForCausalLM.from_pretrained(model_dir) tokenizer = AutoTokenizer.from_pretrained(model_dir) EOS_TOKEN = tokenizer.eos_token if EOS_TOKEN isNone: EOS_TOKEN = "<|endoftext|>" print(f'结束符:{EOS_TOKEN}') return model, tokenizer, EOS_TOKEN # 格式化训练数据 defformatting_prompts_func(examples, EOS_TOKEN): """ 格式化数据集中的每个示例,使其符合训练的要求。 Args: examples (dict): 数据集中的输入示例 EOS_TOKEN (str): 结束符 Returns: dict: 格式化后的文本数据 """ train_prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request. Before answering, think carefully about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response. ### Instruction: You are a medical expert with advanced knowledge in clinical reasoning, diagnostics, and treatment planning. Please answer the following medical question. ### Question: {} ### Response: <think> {} </think> {}""" inputs = examples["Question"] cots = examples["Complex_CoT"] outputs = examples["Response"] texts = [] forinput, cot, output inzip(inputs, cots, outputs): text = train_prompt_style.format(input, cot, output) + EOS_TOKEN texts.append(text) return {"text": texts} # 设置LoRA配置 defsetup_lora(model): """ 设置LoRA(低秩适配器)配置,并将其应用到模型。 Args: model (AutoModelForCausalLM): 加载的模型 Returns: model (AutoModelForCausalLM): 应用LoRA后的模型 """ print("设置LoRA: Setting up LoRA configuration...") lora_config = LoraConfig( r=8, # adapter的秩 lora_alpha=32, # 缩放因子 lora_dropout=0.1, # LoRA层的dropout bias="none", # LoRA的偏置项 ) return get_peft_model(model, lora_config) # 加载数据集 defload_dataset_func(dataset_path, train_size=100): """ 从指定路径加载数据集,训练集大小为 train_size,评估集为训练集的10%,但至少为1。 """ print(f"从 {dataset_path} 加载数据集...") # 加载数据集 dataset = load_dataset(dataset_path, "en", split="train", trust_remote_code=True) # 计算评估集大小 eval_size = max(1, int(train_size * 0.1)) # 评估集大小是训练集的10%,但至少为1 # 切分数据集 train_dataset = dataset.select(range(train_size)) # 使用前 train_size 条作为训练集 eval_dataset = dataset.select(range(train_size, train_size + eval_size)) # 剩余部分作为评估集 print(f"训练集大小: {len(train_dataset)}, 评估集大小: {len(eval_dataset)}") return train_dataset, eval_dataset # 配置训练参数 defsetup_training_args(final_model_dir, enable_evaluation=True): """ 设置训练参数,包括输出目录、学习率、批处理大小等,并根据参数控制是否启用评估。 Args: final_model_dir (str): 微调后模型保存的路径 enable_evaluation (bool): 是否启用评估。默认为True,启用评估;为False时禁用评估。 Returns: training_args (TrainingArguments): 训练参数 """ # 根据是否启用评估设置 evaluation_strategy evaluation_strategy = "epoch"if enable_evaluation else"no" training_args = TrainingArguments( output_dir=final_model_dir, evaluation_strategy=evaluation_strategy, # 控制评估策略 learning_rate=5e-5, per_device_train_batch_size=2, # 适当减少批处理大小(根据M3 Pro的内存限制) gradient_accumulation_steps=4, # 使用梯度累积,模拟更大的批量 num_train_epochs=3, # 训练3个周期 report_to="wandb", # 使用wandb进行训练日志记录 weight_decay=0.01, logging_dir=os.path.join(setting.root_dir, 'logs'), logging_steps=50, # 减少日志记录频率 save_steps=500, # 增加模型保存的步数频率,减少频繁保存 save_total_limit=2, # 保存最多2个模型 dataloader_num_workers=4, # 设置数据加载器的并行数(根据需要调整) ) return training_args # 训练模型 deftrain_model(model, training_args, dataset, eval_dataset, tokenizer, enable_evaluation=True): """ 使用SFTTrainer进行模型训练。 Args: model (AutoModelForCausalLM): 需要训练的模型 training_args (TrainingArguments): 训练参数 dataset (Dataset): 用于训练的数据集 eval_dataset (Dataset): 用于评估的数据集 tokenizer (AutoTokenizer): 分词器 enable_evaluation (bool): 是否进行评估 Returns: trainer (SFTTrainer): 训练器实例 """ # 如果启用了评估,传递评估集 trainer = SFTTrainer( model=model, args=training_args, train_dataset=dataset, eval_dataset=eval_dataset if enable_evaluation elseNone, # 根据参数决定是否传递评估集 tokenizer=tokenizer, data_collator=None, # 可以选择合适的data collator ) trainer.train() return trainer # 保存模型 defsave_model(trainer, final_model_dir): """ 保存训练后的模型到指定目录。 Args: trainer (SFTTrainer): 训练器实例 final_model_dir (str): 模型保存路径 """ print("Saving model...") trainer.save_model(final_model_dir) defmerge_models(models, weights, device="cpu"): """ 合并多个模型的权重(加权平均)。 Args: models (list): 模型列表 weights (list): 权重列表,权重数量与模型数量一致 device (str): 设备,可以是 "cuda" 或 "cpu" Returns: merged_model (nn.Module): 合并后的模型 """ # 确保模型数量与权重数量一致 assertlen(models) == len(weights), "模型数量与权重数量不一致" # 将所有模型加载到相同的设备 for i inrange(len(models)): models[i] = models[i].to(device) # 获取第一个模型的状态字典 merged_state_dict = models[0].state_dict() # 对每一层的权重进行加权平均 for key in merged_state_dict.keys(): merged_state_dict[key] = torch.zeros_like(merged_state_dict[key]) for model, weight inzip(models, weights): merged_state_dict[key] += model.state_dict()[key] * weight # 创建一个新的模型并加载合并后的权重 merged_model = models[0].__class__.from_pretrained(models[0].config) merged_model.load_state_dict(merged_state_dict) return merged_model # 主函数 defmain(): """ 主函数,执行整个训练流程:设置路径、加载模型、训练并保存模型。 参数设置: enable_evaluation = False # 设置为False以禁用评估 如果性能慢可以设置 False 加载数据集: train_size=10 设置数据集大小,评估集是数据集百分之10(如果小于1 则等于1 ) train_dataset, eval_dataset = load_dataset_func(dataset_path, train_size=10) """ setup_wandb() # 登录wandb model_dir, dataset_path, final_model_dir = set_paths() # 设置路径 model, tokenizer, EOS_TOKEN = load_model_and_tokenizer(model_dir) # 加载模型和分词器 train_dataset, eval_dataset = load_dataset_func(dataset_path, train_size=5) # 加载数据集 train_dataset = train_dataset.map(lambda examples: formatting_prompts_func(examples, EOS_TOKEN), batched=True) # 格式化数据集 eval_dataset = eval_dataset.map(lambda examples: formatting_prompts_func(examples, EOS_TOKEN), batched=True) # 格式化评估集 print(train_dataset["text"][0]) # 打印格式化后的数据 model = setup_lora(model) # 配置LoRA # 设置是否开启评估 enable_evaluation = True# 设置为False以禁用评估 training_args = setup_training_args(final_model_dir,enable_evaluation) # 配置训练参数 trainer = train_model(model, training_args, train_dataset, eval_dataset, tokenizer, enable_evaluation) # 开始训练 save_model(trainer, final_model_dir) # 保存模型 wandb.finish() # 完成wandb记录 # 执行主函数 if __name__ == "__main__": main()
3.3 训练模型并保存
复制""" 保存在本地 models/final_model 路径下 """ defsave_model(trainer, final_model_dir): """ 保存训练后的模型到指定目录。 Args: trainer (SFTTrainer): 训练器实例 final_model_dir (str): 模型保存路径 """ print("Saving model...") trainer.save_model(final_model_dir)
3.4 合并模型文件
复制#01 执行即可 new_model_local = "DeepSeek-R1-Medical-COT-Tiny" model.save_pretrained(new_model_local) tokenizer.save_pretrained(new_model_local) model.save_pretrained_merged(new_model_local, tokenizer, save_method = "merged_16bit",)
3.4 评估和监控训练过程
评估(eval/)相关信息:
- eval/runtime 18.3908: 评估过程总共耗时18.39秒。
- eval/samples_per_second 0.054: 每秒处理的样本数为0.054,表示评估的速度较慢。
- eval/steps_per_second 0.054: 每秒进行评估步数为0.054,说明每个评估步骤的时间消耗较大。
训练(train/)相关信息:
- train/epoch 0: 当前训练轮次是第0轮。
- train/global_step 0: 当前全局步骤为0,表示尚未进行任何训练步骤。
- train_loss 14435.36663: 当前训练的损失为14435.37,表明模型的表现尚不理想,通常需要更多的训练来降低损失。
- train/runtime 251.7582: 训练总时间为251.76秒。
- train/samples_per_second 0.06: 每秒处理的训练样本数为0.06,训练的速度较慢。
- train/steps_per_second 0.012: 每秒进行的训练步数为0.012,表示每个训练步骤消耗的时间较长。
#02 详细日志 wandb: ⭐️ View project at https://wandb.ai/z15119911990-beijing/huggingface wandb: 🚀 View run at https://wandb.ai/z15119911990-beijing/huggingface/runs/mgrko2jv 0%| | 0/3 [00:00<?, ?it/s] {'eval_runtime': 14.8693, 'eval_samples_per_second': 0.067, 'eval_steps_per_second': 0.067, 'epoch': 0} 0%| | 0/3 [00:30<?, ?it/s] 100%|██████████| 1/1 [00:00<00:00, 1461.94it/s] {'eval_runtime': 21.2073, 'eval_samples_per_second': 0.047, 'eval_steps_per_second': 0.047, 'epoch': 0} 0%| | 0/3 [02:11<?, ?it/s] 100%|██████████| 1/1 [00:00<00:00, 33.69it/s] 0%| | 0/3 [04:02<?, ?it/s] 100%|██████████| 1/1 [00:00<00:00, 334.66it/s] {'eval_runtime': 18.3908, 'eval_samples_per_second': 0.054, 'eval_steps_per_second': 0.054, 'epoch': 0} {'train_runtime': 251.7582, 'train_samples_per_second': 0.06, 'train_steps_per_second': 0.012, 'train_loss': 14435.3666305542, 'epoch': 0} 0%| | 0/3 [04:10<?, ?it/s] wandb: wandb: wandb: Run history: wandb: eval/runtime ▁█▅ wandb: eval/samples_per_second █▁▃ wandb: eval/steps_per_second █▁▃ wandb: train/epoch ▁▁▁▁ wandb: train/global_step ▁▁▁▁ wandb: wandb: Run summary: wandb: eval/runtime 18.3908 wandb: eval/samples_per_second 0.054 wandb: eval/steps_per_second 0.054 wandb: total_flos 43804457687040.0 wandb: train/epoch 0 wandb: train/global_step 0 wandb: train_loss 14435.36663 wandb: train_runtime 251.7582 wandb: train_samples_per_second 0.06 wandb: train_steps_per_second 0.012 wandb: wandb: 🚀 View run /Users/ningcaichen/Documents/02-python相关文档/01-AI系列/LoRA-DeepSeek-R1/models/final_model at: https://wandb.ai/z15119911990-beijing/huggingface/runs/mgrko2jv wandb: ⭐️ View project at: https://wandb.ai/z15119911990-beijing/huggingface wandb: Synced 5 W&B file(s), 0 media file(s), 0 artifact file(s) and 0 other file(s) wandb: Find logs at: ./wandb/run-20250212_133457-mgrko2jv/logs