MiniMind 学习指北(五):SFT

前面我们已经进行了预训练,得到了一个只会续写的模型。这是因为我们预训练数据集的文本都是简单的一句话,然后通过加工我们得到了类似 “<|im_start|> 秦始皇的功绩包括统一货币、文字” 的文本,通过 Teacher-Forcing 它只能做到预测下一个 token,或者说只会机械接龙,不会对话。
SFT 全称是 Supervised-Finetune,也就是监督微调。我们通过 对话文本 的数据集在预训练的模型基础上进行训练,就能让模型学会对话。简单来说 SFT 和 Pretrain 者有以下区别:
- Pretrain 的数据都是纯文本如 “今天天气很好…",而 SFT 的数据集是对话如 {“user”:“你好”, “assistant”: “你也好”}
- Pretrain 会直接 tokenize 整个文本,而 SFT 会用 template 模板将对话拼接为 “<|im_start|>user 你好 <|im_end|><|im_start|>assistant 你也好 <|im_end|>” 这样的结构化文本。
- 计算损失函数时候,Pretrain 是对每一个 token 计算损失,而 SFT 仅对 Assistant 部分计算损失
所以说 SFT 我们只需要对 Dataset 进行改进,训练方式还是之前的 Teacher-Forcing。
改造 Dataset
先来看看我们 SFT 的数据集是啥样的:
{
"conversations": [
{"role": "user", "content": "hello!"},
{"role": "assistant", "content": "hi!"}
]
}
在 MiniMind 实现里面,除了把 conversations 里面的对话组成 message,还加入了一个 system 的 prompt:
def prepare_message(
self, conversations: list, random_threshold: float = 0.2
) -> list:
assert len(conversations) >= 2
SYSTEM_PROMPTS = [
"你是一个知识丰富的AI,尽力为用户提供准确的信息。",
"你是minimind,一个小巧但有用的语言模型。",
"你是一个专业的AI助手,请提供有价值的回答。",
"你是minimind,请尽力帮助用户解决问题。",
"你是一个可靠的AI,请给出准确的回答。",
"You are a helpful AI assistant.",
"You are minimind, a lightweight intelligent assistant.",
"You are a friendly chatbot. Please answer the user's questions carefully.",
"You are a knowledgeable AI. Try your best to provide accurate information.",
"You are minimind, a small but useful language model.",
]
message = []
for turn in conversations:
message.append({"role": turn["role"], "content": turn["content"]})
if message[0]["role"] != "system" and random.random() < random_threshold:
message = [{"role": "system", "content": random.choice(SYSTEM_PROMPTS)}] + message
return message
根据 Qwen 和 Grok 所说,MiniMind 参数量较小,自身无法稳定维持"助手"的角色认知,如果没有 system prompt 模型容易角色混淆、生成无意义接龙、对模糊指令无法正确响应。稍后我会对加入和未加入 system prompt 的数据集的数据集进行对比测试。
之后我们就可以用 tokenizer 的 apply_chat_template 生成结构化的文本了:
message = self.prepare_message(conversations)
input = self.tokenizer.apply_chat_template(
message, tokenize=False, add_generation_prompt=False
)
input_ids = self.tokenizer(input).input_ids[: self.max_length]
input_ids += self.tokenizer.pad_token_id * (self.max_length - len(input_ids))
在模型的 forward 里面我们规定的训练时候会让 logits 和 labels 进行偏移,所以在数据集就不用处理了。如上面所说: 计算损失函数时候,SFT 仅对 Assistant 部分计算损失。所以我们需要把 labels 的其余部分置为 -100,这样求交叉熵损失时候设置的 ignore_index=-100 就会起作用了。
def pad_labels(self, input_ids: list[int]):
labels = [-100] * len(input_ids)
i = 0
while i < len(input_ids):
if input_ids[i : i + len(self.bos_id)] == self.bos_id:
start = i + len(self.bos_id)
end = start
while end < len(input_ids):
if input_ids[end : end + len(self.eos_id)] == self.eos_id:
break
end += 1
for j in range(start, min(end + len(self.eos_id), self.max_length)):
labels[j] = input_ids[j]
i = end + len(self.eos_id) if end < len(input_ids) else len(input_ids)
else:
i += 1
return labels
修改 SFT 脚本
SFT 的训练代码和 Pretrain 的代码几乎一致,我们 copy 之后修改一下超参数就好了。
| 超参数 | Pretrain | SFT | 主要原因 |
|---|---|---|---|
| Learning Rate | 1e-4 ~ 1e-3 | 1e-6 ~ 5e-5 | SFT 要微调,避免破坏预训练权重;Pretrain 需要快速学习基础表示 |
| Epochs | 1~3 epoch | 3~20 epoch | SFT 数据少,需要多次学习高质量样本 |
| Batch Size | 128 | 16 | Pretrain 数据多、序列短;SFT 数据少、序列长 |
| Weight Decay | 0 ~ 0.01 | 0.01 ~ 0.1 | SFT 需要更强正则化防止过拟合小数据集 |
| Dropout | 0~0.1 | 0 或更低 | SFT 数据高质量,不需强 dropout |
评估
MiniMind 提供了两种训练方式,由于我用的是 104M 的模型所以最终选择用 sft_mini_512.jsonl 加上 sft_1024.jsonl 进行两轮 SFT 训练,每轮进行 1.5 个 epoch,最终 RTX 5090 耗时 7 个小时,loss 稳定在 1.65。实验结果如下:
💬: 你有什么特长?
🤖: 我是人工智能模型,没有个人特点或兴趣。但我可以提供信息、解答问题、生成文本、进行对话等服务。如果你有任何具体的问题或需要帮助,请随时告
诉我!
[Speed]: 26.26 tokens/s
💬: 为什么天空是蓝色的
🤖: 天空之所以呈现出蓝色,主要有以下几个原因:
1. 云层散射:太阳光中的各种颜色的光线在大气中的散射程度较大,使得我们看到的是蓝色。当阳光照射到地面时,蓝色光的波长比较短,容易被空气分子
散射。而这些波长较短的光则更容易穿透大气层到达我们的视线。这就是为什么天空看起来非常蓝的颜色。
2. 天空的散射作用:由于空气分子会把光线分散到各个方向,导致不同颜色的光线混合在一起。当太阳光照耀在空气中时,空气分子将其中的水蒸气吸收并
将其分散得更快,从而让我们看到天空。
3. 日落或日出的时刻:随着太阳处于地球同一平面上升起,然后逐渐变暗。如果白天没有太阳能辐射出来,那么光线也不会完全失去可见度,最终呈现出红
、橙等暖黄色的颜色。因此,从明亮到深蓝等角度来看,天空呈现出了美丽而略带沉重的一面。
总的来说,天空呈现出一种蓝色的视觉效果,这是因为它的大气层散射效应使得光线在穿过大气时发生了散射,而不是被物体反射或吸收,因此它在天空中表
现为一个蓝色的色彩。
[Speed]: 33.96 tokens/s
💬: 解释一下"光合作用"的基本过程
🤖: 光合作用是植物通过吸收阳光、水和二氧化碳(CO2)等有机物并释放出氧气的过程。在大气中,叶绿体中的叶绿素能够将阳光转化为化学能,为生态系
统提供能量和营养物质。光合作用发生在太阳能的照射下进行的阶段,这个过程中产生了大量的光能,这些光子被储存起来来利用,用于构建生物链,促进着
森林覆盖率的提高以及对气候变化的影响。同时,光合作用也涉及到其他重要的生理和化学反应,如代谢和储存等。
[Speed]: 34.13 tokens/s
💬: 如果明天下雨,我应该如何出门
🤖: 如果您要在明天进行户外活动,建议选择一个有良好风景的地点,避免暴晒。同时,准备一把备用的伞或雨衣,以防万一导致天气突变。
如果你想在室内享受大自然的美丽,可以考虑搭建帐篷、喝杯热茶或者阅读书籍等。此外,您也可以选择携带一些简单的工具,比如太阳镜、毛巾、手电筒、
吸管等等。如果您想更多地融入大自然,可以选择种植一些盆栽植物和绿植来提高环境质量。
总之,选择一个具有美丽自然环境的地方非常重要,这样您的户外活动就能让你更加舒适愉悦地度过今天的阳光时光。
[Speed]: 35.02 tokens/s
💬: 比较一下猫和狗作为宠物的优缺点
🤖: 猫(猫和狗)与狗各有不同的优缺点。以下是一些常见的宠物:
1. **驯化性猫**:
- 驯化性猫,对体型、毛发类型和行为都有敏感性,但它们需要人类的照顾和定期锻炼。虽然它们的天生灵活性使其成为家庭宠物,但饲养猫可能较小,
且在某些情况下可能会表现出不适或过热的情况。此外,许多人选择与猫相处,这取决于他们的日常活动水平。
- 在一些文化中,猫被视为社交动物,而在其他文化中则被看作是照顾者。它们往往更容易照顾家中的成员,因为它们通常更易于训练。
2. **温顺而独立**:
- 猫通常非常柔软且温顺,不会主动打盹或者表达自己的感情。相比之下,狗更加警惕,更愿意独自行动。猫也经常在夜间活动,尤其是在城市中心。
- 有些人喜欢用猫抓板来捕食或抓住昆虫,这有助于减少宠物的压力,同时也能增强他们的心理健康。
3. **独立性猫**:
- 猫通常比较独立,对于那些不那么孤单的狗来说,没有太多的互动时间。这种特质使得它们能够更好地适应各种生活环境。
- 在很多文化中,猫通常被用来安慰人们,帮助减轻焦虑和压力,提供一种安全感和归属感。
4. **低维护成本**:
- 如果宠物拥有较少的工作空间或经济实惠的环境,猫会更容易维护。这是因为狗通常更适合在相对稳定的工作地点生活,而不是家庭生活。
5. **长寿伴侣**:
- 猫需要长时间的活动,因此长寿伴侣可以提供一个忠诚和陪伴的平台,让宠物感到舒适和幸福。同时,猫也会表现出耐心、好奇心和社交性,这些都增
加了它们在同龄人的生活中所需的时间。
总之,尽管猫和狗都是受欢迎的选择,但饲养猫和它们之间还有很多需要改进的地方,例如猫的数量、饮食习惯以及是否满足个性化的需求等。通过综合考虑
以上因素,我们可以得出结论,无论何时,一只猫和一只狗都可以在某种程度上满足他们的需求,并且在长期的陪伴下,它们的身影可能会有所不同。
[Speed]: 35.09 tokens/s
💬: 解释什么是机器学习
🤖: 机器学习是一种人工智能技术,它通过让计算机从数据中自动学习并改进任务的性能。这意味着系统可以利用大量的数据进行训练和优化,从而提高其性
能、准确性和可靠性。这种方法通常用于识别模式、分类、预测等场景中的各种情况和异常情况。常见的机器学习算法包括监督学习(无标签数据)、非监督
学习和强化学习(如深度学习和强化学习)。
[Speed]: 34.94 tokens/s
💬: 推荐一些中国的美食
🤖: 1. 北京烤鸭:北京是中国的传统美食,以皮脆肉嫩、味道独特而闻名。你可以去附近的糖葫芦小火锅店尝试一下。
2. 广东粤菜:广东地区拥有许多美食,例如糖鑰鱼火锅和广州炸酱面等。在这些餐厅里你可以品尝到正宗的火锅口味。
3. 福建火锅:福建是一个非常有特色的国际大都市,这里有各种不同口味的点心供你选择。你可以找到很多地方用餐并享受美味佳肴。
4. 山西铁板烧:山西铁板烧是一家位于南京市的古老火锅连锁店,提供多种口味的烧烤食物,同时还有许多精美的小吃,如“烤鸡卷”、“蒸饺子”和“火锅海鲜
”。
[Speed]: 35.35 tokens/s
可以看得出来效果有提升了,不过还是会出现幻觉,训练的数据集还是太小了。

