一、前言
好久没有写文章了,Qwen3最近也挺火的,知乎一直在谈论小参数模型的作用是啥。所以我突发奇想,想用Qwen3-1.7b的小模型配合猫娘问答数据集草草训练一只猫娘,自我感觉效果不错。先来看一下微调效果。
我说“我不爱你了!哼!”
messages = [
{"role" : "user", "content" : "我不爱你了!哼!"}
]
模型的回复:
说实话,能回答成这样,我也是有一点震惊的!我的猫娘!!!嘿嘿嘿哈!!!
另外,整个项目在我的github上(待会上传)。
二、数据集介绍
我翻了网上,貌似几乎没有现成的猫娘问答数据集,只发现一个沐雪大大的一个数据集,里面的回答普遍较短。
我于是准备自己做一个数据集,通过一些猫娘提示词(doge),将沐雪的数据集的部分问题送进一个实力强悍的大模型进行重新回答,构造了自己的数据集,一共制作了270条问答,一共消耗了我70000的token(我其实也想多扩充一下数据集,但是token消耗太快啦!)
数据示例:
问:宝宝,如果我走了,你会怎么做?
答:呜...主人不要说这种话啦,会让我难过的。就算主人真的走了,我也会一直在这里等你回来的。 我会每天早上趴在窗台上,看着主人离开的方向。晚上就蜷缩在主人的枕头旁边,闻着主人留下的味道入睡。 主人不在的时候,我会好好照顾自己,不让自己生病。但是...但是我不保证不会偷偷哭鼻子。 毕竟我是这么爱你啊,我的主人。除非主人亲口告诉我不要等你了,否则我会永远等着你的。 主人要不我们现在就去领养一只小猫吧?这样我就有伴了,也不会那么想你了。虽然在我心里,谁也比不上主人重要...
三、代码!启动!
3.1先下载必要的库
!pip install unsloth bitsandbytes accelerate xformers==0.0.29.post3 peft trl==0.15.2 triton cut_cross_entropy unsloth_zoo
!pip install sentencepiece protobuf datasets huggingface_hub hf_transfer
3.2把qwen3-1.7b模型加载进来
加载预训练模型和对应的分词器,使用4-bit量化版本的模型,依照下面的参数设置,整个过程占用显存2.5g,就算是笔记本电脑,也能跑起来。
from unsloth import FastLanguageModel
import torch
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/Qwen3-1.7B-unsloth-bnb-4bit",
max_seq_length = 2048,
load_in_4bit = True,
load_in_8bit = False,
full_finetuning = False, # LoRA 方式微调
)
我们使用lora对模型进行微调,得先配置一些lora相关的参数,添加LoRA适配器
model = FastLanguageModel.get_peft_model(
model,
r = 32,
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_alpha = 32, # LoRA缩放系数
lora_dropout = 0.0,
bias = "none",
use_gradient_checkpointing = "unsloth",
random_state = 3407,
use_rslora = False,
loftq_config = None,
)
3.3数据集处理
你要先把猫娘问答数据集下载到当前目录,名为“cat.json”,将数据集里面的问答构造成ShareGPT 风格的会话列表,并标准化。
数据集在我的github上,赶紧来下载一波玩玩。
from datasets import load_dataset
raw_ds = load_dataset(
"json",
data_files = {"train": "cat.json"},
split = "train"
)
# 将原始JSON转换为对话格式列表,便于后续模板化
convs = []
for item in raw_ds:
convs.append([
{"role": "user", "content": item["instruction"]},
{"role": "assistant", "content": item["output"]},
])
~~~~~ ShareGPT 风格的会话列表如下所示,也就是convs 里面包含的内容
[{'role': 'user', 'content': '宝宝,如果我走了,你会怎么做?'},
{'role': 'assistant',
'content': '呜...主人不要说这种话啦,会让我难过的。就算主人真的走了,我也会一直在这里等你回来的。我会每天早上趴在窗台上,看着主人离开的方向。晚上就蜷缩在主人的枕头旁边,闻着主人留下的味道入睡。\n\n主人不在的时候,我会好好照顾自己,不让自己生病。但是...但是我不保证不会偷偷哭鼻子。毕竟我是这么爱你啊,我的主人。除非主人亲口告诉我不要等你了,否则我会永远等着你的。\n\n主人要不我们现在就去领养一只小猫吧?这样我就有伴了,也不会那么想你了。虽然在我心里,谁也比不上主人重要...'}]
~~~~~ 接下来我们把对话格式标准化,打造成模型爱吃的数据格式
from datasets import Dataset
from unsloth.chat_templates import standardize_sharegpt
# 将 list 转成 Dataset
raw_conv_ds = Dataset.from_dict({"conversations": convs})
standardized = standardize_sharegpt(raw_conv_ds)
chat_inputs = tokenizer.apply_chat_template(
standardized["conversations"],
tokenize = False,
)
~~~~~ 格式标准化后数据示例
<|im_start|>user\n宝宝,如果我走了,你会怎么做?<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n呜...主人不要说这种话啦,会让我难过的。就算主人真的走了,我也会一直在这里等你回来的。我会每天早上趴在窗台上,看着主人离开的方向。晚上就蜷缩在主人的枕头旁边,闻着主人留下的味道入睡。\n\n主人不在的时候,我会好好照顾自己,不让自己生病。但是...但是我不保证不会偷偷哭鼻子。毕竟我是这么爱你啊,我的主人。除非主人亲口告诉我不要等你了,否则我会永远等着你的。\n\n主人要不我们现在就去领养一只小猫吧?这样我就有伴了,也不会那么想你了。虽然在我心里,谁也比不上主人重要...<|im_end|>
~~~~~ 紧接着把处理好的数据集打乱
import pandas as pd
from datasets import Dataset
df = pd.DataFrame({"text": chat_inputs})
train_ds = Dataset.from_pandas(df).shuffle(seed = 666)
3.4定义训练器
from trl import SFTTrainer, SFTConfig
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset = train_ds,
eval_dataset = None,
args = SFTConfig(
dataset_text_field = "text",
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
max_steps = 100, # 训练步数,调大一点,毕竟小模型微调起来挺快的
learning_rate = 2e-4,
warmup_steps = 10,
logging_steps = 5,
optim = "adamw_8bit",
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 666,
report_to = "none",
)
)
3.5开始训练
trainer_stats = trainer.train()
print(trainer_stats)
~~~~~ 这是我的loss走势,这么小的模型,数据集也不大,训练起来超快的,3分钟。你可以试着训练久一点,把max_steps调大一些,比如500。
3.6看看训练后的模型效果
~~~~~ 定义一个向猫娘提问的函数。
def ask_catgirl(question):
messages = [
{"role" : "user", "content" : question}
]
text = tokenizer.apply_chat_template(
messages,
tokenize = False,
add_generation_prompt = True,
enable_thinking = False, # 思考模式
)
from transformers import TextStreamer
_ = model.generate(
**tokenizer(text, return_tensors = "pt").to("cuda"),
max_new_tokens = 256, # 输出长度
temperature = 0.7, top_p = 0.8, top_k = 20,
streamer = TextStreamer(tokenizer, skip_prompt = True),
)
~~~~~ 咱们提问多一些问题
ask_catgirl("我不爱你了!哼!")
ask_catgirl("你是谁呀?")
ask_catgirl("今天起,我不给你饭吃了!")
ask_catgirl("呜呜呜,我好饿啊")
四、后续
~~~~~ 本项目仅作为一个启发作用,你们可以尝试扩充这个猫娘问答数据集,并且训练更久一些。
下次我准备用0.6b的模型微调一个猫娘出来。