迁移学习与微调
迁移学习复用预训练表示,固定特征、全量微调或逐步解冻。
学习目标
- 冻结骨干只让任务头接收梯度
- 冻结骨干后为什么输出仍会随训练改变?
- 说明:示例验证冻结机制,不证明随机骨干具有可迁移知识或能达到真实任务性能。
为什么需要这个概念
迁移学习将预训练参数用于新任务,常见策略是冻结骨干训练新头,再以较小学习率微调部分或全部骨干。
学习前需要掌握
背景与问题
新任务标注少时,从随机权重训练大型网络可能效率低。迁移学习复用已有表示,但需要验证源任务特征是否适合目标数据,而不是默认所有预训练模型都有效。
概念定义
迁移学习将预训练参数用于新任务,常见策略是冻结骨干训练新头,再以较小学习率微调部分或全部骨干。
直观理解
先使用已经学会的视觉或语言基础,再学习当前任务的输出规则;如果领域变化很大,旧知识也可能成为偏见。
核心原理
冻结参数通过 requires_grad=False 阻止梯度,但新替换的任务头需要训练,优化器必须包含它。若后来解冻骨干,应同时更新参数组;只改变 requires_grad 不会修改优化器持有的参数列表。
任务头通常使用较大学习率,预训练骨干较小。BatchNorm 的运行统计不受 requires_grad 冻结控制,所以冻结骨干还需决定其 train/eval 策略。预训练输入尺寸、通道和均值方差也属于资产。
微调结果应与冻结特征和小模型基线比较。目标域与源域差异、标签语义或输入模态变化都可能造成负迁移,不能把“使用预训练”直接当效果保证。
数学表达
核心公式
迁移学习将预训练参数用于新任务,常见策略是冻结骨干训练新头,再以较小学习率微调部分或全部骨干。
变量含义
Nbatch sizeD/H特征或隐藏维度
计算步骤
- 1冻结骨干全部参数。
- 2优化器只接收新头参数。
- 3计算交叉熵并反向,分别检查梯度。
代码对应位置
示例 1冻结骨干只让任务头接收梯度的最小验证:使用完整构造输入核对冻结骨干只让任务头接收梯度,输出只表示本例计算。
变量解释
| 变量 | 含义 |
|---|---|
N | batch size |
D/H | 特征或隐藏维度 |
完整数值示例
冻结骨干只让任务头接收梯度
已知条件
- 骨干为 3→4 的线性层加 ReLU
- 新任务头为 4→2
- 两条构造样本用于反向检查
计算步骤
- 冻结骨干全部参数。
- 优化器只接收新头参数。
- 计算交叉熵并反向,分别检查梯度。
计算结果
骨干梯度均为空,任务头梯度均存在;可训练参数数为 10。
处理前后对比
- 示例验证冻结机制,不证明随机骨干具有可迁移知识或能达到真实任务性能。
冻结骨干只让任务头接收梯度
新头包含 2×4 个权重和 2 个偏置,所以总计 10 个可训练参数。代码没有下载预训练权重,只演示冻结与参数组机制,不把随机骨干称为有效预训练模型。
若要继续微调,应显式解冻目标层并将其加入优化器,选择较小学习率。真实迁移效果需要在目标验证集上衡量。
计算与实现步骤
- 冻结骨干全部参数。
- 优化器只接收新头参数。
- 计算交叉熵并反向,分别检查梯度。
代码实现
示例 1
冻结骨干只让任务头接收梯度的最小验证
example_01.py用途:使用完整构造输入核对冻结骨干只让任务头接收梯度,输出只表示本例计算。
import torch
from torch import nn
torch.manual_seed(7)
backbone = nn.Sequential(nn.Linear(3, 4), nn.ReLU())
head = nn.Linear(4, 2)
for p in backbone.parameters():
p.requires_grad_(False)
opt = torch.optim.AdamW(head.parameters(), lr=1e-3)
x = torch.ones(2, 3)
loss = nn.CrossEntropyLoss()(head(backbone(x)), torch.tensor([0, 1]))
opt.zero_grad(set_to_none=True)
loss.backward()
print("backbone frozen:", all(p.grad is None for p in backbone.parameters()))
print("head gradients:", all(p.grad is not None for p in head.parameters()))
print("trainable:", sum(p.numel() for p in head.parameters()))代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
使用完整构造输入核对冻结骨干只让任务头接收梯度,输出只表示本例计算。
loss- 当前预测与目标之间的损失值。
Step 1 · 1–15 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import torch
from torch import nn
torch.manual_seed(7)
backbone = nn.Sequential(nn.Linear(3, 4), nn.ReLU())
head = nn.Linear(4, 2)
for p in backbone.parameters():
p.requires_grad_(False)
opt = torch.optim.AdamW(head.parameters(), lr=1e-3)
x = torch.ones(2, 3)
loss = nn.CrossEntropyLoss()(head(backbone(x)), torch.tensor([0, 1]))
opt.zero_grad(set_to_none=True)
loss.backward()
print("backbone frozen:", all(p.grad is None for p in backbone.parameters()))
print("head gradients:", all(p.grad is not None for p in head.parameters()))
print("trainable:", sum(p.numel() for p in head.parameters()))代码与数学原理
参数更新由当前梯度与学习率共同决定。
平方误差代码对应均方损失。
预期输出或运行结果
backbone frozen: True head gradients: True trainable: 10
常见错误 · 4 条
- 先换头再全冻结
- 解冻未入 optimizer
- 预处理不匹配
- BN 统计未管理
实际应用
- 少样本视觉与文本任务微调
- 比较冻结特征和全模型微调
常见错误
- 先换头再全冻结
- 解冻未入 optimizer
- 预处理不匹配
- BN 统计未管理
常见问题
冻结骨干后为什么输出仍会随训练改变?
训练模式的 BatchNorm 会继续更新统计,Dropout 也会随机;冻结参数只阻止梯度更新,不等于冻结全部前向行为。
这个示例能说明什么边界?
示例验证冻结机制,不证明随机骨干具有可迁移知识或能达到真实任务性能。
推荐学习资料
Practical Deep Learning for Coders
以项目驱动方式学习视觉、NLP、表格模型、协同过滤和模型部署。
Jeremy Howard · fast.ai