微调 DeepSeek R1(推理模型)

原文:https://www.datacamp.com/tutorial/fine-tuning-deepseek-r1-reasoning-model

DeepSeek 打破了 AI 局面,通过推出一系列先进的推理模型,挑战了 OpenAI 的主导地位。最棒的是?这些模型完全免费,没有任何限制,每个人都可以使用。

在本教程中,我们将在 Hugging Face 上的 Medical Chain-of-Thought 数据集上微调 DeepSeek-R1-Distill-Llama-8B 模型。这个经过蒸馏的 DeepSeek-R1 模型是通过对 Llama 3.1 8B 模型在 DeepSeek-R1 生成的数据上进行微调创建的。它展示了与原始模型相似的推理能力。

如果你是 LLM(大型语言模型)和微调的新手,我强烈推荐你参加 Python 中的 LLM 入门课程。

![[2866f18742ceb144f4c3d36de6e7fe67_MD5.webp]]

图片由作者提供

介绍 DeepSeek R1

中国 AI 公司 DeepSeek AI 开源了其第一代推理模型 DeepSeek-R1 和 DeepSeek-R1-Zero,这些模型在数学、编程和逻辑等推理任务上的表现与 OpenAI 的 o1 相当。你可以阅读我们关于 DeepSeek R1 的完整指南以了解更多。

DeepSeek-R1-Zero

DeepSeek-R1-Zero 是第一个仅通过大规模强化学习(RL)而非监督微调(SFT)作为初始步骤训练的开源模型。这种方法使模型能够独立探索思维链(CoT)推理,解决复杂问题,并迭代改进其输出。然而,它也带来了诸如重复推理步骤、可读性差、语言混杂等挑战,这些可能会影响其清晰度和可用性。

DeepSeek-R1

DeepSeek-R1 的推出是为了克服 DeepSeek-R1-Zero 的局限性,通过在强化学习之前加入冷启动数据,为推理和非推理任务提供了坚实的推理基础。

这种多阶段训练使模型能够在数学、代码和推理基准测试中实现与 OpenAI-o1 相当的最先进性能,同时提高了其输出的可读性和连贯性。

DeepSeek 蒸馏

除了需要大量计算能力和内存才能运行的大型语言模型外,DeepSeek 还推出了蒸馏模型。这些更小、更高效的模型已经证明它们仍然可以实现令人惊叹的推理性能。

这些模型的参数范围从 15 亿到 700 亿不等,它们保留了强大的推理能力,其中 DeepSeek-R1-Distill-Qwen-32B 在多个基准测试中超过了 OpenAI-o1-mini。

较小的模型继承了较大模型的推理模式,展示了蒸馏过程的有效性。

![[b627ae1c652f7add5b6390e1e9db898f_MD5.webp]]

来源:deepseek-ai/DeepSeek-R1

阅读 DeepSeek-R1:特性、o1 比较、蒸馏模型等博客,了解其关键特性、开发过程、蒸馏模型、访问、定价以及与 OpenAI o1 的比较。

微调 DeepSeek R1:逐步指南

要微调 DeepSeek R1 模型,可以按照以下步骤操作:

1. 设置

为了这个项目,我们使用 Kaggle 作为我们的云 IDE,因为它提供了免费的 GPU 访问权限,这些 GPU 通常比 Google Colab 中可用的更强大。要开始,请启动一个新的 Kaggle 笔记本,并将你的 Hugging Face 令牌和 Weights & Biases 令牌添加为机密。

你可以通过导航到 Kaggle 笔记本界面中的 附加组件 选项卡并选择 机密 选项来添加机密。

在设置好机密后,安装 unsloth Python 包。Unsloth 是一个开源框架,旨在使大型语言模型(LLM)的微调速度加快 2 倍,同时更加节省内存。

阅读我们的 Unsloth 指南:优化和加速 LLM 微调,了解 Unsloth 的关键特性、各种功能以及如何优化你的微调工作流程。

复制

%%capture
!pip install unsloth
!pip install --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git

使用我们从 Kaggle 机密中安全提取的 Hugging Face API 登录 Hugging Face CLI。

复制

from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(hf_token)

使用你的 API 密钥登录 Weights & Biases(wandb),并创建一个新项目以跟踪实验和微调进度。

复制

import wandb

wb_token = user_secrets.get_secret("wandb")

wandb.login(key=wb_token)
run = wandb.init(
    project='Fine-tune-DeepSeek-R1-Distill-Llama-8B on Medical COT Dataset',
    job_type="training",
    anonymous="allow"
)

2. 加载模型和分词器

对于这个项目,我们加载的是 DeepSeek-R1-Distill-Llama-8B 的 Unsloth 版本。此外,我们将以 4 位量化的方式加载模型,以优化内存使用和性能。

复制

from unsloth import FastLanguageModel

max_seq_length = 2048
dtype = None
load_in_4bit = True

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/DeepSeek-R1-Distill-Llama-8B",
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    token = hf_token,
)

3. 微调前的模型推理

为了为模型创建提示风格,我们将定义一个系统提示,并包含问题和响应生成的占位符。该提示将引导模型逐步思考并提供逻辑准确的回答。

复制

prompt_style = """以下是描述任务的指令,以及提供进一步上下文的输入。
写出一个适当完成请求的回应。
在回答之前,仔细思考问题并创建一个逐步的思维链,以确保回答的逻辑性和准确性。

### 指令:
你是一位具有先进临床推理、诊断和治疗计划知识的医学专家。
请回答以下医学问题。

### 问题:
{}

### 回应:
<think>{}"""

在这个例子中,我们将为 prompt_style 提供一个医学问题,将其转换为标记,然后将标记传递给模型以生成回答。

复制

question = "一位 61 岁的女性,长期在咳嗽或打喷嚏等活动中出现非自愿性尿失禁,但夜间没有漏尿。她接受了妇科检查和棉签测试。根据这些发现,膀胱测压术最有可能揭示她的残余体积和逼尿肌收缩情况如何?"

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(response[0].split("### 回应:")[1])

即使没有微调,我们的模型也成功地生成了一个思维链,并在提供最终答案之前进行了推理。推理过程被封装在 <think></think> 标签内。

那么,我们为什么还需要微调呢?虽然推理过程很详细,但冗长且不够简洁。此外,最终答案是以项目符号格式呈现的,这与我们想要微调的数据集的结构和风格不符。

复制

<think>
好的,我有一个医学问题需要回答。让我试着分解一下。患者是一位 61 岁的女性,有在咳嗽或打喷嚏等活动时出现非自愿性尿失禁的病史,但她晚上没有漏尿。她接受了妇科检查和棉签测试。我需要弄清楚膀胱测压术会显示她的残余体积和逼尿肌收缩情况如何。

首先,我应该回忆一下我对尿失禁的了解。在咳嗽或打喷嚏等活动时出现非自愿性尿失禁,让我想到了压力性尿失禁。压力性尿失禁通常发生在尿道括约肌不够强壮,无法抵抗咳嗽、大笑或打喷嚏等活动带来的腹部压力增加时。这种情况通常影响女性,尤其是在分娩后,因为盆底肌肉和韧带被削弱了。

棉签测试是诊断压力性尿失禁的常用工具。测试涉及将一根棉签导管(一种小气球导管)插入尿道。导管连接到压力计。要求患者咳嗽,并记录压力读数。如果压力超过正常值(比如超过 100 毫米汞柱),则表明尿道括约肌没有正常闭合,这是压力性尿失禁的迹象。

所以,根据病史和棉签测试,诊断可能是压力性尿失禁。现在,让我们看看膀胱测压术会显示什么。膀胱测压术,也称为充盈性膀胱测压术,是一种诊断程序,通过向膀胱内插入导管并用液体填充膀胱来测量膀胱的容量(残余体积)以及膀胱对充盈的反应(比如在咳嗽或打喷嚏时)。这有助于评估膀胱的容量和顺应性。

在压力性尿失禁患者中,膀胱的容量可能正常,但括约肌功能受损。因此,在膀胱测压术中,残余体积可能在正常范围内,因为膀胱没有过度充盈。然而,当患者被要求咳嗽或进行Valsalva动作时,逼尿肌(膀胱的平滑肌层)可能无法有效收缩,导致腹部压力增加,从而引起漏尿。

等等,但逼尿肌收缩通常与排尿有关。在压力性尿失禁中,问题不在于逼尿肌收缩,而在于括约肌无法防止漏尿。因此,在膀胱测压术中,逼尿肌收缩将是正常的,因为它们是正常排尿过程的一部分。然而,问题是括约肌无法正常闭合,导致漏尿。

所以,综合来看,残余体积可能是正常的,但逼尿肌收缩也将是正常的。关键发现将是括约肌功能受损导致的漏尿,这通常在棉签测试和临床病史中得到体现。因此,膀胱测压术可能会显示正常的残余体积和正常的逼尿肌收缩,但根本问题是括约肌无法防止漏尿。
</think>

根据提供的信息,这位 61 岁女性的压力性尿失禁患者的膀胱测压术结果最有可能显示以下内容:

1. **残余体积**:残余体积将在正常范围内。这是因为压力性尿失禁患者的膀胱容量通常是正常的,问题主要在于括约肌功能,而非膀胱容量。

2. **逼尿肌收缩**:逼尿肌收缩也将是正常的。这些收缩是正常排尿过程的一部分,在压力性尿失禁中并未受损。问题不在于逼尿肌,而在于括约肌无法防止漏尿。

总结来说,膀胱测压术的关键发现将是正常的残余体积和正常的逼尿肌收缩,突出括约肌功能障碍是漏尿的根本原因。<|end▁of▁sentence|>

4. 加载和处理数据集

我们将稍微更改提示风格,以便为处理数据集添加第三个占位符,用于复杂思维链列。

复制

train_prompt_style = """以下是描述任务的指令,以及提供进一步上下文的输入。
写出一个适当完成请求的回应。
在回答之前,仔细思考问题并创建一个逐步的思维链,以确保回答的逻辑性和准确性。

### 指令:
你是一位具有先进临床推理、诊断和治疗计划知识的医学专家。
请回答以下医学问题。

### 问题:
{}

### 回应:
<think>
{}
</think>
{}"""

编写一个 Python 函数,该函数将在数据集中创建一个“text”列,该列由训练提示风格组成。用问题、思维链和答案填充占位符。

复制

EOS_TOKEN = tokenizer.eos_token  # 必须添加 EOS_TOKEN

def formatting_prompts_func(examples):
    inputs = examples["Question"]
    cots = examples["Complex_CoT"]
    outputs = examples["Response"]
    texts = []
    for input, cot, output in zip(inputs, cots, outputs):
        text = train_prompt_style.format(input, cot, output) + EOS_TOKEN
        texts.append(text)
    return {
        "text": texts,
    }

我们将从 Hugging Face 枢纽上的 FreedomIntelligence/medical-o1-reasoning-SFT 数据集加载前 500 个样本。之后,我们将使用 formatting_prompts_func 函数映射“text”列。

复制

from datasets import load_dataset
dataset = load_dataset("FreedomIntelligence/medical-o1-reasoning-SFT","en", split = "train[0:500]",trust_remote_code=True)
dataset = dataset.map(formatting_prompts_func, batched = True,)
dataset["text"][0]

正如我们所见,“text”列包含系统提示、指令、思维链和答案。

"以下是描述任务的指令,以及提供进一步上下文的输入。 \n写出一个适当完成请求的回应。 \n在回答之前,仔细思考问题并创建一个逐步的思维链,以确保回答的逻辑性和准确性。\n\n### 指令:\n你是一位具有先进临床推理、诊断和治疗计划知识的医学专家。 \n请回答以下医学问题。 \n\n### 问题:\n一位 61 岁的女性,长期在咳嗽或打喷嚏等活动中出现非自愿性尿失禁,但夜间没有漏尿。她接受了妇科检查和棉签测试。根据这些发现,膀胱测压术最有可能揭示她的残余体积和逼尿肌收缩情况如何?\n\n### 回应:\n<think>\n好的,让我们一步一步思考这个问题。这里有一位 61 岁的女性,她在进行增加腹部压力的活动(如咳嗽或打喷嚏)时会出现非自愿性尿失禁。这听起来很像压力性尿失禁。有趣的是,她在夜间没有漏尿;她在睡眠中没有出现漏尿问题。这可能意味着她的膀胱在没有身体压力的情况下能够正常储存尿液。嗯,这是一个线索,表明我们处理的是与压力有关的问题,而不是膀胱肌肉问题。 \n\n她接受了棉签测试这一事实也很有趣。这种测试通常用于评估尿道的活动性。在压力性尿失禁中,棉签可能会显著移动,显示出尿道的过度活动。这种活动通常意味着支持结构存在弱点,这些结构本应帮助在腹部压力增加时保持尿道闭合。所以,这与压力性尿失禁是一致的。\n\n现在,让我们想想膀胱测压术期间会发生什么。由于压力性尿失禁通常不涉及突发的膀胱收缩,因此我不期望在测试期间出现不自主的逼尿肌收缩。她的膀胱并没有痉挛或类似问题;更多的是支持结构在压力下失败。此外,她很可能完全排空了膀胱,因为压力性尿失禁通常不涉及排尿不完全。所以,她的残余体积应该是正常的。 \n\n总之,如果对她进行膀胱测压术,很可能会显示正常的残余体积和没有不自主的收缩。是的,我认为这在她的症状和压力性尿失禁的典型表现中是有意义的。\n</think>\n在这种压力性尿失禁的情况下,膀胱测压术最有可能显示正常的排尿后残余体积,因为压力性尿失禁通常不涉及膀胱排空问题。此外,由于压力性尿失禁主要与体力活动有关,而不是膀胱过度活跃,因此在测试期间不会出现任何不自主的逼尿肌收缩。<|end▁of▁sentence|>"

5. 设置模型

使用目标模块,我们将通过为模型添加低秩适配器来设置模型。

复制

model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
    use_gradient_checkpointing="unsloth",  # True 或 "unsloth" 用于非常长的上下文
    random_state=3407,
    use_rslora=False,
    loftq_config=None,
)

接下来,我们将设置训练参数和训练器,提供模型、分词器、数据集和其他重要的训练参数,这些参数将优化我们的微调过程。

复制

from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    dataset_text_field="text",
    max_seq_length=max_seq_length,
    dataset_num_proc=2,
    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        # 使用 num_train_epochs = 1,warmup_ratio 进行完整的训练运行!
        warmup_steps=5,
        max_steps=60,
        learning_rate=2e-4,
        fp16=not is_bfloat16_supported(),
        bf16=is_bfloat16_supported(),
        logging_steps=10,
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=3407,
        output_dir="outputs",
    ),
)

6. 模型训练

运行以下命令开始训练。

trainer_stats = trainer.train()

训练过程耗时 44 分钟完成。训练损失逐渐减少,这是模型性能提升的良好迹象。

![[3fe8f17b01e7297adbd92d1231c56b83_MD5.avif]]

你可以在 Weights and bais 仪表板上查看完整的模型评估报告,登录网站并查看该项目。

![[4e9e66735504908803b64281e7724b1e_MD5.avif]]

如果运行上述代码时遇到问题,请参考 Fine-tuning DeepSeek R1 (Reasoning Model) Kaggle 笔记本。

7. 微调后的模型推理

为了比较结果,我们将向微调后的模型提出与之前相同的问题,看看有什么变化。

复制

question = "一位 61 岁的女性,长期在咳嗽或打喷嚏等活动中出现非自愿性尿失禁,但夜间没有漏尿。她接受了妇科检查和棉签测试。根据这些发现,膀胱测压术最有可能揭示她的残余体积和逼尿肌收缩情况如何?"

FastLanguageModel.for_inference(model)  # Unsloth 的推理速度提高了 2 倍!
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(response[0].split("### 回应:")[1])

这要好得多,也更准确。思维链很直接,答案简洁明了,且在一个段落中。微调成功了。

复制

<think>
好的,让我们思考一下。我们有一位 61 岁的女性,她在咳嗽或打喷嚏等活动时出现非自愿性尿失禁,但她晚上没有漏尿。这表明她可能有一些盆底肌肉问题,或者可能是膀胱出了问题。

现在,她接受了妇科检查和棉签测试。让我们来分析一下。棉签测试通常用于检查尿道阻塞。如果测试呈阳性,这意味着尿道有某种阻塞,比如尿道狭窄或其他东西。

鉴于她的棉签测试呈阳性,很可能存在尿道阻塞。这意味着她的尿道变窄了,可能是由于尿道狭窄或其他某种狭窄造成的。所以,她的膀胱在咳嗽等活动时无法正常排空,因为尿道阻塞使得排尿困难。

现在,让我们想想膀胱无法正常排空时会发生什么。如果存在尿道阻塞,膀胱被迫保留更多的尿液,增加残余体积。这是因为她的膀胱无法完全排空。所以,她的残余体积可能增加了。

此外,如果膀胱无法正常排空,她可能会出现增加的逼尿肌收缩。这些收缩通常更强,以推动尿液排出。所以,我们预计她的逼尿肌收缩会增加。

综合来看,如果她存在尿道阻塞且棉签测试呈阳性,我们预计她的膀胱测压术结果会显示残余体积增加和逼尿肌收缩增加。这在阻塞以及膀胱试图通过更强的收缩来补偿排尿困难的情况下是有意义的。
</think>
根据妇科检查和棉签测试呈阳性的结果,膀胱测压术最有可能显示残余体积增加和逼尿肌收缩增加。棉签测试呈阳性表明存在尿道阻塞,这迫使膀胱保留更多的尿液,从而增加残余体积。此外,阻塞可能导致逼尿肌收缩增加,因为膀胱试图通过更强的收缩来排出尿液。<|end▁of▁sentence
使用 Hugo 构建
主题 StackJimmy 设计