Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

监督微调:模型训练

上一章,我们构建了 SFT 训练数据:512 个(提示词,回答)数据对。本章,我们将用这些数据对预训练模型进行监督微调。

监督微调

监督微调的核心思想:只计算回答部分的损失。

预训练时,模型对每个位置都计算损失,因为每个位置都在学习下一个词元。但到了监督微调阶段,我们只关心模型是否学会了生成合适的答案,不需要它去学习怎样生成提示词。

掩码

解决办法就是给每个样本加一个掩码(Mask):

  • 提示词部分:掩码为 0,不计算损失;

  • 回答部分:掩码为 1,正常计算损失。

这样,提示词部分不会产生损失,模型只从回答部分学习。

策略

  • 学习率:监督微调通常采用比预训练阶段小一个数量级的学习率。因为模型已经训练好了,我们只想微调它生成文本的方向,而不是重新学习语言知识;

  • 轮数:SFT 数据量小,训练太久容易过拟合,所以轮数通常很少。本章只训练 1 个轮次。

数据集

SFT 数据集

SFT 数据集 SFTDataset 和预训练的数据集不同,要处理(提示词,回答)数据对:

  • 拼接:把提示词和回答拼接到一起,成为一个词元序列;

  • 截断:如果超出上下文尺寸,从提示词部分的头部截断,保留完整的回答部分;

  • 掩码:给提示词部分添加掩码 0,给回答部分添加掩码 1。结果返回 (特征, 标签, 掩码)。

此外,各个样本的长度不同,所以批大小固定为 1,不需要填充。

损失函数

SFT 交叉熵损失函数

SFT 损失函数 SFTLoss 是带掩码的交叉熵损失函数:

  • 计算所有位置的交叉熵;

  • 乘以掩码,只保留回答部分的损失;

  • 除以回答部分的长度(掩码中 1 的个数),求取有效值的平均。

反向传播时,提示词位置的梯度同样为 0。

训练器

SFT 训练器

SFT 训练器 SFTTrainer 继承自 GPT 训练器 GPTTrainer,有以下主要区别:

  • train(训练函数):从数据集接收掩码,作为参数传给损失函数;

  • test(测试函数):监督微调阶段不需要测试,直接返回 None。

SFT 只做 1 轮训练,结束后直接保存模型参数。

超参数

学习率

监督微调阶段采用更小的学习率,避免过度影响预训练的结果。

模型参数

其他模型参数需要保持和预训练阶段一致。

建模

加载模型

预训练数据集 CharDataset 在这里只用来提供词表,以及文本和词元的相互转换。

加载 SFT 数据

SFT 样本按 9:1 划分训练集和测试集。训练集有 460 个样本,批大小为 1,所以一轮训练共 460 步;测试集的 52 个样本在本章不使用。

训练

使用预热余弦衰减学习率调度器:前 50 步线性预热到 LEARNING_RATE,之后按余弦曲线衰减到 LEARNING_RATE / 10。

epoch 1 step 100/460 loss 1.5225 lr 0.000097
epoch 1 step 200/460 loss 1.4181 lr 0.000074
epoch 1 step 300/460 loss 1.3249 lr 0.000040
epoch 1 step 400/460 loss 1.3159 lr 0.000015
epoch 1 saved SFT model to ../../../../data/tinyshakespeare-sft.npz

推理

让微调后的模型从提示词 ROMEO: 开始生成。

ROMEO:
He theople, that low with many thrieve weak:
While crampudged you I know the valous in your blood,
I must my patience, and my Edward's life!

TRANIO:

ANORTEN:

POLIXENES:
I know not mine a deep a shado, your whom

PETRUCHIO:
You say you know! but know re

本章,我们用 SFT 数据集对预训练模型进行了监督微调:只计算回答部分的损失,让模型学习“提示词,回答”的对话格式。从生成的结果可以看到,模型保持了“角色:台词”轮流发言的格式,但是内容依然不通顺。

但是 SFT 也有自己的局限:它只告诉模型怎样回答问题,却不能告诉模型什么是更好的答案。比如两个答案都正确,但是一个简洁明了、另一个冗长啰嗦,SFT 无法告诉模型哪个更好。

下一章,我们将引入直接偏好优化(Direct Preference Optimization,DPO),让模型学会从多个答案中选出更好的一个。

课后练习

试试把 SFT 的学习率提高,比如和预训练一样,观察训练效果如何?思考为什么微调需要更小的学习率?