使用合成推理数据集微调 Deepseek-R1
DeepSeek-R1 进入开源 AI 领域具有里程碑意义。其推理能力与 OpenAI-01 匹敌,吸引了研究人员和 AI 爱好者,登上全球头条新闻并颠覆了市场。作为一种推理模型,它模拟逐步的逻辑和演绎思维来解决复杂问题。这使得它非常适合数学、编码、逻辑、法律和医学任务,尽管其较高的推理要求使其在简单任务中效率较低。
这篇博客文章将引导您使用合成数据微调 DeepSeek。这种方法将使您能够为特定任务定制大型语言模型(LLM),即使在数据稀缺的领域也能提高准确性和可靠性。作为示例,我们将演示如何微调 Deepseek-R1 以使用 Python 解决问题。
我们将涵盖以下步骤:
- 使用 合成数据生成器 生成推理数据集。
- 使用 unsloth 训练 unsloth/DeepSeek-R1-Distill-Qwen-1.5B-unsloth-bnb-4bit。
- 运行推理并评估结果。
让我们深入了解!
生成推理数据集
为了生成高质量的推理数据集,我们将使用 合成数据生成器,这是一个无需代码即可使用 LLM 创建自定义数据集的用户友好型应用程序。本指南中,我们将重点生成合成推理数据,以解决 Python 编码问题。然而,这种方法可以适用于广泛的领域。
有关合成数据生成器的详细信息和用法,请参阅 介绍合成数据生成器 - 使用自然语言构建数据集 和原始 GitHub 仓库。
默认情况下,合成数据生成器通过无服务器推理 API 使用 meta-llama/Llama-3.1-8B-Instruct,该 API 免费但有速率限制。在最新版本中,您可以为补全配置不同的模型。由于我们需要推理数据集,我们将使用 deepseek-ai/DeepSeek-R1-Distill-Qwen-32B,它可以通过无服务器推理 API 访问。要添加此模型,您只需复制 Space 并设置 `MODEL_COMPLETION` 环境变量。
应用程序运行后,生成我们的合成数据集非常简单。它包括三个步骤:选择输入数据,配置生成器,然后生成完整数据集。
选择输入数据
由于我们正在为监督式微调(SFT)生成数据,我们将导航到“聊天数据”选项卡。在这里,有三个选项:从系统提示、现有数据集或您的文档生成。对于我们的用例,我们将重点关注第一个选项。如果您想了解其他选项如何工作,可以在此处查看。
我们需要描述数据集的主题和范围。例如,我们将其定义为:“一个解决 Python 编码问题的助手”。接下来,我们将点击“加载”,应用程序将开始生成改进的系统提示和一些示例。
配置生成器
如前一节所述,根据我们对数据集的描述,合成数据生成器将自动生成系统提示。此系统提示将作为模型生成与任务更好地对齐的数据的指南。因此,您可以进一步完善并迭代它以改进结果。我们将其定义为:
你是一位对 Python 拥有高级知识的专家程序员。你的任务是提供简洁易懂的解决方案。请回答以下 Python 问题。用户问题清晰简洁。
此外,还可以配置轮数。在单轮中,将生成两列:提示和补全。对于多轮对话,消息列将包含对话,包括系统、用户和助手角色。在我们的案例中,我们将轮数设置为 1,确保每个示例都包含一个输入编码问题(提示)和相应的推理 Python 解决方案(补全)。
生成数据集
完成上述步骤后,我们就可以生成完整的数据集了!生成的数据集将自动在 Hub 和 Argilla 中可用,随时可供审查和使用。
在我们的案例中,我们生成了 500 行数据,指令生成温度为 0.9,补全生成温度为 0.6,这与 DeepSeek 的建议一致。整个过程大约花费了 2 小时;然而,时间可能会因所使用的推理模型而异。在这种情况下,DeepSeek 需要更多的计算资源,因此处理时间更长。
太棒了!我们现在有了一个用于微调 Deepseek 的合成推理数据集!
以下部分提供了简化的代码片段,以便清晰易懂。如果您想探索完整的实现,可以访问完整笔记。
微调 DeepSeek
为了微调我们的模型,我们将使用 Unsloth,一个优化微调的库。因此,即使在消费级硬件上,您也可以进行微调。
加载模型
第一步将是加载模型、分词器和适配器。具体来说,我们将微调 unsloth/DeepSeek-R1-Distill-Qwen-1.5B-unsloth-bnb-4bit,它是 deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B 的 4 位量化版本。这个模型比用于生成的模型小,因此我们预期输出质量会略低。然而,微调使我们能够使用更小的模型,同时保持准确性和可靠性。
# Load the 4bit pre quantized model of deepseek and the tokenizer
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = MODEL,
max_seq_length = 2048,
dtype = None,
load_in_4bit = True,
)
# We add the LORA adapters to the model
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",
random_state=3407,
use_rslora=False,
loftq_config=None,
)
准备训练数据集
接下来,我们将通过定义一个提示模板并包含我们生成的数据集中的提示和补全来准备训练数据集。
# Prepare the dataset
prompt_style = """Below is an instruction that describes a task, paired with a question that provides further context.
Write a response that appropriately answers the question.
Before answering, think carefully but concisely about the question and create a step-by-step chain of thoughts to ensure a logical and accurate response.
### Instruction:
You are an expert programmer with advanced knowledge of Python. Your task is to provide concise and easy-to-understand solutions. Please answer the following python question.
### Question:
{}
### Response:
<think>
{}
"""
EOS_TOKEN = tokenizer.eos_token
def formatting_prompts_func(examples):
prompts = examples["prompt"]
completions = examples["completion"]
texts = []
for prompt,completion in zip(prompts, completions):
text = prompt_style.format(prompt, completion) + EOS_TOKEN
texts.append(text)
return {
"text": texts,
}
dataset = load_dataset("sdiazlor/python-reasoning-dataset", split="train")
dataset = dataset.map(formatting_prompts_func, batched = True,)
dataset["text"][0]
训练模型
让我们开始训练模型!Unsloth 完全兼容 `trl` 库。因此,我们可以定义 SFTTrainer 并开始微调。请记住根据您的资源需求配置训练参数,以提高性能和准确性。您可以将微调后的模型本地保存或以 `float16` (sdiazlor/deepseek-r1-distill-qwen-1.5-unsloth-sft-python) 或 GGUF (sdiazlor/deepseek-r1-distill-qwen-1.5-unsloth-sft-python_q4_k_m) 格式推送到 Hub。
# Configure the trainer
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=2048,
dataset_num_proc=2,
packing=False,
args=training_arguments,
)
# Train the model
trainer_stats = trainer.train()
# Save the fine-tuned model
model.save_pretrained_merged(MODEL_NAME, tokenizer, save_method="merged_16bit")
model.push_to_hub_merged(fine_tuned_model, tokenizer, save_method="merged_16bit")
model.push_to_hub_gguf(
f"{fine_tuned_model}_q4_k_m", tokenizer, quantization_method="q4_k_m"
)
运行推理并评估模型
是时候评估我们的微调模型是否有所改进了!为此,我们将运行推理并比较模型在微调前后的结果。
# Run inference
question = "How can I get the prime numbers from 0 to 125?"
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=2048,
use_cache=True,
)
response = tokenizer.batch_decode(outputs)
response
下面的比较表明,经过微调后,模型会生成更全面的响应,包括代码示例。相比之下,预训练模型只提供高级概述,没有额外细节。
# Response before fine-tuning
<think>
To find all prime numbers between 0 and 125, I can follow these steps:
1. **Define the range**: Identify the start and end of the range, which in this case are 0 and 125.
2. **Create a boolean array**: Initialize an array of booleans with the same length as the range. Each index in this array will represent whether the corresponding number is prime (True) or not (False).
3. **Mark non-prime numbers**: Starting from the first prime number (2), iterate through each number. For each prime, mark all its multiples as non-prime in the array.
4. **Identify primes**: After marking the non-primes, the remaining True values in the array correspond to prime numbers within the specified range.
5. **Output the results**: Extract the indices from the array where the value is True and list them to obtain all prime numbers from 0 to 125.
This method ensures that we efficiently identify primes using the Sieve of Eratosthenes algorithm.
</think>
To find all prime numbers between 0 and 125, follow these steps:
[...]
5. **Output the Results**: Extract these indices and list them to obtain all prime numbers from 0 to 125.
This method efficiently finds primes using the Sieve of Eratosthenes algorithm.<|end▁of▁sentence|>
# Response after fine-tuning
Okay, so I need to find all the prime numbers between 0 and 125. Hmm, primes are numbers greater than 1 that have no divisors other than 1 and themselves. So, first, I should probably start by listing all numbers from 0 to 125 and then eliminate the non-primes.
Wait, but 0 and 1 aren't primes. So I can ignore them. The smallest prime is 2. So maybe I should start checking from 2 onwards.
I remember that one method to check for primes is the Sieve of Eratosthenes.
[...]
So, the steps in code:
1. Define a function get_primes(n) that returns primes up to n.
2. Inside the function, create a list of booleans from 0 to n, initializing to True, then setting 0 and 1 to False.
3. For each i from 2 to sqrt(n) + 1, check if it's still True. If so, mark all multiples of i as False.
4. Collect all indices that are still True and return them as a list.
[...]
</think>
To find all prime numbers from 0 to 125, we can use the Sieve of Eratosthenes algorithm, which efficiently identifies primes by marking non-prime numbers. Here's a concise Python solution:
```python
import math
def get_primes_up_to_n(n):
if n < 2:
return []
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(math.sqrt(n)) + 1):
if sieve[i]:
for j in range(i * i, n + 1, i):
sieve[j] = False
primes = [i for i, is_prime in enumerate(sieve) if is_prime]
return primes
primes = get_primes_up_to_n(125)
print(primes)
```
This code initializes a boolean list for numbers up to 125, marks non-primes, and returns the primes. The result is a list of primes from 2 to 125.
<|end▁of▁sentence|>
结论
在这篇博客文章中,我们使用合成数据生成器创建了一个定制的高质量合成推理数据集,用于使用 DeepSeek-R1-Distill-Qwen-32B 解决 Python 编码问题。然后,我们使用这个数据集微调了一个较小的模型 DeepSeek-R1-Distill-Qwen-1.5B。微调后,我们观察到模型产生了更详细和解释性的响应。因此,这个工作流程可以作为一个基础示例,用于实际应用中进行扩展。
下一步
- 探索更多示例
- 为存储库贡献新功能和新想法!
还在等什么?立即开始合成!