1 预训练

GPT使用的是阉割版的Transformer decoder。它取消了第二层的多头注意力,使用最下层带掩码的多头注意力。GPT使用多个这样的Transformer层。

以最后一层的输出计算损失。

在最后一层计算损失时,目标向量在概念上使用的是预测词的One-hot表示,而不是embedding矩阵里的向量。GPT的embedding矩阵向量是和transformer参数一起更新的。
2 下游任务微调

上图是4中不同的任务,采用不同的策略,模型中间的Transformer是之前预训练好的模型,输入和后面的附件网络可以自己设计。
参数调整方式:
1. 全参数微调 - 模型参数会变
这是最传统的方式,所有预训练参数都会更新:
python
# 伪代码示例
from transformers import GPT2ForSequenceClassification
model = GPT2ForSequenceClassification.from_pretrained("gpt2")
# 默认情况下,所有参数都会更新
for batch in train_dataloader:
outputs = model(**batch)
loss = outputs.loss
loss.backward() # 计算所有参数的梯度
optimizer.step() # 更新所有参数,包括预训练权重
特点:
- ✅ 预训练模型参数:会更新
- ✅ 下游分类头:会更新
- 更新比例:100%参数
- 适用场景:数据量充足、计算资源丰富时
2. 冻结主干,仅训练分类头 - 模型参数不变
只更新下游任务添加的层,冻结预训练模型的所有参数:
python
model = GPT2ForSequenceClassification.from_pretrained("gpt2")
# 冻结所有预训练参数
for param in model.parameters():
param.requires_grad = False
# 只解冻分类头(下游添加的层)
for param in model.score.parameters(): # 分类头参数
param.requires_grad = True
特点:
- ❌ 预训练模型参数:不变(冻结)
- ✅ 下游分类头:会更新
- 更新比例:通常<0.1%参数
- 适用场景:小样本学习、计算资源有限
3. 参数高效微调 - 模型参数基本不变
这是当前的主流方法,只更新极少量参数:
LoRA微调
python
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=8, # 秩
target_modules=["c_attn"], # 只在注意力层添加LoRA适配器
)
model = get_peft_model(model, config)
# 训练时只有LoRA适配器参数会被更新
# 原始预训练参数保持不变
Prefix Tuning
python
from peft import PrefixTuningConfig
config = PrefixTuningConfig(
task_type="CAUSAL_LM",
num_virtual_tokens=20 # 只优化前缀令牌
)
model = get_peft_model(model, config)
参数高效微调的特点:
- ❌ 预训练模型参数:基本不变
- ✅ 少量适配器参数:会更新(通常0.1%-2%总参数)
- ✅ 下游分类头:会更新
- 更新比例:0.1%-2%参数
- 适用场景:当前最流行的方式
4. 分层微调 - 部分模型参数会变
选择性解冻某些层:
python
# 解冻最后几层,冻结底层
for name, param in model.named_parameters():
if 'h.23' in name or 'h.22' in name or 'h.21' in name: # 最后3层
param.requires_grad = True
else: # 其他层冻结
param.requires_grad = False
不同策略对比
| 微调策略 | 预训练参数更新 | 新增参数更新 | 参数量 | 训练速度 | 效果 |
|---|---|---|---|---|---|
| 全参数微调 | ✅ 100% | ✅ 100% | 全部 | 慢 | 通常最好 |
| 仅分类头 | ❌ 0% | ✅ 100% | 极少 | 最快 | 通常较差 |
| LoRA | ❌ 0% | ✅ 0.1-2% | 很少 | 快 | 接近全参数 |
| 分层微调 | ✅ 部分层 | ✅ 100% | 中等 | 中等 | 较好 |
实际应用建议
- 数据量充足 + 计算资源丰富
- → 全参数微调(模型参数会变)
- 效果通常最好,但成本最高
- 数据量中等 + 资源有限
- → LoRA等参数高效方法(模型参数基本不变)
- 当前工业界主流选择
- 数据量很少 + 快速原型
- → 仅训练分类头(模型参数不变)
- 快速但效果有限
- 多任务学习
- → Adapter-based方法(模型参数不变)
- 便于管理多个任务
存储和部署影响
- 全参数微调:需要保存整个模型的副本
- 参数高效微调:只需保存适配器权重(几MB),可与原始模型共享基础权重
python
# LoRA只需保存很小的适配器
model.save_pretrained("./lora_adapters") # 只保存适配器,大小约几MB
# 全参数微调需要保存整个模型
model.save_pretrained("./full_finetuned") # 保存整个模型,大小与原始模型相同
总结
- 如果使用全参数微调:模型本身的参数会变
- 如果使用参数高效微调(LoRA等):模型本身的参数基本不变
- 如果使用仅训练分类头:模型本身的参数不变
当前趋势:由于计算效率和可移植性的优势,参数高效微调已成为主流选择,在这种情况下,预训练模型参数基本保持不变,只更新少量适配器参数。