DeepSeek-R1 医疗领域微调项目

142 阅读8分钟

DeepSeek-R1 医疗领域微调项目

本项目基于unsloth微调框架与LoRA技术,对DeepSeek-R1-Distill-Qwen-7B模型进行医疗领域微调,提升其在临床推理和诊断方面的能力。

技术亮点

  • 🚀 高效微调:采用LoRA技术实现参数高效微调
  • 🧠 思维链增强:使用医疗CoT问答数据集增强推理能力
  • 🔥 4bit量化:unsloth框架支持4bit量化加载,降低显存消耗
  • 📊 实时监控:集成Weights & Biases进行训练可视化
  • 🏥 专业领域:针对医疗问答场景优化临床推理能力

image-20250308223538833.png

环境配置

# 创建虚拟环境
sudo apt install python3-venv
python3 -m venv unsloth
source unsloth/bin/activate
​
# 安装依赖
pip install unsloth wandb python-dotenv datasets

快速开始

# 完整训练脚本
python r1-finetuning-unsloth.py

核心流程

1. 模型加载与配置

from unsloth import FastLanguageModel
​
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/DeepSeek-R1-Distill-Qwen-1.5B",
    max_seq_length = 2048,
    load_in_4bit = True,
)

2. 提示模板设计

medical_prompt = """...<think>推理过程</think>..."""

3. 数据集处理

使用医疗推理数据集:

dataset = load_dataset("FreedomIntelligence/medical-o1-reasoning-SFT", "zh")

4. LoRA配置

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=["q_proj", "k_proj", ..., "down_proj"],
    lora_alpha=16
)

5. 训练参数

training_args = TrainingArguments(
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    max_steps=60,
    bf16=True,
)

训练监控

实时查看训练指标:

https://wandb.ai/[your-username]/Fine-tune-DeepSeek-R1-Distill-Qwen-1.5B

效果对比

微调前

建议使用退烧药,如对乙酰氨基酚...

微调后

<think>
1. 确认病毒感染特征:反复发热符合病毒性感冒特点
2. 评估发热管理:物理降温结合药物控制
3. 抗病毒药物选择:考虑奥司他韦...
</think>
推荐使用奥司他韦进行抗病毒治疗...

模型部署

# 本地保存
model.save_pretrained_merged("medical-model", save_method="merged_16bit")

# 上传Hugging Face Hub
model.push_to_hub_merged("your-username/DeepSeek-R1-Medical")

硬件要求

组件最低配置
GPURTX 3090 24GB
显存12GB
RAM32GB

完整代码

from huggingface_hub import login
import wandb

"""
第1步:初始化设置和登录

设置访问令牌并登录到HuggingFace和Weights&Biases平台
"""
# 直接设置访问令牌
hf_token = "XXX"  # 替换为你的token
wb_token = "XXX"        # 替换为你的token

# 验证token是否存在并有效
if not hf_token or hf_token.strip() == "":
    raise ValueError("""
    未找到有效的HUGGINGFACE_TOKEN。
    请确保已设置正确的HuggingFace token。
    可以从 https://huggingface.co/settings/tokens 获取新的token
    """)

try:
    # 登录HuggingFace
    login(token=hf_token, write_permission=True)
    print("HuggingFace登录成功!")
    
    # 登录Weights & Biases
    if wb_token:
        wandb.login(key=wb_token)
        print("Weights & Biases登录成功!")
    else:
        print("警告: 未设置WANDB_TOKEN,将使用匿名模式")
        
except Exception as e:
    print(f"登录失败: {str(e)}")
    print("\n请确保:")
    print("1. token格式正确 (应该以 'hf_' 开头)")
    print("2. token具有足够的权限 (需要 'write' 权限)")
    print("3. token未过期")
    raise

# 初始化wandb项目
run = wandb.init(
    # 项目名称
    project='Fine-tune-DeepSeek-R1-Distill-Qwen-1.5B',
    # 实验类型
    job_type="training",
    # 匿名设置,allow表示允许匿名访问实验结果
    # 可选值:
    # - allow: 允许匿名访问
    # - must: 必须匿名
    # - never: 不允许匿名
    anonymous="allow"
)


"""
第2步:加载模型和分词器

使用unsloth优化的FastLanguageModel加载预训练模型
"""
from unsloth import FastLanguageModel

# 模型配置参数
max_seq_length = 2048  # 最大序列长度
dtype = None          # 数据类型,None表示自动选择
load_in_4bit = True   # 使用4bit量化加载模型以节省显存


# 加载预训练模型和分词器
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/DeepSeek-R1-Distill-Qwen-1.5B",
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    token = hf_token, 
)


"""
第3步:定义提示模板和进行微调前的推理测试
"""
prompt_style = """以下是描述任务的指令,以及提供更多上下文的输入。
请写出恰当完成该请求的回答。
在回答之前,请仔细思考问题,并创建一个逐步的思维链,以确保回答合乎逻辑且准确。

### Instruction:
你是一位在临床推理、诊断和治疗计划方面具有专业知识的医学专家。
请回答以下医学问题。

### Question:
{}

### Response:
<think>{}"""

# 测试用医学问题
question = "宝宝病毒感染,高烧38.6,吃上退烧药就好,停了就又发烧,请问像这种病毒性感冒发烧吃什么药好?"

# 设置模型为推理模式
FastLanguageModel.for_inference(model) 
inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")

# 生成回答
outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
print("### 微调前模型推理结果:")
print(response[0].split("### Response:")[1])


"""
第4步:数据集处理函数
"""
train_prompt_style = """以下是描述任务的指令,以及提供更多上下文的输入。
                        请写出恰当完成该请求的回答。
                        在回答之前,请仔细思考问题,并创建一个逐步的思维链,以确保回答合乎逻辑且准确。

                        ### Instruction:
                        你是一位在临床推理、诊断和治疗计划方面具有专业知识的医学专家。
                        请回答以下医学问题。

                        ### Question:
                        {}

                        ### Response:
                        <think>
                        {}
                        </think>
                        {}"""

EOS_TOKEN = tokenizer.eos_token  # 添加结束符标记

#格式化提示函数,用于处理数据集中的示例
def formatting_prompts_func(examples):
    # 从examples中提取问题、思维链和回答
    inputs = examples["Question"]      # 医学问题列表
    cots = examples["Complex_CoT"]     # 思维链列表 
    outputs = examples["Response"]     # 回答列表
    
    # 存储格式化后的文本
    texts = []

    # 遍历每个示例,将问题、思维链和回答组合成指定格式
    for input, cot, output in zip(inputs, cots, outputs):
        # 使用train_prompt_style模板格式化文本,并添加结束符
        text = train_prompt_style.format(input, cot, output) + EOS_TOKEN
        texts.append(text)
        
    # 返回格式化后的文本字典
    return {
        "text": texts,
    }

# 加载数据集并应用格式化
from datasets import load_dataset
dataset = load_dataset("FreedomIntelligence/medical-o1-reasoning-SFT","zh", split = "train[0:500]",trust_remote_code=True)
dataset = dataset.map(formatting_prompts_func, batched = True,)


"""
第5步:配置LoRA微调参数

使用LoRA技术进行参数高效微调
"""
FastLanguageModel.for_training(model)

model = FastLanguageModel.get_peft_model(
    # 原始模型
    model, 
    # LoRA秩,用于控制低秩矩阵的维度,值越大表示可训练参数越多,模型性能可能更好但训练开销更大
    # 建议: 8-32之间
    r=16,  
    # 需要应用LoRA的目标模块列表
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",  # attention相关层
        "gate_proj", "up_proj", "down_proj",     # FFN相关层
    ],
    # LoRA缩放因子,用于控制LoRA更新的幅度。值越大,LoRA的更新影响越大。
    lora_alpha=16,
    # LoRA层的dropout率,用于防止过拟合,这里设为0表示不使用dropout。
    # 如果数据集较小,建议设置0.1左右。
    lora_dropout=0,  
    # 是否对bias参数进行微调,none表示不微调bias
    # none: 不微调偏置参数;
    # all: 微调所有参数;
    # lora_only: 只微调LoRA参数。
    bias="none",  
    # 是否使用梯度检查点技术节省显存,使用unsloth优化版本
    # 会略微降低训练速度,但可以显著减少显存使用
    use_gradient_checkpointing="unsloth", 
    # 随机数种子,用于结果复现
    random_state=0,
    # 是否使用rank-stabilized LoRA,这里不使用
    # 会略微降低训练速度,但可以显著减少显存使用
    use_rslora=False,  
    # LoFTQ配置,这里不使用该量化技术,用于进一步压缩模型大小
    loftq_config=None,
)


"""
第6步:配置训练参数和初始化训练器
"""
from trl import SFTTrainer  # 用于监督微调的训练器
from transformers import TrainingArguments  # 用于配置训练参数
from unsloth import is_bfloat16_supported  # 检查是否支持bfloat16精度训练

# 初始化SFT训练器
trainer = SFTTrainer(
    model=model,  # 待训练的模型
    tokenizer=tokenizer,  # 分词器
    train_dataset=dataset,  # 训练数据集
    dataset_text_field="text",  # 数据集字段的名称
    max_seq_length=max_seq_length,  # 最大序列长度
    dataset_num_proc=2,  # 数据集处理的并行进程数,提高CPU利用率
    args=TrainingArguments(
        per_device_train_batch_size=1,  # 每个GPU的训练批次大小
        gradient_accumulation_steps=4,   # 梯度累积步数,用于模拟更大的batch size
        warmup_steps=5,  # 预热步数,逐步增加学习率
        learning_rate=2e-4,  # 学习率
        lr_scheduler_type="linear",  # 线性学习率调度器
        max_steps=60,    # 最大训练步数(一步 = 处理一个batch的数据)
        # 根据硬件支持选择训练精度
        fp16=False,  # 禁用混合精度训练
        bf16=True,      # 启用BF16
        logging_steps=10,  # 每10步记录一次日志
        optim="adamw_8bit",  # 使用8位AdamW优化器节省显存,几乎不影响训练效果
        weight_decay=0.01,   # 权重衰减系数,用于正则化,防止过拟合
        seed=3407,  # 随机数种子
        output_dir="outputs",  # 保存模型检查点和训练日志
    ),
)


"""
第7步 开始训练
"""
trainer.train()


"""
第8步:微调后的模型推理测试
"""
question = "宝宝病毒感染,高烧38.6,吃上退烧药就好,停了就又发烧,请问像这种病毒性感冒发烧吃什么药好?"

# 启用模型推理模式,使用Unsloth加速推理速度
FastLanguageModel.for_inference(model)  

# 对输入问题进行编码,转换为模型可处理的张量格式并移至GPU
inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")

# 生成回答
outputs = model.generate(
    input_ids=inputs.input_ids, # 输入token的id序列
    attention_mask=inputs.attention_mask,  # 注意力掩码,用于标记有效输入位置
    max_new_tokens=1200, # 生成的最大新token数量
    use_cache=True, # 是否使用KV缓存加速生成
)

# 解码模型输出
response = tokenizer.batch_decode(outputs)
print("### 微调后模型推理结果:")
print(response[0].split("### Response:")[1])


"""
第9步:保存模型

包括保存完整模型和合并后的模型
"""
new_model_local = "DeepSeek-R1-Medical-COT-Qwen-1.5B"
model.save_pretrained(new_model_local) 
tokenizer.save_pretrained(new_model_local)

# 保存合并后的16bit模型
model.save_pretrained_merged(new_model_local, tokenizer, save_method = "merged_16bit",)


"""
第10步:模型上传代码
"""
# 定义在线仓库地址 Your_HuggingFace_Name为你HuggingFace的用户名称
new_model_online = "Your_HuggingFace_Name/DeepSeek-R1-Medical-COT-Qwen-1.5B"
# 上传LoRA权重和配置
model.push_to_hub(new_model_online)
# 上传分词器    
tokenizer.push_to_hub(new_model_online)
# 上传合并后的16bit模型
model.push_to_hub_merged(new_model_online, tokenizer, save_method = "merged_16bit")

许可协议

本项目基于 Apache-2.0 license 开源

Hugging Face Model