AI 学习笔记:在 Macbook M1上对 DeepSeek进行无 GPU 环境下的 SFT微调,Transformers+LoRA,已跑通并出结果。

一、背景

通用模型除了挂载知识库,去回答垂类问题以外,还有就是做 SFT 的微调,而大多数人其实是没有英伟达显卡的,但又挡不住学习的渴望,还想在老旧的电脑上去尝试微调,而我翻看了很多教程,都没有一个完整能够完全跑通的完整案例,决定一定要整一个出来。

二、目标

在没有专业显卡的普通笔记本上去做 Deepseek 的微调,将它由一个通用模型改造为能够回答专业医疗问题的模型。它的特点是:微调电脑只有集成显卡,纯 CPU 微调,SFT 模式,transformers+LoRA,医疗类的垂类数据集。

三、最终效果

微调前胡说,给定数据集微调后,回答相对靠谱了。


四、整体三个步骤

第一步是下载模型和做本地的基础配置,让模型部署在本地,还能能够跑起来,看看它对医疗类问题如何解答。
第二步是核心,准备好数据集,建立python工程,编码,调参数,投喂数据,让模型开始微调,并且对微调后的模型进行保存。这个过程非常麻烦,虽然最终能跑的参数设置已在代码中了,但这都是我屡试屡不爽的调出来的,未必是最优,但确实能跑了。
第三步是做拿微调后的模型做推理验证,看看与之前有没有不一样,是否能够能够回答专业问题了。

五、开整

心急的同学直接跳到第 5 步:

1)先自检一下本机的配置,建议至少要达到我这个五年前的配置,低了会出现什么情况,我也不知道,当然是越高越好。

留个 20G 左右的空闲存储空间,用于保存原始模型、数据集、微调的过程模型。

2)先把基础模型下载下来,我的电脑算下来,基本上只能用huggingface 上的deepseek R1 1.5b 模型,整体下载下来大概不到 4 G,下载地址如下: https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B,相关文件都可以下,包括自带的数据集,考虑到科学上网的问题,我把这部分放到了百度网盘(包含一个小数据集),自行取用:

链接: https://pan.baidu.com/s/1DgF9iv62qAH9qxBN37G6cw?pwd=4twx 提取码: 4twx

3)下载完成后,存到自己指定的目录,如我的是/Users/facaishu/DeepSeek15B,再下个 ollama,把下载好的 deepseek基础模型run起来试一下(ollama挂载本地已有模型的方法如下:AI学习笔记 本地下载好的 deepseek模型如何导入 ollama 中_ollama打开本地下载好的大模型-CSDN博客
问它一个专业问题,看它怎么回复: 



4)居然说是中风,感觉它是不是一个庸医?一本正经的胡扯…..那我们就开始上科技,用 pycharm(或者vscode,新建一个 py 工程,建好虚拟环境。可能需要 pip 安装transformerspeft等,不过没关系,缺啥代码跑进来时,会有提示,到时候安装即可。

5)新建一个 python 文件,内容如下,相关参数是经几经调整后,确保可以在我的配置电脑上跑起来的,每行参数做了备注,当前进度也做了一定的输出,方便掌握大概到了哪一步,如有需要,可以酌情调整:

from transformers import AutoTokenizer, AutoModelForCausalLM

from datasets import load_dataset

from peft import get_peft_model, LoraConfig, TaskType

from transformers import TrainingArguments, Trainer

from peft import PeftModel

from transformers import pipeline



# 微调部分代码

print("------开始做各种准备-----")

model_name = "/Users/facaishu/DeepSeek15B"

tokenizer = AutoTokenizer.from_pretrained(model_name)



print("---模型ok----")



# 加载数据集

dataset = load_dataset(path="json", data_files={"train": "medical_sft.json"}, split="train")



print("------数据集加载完成,条数为:", len(dataset))



# 划分训练集和验证集

train_test_split = dataset.train_test_split(test_size=0.1)

train_dataset = train_test_split["train"]

eval_dataset = train_test_split["test"]



# 定义分词函数

def tokenizer_function(many_samples):

    texts = [f"{Question}\n{Response}" for Question, Response in

             zip(many_samples["Question"], many_samples["Response"])]

    print("texts:", texts)

    tokens = tokenizer(texts, truncation=True, max_length=512, padding="max_length")

    tokens["labels"] = tokens["input_ids"].copy()

    return tokens



# 对数据集进行分词处理

tokenized_train_dataset = train_dataset.map(tokenizer_function, batched=True)

tokenized_eval_dataset = eval_dataset.map(tokenizer_function, batched=True)



print("---------分词配置完成--------")



# 配置 8 位量化

# quantization_config = BitsAndBytesConfig(load_in_8bit=True)

# 将模型加载到 CPU 上

# model = AutoModelForCausalLM.from_pretrained(

#     model_name, quantization_config=quantization_config, device_map="cpu")

model = AutoModelForCausalLM.from_pretrained(model_name, device_map="cpu")



print("-----完成模型加载-----------")



# 配置 LoRA

lora_config = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.05,

                         task_type=TaskType.CAUSAL_LM)

model = get_peft_model(model, lora_config)

model.print_trainable_parameters()



print("----lora设置完成-------")



# 配置训练参数

training_args = TrainingArguments(

    output_dir="./finetuned_models",

    num_train_epochs=10,  # 增加训练轮数

    per_device_train_batch_size=2,

    gradient_accumulation_steps=4,

    fp16=False,

    logging_steps=10,

    save_steps=100,

    eval_strategy="steps",

    eval_steps=10,

    learning_rate=3e-5,

    logging_dir="./logs",

    run_name="deepseek-r1-distill-finetune"

)



print("------训练参数设置完毕-----")



print("------准备完事,准备开始微调-----")

# 创建 Trainer 实例

trainer = Trainer(

    model=model,

    args=training_args,

    train_dataset=tokenized_train_dataset,

    eval_dataset=tokenized_eval_dataset

)



print("------进行微调--------")

trainer.train()  # 调用 trainer 实例的 train 方法

print("------微调完成--------")



print("------微调完成了,对结果进行保存------")

#----------微调完了,要对结果进行保存----------------

sft_save_path = "./sft_save_models"

# 设计保存路径



model.save_pretrained(sft_save_path)

tokenizer.save_pretrained(sft_save_path)



print("---保存 lora 模型成功---")



final_save_path = "./final_saved_path"



base_model = AutoModelForCausalLM.from_pretrained(model_name)

model = PeftModel.from_pretrained(base_model, sft_save_path)

model = model.merge_and_unload()



model.save_pretrained(final_save_path)

tokenizer.save_pretrained(final_save_path)

print("-----全量保存成功-----")

6)我这边微调大概用了 40 分钟左右,如果看到以下输出,说明微调完事了。

 

7)完成后,再新建一个 python 代码,用于调用微调后的模型进行推理验证,这里面要注意的是,包括分词器和 lora 等参数,最好是与微调时一致,相关代码如下:

from transformers import AutoModelForCausalLM

from transformers import AutoTokenizer

from transformers import pipeline

import os

print("------开始进行推理验证-----")

# 模型和分词器的路径

final_saved_path = "./final_saved_path"

# 检查路径是否正确

if not os.path.exists(final_saved_path):

    print(f"模型保存路径 {final_saved_path} 不存在,请检查!")



# 使用 AutoModelForCausalLM 的 from_pretrained 方法从指定路径加载因果语言模型

try:

    model = AutoModelForCausalLM.from_pretrained(final_saved_path)

except Exception as e:

    print(f"模型加载失败,错误信息:{e}")



# 使用 AutoTokenizer 的 from_pretrained 方法从指定路径加载分词器

try:

    tokenizer = AutoTokenizer.from_pretrained(final_saved_path)

except Exception as e:

    print(f"分词器加载失败,错误信息:{e}")



# 检查分词器参数是否与训练时一致

if tokenizer.pad_token is None:

    tokenizer.pad_token = tokenizer.eos_token

if tokenizer.padding_side != 'right':

    tokenizer.padding_side = 'right'



# 使用 pipeline 函数创建一个文本生成任务的管道,指定使用加载的模型和分词器

pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)



# 定义一个提示文本,这是用户输入的问题,用于让模型生成回答

prompt = "一个男孩,2岁,6小时前出现惊厥,其后昏迷,头CT加强显示:基底节显影增强,最可能的诊断是什么"

# 模拟微调时的格式拼接

formatted_prompt = f"[START] {prompt} [END]\n"



# 调用管道对象,传入提示文本,调整生成参数



generated_text = pipe(formatted_prompt, max_length=2048, num_return_sequences=1, truncation=True,

                      temperature=0.2, top_p=0.8, top_k=40)

# 打印开始回答的提示信息,并从生成的文本列表中取出第一个结果,

# 再从结果字典中获取生成的文本内容进行输出

print("开始回答:", generated_text[0]["generated_text"])

8)运行此python,对微调后的模型进行验证,看看相同的问题,这次会怎么回答

看结果还可以,至少没有说是中风。

至此,微调算是成功了,但整体看,效果不是特别好,分析与微调时的参数有很大的关系。

 
 

                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值