想象你是一位做了十年家常菜的大厨,刀工、火候、调味都已炉火纯青。突然有一天,餐厅老板递给你一张外国菜系的菜单,要求你三天内上新菜。你会怎么做?把锅碗瓢盆全扔掉、从零开始学?还是完全无视自己十年的经验,照着菜谱一步步摸索?聪明的做法当然是——保留已经融入肌肉记忆的基本功(切菜、控火、调味),只针对新菜系的特色(酱汁、香料、摆盘)做定向调整。这个过程,就是深度学习中的“微调”(Fine-tuning)。 在图像分类任务里,那些预训练好的模型(比如在 ImageNet 上训练过的 ResNet)就像那位大厨。它们已经见过千万张图片,学会了识别边缘、纹理、形状等通用特征。而你的任务,可能是区分猫狗、识别肿瘤、给产品做质检——这些就是“新菜系”。微调策略要回答的核心问题只有一个:如何让一位老厨师高效学会新菜,既不丢失手艺,又不过度死记菜谱? 本文会用这个比喻贯穿全程,带你认识全量微调、部分微调、渐进式微调、学习率技巧和防过拟合等一整套“厨房心法”。

岗位关联

对 AI 从业者而言,微调是让预训练模型落地到真实业务的核心技能。掌握它,你就能用少量数据和有限算力,把通用模型改造成专属于你场景的高性能分类器——这是从“会调库”走向“会优化”的关键一步。

目录
  1. 可视化演示
  2. 微调策略概览
  3. 全量微调
  4. 部分微调
  5. 渐进式微调
  6. 学习率技巧
  7. 防过拟合技巧
  8. 实战选择指南
  9. PyTorch实践
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示全量微调、部分微调与渐进式微调三种策略下神经网络各层的冻结与训练状态,配合反向传播梯度流动与损失曲线变化。

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

全量微调 所有层参数同步更新,梯度从输出层反向传播至输入层 学习率统一设置,收敛快但有过拟合风险 θ←θ-η·∇L(θ) 适用于数据充足、与预训练任务差异较大的场景

微调策略概览

大厨拿到新菜谱,第一件事不是抄起锅铲就炒,而是先想想:这道菜和我的拿手菜有多大差别?是换了个酱汁,还是完全陌生的烹饪体系?深度学习里的微调也一样——你拿到一个预训练模型,先要判断它“会什么、不会什么”,再决定怎么动手改。预训练模型在 ImageNet 等大规模数据集上学到的是通用视觉特征:边缘、纹理、形状、明暗——这些相当于大厨的刀工火候,几乎对所有图像任务都有用。微调,就是在这个基础上,用你自己的少量标注数据,让模型学会你关心的类别。

从数学上看,微调的本质是“换了一个优化起点”的训练。普通训练从随机初始化的权重出发,像一张白纸;微调则从预训练权重出发,像一位已经有功底的厨师:

微调与迁移学习的关系也很简单:迁移学习是“把老本领用到新领域”这个大概念,而微调是其中最常用、最落地的实现手段。是否适合微调,主要看两件事——你的新任务与预训练任务是否相关(菜系差别大不大),以及你手头有多少标注数据(食材够不够)。如果新任务是非常特殊的医学影像,而预训练是在自然图片上做的,那就要更小心地选择微调策略,甚至可能要多微调几层来“纠正”大厨的旧习惯。

这一节就像大厨的“开工前评估”:先摸清自己的家底,再决定这道新菜该怎么学。接下来几节,我们会依次看看三种典型的“学法”。

\theta^{\*} = \mathop{\arg\min}_{\theta} \mathcal{L}_{\text{CE}}\big(f(x; \theta), y\big), \quad \theta_0 = \theta_{\text{pretrained}}
  1. 初始状态$\theta_0 = \theta_{\text{pretrained}}$ 表示把预训练好的权重作为优化的起点,而不是随机数。
  2. 模型预测$f(x; \theta)$ 表示输入图片 $x$ 经过带参数 $\theta$ 的模型后输出的预测结果。
  3. 损失计算$\mathcal{L}_{\text{CE}}\big(\cdot, y\big)$ 是交叉熵损失,用来衡量预测与真实标签 $y$ 之间的差距。
  4. 求解目标$\mathop{\arg\min}_{\theta}$ 表示我们要找到一组参数,让这个损失值尽量小。

全量微调

如果餐厅给了你充足的新菜食材,让你放开手脚大胆改,你会怎么做?大概会把整套菜谱从头到尾练上几遍,连刀法习惯都针对新菜微调一番。全量微调就是这个思路:让预训练模型的每一层参数都参与梯度更新。既然模型原本已经能识别通用特征,全量微调就是让所有层在保持“大体还是老厨师”的前提下,逐步向新任务的方向偏移。

全量微调的公式看起来和普通训练几乎一样,唯一的区别就是初始权重不是随机值:

全量微调的最大优点是上限高——当新任务数据足够多、与预训练任务差异较大时,它能最大程度地适配新领域。但它的风险同样突出:一是灾难性遗忘,模型可能在追逐新任务时把原本学到的通用特征“冲淡”了,就像大厨练了新菜法餐,回头连最拿手的宫保鸡丁都变味了;二是需要较多数据和较强算力,因为所有层都在计算梯度。

所以全量微调适合什么场景?你的数据集够大(比如几万张图),或者新任务与预训练任务差异很明显,必须大改模型内部。否则,就像让大厨为了一个甜品彻底改变炒菜手法——用力过猛,得不偿失。

θt+1=θt−η∇θL(f(xt;θt),yt),∀θ∈Θ\theta_{t+1} = \theta_t - \eta \nabla_{\theta} \mathcal{L}\big(f(x_t; \theta_t), y_t\big), \quad \forall \theta \in \Theta
  1. 全体参数$\forall \theta \in \Theta$ 表示 $\Theta$ 中所有的参数(浅层、深层、分类头)都要被更新。
  2. 梯度指引$\nabla_{\theta} \mathcal{L}\big(\cdot\big)$ 是损失函数对每个参数的偏导,指明“该往哪个方向调”。
  3. 更新规则$\theta_{t+1} = \theta_t - \eta \nabla$ 表示沿着梯度反方向迈一小步,让损失下降。
  4. 步长控制$\eta$ 是学习率,相当于大厨调整做法的“步子大小”,太大容易翻车,太小则学得慢。

部分微调

现实中的餐厅可不会总给你无限食材。更多时候,你只有一小袋新食材,还得在三天内上新菜。这时聪明的大厨会怎么做?刀工火候全按老规矩来,只针对新菜的酱汁和摆盘专门研究。 部分微调正是这个逻辑:把模型浅层(低层特征提取器)的参数冻结住,只训练深层特征层和分类头。

为什么可以这样?因为神经网络有一个很有意思的特性:越靠近输入的层,学到的东西越通用——边缘、颜色、纹理这些底层特征几乎适用于所有图像任务;而越靠近输出的层,越与具体任务绑定——比如 ImageNet 里学到的“狗鼻子”“车轮”等高级部件,不一定对你的任务有用。所以,当你的数据量不大时,保留浅层这些“通用基本功”,只让高层重新适配,是最稳妥的选择。

部分微调的数学表达,关键在于“哪些参数参与更新”:

部分微调还有一个实际的好处——显存和算力大幅降低。冻结的层不需要存梯度,训练时的显存占用明显减少,迭代速度也更快。这在 GPU 资源有限的公司环境里非常实用。常见做法是把分类头整个换掉(因为原来的分类头是给 ImageNet 的 1000 类设计的),再连带着训练最后几个残差块或全连接层。

一句话总结部分微调:少动浅层保通用,多动深层去适配,换掉分类头来接手。 这就像大厨只学新菜的酱汁配方和摆盘方式,不动自己的基本功——又快又稳。

θhigh←θhigh−η∇θhighL,θlow=θlowfrozen\theta_{\text{high}} \leftarrow \theta_{\text{high}} - \eta \nabla_{\theta_{\text{high}}} \mathcal{L}, \qquad \theta_{\text{low}} = \theta_{\text{low}}^{\text{frozen}}
  1. 参数划分$\theta_{\text{high}}$ 表示高层与分类头的参数,$\theta_{\text{low}}$ 表示浅层参数,两者被分开对待。
  2. 冻结约束$\theta_{\text{low}} = \theta_{\text{low}}^{\text{frozen}}$ 表示低层参数固定不变,梯度不参与计算、权重不被更新。
  3. 高层更新$-\eta \nabla_{\theta_{\text{high}}} \mathcal{L}$ 表示只有高层部分执行梯度下降。
  4. 整体含义模型“保留旧手艺”,仅让最贴近任务输出的部分去学习新知识。

渐进式微调

前两种策略一个“太猛”、一个“太保守”,那有没有折中的办法?想象一位谨慎的大厨学新菜的方式:第一周只学摆盘(训练分类头),第二周开始研究核心食材的处理(解冻深层特征),第三周才尝试调整自己的刀工习惯(解冻浅层)——每一步都等前一步稳定了再迈下一步。这就是渐进式微调(Progressive Fine-tuning) 的核心思想:把训练过程拆成多个阶段,从分类头开始,逐层向输入方向“解冻”网络的参数。

这种方式最吸引人的地方在于稳定性。一开始,模型几乎保持预训练原样,只是把分类头调教好,相当于先让模型“学会识别新菜名”;然后逐步放开更深层的参数,让高层特征慢慢适应新任务;最后才让浅层参与微调。在整个过程中,模型始终有“预训练得到的良好感知”作为锚点,不容易出现灾难性遗忘,也天然地比一上来就全量微调更抗过拟合。

渐进式微调的第 $k$ 个阶段可以这样描述——哪些层被解锁是决定性的:

渐进式微调在实践中还有一个天然优势——省心。你可以给每个阶段设定独立的学习率,比如先期用稍大的学习率快速训好分类头,后期解冻浅层时用很小的学习率避免破坏通用特征。它尤其适合那些“数据量不大不小”的尴尬场景:直接全量微调怕过拟合,只做部分微调又怕不够适配。

作为代价,渐进式微调需要比前两种策略多几倍的时间——毕竟要分多个阶段训练。但如果你的任务数据稀缺、任务又与预训练差异较大,这笔时间投资通常是值得的。正如大厨分阶段学新菜,每一步都稳扎稳打,最后往往比“猛火乱炒”更入味。

Stagek:∇θiL=0, i≤n−k;∇θiL≠0, i>n−k\text{Stage}_k: \quad \nabla_{\theta_i} \mathcal{L} = 0, \ i \le n-k; \qquad \nabla_{\theta_i} \mathcal{L} \neq 0, \ i > n-k
  1. 阶段下标$\text{Stage}_k$ 表示第 $k$ 个微调阶段,$n$ 是模型总层数。
  2. 冻结条件$\nabla_{\theta_i} \mathcal{L} = 0, \ i \le n-k$ 表示编号靠前的低层梯度为 0,参数不更新。
  3. 解冻条件$\nabla_{\theta_i} \mathcal{L} \neq 0, \ i > n-k$ 表示编号靠后的高层正常计算梯度并更新。
  4. 逐级推进随着 $k$ 从 1 增大到 $n$,被解冻的层越来越多,直到全部参与训练。

学习率技巧

无论选哪种微调策略,你都得面对一个比策略更精细的问题:每层用多大的学习率? 用厨师的话说,就是“火候怎么掌握”。火太大,菜一下就焦了(模型震荡甚至发散);火太小,菜永远炒不熟(收敛太慢)。微调中的学习率设置,比从零训练更讲究——因为你面对的是一个已经有“手感”的模型。

最经典的做法是分层学习率(Layer-wise Learning Rate):离输入越近的层,学习率设得越小;离输出越近的层(尤其是新换的分类头),学习率可以设得大一些。道理很简单:浅层保存的是宝贵的通用特征,改动幅度一大就“伤筋动骨”;分类头是全新的、从零训练的,用较大的学习率才能快速上道。在 PyTorch 里,这就是给优化器传多个参数组(param groups)。

当你的批大小(batch size)发生变化时,还有一个实用的线性缩放规则可以帮忙校准学习率:

此外,余弦退火(Cosine Annealing)是微调中非常受欢迎的学习率调度器:训练初期学习率较大,快速探索;训练后期学习率按照余弦曲线平滑下降到接近 0,精细收敛。它像做菜时先大火煮沸、再小火慢炖、最后关火焖一会儿——每一阶段都有最合适的火候。

关于学习率还有一条黄金经验:换新分类头时,对它使用单独的学习率,通常是特征层基础学习率的 5~10 倍。 因为分类头是“白纸一张”,理应获得更激进的火力。接下来我们还要看一个问题:万一火候掌握不好,模型“背菜谱背傻了”怎么办?这就是防过拟合的战场。

ηnew=ηbase×batchnewbatchbase\eta_{\text{new}} = \eta_{\text{base}} \times \frac{\text{batch}_{\text{new}}}{\text{batch}_{\text{base}}}
  1. 基准学习率$\eta_{\text{base}}$ 是先前实验中效果良好的学习率基准值。
  2. 批量比值$\frac{\text{batch}_{\text{new}}}{\text{batch}_{\text{base}}}$ 是新批量大小与基准批量大小的比值。
  3. 线性放大$\eta_{\text{new}} = \dots \times \dots$ 表示批量翻倍时学习率也翻倍,保持梯度噪声水平相近。
  4. 实践直觉更大的批次意味着梯度估计更准、方向更稳,因此可以放心迈更大的步子。

防过拟合技巧

大厨如果拿到一份菜谱,就照着每一句话死记硬背、练习一万遍,最后他能完美复刻菜谱,但换一个厨房、换一批食材就不会做了——这就是过拟合。微调阶段数据量通常远小于预训练阶段,模型很容易在训练集上“背答案”,却在验证集上表现糟糕。所以,微调不仅是“学”,还得学会“怎么防死记硬背”。

第一件利器是数据增强——在训练时对图片做随机裁剪、翻转、旋转、颜色扰动等操作,相当于给大厨准备了几十种“变形食材”,逼他掌握做菜的本质而不是死背某个食材的大小形状。第二件利器是 Dropout:训练时随机让一部分神经元失活,强迫模型不过度依赖某几个“招牌动作”,让特征提取路径更冗余、更稳健。第三件是早停(Early Stopping)——监控验证集指标,一旦验证损失开始上升就停止训练,就像试菜时厨师长说“味道刚好,别再继续调味了”。

还可以给损失函数加上权重正则化(L2 正则化),让模型参数不要长得太大、太极端:

在实际微调中,冻结浅层本身也是一种正则化——参数少了一个大块,可拟合的复杂度自然降低,这正是部分微调稳的一个原因。渐进式微调同样如此,它通过逐步放开自由度来控制模型的“记忆上限”。

防过拟合就像一个“做菜纪律”:数据增强是换着花样练食材,Dropout 是不许偷懒靠单一动作,早停是见好就收,正则化是收敛脾气不狂野。把这些纪律贯彻到微调流程里,模型才能在真实世界经得起考验。

Ltotal=LCE+λ∥θ∥22\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{CE}} + \lambda \|\theta\|_2^2
  1. 原始损失$\mathcal{L}_{\text{CE}}$ 是分类任务的交叉熵损失,衡量预测与真实标签的差距。
  2. 正则惩罚$\|\theta\|_2^2$ 是所有参数的平方和,参数越大、惩罚越重。
  3. 权重系数$\lambda$ 控制正则化的强度,相当于“约束的大手”有多紧。
  4. 综合目标$\mathcal{L}_{\text{total}}$ 把“分类正确”和“参数别太夸张”两个目标合在一起。

实战选择指南

学了这么多策略,真到了项目里,到底该选哪种?这就像大厨面对一道新菜要做的最后决策:食材有多少、菜系差多远、后厨炉灶给不给力。我们可以把这些因素翻译成一条简单的决策链。

第一个问题是数据量。最常用的经验判断是看“样本数 ÷ 模型参数量”这个比值。数据极少时,连深层都别动,只训练分类头即可;数据中等时,适合部分微调或渐进式微调;数据充足时,才放心全量微调:

第二个问题是任务相似度。你的新任务和预训练任务差别越小,需要动的地方就越少。比如在 ImageNet 上预训练的模型,去做另一个自然图像分类任务时,只需要微调分类头和最后几层;但如果要去识别医学 X 光片,视觉风格差异很大,则可能需要渐进式微调让更多层适应新的图像分布。

第三个问题是资源与工期。全量微调需要最大显存和最长训练时间,部分微调最省,渐进式微调时间最长但分段可控。项目排期只有一天?那就老老实实冻结特征层、只训分类头。最后还有模型规模的选择:同样数据量下,更大的模型更容易过拟合,可以考虑用更小的模型或者更强的正则化。

这节就是我们微调策略的“总决策大脑”。前面四节讲的都是“怎么调”,这一节回答的是“怎么选”。记住口诀:数据少→冻得多,任务远→解冻多,资源紧→少全量。 有了这套心法,你就能像老厨师一样,看一眼食材和菜单,心里就有了完整的烹饪方案。

策略={冻结 + 分类头训练,nd/np<0.1部分微调,0.1≤nd/np<1全量微调,nd/np≥1\text{策略} = \begin{cases} \text{冻结 + 分类头训练}, & n_d / n_p < 0.1 \\ \text{部分微调}, & 0.1 \le n_d / n_p < 1 \\ \text{全量微调}, & n_d / n_p \ge 1 \end{cases}
  1. 数据参数量$n_d$ 是目标任务可用的标注样本数量,$n_p$ 是模型参数总数量。
  2. 比例含义$n_d / n_p$ 反映“每个参数能分到多少样本”,比值越小越容易过拟合。
  3. 第一分支比值小于 0.1 时,数据严重不足,只训练分类头,把模型当“特征提取器”用。
  4. 第二分支比值在 0.1 到 1 之间,数据中等,可以解冻高层做部分微调。
  5. 第三分支比值大于 1,数据足够,全量微调能发挥模型最大潜力。

PyTorch实践

理论聊完了,把大厨请进厨房实操。在 PyTorch 中实现微调,核心就三件事:加载预训练模型、冻结不想训练的层、给不同层配置不同的优化器参数。PyTorch 提供了很好的工具,让这三件事都像用对了一把好厨刀那么简单。

第一步,用 torchvision.models 加载预训练权重。比如 resnet18(pretrained=True) 会下载在 ImageNet 上训练好的权重,此时模型已经是一位“见多识广的大厨”。第二步,通过设置 requires_grad = False 来冻结参数——被冻结的层在反向传播中不会计算梯度,就像你告许大厨“刀工部分别改了”。第三步,构造优化器时传入多个参数组,每组可以有不同的学习率。

关键代码模式如下(PyTorch 风格的参数冻结与分组):

这段代码有两个细节值得注意。第一,替换 model.fc(分类头)后,新分类头的参数默认是 requires_grad=True 的,因此会自动参与训练;而特征层因为前面统一设置了 requires_grad=False,在优化器里即使传给优化器,也不会更新权重。第二,优化器参数组允许分别指定学习率,这正是我们前面讲的分层学习率的标准落地方案。

此外,渐进式微调在代码上只是把“冻结/解冻”的动作拆成多个阶段执行:先冻结所有层只训练分类头,跑完几个 epoch 后,解冻最后几个残差块继续训练,再解冻更多层……每个阶段开始时重新构造优化器或调整学习率即可。常见的坑包括:忘了把模型切回 train() 模式(导致 BN 层统计量不更新)、解冻后忘记把新解冻的层也加入优化器(它们不会自动获得梯度更新)。

到这里,我们已经从“选择策略”走到了“代码落地”。厨艺之道,终究要在灶台上见真章。现在,让我们看看这套“大厨学新菜”的方法在真实 AI 项目里都做了什么菜。

python10 行
# 冻结所有特征层,只保留分类头可训练for param in model.parameters():    param.requires_grad = Falsemodel.fc = nn.Linear(512, num_classes)  # 换一个新的分类头 # 优化器分两个组:特征层小学习率,分类头大学习率optimizer = torch.optim.SGD([    {"params": model.features.parameters(), "lr": 1e-4},    {"params": model.fc.parameters(), "lr": 1e-3},], momentum=0.9)

AI 应用场景

医疗影像分类——小数据下的稳健微调

阶段:模型适配与训练优化

解决的问题:医学影像标注成本极高,一个医院的胸部 X 光片数据集可能只有几千张,直接训练一个深度卷积网络几乎必然过拟合。而 ImageNet 预训练模型学的是自然图像的通用特征,与 X 光片差异很大——这正是渐进式微调的用武之地:先训练分类头,再逐步解冻高层,让模型从“普通视觉”平稳过渡到“医学视觉”,既保住了底层特征提取能力,又避免了过拟合。

具体案例:某三甲医院与 AI 公司合作开发肺炎筛查系统,收集了 5000 张标注好的胸部 X 光片。团队采用 ResNet-50 预训练模型,先用第一阶段(只训分类头)跑 5 个 epoch,然后解冻最后两层跑 10 个 epoch,全程配合数据增强,最终在独立测试集上达到 92% 的准确率,比直接全量微调高出 7 个百分点。

工业产品质检——算力受限下的快速迭代

阶段:模型部署前的资源优化

解决的问题:工厂产线上的部署终端往往只有一块低端 GPU 甚至只有 CPU 推理设备,训练环节的显存预算也很有限。部分微调只需要训练高层和分类头,冻结的浅层不占梯度显存,训练速度明显提升,模型体积也可以通过结构化剪枝进一步压缩。

具体案例:一家手机代工厂需要对流水线上的手机外壳做划痕分类(良品/轻微划痕/严重划痕/异物),每个品类仅收集到 800 张图片。算法团队采用 EfficientNet-B0 预训练模型,冻结全部特征层、只训练新加的分类头,在单张 RTX 3060 上用不到半小时就完成了训练,部署后检测精度达到 96%,且误检率远低于人工抽检。

卫星遥感地物分类——多阶段训练应对分布漂移

阶段:领域适配与迁移学习

解决的问题:卫星遥感影像(航拍视角、多光谱通道)与 ImageNet 的自然图片分布差异巨大,直接微调容易把预训练模型的“自然场景先验”错误迁移到遥感任务上。渐进式微调通过多次分阶段解冻,让模型一个阶段一个阶段地适应遥感图像的独特分布,同时持续用验证集监控每阶段的过拟合风险。

具体案例:某测绘公司要基于高分辨率卫星影像分类“建筑物/水体/道路/农田/林地”五类地物,训练集有 1.2 万张影像。团队以 ResNet-34 为 backbone,设计了三个阶段的渐进式微调方案,每阶段结束评估一次验证集准确率,若下降则回退到上一阶段并降低学习率重试,最终 mIoU 达到 0.81,明显优于一次性全量微调的 0.74。

Python 演示

python116 行
"""图像分类微调策略演示 (PyTorch 实现) 用随机数据模拟三种微调策略:1. 全量微调: 所有层参与训练2. 部分微调: 冻结低层, 只训练高层与分类头3. 渐进式微调: 分阶段从分类头到浅层逐级解冻"""import torchimport torch.nn as nnfrom torch.utils.data import DataLoader, TensorDataset torch.manual_seed(42)  class SimpleCNN(nn.Module):    """两层卷积 + 全局池化 + 全连接分类头"""     def __init__(self, num_classes=4):        super().__init__()        self.features = nn.Sequential(            nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(),   # 浅层            nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(),  # 深层            nn.AdaptiveAvgPool2d(1),        )        self.head = nn.Linear(32, num_classes)            # 分类头     def forward(self, x):        h = self.features(x).flatten(1)        return self.head(h)  def make_loader(num_samples=64, batch_size=16):    x = torch.randn(num_samples, 3, 32, 32)    y = torch.randint(0, 4, (num_samples,))    return DataLoader(TensorDataset(x, y), batch_size=batch_size)  def freeze_all(model):    """冻结全部特征层, 仅分类头可训练"""    for p in model.parameters():        p.requires_grad_(False)    model.head.weight.requires_grad_(True)    model.head.bias.requires_grad_(True)  def partial_freeze(model):    """部分微调: 冻结第一个卷积层, 其余可训练"""    for i, layer in enumerate(model.features):        if isinstance(layer, nn.Conv2d) and i == 0:            for p in layer.parameters():                p.requires_grad_(False)  def gradual_unfreeze(model, stage):    """渐进式微调: 按阶段解冻    stage 0: 只训分类头    stage 1: 解冻深层卷积    stage 2: 解冻全部    """    for p in model.parameters():        p.requires_grad_(False)    model.head.weight.requires_grad_(True)    model.head.bias.requires_grad_(True)    if stage >= 1:        for i, layer in enumerate(model.features):            if isinstance(layer, nn.Conv2d) and i == 1:                for p in layer.parameters():                    p.requires_grad_(True)    if stage >= 2:        for layer in model.features:            for p in layer.parameters():                p.requires_grad_(True)  def train(model, loader, epochs=2, lr=0.01):    opt = torch.optim.SGD(model.parameters(), lr=lr)    loss_fn = nn.CrossEntropyLoss()    model.train()    for epoch in range(epochs):        total = 0.0        for xb, yb in loader:            opt.zero_grad()            out = model(xb)            loss = loss_fn(out, yb)            loss.backward()            opt.step()            total += loss.item()        print(f"epoch {epoch + 1}: loss={total / len(loader):.4f}")  def count_trainable(model):    return sum(p.numel() for p in model.parameters() if p.requires_grad)  if __name__ == "__main__":    loader = make_loader()     print("=== 策略1: 全量微调 ===")    m1 = SimpleCNN()    print(f"可训练参数量: {count_trainable(m1)}")    train(m1, loader)     print("=== 策略2: 部分微调 ===")    m2 = SimpleCNN()    partial_freeze(m2)    print(f"可训练参数量: {count_trainable(m2)}")    train(m2, loader)     print("=== 策略3: 渐进式微调 ===")    m3 = SimpleCNN()    gradual_unfreeze(m3, 0)    train(m3, loader, lr=0.01)    gradual_unfreeze(m3, 1)    train(m3, loader, lr=0.005)    gradual_unfreeze(m3, 2)    train(m3, loader, lr=0.001)
目的:展示三种微调策略下“可训练参数量”的差异,以及冻结/解冻参数的标准操作。
  1. model.head.weight.requires_grad_(True)把分类头权重标记为可训练,它是“新学的菜”,必须打开火力。
  2. if isinstance(layer, nn.Conv2d) and i == 0:只锁定第一个卷积层,相当于保留大厨的底层刀工。
  3. for p in layer.parameters(): p.requires_grad_(False)让该层所有参数不参与梯度计算,被冻结的层不再更新。
  4. stage 0/1/2渐进式微调把训练拆成三段,每段解冻一部分层并降低学习率,稳扎稳打。
  5. opt = torch.optim.SGD(model.parameters(), lr=lr)优化器只接收 requires_grad=True 的参数,冻结层自然被排除。
  6. count_trainable(model)统计可训练参数量,让你直观看到各策略的计算开销差异。

常见误区

误区微调不就是在预训练模型上继续训练吗?那我把学习率设成和正常训练一样,多跑几个 epoch 不就行了?

正解微调与正常训练的差异远不止“换个起点”。预训练模型已经有很好的特征提取能力,如果仍用较大的学习率从头跑,很容易一步跨出“好参数区域”,破坏已经学好的特征,导致损失反而上升。微调通常需要更小的学习率、更少的 epoch,并且常常配合“冻结浅层”“分层学习率”等保护性操作。就像让大厨学新菜时,你不能让他用颠锅的蛮力去调酱汁——微调讲究的是精细控制。

为何常见:因为从形式上看,微调和正常训练都是“前向传播 + 反向传播 + 参数更新”,很多人只看到了流程相似,忽略了初始权重状态带来的本质差异。

误区既然怕破坏预训练特征,那把学习率设到 1e-7 级,总该万无一失了吧?

正解学习率过小,模型几乎无法从新任务数据中学到任何有效信息,训练几十个 epoch 后损失都降不下去。更合理的做法是分层设置:分类头(从零训练)用较大的学习率快速学习,特征层(已有良好初始化)用小学习率精细调整。学习率不是“越小越安全”,而是“该大的大、该小的小”。

为何常见:过度吸收了“大学习率会破坏预训练特征”的警示,从一个极端走向另一个极端,忽略了新分类头需要独立的大学习率。

误区全量微调让所有层都参与更新,自由度最高,所以效果应该比部分微调更好吧?

正解当数据充足时,全量微调确实上限更高;但数据不足时,全量微调反而更容易过拟合和灾难性遗忘。解冻的层越多,需要拟合的自由参数就越多,而微调阶段的数据量通常远小于预训练阶段。判断标准不是“解冻多少层”,而是“数据能支撑多少层解冻”——就像新食材只够练摆盘,就别硬去改大厨的刀法。

为何常见:人们容易把“模型能力”等同于“可训练参数量”,忽略了数据量这个约束条件。

误区我在模型里加了 Dropout 层,验证集上的表现也还行,所以不用担心过拟合了吧?

正解Dropout 只是防过拟合的众多手段之一,而且主要作用于全连接层;卷积层的过拟合更多要靠数据增强和正则化来控制。更重要的是,微调中“冻结层数”“学习率”“epoch 数量”这几个因素对过拟合的影响通常比 Dropout 大得多。防过拟合是一门组合拳,单靠一个 Dropout 远远不够。

为何常见:Dropout 是深度学习课程中最先学到的正则化方法,容易让人形成“用了 Dropout 就安全”的思维定式。

误区预训练模型那么强大,不管什么任务,微调一定比从零训练效果好。

正解当新任务与预训练任务差异极大(比如红外影像、特殊医学信号),且新数据充足时,从零训练也有可能超过微调——因为预训练带来的“先验”不仅可能无用,还可能干扰新任务的学习。此外,如果数据分布严重偏移,微调时需要花更多力气“纠正”旧特征,反而不如白纸一张好教。选择微调还是从零训练,取决于任务相似度和数据量两个维度。

为何常见:迁移学习在多数公开数据集上表现优异,让人忽略了“负迁移”(预训练知识反而拖后腿)的存在。

面试题

Q1. 下列关于微调(Fine-tuning)的说法,正确的是哪一项?

单选题 · easy

  • A.从随机初始化权重开始训练模型
  • B.以预训练权重为起点继续训练模型← 正确答案
  • C.完全冻结模型所有权重只做推理
  • D.只修改模型的输入数据分布

答案:B。微调的核心特征是以预训练权重作为优化起点,让模型在已有特征基础上适配新任务。选项 A 是从零训练,C 是推理(完全不训练),D 只是数据层面的处理,均不符合微调定义。

Q2. 全量微调最需要警惕的风险是什么?

单选题 · easy

  • A.模型参数量太小
  • B.训练速度太快
  • C.灾难性遗忘← 正确答案
  • D.数据增强过度

答案:C。全量微调让所有层参与更新,当新任务数据较少时,模型可能在适配新数据的过程中破坏原先学到的通用特征,即“灾难性遗忘”。A 与全量微调无关,B 与事实相反,D 是数据层面的问题。

Q3. 下列哪些做法属于部分微调的典型操作?

多选题 · mid

  • A.冻结浅层卷积层参数← 正确答案
  • B.只训练深层特征与分类头← 正确答案
  • C.将所有层的学习率设为相同且极小
  • D.替换分类头并单独训练← 正确答案

答案:ABD。部分微调的核心是“冻结浅层、训练深层”,并且通常要替换分类头来匹配新任务的类别数。C 把所有层统一设置极小学习率既不符合部分微调的分层思想,也不利于分类头的快速学习。

Q4. 渐进式微调的“渐进”主要体现在哪个方面?

单选题 · mid

  • A.数据集按批次逐步增大
  • B.解冻范围从分类头向浅层逐步扩大← 正确答案
  • C.模型参数量逐步增加
  • D.损失函数逐步增加权重

答案:B。渐进式微调分为多个阶段:先训练分类头,再逐层向输入方向解冻网络参数。它改变的是可训练层的范围,而不是数据量、模型结构或损失函数,因此 B 正确。

Q5. 将基准批量大小从 64 增大到 256,按线性缩放规则,学习率应如何处理?

单选题 · mid

  • A.减半
  • B.保持不变
  • C.翻倍
  • D.扩大为原来的 4 倍← 正确答案

答案:D。线性缩放规则给出了 学习率新值 = 基准学习率 ×(新批量/基准批量) 的比例关系。批量从 64 变为 256,即扩大了 4 倍,学习率也相应扩大为原来的 4 倍(D)。A、B、C 均不符合该比例。

Q6. 下列哪些方式可以有效缓解微调中的过拟合?

多选题 · mid

  • A.对训练图像做随机裁剪和翻转← 正确答案
  • B.在全连接层后加入 Dropout← 正确答案
  • C.监控验证集指标并提前停止训练← 正确答案
  • D.将验证集样本也加入训练集

答案:ABC。数据增强(A)、Dropout(B)、早停(C)都是公认的防过拟合手段。D 把验证集塞进训练集会泄露测试信息,导致评估结果失真,属于错误做法。

Q7. 当目标任务数据量极少(样本数/参数量远小于 0.1)时,最稳妥的微调策略是什么?

单选题 · hard

  • A.全量微调所有层
  • B.冻结特征层,只训练分类头← 正确答案
  • C.从零开始训练一个更大的模型
  • D.不做任何训练,直接使用预训练模型输出

答案:B。数据极度稀缺时,全量微调必然过拟合,从零训练更不可行。把预训练模型当作特征提取器、只训练分类头,是风险最低的“冻结+分类头”策略,因此 B 正确。D 不训练模型无法适配新类别,实际不可用。

Q8. 在 PyTorch 中冻结某层参数的标准操作是什么?

单选题 · easy

  • A.将该层从模型中删除
  • B.设置该层参数的 requires_grad 为 False← 正确答案
  • C.将该层的学习率设为 0
  • D.设置 model.eval() 模式

答案:B。PyTorch 中通过 param.requires_grad = False 让参数不参与梯度计算,从而在反向传播中保持不变。A 是删除层,C 只是优化器层面控制,D 只影响 BN/Dropout 的行为,都不等于冻结参数。

Q9. 下列关于微调策略选择的说法,哪些是正确的?

多选题 · hard

  • A.数据量充足时全量微调通常上限更高← 正确答案
  • B.任务与预训练差异越大,需要解冻的层越多← 正确答案
  • C.模型规模越大越不容易过拟合
  • D.资源受限时部分微调更节省显存← 正确答案

答案:ABD。数据多时全量微调能发挥最大潜力(A 对);任务差异大说明预训练特征不适用,需要让更多层适配(B 对);模型规模越大、可拟合自由度越高,数据不足时反而更容易过拟合(C 错);冻结层不存梯度,显著节省显存(D 对)。

Q10. 关于分类头(head)在微调中的学习率设置,经验上推荐的做法是?

单选题 · hard

  • A.比特征层学习率更小
  • B.与特征层学习率完全一致
  • C.比特征层学习率大 5~10 倍← 正确答案
  • D.直接设为 0

答案:C。分类头是从零初始化的“新参数”,没有预训练提供的良好起点,需要用较大的学习率快速收敛;而特征层已有较好初始化,应该用小学习率精细调整。经验上分类头学习率取特征层的 5~10 倍(C),A、B、D 都会减缓分类头的学习或使其完全无法学习。