换一个分类头不等于完成迁移学习。一次实验至少有三份需要分别核对的状态:哪些参数可以训练、哪些模块处于训练模式、图像怎样进入网络。

骨干网络解决什么问题

骨干把图像变成可供后续任务使用的特征。分类头、检测头和分割解码器的输出目标不同,不能只替换输出维数就默认其他设置完全通用。ResNet 的残差连接可写成 y = F(x) + x;尺寸不同还需要投影或其他对齐方式。它改善深层网络的优化,不保证任意任务中越深越好。ResNet 原论文

选型时先固定数据划分和训练预算,再比较精度、显存、延迟及失败样本。不把“某个骨干永远最强”或“某个样本数以下必须冻结”当成通用规则。

三种状态不要混为一谈

检查对象 它控制什么 容易遗漏什么
参数的 requires_grad 是否为该参数记录梯度 不会自动切换 BatchNorm、Dropout 的行为
优化器参数组 哪些参数真正参与更新 可训练参数可能没被加入优化器,也可能被重复加入
train() / eval() 模块的训练与评估行为 后续调用总模型的 train() 可能又切回骨干模式

训练分类头且固定骨干统计量时,应显式维护“骨干评估、分类头训练”的策略;如果希望域适应时更新统计量,也应记录这个选择,而不是偶然更新。不能用一次 eval() 代替整个训练循环的模式管理。PyTorch 迁移学习教程

一个参数清单检查

下面是独立的参数登记示例,不加载模型、不下载权重,也不是图像分类训练。它说明为什么只登记 layer1 到 layer4 可能漏掉输入端的卷积与归一化层。

from collections import Counter

trainable = {
    "conv1.weight", "bn1.weight", "bn1.bias",
    "layer1.weight", "layer4.weight", "fc.weight", "fc.bias",
}
groups = {
    "backbone": sorted(name for name in trainable if not name.startswith("fc.")),
    "head": sorted(name for name in trainable if name.startswith("fc.")),
}
counts = Counter(name for group in groups.values() for name in group)
assert set(counts) == trainable
assert all(count == 1 for count in counts.values())

incomplete = {name for name in trainable if name.startswith(("layer", "fc."))}
assert trainable - incomplete == {"conv1.weight", "bn1.weight", "bn1.bias"}
print("parameter coverage and uniqueness checks passed")

实际 PyTorch 项目中,应对 model.named_parameters() 与优化器里的参数对象做同样的覆盖、重复检查。逐层解冻后还要更新优化器参数组;仅把 requires_grad 改回 True,不保证原优化器包含这些参数。

预处理跟着具体权重走

不要给所有预训练模型统一套一组固定的尺寸和归一化常数。TorchVision 的具体权重对象提供对应推理变换;训练增强可以另行设计,但要记录与权重预处理约定的关系。TorchVision 权重与变换说明

我的实验记录至少应包含:数据划分标识、权重具体版本、输入尺寸、冻结范围、统计量更新策略、各参数组学习率和选模指标。验证集负责选方案,测试集留到方案确定后再评估。

来源与核验边界

本篇结合疏锦行计算机视觉专题《经典骨干网络》讲义第 29—34 页的迁移学习部分,重新组织为工程检查清单,并补充官方文档核对。上面的清单示例已运行;没有复现讲义中的 Flowers-102 训练成绩。视频口头内容尚未逐节核验,原课件不随笔记公开。