
人性化 Deepseek R1:在 Python 中进行微调!
DeepSeek | DeepSeek R1 | Python | 微调 | 人工智能 | 大语言模型 | 初学者友好 | 类人 LLM
学习如何微调 Deep Seek R1,使其像人类一样响应,通过这个适合初学者的教程!
让我们让 DeepSeek R1 像我们一样回应——人类!🚀
这是几乎所有 LLM(无论是 Gemini、Llama 还是 GPT)都尝试实现的任务之一,现在凭借惊人的性能指标,是时候让 DeepSeek-R1 证明自己了。
通过本文,您将学习如何使通用的 DeepSeek R1 模型,停止像机器一样响应,变得像我们——人类一样充满情感和吸引力!
坚持到最后,您将能够为自己制作一个这样的模型!
介绍
DeepSeek R1 引入了一种全新的训练 LLMs 的方法,并在这些模型在思考和进行一系列推理后响应的方式上带来了显著的变化。
在响应之前进行思考和推理的这一小变化,在大多数指标上带来了真正显著的结果。这就是为什么 DeepSeek R1 成为几乎所有精明开发者和创始人的首选。
一些开发者和创始人也在探索如何将这个最佳可用模型应用于他们特定的项目和产品。本文也试图阐明微调 DeepSeek-R1 模型 的过程。
在本文中,您将微调 DeepSeek-R1 模型的精简版本,特别是为了使生成的响应尽可能富有情感和引人入胜,像我们——人类一样!
通过本文,您将学习如何以用于微调模型的方式构建数据集。调优后,将其合并并保存在 Hugging Face Hub 上。
尽管微调是一项计算密集型任务,但本文试图通过利用普遍可用的资源,使其变得尽可能可及,使用 Google 的 Colab Notebook。
前提条件和设置
在我们实际开始微调LLM之前,先简要介绍一下技术前提或设置要求。
Python库和框架
用于微调LLM所需的Python库和框架包括:
unsloth
,这个包使得微调大型语言模型如Llama-3、Mistral、Phi-4和Gemma的速度提高了2倍,内存使用减少了70%,而且没有准确度的下降!您可以在这里阅读更多信息。torch
,这个包是使用PyTorch进行深度学习的基本构建块。它提供了一个强大的张量库,类似于NumPy,但具有GPU加速的额外优势,这在处理LLM时至关重要。transformers
是一个强大且流行的开源自然语言处理(NLP)库。它提供了易于使用的接口,用于广泛的最先进的预训练模型。由于预训练模型构成了任何微调任务的基础,这个包有助于轻松访问训练模型。- Python中的
trl
包是一个专门用于**强化学习(RL)**与变换器模型的库。它建立在Hugging Face的transformers
库之上,利用其优势使得与变换器的RL变得更加可访问和高效。
计算需求
微调模型是一种使LLM的响应更加结构化和领域特定的技术。为了微调模型,采用了许多技术,有些技术在不实际进行全面参数训练的情况下简化了这一过程。
然而,对于大多数普通计算机硬件而言,微调更大规模的LLM的过程仍然不可行,因为所有可训练参数以及实际的LLM都存储在GPU的vRAM(虚拟内存)中,而LLM的巨大规模在实现这一目标时构成了主要障碍。
因此,出于本文的目的,我们将微调DeepSeek-R1 LLM的精简版本,即DeepSeek-R1-Distill,具有47.4亿参数。该LLM至少需要8–12 GB的vRAM,为了让所有人都能使用,我们将使用Google Colab的免费T4 GPU,具有16 GB的vRAM。
数据准备策略
为了微调一个LLM,我们需要结构化和特定任务的数据。有许多数据准备策略,无论是抓取社交媒体平台、网站、书籍还是研究论文。
在本文中,我们将使用datasets库加载Hugging Face Hub中存在的数据。我们将使用来自Hugging Face的HumanLLMs/Human-Like-DPO-Dataset
数据集,您可以在这里探索该数据集。
Python 实现
安装包
使用 Google Colab 进行此微调任务的一个主要好处是大多数包已经预装。我们只需要安装一个包,即 unsloth
。
安装该包的过程是, —
!pip install unsloth
初始化模型和分词器
我们将使用 unsloth
包来加载预训练模型,因为它提供了许多有用的技术,可以帮助我们更快地下载和微调 LLM。
加载模型和分词器的代码是,—
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit",
max_seq_length = 2048,
dtype = None,
load_in_4bit = True,
# token = "hf_...", # use one if using gated models like meta-llama/Llama-2-7b-hf
)
- 这里我们指定了模型名称,
'unsloth/DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit'
,用于访问预训练的 DeepSeek-R1-Distill 模型。 - 我们将
max_seq_length
定义为2048
,这设置了模型可以处理的输入序列的最大长度。通过合理设置,我们可以优化内存使用和处理速度。 dtype
设置为None
,这有助于映射模型将要获取的数据类型,与可用的硬件兼容。通过使用这个,我们不需要明确检查和提及数据类型,unsloth
会处理所有相关内容。load_in_4bit
提升了推理性能并减少了内存使用。基本上,我们将模型量化为4bit
精度。
添加 LoRA 适配器
我们将向预训练的 LLM 添加 LoRA 矩阵,这将有助于微调模型的响应。使用 unsloth
,整个过程只需几行代码。
具体步骤如下,-
model = FastLanguageModel.get_peft_model(
model,
r = 64,
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",],
lora_alpha = 16,
lora_dropout = 0, # Can be set to any, but = 0 is optimized
bias = "none", # Can be set to any, but = "none" is optimized
use_gradient_checkpointing = "unsloth", # True or "unsloth" for very long context
random_state = 3927,
use_rslora = False, # unsloth also supports rank stabilized LoRA
loftq_config = None, # And LoftQ
)
代码解释如下,—
- 现在,我们使用
FastLanguageModel
中的get_peft_model
重新初始化了model
,以使用 PEFT 技术。 - 我们还需要传递在上一步中获取的预训练
model
。 - 这里,
r=64
参数定义了 LoRA 适配中的低秩矩阵的秩。这个秩通常在8–128
范围内能产生最佳结果。 lora_dropout
参数在训练此 LoRA 适配器模型时引入了低秩矩阵的 dropout。该参数防止模型过拟合。unsloth
通过将其设置为0
为我们提供了自动选择优化值的功能。target_modules
指定了我们希望应用 LoRA 适配的模型中特定类或模块的名称列表。
数据准备
现在,我们已经在预训练的 LLM 上设置了 LoRA 适配器,我们可以开始构建用于训练模型的数据。
为了构建数据,我们必须以包含指令和响应的方式指定提示。
Instructions
,表示对 LLM 的主要查询。这是我们向 LLM 提出的问询。Response
,表示来自 LLM 的输出。它用于说明如何将 LLM 的响应调整为特定的instruction
(查询)。
提示的结构是,—
human_prompt = """Below is an instruction that describes a task. Write a response that appropriately completes the request.
#### Instruction:
{}
#### Response:
{}"""
我们创建了一个函数,可以正确构建 human_prompt
中的所有数据,具体如下,—
EOS_TOKEN = tokenizer.eos_token # Must add EOS_TOKEN
def formatting_human_prompts_func(examples):
instructions = examples["prompt"]
outputs = examples["chosen"]
texts = []
for instruction, output in zip(instructions, outputs):
# Must add EOS_TOKEN, otherwise your generation will go on forever!
text = human_prompt.format(instruction, output) + EOS_TOKEN
texts.append(text)
return { "text" : texts, }
pass
现在,我们必须加载将用于微调模型的数据集,在我们的案例中,它是来自 Hugging Face Hub 的“HumanLLMs/Human-Like-DPO-Dataset”。您可以在 这里 探索该数据集。
from datasets import load_dataset
dataset = load_dataset("HumanLLMs/Human-Like-DPO-Dataset", split = "train")
dataset = dataset.map(formatting_human_prompts_func, batched = True,)
训练模型
现在我们已经拥有了结构化数据和带有 LoRA 适配器或矩阵的模型,我们可以继续进行模型的训练。
为了训练模型,我们需要初始化某些超参数,这将有助于训练过程,并在一定程度上影响模型的准确性。
我们将使用 SFTTrainer
和超参数初始化一个 trainer
。
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model = model, # 带有 LoRA 适配器的模型
tokenizer = tokenizer, # 模型的分词器
train_dataset = dataset, # 用于训练的数据集
dataset_text_field = "text", # 数据集中包含结构化数据的字段
max_seq_length = 2048, # 模型可以处理的输入序列的最大长度
dataset_num_proc = 2, # 用于加载和处理数据的进程数量
packing = False, # 可使短序列的训练速度提高 5 倍。
args = TrainingArguments(
per_device_train_batch_size = 2, # 每个 GPU 的批次大小
gradient_accumulation_steps = 4, # 梯度累积的步长
warmup_steps = 5,
# num_train_epochs = 1, # 将此设置为 1 次完整训练运行。
max_steps = 120, # 训练的最大步数
learning_rate = 2e-4, # 初始学习率
fp16 = not is_bfloat16_supported(),
bf16 = is_bfloat16_supported(),
logging_steps = 1,
optim = "adamw_8bit", # 用于更新权重的优化器
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 3407,
output_dir = "outputs",
report_to = "none", # 用于 WandB 等
),
)
现在要开始模型的训练,使用这个训练器,—
trainer_stats = trainer.train()
这将开始模型的训练,并将所有步骤及其各自的 训练损失 记录在内核上。
推理微调模型
现在,随着我们完成了模型的训练,我们需要做的就是对微调后的模型进行推理,以评估其响应。
进行模型推理的代码如下,—
FastLanguageModel.for_inference(model) # Enable native 2x faster inference
inputs = tokenizer(
[
human_prompt.format(
"Oh, I just saw the best meme - have you seen it?", # instruction
"", # output - leave this blank for generation!
)
], return_tensors = "pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens = 1024, use_cache = True)
tokenizer.batch_decode(outputs)
代码解释,—
- 我们使用了来自
unsloth
包的FastLanguageModel
来加载微调后的模型进行推理。此方法能提供更快的结果。 - 为了对模型进行推理,我们首先需要将查询转换为结构化的提示,然后对提示进行标记化。
- 我们还设置了
return_tensors="pt"
,以使标记器返回一个 PyTorch 张量,然后使用.to("cuda")
将该张量加载到 GPU 中,以提高处理速度。 - 然后我们调用
model.generate()
来生成查询的响应。 - 在生成时,我们提到
max_new_tokens=1024
,这表示模型可以生成的最大令牌数。 use_cache=True
有助于加快生成速度,特别是对于较长的序列。- 最后,我们将微调模型的输出从张量解码为文本。
微调模型的结果
本节文章包含了一些微调模型的其他结果。
查询 — 1: 我喜欢阅读和写作,你有什么爱好?
查询 — 2: 你最喜欢做或吃的菜系是什么?
在这里,可以注意到生成的响应的表现力水平。响应更具吸引力,同时保持了实际的响应质量。
保存微调后的模型
此步骤完成了微调模型的整个过程,现在我们可以保存微调后的模型以进行推理或在未来使用。
我们还需要与模型一起保存分词器。以下是将微调后的模型保存到 Hugging Face Hub 的方法。
## Pushing with 4bit precision
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_4bit", token = "<YOUR_HF_TOKEN>")
## Pushing with 16 bit precision
model.push_to_hub_merged("<YOUR_HF_ID>/<MODEL_NAME>", tokenizer, save_method = "merged_16bit", token = "<YOUR_HF_TOKEN>")
- 在这里,您需要设置模型的名称,这将用于在 Hugging Face Hub 上设置模型的
ID
。 - 可以以
4bit
或16bit
精度上传完整的合并模型。合并模型意味着预训练模型与 LoRA 矩阵一起上传到 Hub,而还有选项可以仅推送 LoRA 矩阵而不包括模型。 - 您可以在 这里 获取您的 Hugging Face 令牌。
结论
没错!这篇文章就到这里。以下是本文讨论的主要主题——
- 微调是一个过程,通过它我们可以使大型语言模型以我们希望的方式响应。特别是为了使响应具有特定领域的针对性和结构良好。
- 我们定义了一个结构,用于以适合微调模型的方式排列数据集。
- 我们使用的主要 Python 库和框架包括
unsloth
、torch
、transformers
和trl
。此外,我们讨论了微调 LLM 的计算要求。 - 我们讨论了多个影响微调模型生成的响应质量的超参数,并尝试为实现我们的特定用例初始化它们。
- 我们还将 LoRA 适配器或矩阵与预训练模型合并,以便将其推送到 Hugging Face Hub。
作者注
感谢您阅读本文。如果您有任何问题或建议,请随时在评论区留言。我非常欣赏反馈