☰
预训练权重与迁移学习:为何读、怎么用、坑在哪
2026/10/23 3:26:01 网站建设 项目流程

有段时间经常被问到同一个问题:“训练模型的时候,代码里为什么要先读一份预训练权重?直接随机初始化从头训练,不是更干净吗?”问这个问题的多半是刚接触深度学习不久的同学,也有些做过几个项目的朋友,心里依旧把“加载预训练权重”当成某种不自信的手段。我今天想把这件事彻底讲明白,包括它到底是什么、为什么有效、以及加载时那些让人挠头的坑。

如果你是刚入门,正在纠结“自己训练的模型要不要加载预训练权重”;或者你已经在跑实验,被各种size mismatch、missing key折磨过;又或者你只是好奇迁移学习为什么好使——这篇文章都适合你。

1. 预训练权重到底是什么

1.1 模型 = 网络结构 + 一组“背下来的经验”

先拆一个基本概念。任何一个深度学习模型,本质上由两部分组成:网络结构,以及网络里的权重。

网络结构回答的是“这个函数长什么样”,比如有多少层卷积、每层多少通道、激活函数是什么。它定义了模型的容量和表达能力。而权重回答的是“这个函数具体怎么算”,也就是成千上万个浮点数,它们决定了输入数据经过层层计算之后,最终输出的结果偏向什么。

随机初始化,相当于给这组权重随便填了一批数值,模型是一张白纸。它没有见过任何真实世界的样本,所有的知识都得靠接下来的训练数据一点一点喂出来。

预训练权重,则是另一个模型在某个更大的数据集上训练结束后,保存下来的那批浮点数。它记录的不是某个任务的“标准答案”,而是模型从海量样本里总结出来的规律和特征。你可以把它理解为一个优秀学长的解题笔记:你已经知道怎么识别边缘、纹理、形状、甚至更抽象的模式,接手新任务时不需要把这些基础能力重新摸索一遍。

所以“训练时读取预训练权重”这句话,翻译成人话就是:我不打算从零开始教这个模型认世界,我让它先继承别人已经学好的通用能力,再针对我自己的任务做定向调整。

1.2 别和“断点续训”的checkpoint搞混

这里要马上澄清一个常见混淆点:训练脚本里的“读取权重”,有两种完全不同的用途。

第一种是读取外部预训练权重,目的是迁移学习。比如你用别人在千万级图片上训好的模型参数,初始化你自己的模型,然后在自己小数据集上继续训练。这就是标题里说的“预训练权重”。

第二种是读取自己之前训练中途保存的checkpoint,目的是断点续训。比如训练到第50个epoch服务器崩了,或者你想接着上次的状态继续跑,于是加载上次保存的模型继续训练。这种“读取”跟预训练权重没太大关系,它只是为了恢复训练现场,而不是为了引入外部知识。

很多初学者看到代码里有load_state_dict就开始困惑:老师不是说训练是从随机初始化开始吗?怎么又加载权重?其实只要区分清楚这两类“读取”,迷雾就散了一半。我们今天重点讨论的是第一类:用别人已经训练好的权重,作为自己训练的起点。

2. 为什么预训练权重能起作用:特征的可迁移性

2.1 深度网络低层学到的是通用特征

要回答“为什么有用”,得先搞清楚深度神经网络到底在学什么。

拿图像分类举例。你训练一个卷积网络,它前面几层卷积核学习到的往往是通用度极高的特征:边缘、色块、角点、纹理方向。中间层开始出现更复杂的组合,比如弧线、圆圈、局部纹理。再往后,才会逐步出现跟任务强相关的语义特征,比如人脸轮廓、车轮形状、某种病灶的形态特征。

这意味着什么?意味着网络越靠前的层,越不依赖具体任务。在几千万张自然图片上训练出来的低层特征,放到医疗影像、卫星图像、工业瑕疵检测上,同样有效。因为无论是CT片、航空照片还是钢材表面,都是由边缘、纹理、颜色渐变这些基本元素构成的。这些基础特征不需要在新任务上重新从零学习,它们是可以“继承”的。

NLP领域也是同一套逻辑。像BERT这类在大规模文本上预训练的模型,早期层学习到的是词法、句法、词性等信息,这些语言共有的知识,对绝大多数下游任务都有用。你让它直接去分析金融文本、法律文书或者客服对话,它可以带着“已经会中文”的基础,只需要再学一下行业术语和表达习惯。

所以预训练权重本质上是一笔“通用知识资产”。它把人类积累下来的、从海量数据里凝练出的通用特征,以数值形式固化下来,让你在启动训练的那一瞬间,就已经站在一个很高的起点上。

2.2 迁移学习不是万能,但边界比想象中宽

有人会问:如果我的任务和预训练任务差别很大呢?比如预训练模型是在自然图片上训练的,我拿去做显微镜图像识别,低层特征还有用吗?

答案是有用,但作用会打折扣。显微镜图像虽然和自然图片差异很大,但边缘、纹理、局部形态这些底层视觉特征依然是共通的。真正需要重点调整的是中高层特征,因为它更接近具体任务。这种时候常见的做法是:加载预训练权重,但只让高层参与大幅更新,把低层冻结住或者用小学习率微调。

反过来,如果你的任务底层规律就和预训练数据完全不同,比如某些抽象的数据分布、或者你把图像像素随意打乱后的任务,那预训练权重可能真的帮不上忙,甚至带来负迁移。这种情况较少见,但确实存在。所以“预训练权重有没有用”,最终要落到“你的任务和预训练任务共享多少底层规律”这个问题上。

我个人的经验是:绝大多数实际问题,任务之间共享的底层规律都比想象中多。图像任务共享视觉基础,文本任务共享语言基础,甚至跨模态任务也能找到不少底层结构。因此,优先考虑使用预训练权重,遇到效果不好的情况再分析原因,通常是比较稳妥的策略。

3. 预训练权重给训练带来的具体收益

3.1 收敛速度:站在半山腰,而不是山脚

随机初始化的模型,它所有层的参数都是噪声,前向传播输出几乎没有任何有效信息,反向传播的梯度信号也不稳定。这种情况下,模型前期基本是在漫无目的地探索,损失下降很慢,还容易掉进陡峭的局部极小值。

加载预训练权重之后,情况完全不同。低层特征已经处于“比较好用”的状态,模型在一开始就有一定预测能力,损失也处在一个比较低的位置。你不再需要花大量时间去学最基础的表示,只需要在这个基础上精细化调整。这就像登山,别人已经把你送到半山腰,你只需要沿着路继续爬,而不是从山脚重新开路。

实际体感差距有多大?我做过一次图像分类实验,同样一个小规模数据集,同一个模型结构,从随机初始化开始训,可能需要几十个epoch才能达到可接受的精度;加载预训练权重后,往往五到十个epoch就已经超过它。时间成本直接省下一大截。

这还只是时间上的收益。更关键的是,由于起点更靠近好的解,最终收敛到的局部最优通常也更平滑、泛化更好。训练深度学习模型本质上是在一个高维非凸的损失面上寻找一个足够好的极小值点,好的初始位置对结果的影响,很多时候比后面调学习率、调batch size更重要。

3.2 数据效率与泛化:小样本时的救命稻草

预训练权重最大的价值,其实体现在数据量不够的时候。

深度学习有个朴素规律:数据越多,模型越强。但现实中你手里往往只有几千张图、几千条文本,这点数据要支撑一个深层的网络从零开始学,结果几乎必然是过拟合——训练集上表现很好,测试集上立刻露馅。模型靠死记硬背把训练样本记住了,根本没有学到可泛化的规律。

预训练权重提供了额外的信息源。它让模型在开始训练之前,就已经掌握了一整套通用特征。你只需要用少量数据去调整高层,或者微调一下所有层,就能得到一个泛化能力不错的模型。这变相等于把外部海量数据的信息“压缩”进了初始化参数里,弥补了你自己数据集规模不足的短板。

当然,预训练权重不能完全替代数据。如果你的样本量和任务复杂度相差太远,即使有预训练兜底,也可能过拟合到这个小数据集上。但把它看作一种先验约束:模型被限制在一个合理的参数空间附近,不会跑到奇怪的地方去,泛化能力自然更有保障。

4. 实操:加载预训练权重的标准动作

4.1 主流程:结构对齐、参数导入、分类头替换

加载预训练权重,核心就三步:准备模型结构、导入权重、处理不匹配的层。

以某个主流深度学习框架为例,使用它的模型库可以直接下载预训练权重:

import torch import torchvision.models as models # 默认加载预训练权重 model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2) # 如果不想加载预训练权重,就传 None # model = models.resnet50(weights=None)

这里的weights参数就是决定要不要读预训练权重的地方。很多框架都内置了常用预训练模型,你只需要指定,框架会自动下载并加载。这是最省心的方式,因为代码内部已经把结构对齐、权重匹配都处理好了。

但实际项目中,你通常不会直接用原版模型,而是会改造结构。最常见的就是替换分类头:比如预训练模型是在1000类ImageNet上训练的,你的任务是10分类。模型的最后一个全连接层输出维度就从1000变成了10,这样一来,最后一层的权重和预训练权重就对不上了。

处理办法很简单:先创建模型,替换最后一层,再加载权重时跳过不匹配的层。

import torch import torch.nn as nn import torchvision.models as models # 1. 改成自己的分类数量 num_classes = 10 model = models.resnet50(weights=None) model.fc = nn.Linear(model.fc.in_features, num_classes) # 2. 加载预训练权重,跳过 fc 层(因为在结构上已经不一致) checkpoint = torch.load("resnet50_weights.pt", map_location="cpu") checkpoint.pop("fc.weight", None) checkpoint.pop("fc.bias", None) model.load_state_dict(checkpoint, strict=False)

关键就是strict=False。它的意思是:允许权重不完全匹配,只要名字和形状一致的就加载,不一致的就忽略。分类头在加载之后又会被随机初始化,但实际上我们往往会单独初始化这一层,然后从头训练它。

严格模式strict=True是默认值,会强制要求权重文件里的所有参数名和模型里的完全对应,否则直接报错。对于我们替换了分类头的情况,必须用strict=False。

如果你的框架是类似Keras的接口,做法也很直观:

import tensorflow as tf # 加载预训练模型作为基础,不包含分类头 base_model = tf.keras.applications.EfficientNetB0( include_top=False, # 去掉顶部分类层 weights="imagenet", # 加载ImageNet预训练权重 input_shape=(224, 224, 3) ) # 拼接自己的分类头 x = base_model.output x = tf.keras.layers.GlobalAveragePooling2D()(x) x = tf.keras.layers.Dense(256, activation="relu")(x) output = tf.keras.layers.Dense(num_classes, activation="softmax")(x) model = tf.keras.Model(inputs=base_model.input, outputs=output)

include_top=False是这里的关键参数,它让你把顶部的分类层去掉,只保留特征提取部分,然后自己拼接新的分类头。分类头是新初始化的,而底部的卷积层则带着预训练知识进入新任务。

4.2 微调策略:冻结、解冻、学习率怎么定

权重加载进来之后,接下来要决定的是“哪些层要更新,哪些层不要动”。这就涉及冻结(freeze)和解冻(unfreeze)。

最常见的策略是分阶段微调:

第一阶段,冻结所有预训练层,只训练新加的分类头。原因是新分类头是随机初始化的,梯度很大。如果一开始就让所有层一起更新,强烈的梯度信号会迅速破坏预训练好的低层特征,导致前面几个epoch损失剧烈震荡,最后效果反而很差。先只训练分类头,让它在预训练特征的基础上快速达到一个合理水平。

第二阶段,解冻浅层(也就是靠近输入的层),以小学习率全局微调。这时候分类头已经有一定判别能力,梯度信号比较稳定,再用很小的学习率去微调预训练层的参数,让低层特征向目标数据的方向做细微调整。

在框架里冻结参数,操作上就是停止某些层的梯度更新:

for param in model.parameters(): param.requires_grad = False # 先冻结所有层 # 只让新的分类头更新 for param in model.module.fc.parameters(): param.requires_grad = True # 训练完分类头之后,再解冻全部层做微调 for param in model.parameters(): param.requires_grad = True

学习率怎么选?经验法则是:加载了预训练权重的层,用较小的学习率,比如1e-4、5e-5;新加的随机初始化层,可以用稍大一点的学习率,比如1e-3。原因是已经预训练的层只差一点微调,而随机初始化的层需要从头学习,步子可以大一些。

顺序上不要搞反:先冻结训分类头,再解冻全部层微调。很多人图省事,加载完预训练权重直接就全量微调,图像领域里如果你的数据量和任务差异都适中,也能得到一个不错的结果。但一旦你的分类头随机初始化带来的梯度波动太大,前期训练就会很不稳定。所以项目初期,我建议老老实实按照“先冻结后解冻”来,能少踩很多坑。

5. 加载预训练权重时的坑与排查

5.1 key mismatch和size mismatch的排查

我看到过最多的报错,就是这两种。

size mismatch说的是参数形状对不上。比如你的模型某一层是[1000, 2048],权重文件里对应的是[10, 2048]。这种情况最常见的原因就是你改了分类头。遇到这个问题,直接把对应层从加载清单里剔除,就像上面代码里的pop操作。诊断方法也不难:把报错信息里列出的层名打印出来,跟模型的state_dict做一个交叉对比,你就能清楚看到是哪几层对不上。

missing key的意思是权重文件里缺少模型需要的某些键。如果你是拿一个预训练权重去初始化一个比原来更大的模型,比如在原有网络上新增了一些分支,那新分支的参数就不会出现在权重文件里。解决办法同样是strict=False,然后在初始化后给缺的参数单独做初始化。

另一种情况是权重名称不一致。同一个模型,不同框架、不同版本的命名规则可能不同。比如某些层叫conv1,另一些叫features.conv1。这时候需要手动做一个名字映射,把权重文件里的键名转换成当前模型的键名。这个操作比较繁琐,但只要你把这些键名当成普通的字典key来重命名,其实也没什么技术难度。

经验而言,加载权重前最好先打印一遍两边的键名,确认一下对齐情况。这个动作花不了几分钟,但能省下大量排查时间。

5.2 加载后效果反而更差怎么定位

这是一个很打击人的情况:明明加载了预训练权重,训练完之后精度还不如随机初始化来的高。

首先要明确一个基本判断:加载预训练权重不等于直接得到一个好模型。它只是提供了一个好起点,后面能不能学好,还取决于你的数据质量、训练配置、微调策略。

最常见的罪魁祸首是学习率太大。微调阶段的模型起点已经比较好,梯度更新量如果太大,很容易一步跳出原有优势区域。这时候损失可能先涨后跌,最终落到一个比较差的结果。解决方式很简单:把微调阶段的学习率降下来,降到从零训练学习率的十分之一甚至更低。

第二个常见原因是不合理的全量微调。分类头和预训练层的训练进度差异巨大,一起从头训,容易互相干扰。解决办法就是我上面说的分阶段微调。

第三个原因则是任务本身跟预训练任务差异太大。预训练权重带来的负迁移压过了它带来的好初值,模型在后续训练中很难跳出一个被错误先验影响的位置。这种情况下,你可以尝试少加载一些低层权重,或者干脆不加载,从零训练。

还有一个容易忽略的点:数据分布和预处理方式。预训练模型对输入有固定的归一化方式(比如ImageNet的mean和std),如果测试和训练时预处理不一致,模型的输入分布和预训练阶段严重偏离,效果会大打折扣。务必检查你的数据预处理代码,是否严格复用了预训练模型配套的归一化参数。

5.3 什么时候不要加载预训练权重

虽然预训练权重很香,但它不是万能的,有些情形下真的不建议用。

第一,你的数据量足够大且任务特殊。假如你有上千万张图,而且任务的照片风格和预训练数据差异很大,从零训练可能更自由,模型会自己去发现适合这个数据分布的特征。这时候预训练权重反而可能把模型往“自然图片特征”方向拉,限制了它学习全新分布的能力。

第二,你设计的网络结构并没有对应的预训练权重。自己搭了一个全新的网络结构,别人没有训过,这时只能从零开始。强行用别的结构的权重做部分初始化也不是不行,但技术成本和调试成本都很高,性价比低。

第三,某些特殊模态或者特殊任务。比如图神经网络、强化学习里的策略网络、推荐系统里的深度排序模型,预训练权重的定义和使用方式跟CV/NLP差别很大。这类任务里迁移学习的通用规律没有那么强,更多还是靠任务数据本身来训练结构。即使有初始化的概念,它也不是其他领域那种“大模型预训练小模型微调”的路线。

判断标准其实很简单:你期望模型继承的“底层能力”是否在预训练数据里有充分体现。如果是,就加载;如果不是,或者不确定,可以先做一组小实验对比一下加载和不加载的结果,用数据说话。

6. 场景与选择:几个可以抄作业的判断

6.1 一张速查表

我把自己工作中常用的判断逻辑整理成一张表,不敢说绝对正确,但对大多数常规任务都适用。

场景建议原因
图像分类/检测/分割,数据量几万以下加载预训练权重数据不够,特征复用能大幅提升泛化
图像任务,数据量百万级、分布较特殊先做对比实验再决定数据够了,预训练权重的约束可能变成束缚
NLP文本分类,小语料库加载预训练模型语言基础能力复用,收益极大
自定义全新网络结构从零训练没有现成权重可用,优先保证结构验证
任务域和预训练域差异巨大(像素乱序等)不大建议加载底层特征不共享,可能出现负迁移
算力紧张,训练时长受限优先加载预训练权重收敛快,能省下大量GPU时间

这个表的核心逻辑,就是回答“底层特征到底共不共享”这个问题。视觉任务之间共享多,语言任务之间共享多,跨得远就要小心。

6.2 一套稳妥的默认方案

如果你暂时没有精力做细致的消融实验,我推荐一套比较稳妥的默认配置,它在我做过的多个项目里都表现良好。

第一步,加载与任务最接近的预训练权重。图像任务用ImageNet预训练,NLP任务用大规模文本预训练模型。不要贪图复杂,选择主流模型库默认带权重的版本,能省去很多麻烦。

第二步,替换分类头,并随机初始化新层。确保输入输出维度跟你的任务匹配,数据预处理严格使用预训练模型的归一化方式。

第三步,先冻结所有预训练层,只训练新分类头若干epoch,直到验证损失不再明显下降。这一步通常很快,但价值很大。

第四步,解冻全部层,用较小的学习率(比如从零训练的十分之一)全局微调。微调过程中建议监控每一层的梯度范数,如果某一层梯度异常大,说明它还没稳定,可以考虑降低学习率或者只解冻部分层。

第五步,最终模型效果如果还是不够理想,再从数据增强、难样本挖掘、模型容量这些角度继续优化。预训练权重只是起点,后面的路还得靠你自己一步步走。

这套方案不是最优解,但它是高成功率的稳妥解。项目初期用它快速建立基线,后期再根据情况做精细化调整,效率会高很多。

聊到这儿,预训练权重的原理、收益和坑应该都覆盖到了。我自己刚接触深度学习的时候也犯过错,总觉得加载别人训练好的权重不够“纯粹”,后来在几个小数据任务上用实测对比,才真正服气:同样一个模型,加载预训练权重之后效果从七十几分到九十几分,训练时间还少了一大半。从此以后,我的默认动作就是先找合适的预训练权重,再谈训练策略。不管你是做图像、文本还是其他领域,只要任务里存在可以复用的底层特征,这笔“继承”总是划算的。最后提醒一句:权重文件下载之后记得校验完整,加载之前多打印一遍键名,这两步能帮你避开大多数莫名其妙的报错。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询