本节摘要:当你的数据不够时,让一个"在大数据上学过"的模型来搭把手。本节讲解迁移学习的原理与套路——特征提取式
冻结与微调式解冻,以及"冻结多少、学率怎么定"在实践里的取舍。
阅读完本节,你应当能够:
一路摸到第七章,你多半会撞上那个现实痛点:手上的数据太少,从头训一个深度模型不是塌在欠拟合就是过拟合。可没人规定一定要从零开始。迁移学习的前提很朴素:某个已经在一个超大通用数据集上训练好的模型,它的第一层学到的往往是很泛的特征(边缘、纹理、基本形状),而你新任务往往能直接借用这些特征,只要适配一下后面的层面即可。
一个最常用的例子就是计算机视觉里的 ImageNet 预训练模型。ImageNet 在几百万张图上训出了一个"通用视觉底座",它能识别出图像里的边缘、角点、物体部件。你想做的是"分辨红隼和猎隼",这俩的差别比 ImageNet 里随便两个类别细得多,你自己凑不出几千张标签图。这时候把预训练模型拿过来,前面那些"会看边缘和形状"的层原封不动地借用,只在你新任务上重训最后的分类层——数据量再小也有机会站住脚。图像之外的文本、语音任务同理,都存在大量开箱即用的预训练底座。
实践中常混着用:比如先冻结主干、只训分类头,再视数据量解冻最后几层做微调。原则:预训练数据与你的任务越像,越可以放心多解冻;越不像,越倾向少解冻甚至只提特征。
微调学习率通常要比从头训更小——因为权重已经是"很好的点",步子大了反而把人踢出好状态。冻结与解冻并不是二选一:你可以只解冻尾部两层,同意前面继续当通用特征。数据量很小时,甚至是"只训分类头"胜于"整体微调"。而如果两个任务语义上差别极大(比如图像分类转去预测心率波形),迁移收益会显著下降,此时该怀疑的正是"前提是否成立"。
# 示意:冻结特征层 + 微调尾层的概念性写法 import torch.nn as nn pretrained = load_pretrained() for p in pretrained.parameters(): p.requires_grad = False # 全冻结 for p in pretrained.backbone[-2:].parameters(): p.requires_grad = True # 只解冻最后两层
解冻范围给一个经验上的递进:数据越少、任务离预训练分布越偏,越该保守(只动分类头);数据逐渐变多、任务足够近似,才逐步往深处解冻。许多人犯的错是一上来就"全量微调",结果小数据集把预训练学到的通用特征也一起带歪了,分类没做成,底子倒是先毁了。
一条常被问到的经验是"到底该解冻到第几层"。其实不必靠数层数,靠看现象更靠谱:你先只解冻分类头训起来,若验证在很快地涨、且涨着涨着就顶住不再动,说明底层特征够用、差的只是顶层,那保持"只训头"就好;若只训头明显不够、验证顶在偏低的位置,再逐步往下解冻一两个块、每步看验证是否继续改善,直到改善变平。一句话:先用便宜的分类头浅微调,把验证的"基准线"立起来,再考虑要不要往深解冻——每一步都拿"解冻后 vs 解冻前"的验证差来说话。这样你的"解冻到几层"就从"拍脑袋的直觉"变成了"被数据证实的决策"。这个过程本身也是一次小型实验:冻结住当下最优、锁定其余、一次只往下解冻一层,看验证是否还有净收益。它恰恰是本册"单因子、用验证信号说话"这一整套纪律在迁移学习里的日常化应用。
还需要留意一个容易误判的细节:"低层通用"并不等于对任何任务都无脑通用。不同任务在低层需要的特征也可能不同——比如你借一个文本模型去做代码分类,它的分词与语法底层可能并不完全契合。所以"低层可借用"是一个要先验证、再相信的假设,而不是天然成立的事实。迁移是否成功的判据,始终是"迁移后的验证分 vs 你从零训或用一个更浅模型训的基线",而不是"它接了个预训练底座值得夸"。带着这个对比去用迁移,你才不会被"预训练必胜"的错觉带走。
不是所有任务都吃迁移的甜头。判据看三点:①你任务的数据分布是否和预训练语料/图像集接近;②你的数据量是不是真的少到"不借不行";③算力预算能否容纳微调。若三者都倾向反方向,迁移对你就是负优化——不如回到第三章选个浅层模型先把基线立住。也就是说,迁移是"数据不足时的搭把手",而不是"每到新任务都该例行的惯性动作"。
⚠️ 常见坑:把预训练整库或各类别高度相关的测试样本混进微调,等于又开了一次泄漏的口子。预训练/微调/测试的边界要遵守前文切分的纪律。
💡 直觉:迁移学习常被类比为"借别人的起跑优势"——你不需要从学会认出线条开始,你从"已经会认纹理"开始,只补一点新任务的差别。省下的是大量从头学习的成本。
模型变大了想省推理,迁移之外还有一条"压缩知识"的路——知识蒸馏。