想象这样一个场景:你认识一位叫老陈的米其林三星大厨,他做了三十年法餐,刀工、火候、酱汁调配都已炉火纯青。现在你想开一家川菜馆,难道要让老陈从切土豆丝开始重新练起吗?当然不用——他可以保留那套扎实的基本功(识别边缘、纹理、形状),只需针对川菜的特点调整调味方式(分类逻辑)和部分烹饪手法(高层特征)。这就是迁移学习的本质:站在巨人的肩膀上,借用别人已经练好的本领,快速入门你自己的新任务。 在计算机视觉领域,这个“巨人”就是预训练模型——一个已经在海量图片(如 ImageNet 的一千多万张图)上学会“如何看东西”的神经网络。迁移学习让你不用从零训练一个动辄几十天的模型,而是直接复用它的“眼神”和“审美”,花几小时甚至几分钟就能在全新的图像分类任务上取得不错的效果。这篇入门指南会用老陈开川菜馆的故事,带你走一遍迁移学习的完整流程。

岗位关联

对 CV 工程师而言,迁移学习是“小数据+大模型”场景的救命稻草,掌握它能让你的图像分类项目交付周期从数月压缩到数天,是工业界落地必备的核心技能。

目录
  1. 可视化演示
  2. 迁移学习概览
  3. 预训练模型
  4. 特征提取
  5. 微调技术
  6. 冻结策略
  7. 实战流程
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

可视化预训练模型迁移流程、特征提取与微调方法对比及层冻结策略设计,滑块调节解冻比例观察准确率与训练成本变化。

本节配有交互动效(进入视口后自动加载,约 3KB)。

迁移流程:预训练模型→特征适配→新任务输出 冻结层:保留ImageNet预训练特征 可训练层:适配新任务特征表示 前向传播:数据流经网络 y = f_train(f_frozen(x)) 迁移预训练特征到新任务

迁移学习概览

老陈做了一辈子法餐,他清楚地知道:切洋葱的刀工、熬高汤的火候、摆盘的美学,这些基本功在任何菜系里都通用。开川菜馆时,他不需要重新学习“如何握刀”,只需要学“如何调制麻辣”——把已经练成本能的基本功迁移到新菜系上。

迁移学习(Transfer Learning)正是这个道理。传统深度学习就像培养一个新厨师:从认识“什么是刀”开始,一点一点学,动辄需要海量数据和数月训练时间。而迁移学习,是让一个已经在大规模数据集(如 ImageNet 的 128 万张图片、1000 个类别)上训练好的成熟模型,把它的“视觉基本功”迁移到你的新任务上。新任务可以只有几千张图,但模型依然能表现不俗,因为真正困难的特征提取能力已经“内置”了。

从数学上看,从零训练是随机初始化参数 $\theta_{\text{rand}}$ 然后从头学习;而迁移学习则是把预训练模型的参数作为起点,只在任务差异上做小幅度调整:

回到老陈的故事:他只需要在川菜馆试菜几周,就能融会贯通;而一个刚毕业的学徒要学三年才能独当一面。迁移学习给 CV 工程师的启示是:永远先找有没有“老陈”可以请——也就是现成的预训练模型——再决定要不要自己从零培养“学徒”。

θnew=θpretrained+Δθ\theta_{\text{new}} = \theta_{\text{pretrained}} + \Delta\theta
公式通俗说明:新模型的参数 = 老厨师已掌握的全部本领 + 针对川菜做的小调整。“调整量”Δθ 理论上远小于从头学的量,这正是迁移学习省时省力的根本原因。
  1. 新任务参数$\theta_{\text{new}}$ 表示迁移后模型在新任务(比如猫狗分类)上的最终参数,相当于老陈到川菜馆后的完整手艺。
    θnew\theta_{\text{new}}
  2. 预训练参数$\theta_{\text{pretrained}}$ 是模型在 ImageNet 上学到的旧参数,好比老陈三十年积累的法餐基本功,这部分通常直接复制过来。
    θpretrained\theta_{\text{pretrained}}
  3. 增量微调节$\Delta\theta$ 是只针对新任务的参数修正量。因为起点好,这个增量很小,训练只需少量数据和迭代即可收敛。
    Δθ\Delta\theta

预训练模型

老陈的“功力”从哪来?答案是三十年如一日的后厨历练,以及见过无数种食材、翻过无数次车的实战经验。在深度学习里,这份“历练”就叫预训练(Pre-training),而历练的场地通常是 ImageNet——一个拥有 128 万张标注图片、覆盖 1000 个日常物体类别的大规模数据集。谁能在这个数据上把 1000 类物体认准,谁就练出了扎实的视觉基本功。

为什么 ImageNet 上练出来的模型能通用?因为卷积神经网络(CNN)在“看”图时,是一层一层递进的:前几层学的是边缘、颜色、纹理(相当于刀工),中间层学的是“眼睛”“轮子”这类零部件(相当于配菜处理),最后几层才学“狗”“汽车”这类完整语义(相当于整道菜)。越靠底层的特征越通用——无论是法餐还是川菜,刀工都是切菜;无论是 ImageNet 还是你的产品图,边缘纹理识别都是基础。这就是迁移能成立的深层原因。

经典的预训练模型(如 ResNet、EfficientNet、ViT)在 ImageNet 竞赛和后续的公开基准上不断刷新纪录,形成了一个“模型超市”:你按显存、速度、精度需求挑选合适的“老陈”。选好后,模型对一张输入图片 $x$ 的预测过程可以写成:

你在 PyTorch 里可能见过 torchvision.models.resnet18(weights='DEFAULT') 这样的代码,本质上就是“把老陈请过来”。但要注意:老陈只能做法餐(1000 类 ImageNet),你要他做川菜(比如 10 类猫狗品种),必须替换最后的分类层——这个动作我们在下一节展开。

P(y∣x)=softmax(W⋅h+b)P(y \mid x) = \text{softmax}(W \cdot h + b)
公式通俗说明:图片 $x$ 经过 CNN 的层层特征提取,变成一维特征向量 $h$(相当于“老陈眼中这道菜的样子”),然后由一个线性分类器(权重 $W$ 和偏置 $b$)把特征映射到 1000 个类别的概率分布上。
  1. 特征提取结果$h$ 是图片经过骨干网络(Backbone)后得到的特征向量,浓缩了这张图“是什么样子”的关键信息,好比老陈一眼看出“这肉是五分熟”。
    h=fCNN(x)h = f_{\text{CNN}}(x)
  2. 线性分类层$W \cdot h + b$ 是分类器的工作,把特征向量线性投影到每个类别的“得分”上,相当于给每道菜按 1000 个菜名打分。
    W⋅h+bW \cdot h + b
  3. 概率归一化$\text{softmax}$ 把得分压成 0 到 1 之间且总和为 1 的概率分布,最终取概率最大的类别作为预测结果。
    softmax(⋅)\text{softmax}(\cdot)

特征提取

老陈到川菜馆的第一天,你决定先只用他的基本功:刀工、火候、味觉全部照搬,只给他一本新的川菜菜谱让他照着执行。在迁移学习里,这个操作叫特征提取(Feature Extraction):把预训练模型的骨干网络(特征提取部分)全部冻结,只训练新加的、针对你任务的分类头(分类器)。

冻结(Freeze)是什么意思?就是让这些层的参数在训练过程中不参与梯度更新,始终保持 ImageNet 上学到的值。在代码里,通常这样实现:遍历模型参数,把 requires_grad 设为 False。这样反向传播时,梯度不会流经这些层,训练时只更新分类头的权重。这样做的好处非常直接:需要训练的参数从几百万骤降到几千,训练速度快、显存占用低、不容易过拟合,尤其适合几千张图的小数据集。

在特征提取模式下,特征向量 $h$ 是由冻结的骨干网络输出的不再变化;只有分类头的参数 $\theta_{\text{cls}}$ 在更新。假设你的新任务有 $C$ 个类别,分类头用交叉熵损失函数来衡量预测分布与真实标签的差距:

特征提取就像给老陈发了一本固定菜谱:他不能改刀工、不能改火候,只能按新菜谱的步骤走。优点是稳,缺点是天花板有限——因为 ImageNet 的“口味”和你的任务可能有一定偏差。如果数据量允许,你可以考虑让老陈也稍微调整一下手法,这就是下一节的微调。

L(θcls)=−∑i=1Cyilog⁡pi\mathcal{L}(\theta_{\text{cls}}) = -\sum_{i=1}^{C} y_i \log p_i
公式通俗说明:$y_i$ 是真实标签的独热编码(哪个类是正确答案,那一项就是 1,其余是 0),$p_i$ 是模型预测该类的概率。真实类别对应的概率越接近 1,损失越小——相当于老陈做出的菜,是菜谱上要求的那道菜,就算成功。
  1. 真实标签$y_i$ 是第 $i$ 类的真实标注。如果图片确实是第 $k$ 类,则 $y_k = 1$,其余为 0,这叫独热编码。
    yi∈{0,1},∑i=1Cyi=1y_i \in \{0, 1\}, \quad \sum_{i=1}^{C} y_i = 1
  2. 预测概率$p_i$ 是模型对第 $i$ 类的预测概率,来自 softmax 输出,范围在 (0, 1) 之间。
    pi∈(0,1)p_i \in (0, 1)
  3. 对数惩罚$\log p_i$ 是“惩罚项”。真实类别的概率越小,$-\log p_i$ 越大,即“这道菜做得越不像,扣分越多”。
    −∑i=1Cyilog⁡pi-\sum_{i=1}^{C} y_i \log p_i

微调技术

当你的新任务和 ImageNet 差异较大(比如医学影像的黑白 X 光片、卫星遥感图),或者你手里的数据足够多(几万张以上),只训练分类头可能不够用了。这时需要微调(Fine-tuning):解冻部分骨干网络层,让它们的参数也参与训练,在新任务的数据上继续更新。

微调的精髓在于“小心轻放”。老陈做川菜时可以稍微调整他的火候和调味习惯,但绝不能让他把三十年练就的刀工彻底推翻重来。对应到技术上,微调有几个关键原则:

  • 学习率要小:预训练参数已经处在很好的位置,如果学习率太大,一步就会“走过头”,把学好的特征破坏掉。通常微调学习率比从零训练小 10 到 100 倍,比如从零训练用 $10^{-3}$,微调用 $10^{-4}$ 或 $10^{-5}$。
  • 解冻要克制:一般只解冻高层(靠近分类头的层),因为底层特征通用性最强、最不值得改动。
  • 配合数据增强:随机裁剪、翻转、颜色抖动等操作,能有效防止微调在小数据集上的过拟合。

梯度下降是微调的基本更新规则。设 $\mathcal{L}$ 为损失函数,$\eta$ 为学习率,则每一轮参数更新为:

在老陈的故事里,微调相当于让他“因材施教”地调整手艺:刀工基本不动,但调味风格可以大幅调整。在工程上,微调效果通常优于特征提取,代价是训练时间更长、更易过拟合。到底要不要微调、解冻多少层?答案写在下一节的冻结策略里。

θ←θ−η∇θL(θ)\theta \leftarrow \theta - \eta \nabla_{\theta} \mathcal{L}(\theta)
公式通俗说明:每次沿损失下降最快的方向(负梯度方向)迈一小步。微调时步子(学习率 $\eta$)必须迈得小,因为我们已经站在山顶附近了,大步跑容易掉下悬崖。
  1. 参数更新$\theta \leftarrow \theta$ 表示用右边的计算结果覆盖掉旧参数,是新参数的赋值过程。
    θnew=θold−η∇θL(θold)\theta_{\text{new}} = \theta_{\text{old}} - \eta \nabla_{\theta} \mathcal{L}(\theta_{\text{old}})
  2. 学习率$\eta$ 控制每步迈多大。微调场景下,它通常被设得很小(如 $10^{-4}$),以免破坏预训练学好的特征。
    η≪ηscratch\eta \ll \eta_{\text{scratch}}
  3. 梯度方向$\nabla_{\theta} \mathcal{L}(\theta)$ 是损失函数对参数 $\theta$ 的梯度,它指向损失上升最快的方向,取负号则让损失下降。
    ∇θL(θ)\nabla_{\theta} \mathcal{L}(\theta)

冻结策略

现在你面对一个实际问题:老陈的基本功,到底让他保留多少、调整多少?这就引出了冻结策略(Freezing Strategy)——决定模型中哪些层冻结、哪些层参与训练。三种常见策略,对应三种不同的“用人方式”:

1. 全冻结(特征提取):骨干网络全部冻结,只训练新分类头。适合数据极少(几百张)、任务与 ImageNet 相近的场景,训练快如闪电。

2. 部分解冻:冻结底层和中层,解冻靠近输出的高层。适合中等规模数据(几千到几万张),让模型在保留通用特征的同时,针对新任务调整语义特征。

3. 渐进解冻(Progressive Unfreezing):先只训练分类头,训练稳定后,再逐层解冻高层、接着是中层……像剥洋葱一样由浅入深。适合训练不稳定或任务差异较大的场景。

从公式上看,冻结策略可以统一用一个带掩码的更新规则来描述。设掩码向量 $\mathbf{m}$,其中 $m_i = 0$ 表示第 $i$ 层冻结,$m_i = 1$ 表示第 $i$ 层可训练,则:

选择哪种策略,核心依据是你的数据量:数据越少,越应该多冻结;数据越多,越可以大胆解冻。老陈的比喻依然成立:如果你的餐厅只有十个客人(小数据),让老陈完全按法餐基本功来做川菜,可能更稳妥;若你的餐厅生意火爆、反馈数据源源不断(大数据),再让老陈逐步放开手脚调整也不迟。

θ←θ−η⋅(m⊙∇θL),mi∈{0,1}\theta \leftarrow \theta - \eta \cdot (\mathbf{m} \odot \nabla_{\theta} \mathcal{L}), \quad m_i \in \{0, 1\}
公式通俗说明:把所有参数看成一排开关,掩码 $\mathbf{m}$ 决定哪些开关是闭合的(允许更新)。冻结的层 $m_i=0$,梯度即使算出来了也会被“屏蔽”掉,参数纹丝不动。
  1. 掩码向量$\mathbf{m}$ 是一个长度等于参数层数的向量,每个分量取值 0 或 1,决定该层是否参与训练。
    m=[m1,m2,…,mL],mi∈{0,1}\mathbf{m} = [m_1, m_2, \dots, m_L], \quad m_i \in \{0, 1\}
  2. 逐元素相乘$\odot$ 是 Hadamard 积,即两个向量对应位置相乘。$m_i = 0$ 时该层的梯度被清零,等效于冻结。
    [m⊙∇θL]i=mi⋅∂L∂θi[\mathbf{m} \odot \nabla_{\theta} \mathcal{L}]_i = m_i \cdot \frac{\partial \mathcal{L}}{\partial \theta_i}
  3. 更新等式整个式子表示只在掩码为 1 的层上执行梯度下降,掩码为 0 的层参数保持不变。
    θ←θ−η⋅(m⊙∇θL)\theta \leftarrow \theta - \eta \cdot (\mathbf{m} \odot \nabla_{\theta} \mathcal{L})

实战流程

理论讲完,回到现实:老陈的川菜馆今天开业,完整流程怎么走?迁移学习的工程实践可以分成四步:数据准备 → 模型加载 → 训练配置 → 评估部署。

数据准备:首先把你的图片整理成标准格式。和 ImageNet 一样,通常把训练图片缩放到 $224 \times 224$ 像素,做归一化(均值和标准差对齐 ImageNet 的统计值),再配合随机翻转、裁剪等数据增强操作。这一步相当于把食材统一洗净切好,让老陈一进后厨就能上手。

模型加载:使用 torchvision.models 等库加载预训练权重,然后替换分类头。替换时要注意:原模型的 fc 层输出是 1000(ImageNet 类别数),你要改成自己的类别数,比如 num_classes=10。

训练配置:根据你的数据量选择冻结策略。数据少就全冻结,只优化分类头;数据多就部分解冻,把骨干网络的学习率设小。优化器一般用 Adam 或 SGD,微调时骨干网络的学习率设 $10^{-5} \sim 10^{-4}$,分类头可以稍大。训练过程中监控验证集损失,早停(Early Stopping)防止过拟合。

评估部署:训练完成后在测试集上计算准确率。图像分类最常用 Top-1 准确率——也就是预测概率最大的类别正好是真实类别的比例:

实战跑通后,把模型导出为部署格式(如 ONNX 或 TensorRT 引擎),接入服务。到这一步,老陈的川菜馆就正式营业了:用别人练了三十年的厨艺,一周内端出你自己的招牌菜。这就是迁移学习在 CV 领域的完整落地路径——省时、省力、省数据,还足够美味。

Top-1 Acc=1N∑n=1NI(y^n=yn)\text{Top-1 Acc} = \frac{1}{N} \sum_{n=1}^{N} \mathbb{I}(\hat{y}_n = y_n)
公式通俗说明:$N$ 是测试图片总数,$\hat{y}_n$ 是第 $n$ 张图的预测类别,$y_n$ 是真实类别。$\mathbb{I}(\cdot)$ 是一个“指示函数”——条件为真记 1,否则记 0。把所有猜对的票数加起来除以总人数,就是准确率。
  1. 样本总数$N$ 表示测试集里的图片数量,是准确率的分母。
    N=∣Dtest∣N = |\mathcal{D}_{\text{test}}|
  2. 预测对齐判断$\mathbb{I}(\hat{y}_n = y_n)$ 表示“第 $n$ 张图片的预测类别与真实类别是否一致”,一致为 1,不一致为 0。
    I(y^n=yn)={1,y^n=yn0,otherwise\mathbb{I}(\hat{y}_n = y_n) = \begin{cases} 1, & \hat{y}_n = y_n \\ 0, & \text{otherwise} \end{cases}
  3. 加权平均求和后除以 $N$,得到猜对的样本占比,即 Top-1 准确率。
    1N∑n=1NI(y^n=yn)\frac{1}{N} \sum_{n=1}^{N} \mathbb{I}(\hat{y}_n = y_n)

AI 应用场景

医疗影像辅助诊断

阶段:模型训练与微调

解决的问题:医学影像(如 X 光胸片)的标注需要专业放射科医生逐张勾画,成本极高,一个医院能积累的标注数据往往只有几千张,远不够从零训练一个深度网络。传统方案要么效果差,要么需要昂贵的专家标注。

具体案例:一家三甲医院要构建肺炎检测系统。算法团队加载在 ImageNet 上预训练的 ResNet-50,冻结前 80% 的层,仅解冻最后几层卷积和新的二分类头,用 3000 张标注胸片微调 20 个 epoch,最终在测试集上达到了与资深医生相当的灵敏度。整个训练在单张 RTX 3080 上耗时不到 2 小时。

工业零件缺陷检测

阶段:数据准备与模型迁移

解决的问题:流水线上某型号零件的划痕、凹坑样本极少(每家厂商只有几百张),而且不同型号差异大,传统视觉算法靠人工设计特征,泛化能力差。

具体案例:某 3C 制造商使用 EfficientNet-B0 预训练模型做特征提取——骨干网络全部冻结,只训练一个新的 3 类分类头(正常/划痕/凹坑)。由于训练参数极少,模型在产线边缘盒子上也能快速推理,上线首周即拦截了 12 个漏检缺陷件。

遥感图像地物分类

阶段:模型选择与微调

解决的问题:卫星遥感影像与自然照片差异大,且标注成本高,直接使用 ImageNet 预训练模型效果有限。

具体案例:某地理信息公司对高分辨率遥感影像做土地利用分类(建筑/农田/水域/森林等 8 类),选用在 ImageNet 上预训练的 ResNet-34,采用“渐进解冻”策略:先训分类头,再逐层解冻后 4 层,每次解冻时学习率减半。最终在 5000 张标注影像上达到 91% 的准确率,比从零训练高出 17 个百分点。

Python 演示

python111 行
import torchimport torch.nn as nnimport torch.optim as optimfrom torchvision import datasets, transforms, models  def build_model(mode="feature_extract", num_classes=10, unfreeze_from=6):    """构建迁移学习模型。    mode: feature_extract=全冻结 / fine_tune=部分解冻    unfreeze_from: 解冻起始层索引(仅 fine_tune 模式生效)    """    model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)     if mode == "feature_extract":        for param in model.parameters():            param.requires_grad = False    else:        # 解冻最后几层:遍历命名参数,只保留 layer4 之后可训练        for name, param in model.named_parameters():            if "layer4" not in name:                param.requires_grad = False     # 替换分类头:把 1000 类输出改成自己的类别数    in_features = model.fc.in_features    model.fc = nn.Linear(in_features, num_classes)    return model  def train_one_epoch(model, loader, criterion, optimizer, device):    """训练一个 epoch,返回平均损失。"""    model.train()    total_loss = 0.0    for images, labels in loader:        images, labels = images.to(device), labels.to(device)         optimizer.zero_grad()        outputs = model(images)        loss = criterion(outputs, labels)        loss.backward()        optimizer.step()         total_loss += loss.item()    return total_loss / len(loader)  def evaluate(model, loader, device):    """在验证集上计算 Top-1 准确率。"""    model.eval()    correct = 0    total = 0    with torch.no_grad():        for images, labels in loader:            images, labels = images.to(device), labels.to(device)            outputs = model(images)            preds = outputs.argmax(dim=1)            correct += (preds == labels).sum().item()            total += labels.size(0)    return correct / total  def main():    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")     # 数据准备:与 ImageNet 相同的归一化参数    transform_train = transforms.Compose([        transforms.Resize((224, 224)),        transforms.RandomHorizontalFlip(),        transforms.ToTensor(),        transforms.Normalize(mean=[0.485, 0.456, 0.406],                             std=[0.229, 0.224, 0.225]),    ])    transform_test = transforms.Compose([        transforms.Resize((224, 224)),        transforms.ToTensor(),        transforms.Normalize(mean=[0.485, 0.456, 0.406],                             std=[0.229, 0.224, 0.225]),    ])     train_set = datasets.CIFAR10(root="./data", train=True,                                 download=True, transform=transform_train)    test_set = datasets.CIFAR10(root="./data", train=False,                                download=True, transform=transform_test)    train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)    test_loader = torch.utils.data.DataLoader(test_set, batch_size=64, shuffle=False)     # 使用特征提取模式:骨干全冻结,只训练分类头    model = build_model(mode="feature_extract", num_classes=10).to(device)    criterion = nn.CrossEntropyLoss()    optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)     for epoch in range(3):        loss = train_one_epoch(model, train_loader, criterion, optimizer, device)        acc = evaluate(model, test_loader, device)        print(f"Epoch {epoch + 1}: loss={loss:.4f} acc={acc:.4f}")     # 切换到微调模式:解冻 layer4,用小学习率继续训练    model = build_model(mode="fine_tune", num_classes=10).to(device)    optimizer = optim.Adam([        {"params": model.layer4.parameters(), "lr": 1e-4},        {"params": model.fc.parameters(), "lr": 1e-3},    ])    for epoch in range(3):        loss = train_one_epoch(model, train_loader, criterion, optimizer, device)        acc = evaluate(model, test_loader, device)        print(f"FT Epoch {epoch + 1}: loss={loss:.4f} acc={acc:.4f}")     torch.save(model.state_dict(), "transfer_cifar10.pth")  if __name__ == "__main__":    main()
目的:展示迁移学习两个阶段——先用特征提取快速建立基线,再解冻高层微调提升精度。全程只需 3 个 epoch,就能在 CIFAR-10 上达到 80% 以上的准确率。
  1. model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)加载在 ImageNet 上预训练好的 ResNet-18,“把老陈请进后厨”。
  2. param.requires_grad = False把骨干网络参数冻结,梯度不回传,等价于“让老陈先不动基本功”。
  3. model.fc = nn.Linear(in_features, num_classes)替换最后的全连接层,输出从 1000 类改为 10 类,相当于给老陈换一本川菜菜谱。
  4. "layer4" not in name微调模式下只解冻 layer4 这一层,保留更底层的通用特征。
  5. optim.Adam(model.fc.parameters(), lr=1e-3)特征提取时只用 Adam 优化分类头,1e-3 的学习率对新初始化的分类头是安全的。
  6. optim.Adam([{"params": ..., "lr": 1e-4}, ...])微调时为不同层设置不同学习率:骨干网络 1e-4(小步慢走),分类头 1e-3(大步快跑)。
  7. outputs.argmax(dim=1)取概率最大的类别索引,这就是 Top-1 预测结果。

常见误区

误区我下载了一个在 ImageNet 上训练好的 ResNet,直接把我的产品图片喂进去,就能分类了。输出 1000 个类别里有“猫”和“狗”,正好能用!

正解预训练模型最后的分类层是 1000 类,只能识别 ImageNet 预定义的类别。你的新任务类别(比如口罩佩戴、零件缺陷)根本不在其中。正确做法是丢弃最后一层,换上自己的分类头,然后做特征提取或微调。就像老陈不会做川菜,你让他直接按法餐菜谱做回锅肉,当然不行——你得给他一本新菜谱。

为何常见:很多人把“能用预训练模型做推理”误当成“迁移学习”,因为前向推理在代码上确实一行就能跑通,看起来“有手就行”。

误区既然冻结能省显存、防过拟合,那把骨干网络全冻结肯定是最优选择,数据少就全靠冻结保平安。

正解全冻结只训练分类头,模型可学习的参数很少,表达能力的上限会被锁死。如果新任务与 ImageNet 差异较大(如医学影像、红外图),底层特征也需要微调才能对齐。正确心态是:冻结是防御过拟合的手段,不是目的。数据越多、任务差异越大,越应倾向解冻更多层。

为何常见:初学者做实验发现“全冻结也能跑出不错的准确率”,就误以为解冻是多余的。实际上那是因为任务简单或与 ImageNet 相似,塔尖优势掩盖了底层不适配。

误区我从零训练时用 1e-3 挺好,微调也是训练,那就继续用 1e-3 吧,肯定收敛更快。

正解预训练参数已经处在一个很好的局部最优附近,学习率过大会让一步更新直接跳出这个区域,把珍贵的预训练特征破坏殆尽(业界叫“灾难性遗忘”)。微调的骨干网络学习率通常要降到 1e-4 甚至 1e-5,相当于老陈已经做得很好了,你只需让他微调咸淡,而不是把他的锅掀了重炒。

为何常见:直觉上“学习率大收敛快”,但没考虑到起点不同——从零训练是平地上起跑,预热后可以加速;微调是山顶上调整位置,步子太大反而坠崖。

误区微调能让更多层适应新任务,所以只要计算资源够,无脑微调一定比特征提取准确率高。

正解微调不是免费的午餐。在小数据集上(几百到几千张),解冻大量层反而容易过拟合,导致验证集效果还不如全冻结的特征提取。数据量、任务相似度、训练时长三者共同决定收益。工程上正确的做法是:先用特征提取快速建基线,再尝试微调对比,用验证集数据说话,而不是凭感觉站队。

为何常见:网上大部分教程都在讲微调的高精度,容易让人忽略它的进入门槛。看到微调在公开数据集刷榜,就误以为它在所有规模的数据上都优于特征提取。

面试题

Q1. 迁移学习的核心思想是什么?

单选题 · easy

  • A.用更大数据集从头训练新模型
  • B.把已学任务的知识复用到新任务上← 正确答案
  • C.在多个 GPU 上并行训练模型
  • D.将模型参数压缩以减少显存

答案:B。迁移学习本质是让在源任务(如 ImageNet 分类)上训练好的模型,把学到的特征知识复用到目标任务上。A 是从零训练,C 是分布式训练,D 是模型压缩,都与“知识复用”无关。

Q2. 预训练模型通常在哪个数据集上训练得到?

单选题 · easy

  • A.CIFAR-10
  • B.MNIST
  • C.ImageNet← 正确答案
  • D.COCO

答案:C。主流 CV 预训练模型(ResNet、EfficientNet 等)默认指在 ImageNet 上训练,它有 128 万张图和 1000 类,覆盖极广。CIFAR-10 和 MNIST 太小,COCO 主要用于检测/分割任务而非分类预训练。

Q3. 特征提取模式下,骨干网络的参数状态是?

单选题 · mid

  • A.随机初始化并参与训练
  • B.加载预训练权重且不更新← 正确答案
  • C.加载预训练权重并持续更新
  • D.逐层按比例缩放权重

答案:B。特征提取 = 全冻结,骨干网络参数加载预训练权重后将 requires_grad 置为 False,梯度不回传、参数不更新。A 是从零训练,C 是微调,D 不属于标准做法。

Q4. 迁移到新任务时,为什么必须替换模型的分类头?

单选题 · mid

  • A.原分类头参数太大,占用显存
  • B.原分类头输出类别数和新任务不一致← 正确答案
  • C.原分类头会降低特征提取速度
  • D.原分类头无法使用 softmax 激活

答案:B。预训练模型分类头输出是 1000(ImageNet 类别数),新任务类别数通常是 10 或 2 等,维度不匹配,必须用新的 nn.Linear(in_features, num_classes) 替换。这相当于给老陈换一本符合新菜系的菜谱。

Q5. 关于微调(Fine-tuning),以下说法正确的有?

多选题 · mid

  • A.微调时骨干网络的学习率应设得比分类头小← 正确答案
  • B.微调能缓解全部层冻结带来的表达上限← 正确答案
  • C.微调在数据量极少时一定优于特征提取
  • D.微调前通常先移除原模型的分类头← 正确答案

答案:ABD。微调时骨干网络已在较好位置,应配小学习率(如 1e-4),分类头可稍大,A 正确。解冻更多层确实提升了表达能力上限,B 正确。C 错误:数据极少时微调容易过拟合,特征提取可能更好。替换分类头是微调的前置步骤,D 正确。

Q6. 当你只有 500 张标注数据且新任务与 ImageNet 差异较大时,最稳妥的迁移策略是?

单选题 · hard

  • A.全冻结骨干网络,只训练分类头
  • B.解冻所有层,用大学习率微调
  • C.从零随机初始化训练一个深度网络
  • D.先冻结骨干训练分类头,再渐进解冻高层微调← 正确答案

答案:D。数据量极小且任务差异大,直接全冻结可能特征不对齐,全解冻容易过拟合,从零训练更是无米之炊。渐进解冻(先训分类头,稳定后逐层解冻高层)能在保留通用特征的同时逐步适配新域,是此类场景的稳健方案。

Q7. 以下哪些做法有助于防止微调时的过拟合?

多选题 · mid

  • A.使用随机翻转和裁剪做数据增强← 正确答案
  • B.在验证集 loss 不再下降时提前停止训练← 正确答案
  • C.把骨干网络学习率调大到 1e-1
  • D.在分类头后添加 Dropout 层← 正确答案

答案:ABD。数据增强(A)和早停(B)是通用防过拟合手段。Dropout(D)随机丢弃部分神经元,也能抑制过拟合。C 恰恰相反:大学习率会破坏预训练特征,导致训练崩溃而非泛化。

Q8. 渐进解冻(Progressive Unfreezing)的正确操作顺序是?

单选题 · hard

  • A.先全解冻,再逐层冻结底层
  • B.先训分类头,再逐层解冻靠近输出的高层← 正确答案
  • C.先训骨干,再冻结分类头
  • D.每轮随机选择一层解冻

答案:B。渐进解冻的典型流程是:先冻结全部骨干只训新分类头;训练稳定后,从最靠近输出的高层开始逐层解冻,每解冻一层就微调若干轮。这样由易到难,避免一开始就解冻大量层导致的不稳定。B 准确描述了该顺序。

Q9. 迁移学习相对从零训练的主要优势包括?

多选题 · easy

  • A.需要的标注数据更少← 正确答案
  • B.训练收敛速度更快← 正确答案
  • C.模型推理速度更快
  • D.小数据集上不易过拟合← 正确答案

答案:ABD。预训练模型已具备泛化特征,因此少量数据也能学到有效分类边界(A),参数起点好所以收敛快(B),可训练参数量少、鲁棒性好,不易过拟合(D)。推理速度由网络结构决定,与是否迁移无关,C 错误。

Q10. 在 PyTorch 中冻结模型参数的正确代码是?

单选题 · easy

  • A.model.parameters().freeze()
  • B.param.requires_grad = False← 正确答案
  • C.model.layers.lock()
  • D.torch.no_grad(param)

答案:B。PyTorch 中冻结的标准操作是遍历模型参数,把 requires_grad 设为 False,这样反向传播时不会计算并更新这些参数的梯度。其他三个选项都不是 PyTorch 的合法 API。