迁移学习这个名词近几年被反复提起,但我真正下决心整理相关资料,是因为手头一个视觉分类项目卡了壳:模拟数据上训练得很好的模型,换到真实业务数据上准确率直接掉了二十多个点。那段时间我把笔记翻得乱七八糟,四处攒的教程、代码片段、理论框架全都混在一起,越整理越觉得线索琐碎,所以到今天也不敢说“完全整理好了”。但核心思路、常用流程和几个真正影响成败的细节,我已经摸得比较清楚,这篇文章就当作一份阶段性整理记录,把我已经理清的部分摊开讲,也给同样在整理东西的人一条相对省力的路线。
我聊的内容会从“为什么需要迁移”讲起,然后把几种常见迁移路线按场景拆开,再重点讲“预训练模型+微调”这条主流程的实操细节。后面会专门说负迁移和灾难性遗忘,这两块是我至今还在持续整理的部分,最后放一份踩坑记录和排查清单。不管你是刚接触概念,还是已经在项目里被迁移效果不好折磨过,这篇应该都能对得上号。
1. 先搞清楚一件事:为什么要迁移,迁移的到底是什么
1.1 从头训练的昂贵代价
很多人一提深度学习,第一反应就是“准备大量数据、搭一个网络、从零开始训练”。这个思路在小规模试验里没问题,一旦放到真实业务里就处处掣肘。模型容量稍大,收敛就需要几百万甚至上千万张标注样本;算力账单也随之上涨,一次完整训练动辄几天不是新鲜事。更尴尬的是,很多垂直场景根本没有那么多标注数据,比如医疗影像、工业缺陷检测,样本能凑齐几千张已经算富裕了,这点量从头训练一个深层网络,大概率只能得到过拟合严重的模型。
我见过最典型的例子:某个项目拿几万张正常样本加几百张异常样本训练异常检测模型,如果从随机初始化开始训,后台日志里loss确实在降,但验证集上的召回率始终上不去。后来换成预训练主干做迁移,只花了一天把分类头替换掉并做微调,指标立刻变得可用。这说明一个问题:在很多实际任务里,我们缺的不是模型结构,而是让模型“学会通用特征”的时间和数据。
1.2 迁移的本质是“借知识”
迁移学习的本质并不神秘:模型在源任务上学到的知识,通过某种方式转移到目标任务中。拿人打比方,一个人会弹吉他,再学尤克里里会非常快,因为指法、乐理、节奏感这些底层技能是可以共用的;会骑自行车,学骑摩托车也快,因为平衡感和操控逻辑有重叠。
放到模型上,预训练模型在源数据集上已经学到的大量通用低中阶特征,比如边缘、纹理、形状、局部结构,这些特征在绝大多数视觉任务里都有用。我们真正需要改变的,往往是那些和任务强相关的高层特征,比如分类头里的类别决策边界。典型做法是“迁移模型参数,替换上层结构,再做针对性微调”,这也是目前主流框架里最常走的路线。
这里要特别说明,迁移不是万能的,它的前提是源任务和目标任务存在可迁移的共性。两个任务差异太大,强行迁移会带来负迁移,这个后面我会单独用一整章来讲。下结论之前,先记住一句话:迁移的其实是“可复用的特征知识”,而不是“照搬整个模型”。
2. 迁移学习的四条路线:按场景选方案
迁移学习不是一个单一算法,而是一族方法的统称。我整理资料时,习惯按照“迁移什么”把方法分成四类:实例迁移、特征迁移、参数迁移、关系迁移。很多教程会用长篇公式解释,但落到项目选择上,更需要你先判断自己手里有什么、缺什么。
2.1 实例迁移:给样本重新加权
实例迁移的核心逻辑是“虽然源域和目标域分布不完全一样,但源域里总有部分样本对目标任务有价值”。实现方式通常是给源域样本分配权重,让与目标分布更接近的样本权重更高,把与目标分布差异大的样本权重压低,再用重新加权后的样本训练模型。
这种方法的适用前提是源域和目标域的数据比较接近,差异主要体现在采样偏置。比如我做一个线上商品的图像分类,网图样本多、实拍样本少,最简单粗暴的思路就是提高实拍样本的权重,再混入一部分网图样本做预训练式补充。实例迁移实现成本低、可解释性强,但效果上限有限,因为样本层面的共性终究不如特征层面的共性抽象。
2.2 特征迁移:把特征空间对齐
特征迁移的思路更进一层:不管源域和目标域的原始数据分布差多少,都想办法把它们映射到一个公共特征空间里,让这个共享空间中的特征分布尽量一致,然后在这个空间上学习目标任务。常见的实现手段包括域适应中的最大均值差异约束、对抗式域判别等。
这类方法比较适合“数据来源不同但任务语义一致”的场景。比如同一个物体在不同相机、不同光照、不同角度下拍摄,像素分布差异很大,但语义特征是相同的。特征迁移要解决的问题就是让模型忽略“相机风格”这类域差异,专注“物体形状”这类任务特征。
不过特征迁移的实现复杂度明显更高,需要额外设计对齐损失,而且调参敏感。我通常只在样本量确实很小、直接微调又容易过拟合的场合考虑。
2.3 参数迁移:共享模型参数和结构
参数迁移是当前实践中最常见的一种路线,核心做法就是“把源任务训练好的模型参数作为初始值,迁移到目标任务上继续训练”。预训练模型微调就是标准参数迁移。它的优势在于落地简单,开源生态里能直接拿到训练好的模型权重,我们只需要改改输入输出层、调整学习率和训练策略。
参数迁移之所以有效,是因为深层特征具有较好的泛化性。需要提醒的是,这种方法的迁移效果受两个因素影响很大:一是源任务和目标任务的数据分布差异,差异越大,可复用程度越低;二是在微调过程中是否保护了已经学好的通用特征,盲目用大学习率训全部参数,很容易把预训练知识冲掉。
2.4 关系迁移:迁移样本之间的关系
关系迁移是最抽象的一类,它迁移的不是特征,而是“事物之间的关系结构”。比如某个域里有师徒、上下级、依赖关系,另一个域里可能也有类似结构,关系迁移就是把这些结构规律带过去。这类方法常见于图数据、社交网络分析和知识图谱场景,在普通图像、文本任务里很少直接用。
我为什么还是要把这四种路线都列出来?因为很多人一谈迁移就默认是“预训练模型微调”,碰壁之后不知道还有其他选择。当你发现参数迁移效果不佳时,至少知道可以回头评估一下数据分布差异,考虑实例层面的加权,或者尝试特征空间对齐。方法没有绝对优劣,只有贴合场景之后的合适与不合适。
3. “预训练+微调”这条主流程:我整理好的执行框架
在几种路线里,参数迁移应该是多数人实际工程里的优先选择。近几年的开源生态特别成熟,大量预训练模型权重可以直接下载,不需要自己烧资源从零训练。我整理笔记时把整条流程抽象成了四个阶段:模型选型、数据准备、冻结与解冻策略、微调配置。每个阶段都有值得展开的细节。
3.1 模型选型的四个维度
选预训练模型不是“越大的模型越好”,更不是“rank靠前的模型就好”,我一般按四个维度逐一过滤:
第一个是任务模态匹配。图像分类任务就选视觉预训练模型,文本分类任务就选语言预训练模型,跨模态任务优先考虑统一多模态模型,不要为了炫技把语言模型硬用在图像输入上。
第二个是输入尺寸和结构匹配。模型要求输入什么尺寸、是否带位置编码、对数据增强的容忍度如何,都要提前看。我第一次试用某个预训练模型时没注意输入尺寸限制,直接塞了大图,结果前向传播的内存直接爆掉。
第三个是规模与效果平衡。大模型在小样本任务上确实更容易欠拟合,因为可学习的参数量远超样本量,模型会把训练集细节背下来,但这并不是真正想要的泛化能力。实际项目里,我会先用中等规模模型跑通基线,再根据性能决定是否升级更大规模。
第四个是数据口径与许可。预训练模型是在什么来源的数据上训练的、对商业使用是否友好,都要核实清楚。我在一个对外项目里就踩过类似的坑,因为数据口径和上游许可问题,最后不得不换模型,白白浪费了两天。
3.2 数据准备:越贴近目标任务越好
数据准备容易被低估。很多人以为预训练模型是万能的,喂进去什么它都能消化,事实不是这样。微调阶段的数据分布会直接影响模型最终学到的决策边界,准备数据需要盯着三个点:
一是类别体系要对齐。分类头要改成目标任务自己的类别数,如果源模型在1000类上训练,我们要做10类分类,最后那个全连接层必须重新初始化。这点很直观,但经常被忽略,不少报错都源于维度不匹配。
二是样本多样性要足够。每个类别至少保证几十到几百张代表性样本,尽量涵盖目标场景里的光照、角度、背景等变化。样本量小而类别多时,可以先用类别平衡采样避免模型被头部类别带偏。
三是数据清洗不能省。微调阶段如果混入大量错标和无关样本,模型很容易被噪声干扰。尤其“域内混杂”问题,比如目标场景是车间流水线,却在数据里混入了户外风景图,这样的样本会让分类头学到错误关联。
3.3 冻结与解冻:从只换分类头到全量微调
这是微调流程里最需要经验的地方,因为冻结策略直接影响训练速度和最终效果。我把常见策略分成三个梯度:
第一个梯度是“只训练分类头”,把预训练主干完全冻结。这种做法适合目标任务数据量特别小的情况,几千张图片也够用,而且训练极快、不容易过拟合。缺点是高层特征可能不完全契合新任务,精度天花板较低。
第二个梯度是“解冻最后几层”,冻结主干前段,只让后半部分参与微调。这个折中方案既能让模型适应新任务细节,又不会把底层通用特征破坏太多。到底解冻多少层,需要根据数据量和任务差异实验确定,没有固定答案。
第三个梯度是“全量微调”,所有参数参与训练。这种方式适合目标任务数据量较大、或者和源任务分布有明显差异的情况。全量微调效果好,但风险也大,如果不控制学习率和训练轮数,预训练模型学到的知识会被冲得面目全非。
我常用的策略是先从第二个梯度开始跑,观察验证集变化,如果依然欠拟合,再逐步往第三梯度靠。操作上对应的是给不同参数组设置不同学习率,主干的学习率通常设为分类头的十分之一到百分之一。
这里放一个简化版的微调流程,方便对照理解:
import torch import torch.nn as nn # 假设 model 是从开源框架加载的预训练模型 model = load_pretrained_model(backbone="resnet_base", num_classes=10) # 对参数分组:主干部分学习率低,分类头学习率高 param_groups = [] for name, param in model.backbone.named_parameters(): param_groups.append({"params": param, "lr": 1e-5}) for name, param in model.head.named_parameters(): param_groups.append({"params": param, "lr": 1e-3}) optimizer = torch.optim.Adam(param_groups) # 训练循环:主干冻结逻辑通过 requires_grad 控制 for name, param in model.backbone.named_parameters(): if not unfreeze_backbone: param.requires_grad = False for epoch in range(epochs): for x, y in dataloader: out = model(x) loss = nn.CrossEntropyLoss()(out, y) optimizer.zero_grad() loss.backward() optimizer.step()这段代码示范的是“主干低学习率、分类头高学习率”的做法,具体层数冻结范围请按照模型结构和你自己的验证集表现去调整。跑通这个流程之后再考虑更复杂的策略。
3.4 微调配置:学习率、轮数与数据增强
微调不是完整的训练过程,学习率策略必须和从头训练区分。从头训练常用大学习率加周期衰减,微调却要求保守。我一般从1e-5到1e-4这个区间起步,用一个小数据集快速试错,观察loss是否稳定下降,再决定放大还是缩小。
训练轮数也不宜多。很多人在微调阶段沿用从头训练的习惯,一跑就是几十个epoch,结果很快在验证集上看到过拟合迹象。微调通常在几个到十几个epoch内就能收敛,甚至更少。训练过程中需要定期保存验证集上表现最好的检查点,而不是最后一轮的结果。
数据增强同样重要,但幅度要拿捏。过强的增强会破坏预训练模型熟悉的数据分布,反而增加训练难度。我的经验是普通尺寸裁剪、轻微翻转和归一化就够,除非目标域本身有特殊变化,否则没必要把增强策略堆得太复杂。
4. “还没整理好”的部分:负迁移与灾难性遗忘
每次整理迁移学习资料,负迁移和灾难性遗忘都是让我最头疼的章节。它们不像训练代码那样有固定模板可抄,更多依赖对问题成因的判断。到现在我也不敢说我完全理清了,但至少已经能从“玄学问题”归因成“可诊断的问题”。
4.1 负迁移:比“没提升”更麻烦
负迁移的定义很直接:在源域学到的知识不仅没帮助目标任务,反而把效果拉低了。我用一个简单实验来说明——同样一个分类任务,从零训练的模型准确率达到70%,用了预训练模型做迁移之后只有65%,这就是负迁移发生的证据。
负迁移常见的诱因有三个。第一个是源任务与目标任务差异过大,比如拿人脸识别模型去做医学图像病灶分类,共享特征太少,前几层的特征统计和目标任务完全不匹配。第二个是源域数据分布和目标域分布存在冲突,比如预训练模型从自然图像学到的是“背景纹理和物体强相关”的统计规律,而目标场景里背景完全不可区分,模型反而被背景特征误导。第三个是微调方式不当,包括学习率过大、训练过久、数据噪声等,导致预训练参数被破坏,最终效果反而不如从头训练。
诊断负迁移最有效的办法是“多一组对照实验”:同一个目标任务,一边从随机初始化开始训练,一边从预训练模型开始微调,两边用相同的训练配置。如果微调组明显更差,几乎可以断定存在负迁移,接下来再逐层检查特征和分布差异。
4.2 灾难性遗忘:微调把预训练知识覆盖掉了
微调过程中还有一个隐蔽问题——灾难性遗忘。模型在适应新任务时,会把预训练阶段学到的知识逐步覆盖或遗忘。表面上看新任务指标在上涨,但如果此时回到源任务的测试集上验证,成绩往往大幅下跌。
灾难性遗忘对“通用性”有直接影响。很多项目不仅要求模型在新任务上好用,还希望它能保留一定通用能力。比如同一个模型既要识别日常物体,又要识别特定工业缺陷,如果只盯着新任务微调,模型可能把所有特征都调整成缺陷识别的形状,最终其他任务完全不能复用。
缓解灾难性遗忘的方法很多,我用得最多的是三种:一是降低微调时的整体学习率,避免大幅修改参数;二是在损失函数中加入对预训练参数的约束项,让新参数不要偏离初始值太远;三是混合部分源域数据进行联合训练,相当于让模型同时记住两个任务的知识。
4.3 我的渐进式微调思路
现在我遇到分布差异较大的任务,更倾向采用“渐进式微调”:先冻结绝大部分主干,只训练分类头和最后两层,等模型在新任务上表现稳定后,再逐层解冻更深层特征,并且每层用较小的学习率继续训练。
这个过程有点像拿锤子敲钉子,不能一开始就抡圆了砸,而是先用轻力把钉子定位,再逐渐加力固定位置。渐进式微调的好处在于每一步都能观察到验证集的变化,如果某个阶段出现指标下滑,可以立刻回退到上一阶段的权重,问题定位也更精确。
配合渐进式微调,我还会定期在“源任务验证集”上做一次快速评估,用来监测灾难性遗忘是否失控。如果源任务指标下降过快,说明微调步子太大,应该回退并减小学习率。
5. 整理资料时踩过的坑:这些细节常规文档不会写
这一部分是我觉得最有分享价值的。迁移学习入门资料很多,但真正让人抓狂的往往是那些看起来很小、却能在关键时刻卡住整个流程的细节问题。我挑几个印象最深的讲。
5.1 批量归一化在冻结层里的隐蔽问题
很多预训练网络包含批量归一化层。开始做微调时,我习惯把早期主干直接冻结,却不小心把批量归一化层也一起冻结了。结果模型在训练集上表现正常,一到验证集上性能就波动很大,甚至出现明显退化。
原因是批量归一化层训练时用的是当前批次的均值和方差,推理时用的则是训练期间累计的统计量。如果冻结了整个主干,同时冻结了这些统计量的更新,而新任务数据分布又和预训练数据不完全一致,模型就会因为统计量不匹配而表现异常。正确处理方式是:主干里可以冻结卷积层参数,但批量归一化层建议保持运行状态,让它根据新任务的batch统计量去适配,至少把统计量更新打开。
5.2 学习率设成“训练模式”会导致特征被冲掉
这是另一个高频问题。我早期做微调时直接沿用从头训练的学习率,例如0.01,结果训练几个epoch后验证集指标反而下降。后来检查权重变化量才发现,主干参数已经被更新到和预训练权重差异非常大的位置,通用特征基本被覆盖。
做微调时,主干参数要设定非常小的学习率,分类头可以用相对大的学习率。拿Adam优化器举例,我通常会给主干1e-5到1e-4,分类头1e-3到1e-4。如果你发现loss下降得异常快或者异常猛,第一反应不是开心,而是应该回去检查一下权重更新幅度。
5.3 数据分布漂移:用特征统计来诊断
如果迁移效果不理想,又不想凭感觉猜测,最简单实用的诊断方法是对比源域数据经过网络中间层后的特征统计。你可以把一部分源域图像和目标域图像分别输入模型,抽出某一层输出的特征,计算它们的均值、方差,甚至直接算两个特征集合之间的最大均值差异。
这个数值不会直接告诉你“怎么改”,但能帮你确认问题到底出在哪个抽象层次。如果底层特征的分布已经很不一样,说明源任务和目标任务的低级视觉规律不太匹配,这时候优先选择更强的数据增强或域适应方法;如果只是高层特征分布有差异,那通过微调分类头和解冻部分高层参数通常就能解决。
6. 迁移效果不理想时的排查清单
最后把我会用到的排查步骤整理成一份清单。如果你在项目里用了迁移学习但效果达不到预期,可以按这个顺序逐一检查,比漫无目的地调参更有效率。
6.1 先确认“是不是迁移的锅”
第一件事是建立一个从零训练的同结构模型作为基线。不要跳过这步,没有基线就无法判断迁移是产生了正效果还是负效果。如果迁移模型性能还不如基线,优先怀疑负迁移,而不是继续调学习率。我见过不少团队在负迁移问题上反复折腾优化器,最后换上更合适的预训练模型才解决。
6.2 检查数据分布匹配度
第二步计算源任务与目标任务在关键层特征上的分布差异。如果差异很大,考虑三条路:增加目标域样本、使用更强的域适应方法、更换与目标域更接近的预训练模型。数据实在太少时,还可以考虑用增强手段扩充目标域样本量。
6.3 逐层检查冻结范围
第三步自上而下逐步解冻。很多人习惯直接全量微调,一旦出现灾难性遗忘就束手无策。更稳妥的做法是先冻结主干,只训练分类头,然后逐层解冻,每解开一段就重新评估验证集。哪个阶段出现指标回退,说明当前解冻层和任务需求的适配还不成熟,就用更小的学习率或者干脆保持冻结。
6.4 训练细节的最后一轮核对
第四步核对几个容易被忽视的细节:批量归一化统计量是否更新、优化器是否对不同参数组设置了不同学习率、数据增强是否过强、训练轮数是否过长。这些细节单独看都不致命,但叠加在一起就可能让迁移效果大打折扣,而且常常表现得很隐蔽。
我把排查要点整理成表格,方便你直接对照参考:
| 检查项 | 问题信号 | 常见处理 |
|---|---|---|
| 基线对比 | 迁移模型不如随机初始化 | 排查负迁移,更换预训练模型 |
| 特征分布差异 | 中间层特征统计差距大 | 增大目标域数据量,引入域适应 |
| 冻结范围 | 全量微调后源任务指标暴跌 | 改为渐进式解冻,控制更新幅度 |
| 批量归一化 | 验证集波动、性能不稳定 | 解冻统计量更新,保持运行状态 |
| 学习率 | loss震荡或权重更新过大 | 主干低学习率、分类头高学习率 |
| 训练轮数 | 验证集过拟合、源任务遗忘 | 减少轮数,加正则约束 |
| 数据质量 | 类别错标、无关样本混杂 | 清洗数据,保证域内一致性 |
这份清单不是一次就能查完的,迁移学习的调试本质上是一个环环相扣的排查过程。大部分时候,问题不会只出在一个环节,而是多个环节共同作用的结果。
整理迁移学习资料这件事,我现在也只能算是“理出了主线,细枝末节还在补”。真正常用的动作其实不复杂:选一个和任务贴近的预训练模型、控制好学习率、分层去微调、时刻盯着过拟合和灾难性遗忘。最难的地方在于每次遇到问题都要冷静判断是哪一层出了状况,而不是直接换个更大的模型或者无脑增加训练轮数。我自己经历过几次负迁移和灾难性遗忘之后,现在养成了一个习惯:每个迁移实验都保存预训练初始权重、每个阶段解冻的检查点,并且写清楚当时的验证集表现。这样一旦效果不理想,就能快速回退对比,而不是对着一条丢失信息的训练曲线空想。希望这篇阶段性整理能让你少走一些我走过的弯路。