一、概念先行:模型冻结究竟是何方神圣?

在深度学习和大模型的世界里,“模型冻结”并不是什么高深莫测的黑魔法,而是一种极为实用且优雅的优化策略。

1.1 用一个动画般的比喻让你秒懂

想象你是一个雕塑大师,面前有一尊已经粗雕成型的石像——人物的大致轮廓、身体比例都已经到位了。现在,你需要在石像的头部精雕出五官的细节。你会怎么做?

聪明的你肯定不会把整个石像敲碎重来,而是会把石像身体部分的凿子拿开,只专注于头部进行精细雕刻。在这个过程中,身体部分就如同一片 “冰封王国” ,纹丝不动;唯有头部的区域一片火热,精雕细刻。

模型冻结,就是这个道理:

在神经网络训练时,我们把模型中某些层的参数“冻住”——不让它的权重(参数值)参与更新。训练过程中,模型依然会正向传播数据通过冻结的层,但在反向传播(计算误差并调整参数的过程)时,被冻结的区域果断跳过权重更新,梯度根本不计算,参数维持原样。

1.2 模型冻结在代码层面是如何实现的?

在深度学习领域最主流的框架PyTorch中,冻结一个参数(可以理解成模型中的一个权重)的方法极其简单,就一句代码:

param.requires_grad = False

赋值为False意味着这个参数不需要计算梯度,优化器在更新模型参数时自然就会跳过它,保持不变。而在TensorFlow/Keras中,只需要设置layer.trainable = False即可达到完全相同的效果。

二、模型冻结与微调——难舍难分的“最佳拍档”

要真正理解冻结的实际价值,我们必须认识它的搭子——微调(Fine-tuning) ,以及它们的共同上层概念迁移学习(Transfer Learning)

2.1 迁移学习:站在巨人的肩膀上

现在AI领域最主流的做法是,研究人员已经用海量的数据和超强的算力训练好了各种强大的模型,比如ResNet(图像识别界的常青树)、BERT(自然语言处理的“革命家”)、LLaMA 3、GPT等等。这些大礼包被称作预训练模型(Pre-trained models) ,已经在超级数据集(比如ImageNet由1400万张图片构成)上学习到了极其可靠的通用特征提取能力。

我们不再从零造轮子,而是直接拿来这些巨人开发的半成品,用在我们自己的任务上。这个过程就是迁移学习。

2.2 微调:给专家“开小灶”

拿过来的预训练模型懂很多,但对于特定业务(比如识别你公司的专属Logo铭牌、总结你公司的客服对话历史,或者只针对10种特定的医疗病理图片做出精准分类),还需要针对具体任务做一些“特训”。给这个专家特地为你的问题开小灶的过程,就叫微调

在这个过程中,我们把专家脑子里的全部知识(成千上百亿的庞大模型参数)全部推倒重来一遍,在经济上、时间上、成本上都极其笨拙。例如,对Llama-7B这种70亿参数的庞然大物做全参数微调(Full Fine-tuning) ,显存往往要求80GB以上——这根本不是普通团队能承受的。

2.3 模型冻结:微调的第一步“开山斧”

正因如此,模型冻结就成为了微调时必须运用的一种实践。我们冻结预训练模型的大部分基础层,只微调最后的分类器层或部分高层特征提取层。

具体例子:如果一个人的目标是把ResNet-50(一个50层深度卷积残差网络)用来专门识别“柴犬”与“橘猫”,那么网络最前面几层被冻结的参数还在拼命计算“横线条、竖线条、简单的几何花纹”等通用特征,训练时就无需把时间浪费在这些已经掌握的底层能力上;只训练最后那几层学会如何组合这些线条成为“狗鼻子”和“猫耳朵”。这样,训练效率大幅提升,时间缩短,并且由于新鲜数据量可能较少,还完美防止了过拟合风险

三、图解代码:PyTorch模型冻结实战(完整运行版)

理论说得天花乱坠,不如看看亲手敲出的代码怎么写。

在我们输出之前先搞清本案例的目标

  • 环境:PyTorch 2.x
  • 任务:对这个ResNet-18进行模型冻结操作
  • 最终目的:迁移到新任务上进行高效训练
# ============================================================
# 第一部分:安装依赖与导入一切必要的库
# ============================================================
# 建议新建一个Python脚本文件,例如 freezing_demo.py
# 运行命令:python freezing_demo.py
# 首先确保你已经安装了 torch 和 torchvision
# 若未安装请在终端或conda环境中执行:
# pip install torch torchvision

import torch                     # PyTorch核心库
import torch.nn as nn            # 神经网络模块
import torchvision.models as models   # 预训练经典模型库,包含ResNet、VGG等
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# ============================================================
# 第二部分:加载一个已经在ImageNet数据集上训练好的ResNet-18模型
# ============================================================
# pretrained=True:告诉torchvision自动加载官方已经训练好的权重参数
model = models.resnet18(pretrained=True)

# 让我们先来看一下ResNet-18模型的结构
# ResNet-18 = 卷积基础层(特征提取部分) + 最后的全连接层(用于1000个ImageNet类别的分类)
print("========== 原始模型结构预览 ==========")
print(model)   # 不必看得太细致,先领会其层次结构即可
print("======================================")

# ============================================================
# 第三部分:核心操作——模型冻结!!!
# ============================================================
# 通俗解释:
# 我们遍历model中的每一个参数(param),把它们的 requires_grad 属性设置为 False,
# 意味着所有的参数在误差反向传播时都不计算梯度,也就不会更新。
# 这就是把整个模型“全身冻住”的操作。冻结之后的参数在后续训练周期中将保持不动。
for param in model.parameters():
    param.requires_grad = False

# 到这里,整个ResNet-18模型的主体部分已经被冻得严严实实了!
# 接下来我们需要把最后的全连接层“解冻”,因为它要根据新的分类任务进行学习。
# 模型的原始分类层名为 "fc" (fully connected),原本有1000个输出
num_features = model.fc.in_features   # 获取原始fc层的输入特征维度(例如:512)
# 彻底替换掉原先的fc层,重新定义为一个新的输出维度(比如:10 分类任务)
model.fc = nn.Linear(num_features, 10)  # 任务假设是 10 分类

# ============================================================
# 第四部分:验证冻结是否生效(通过打印requires_grad标志验证)
# ============================================================
print("\n$$$$$$ 验证模型冻结与解冻结果 $$$$$$")
# 遍历模型的全部命名参数,打印参数名以及requires_grad标志的状态
frozen_count = 0
trainable_count = 0
for name, param in model.named_parameters():
    # 在命名中,如果是属于 'fc'(最后的全连接层),则 requires_grad 自动为 True(新层默认可训练)
    # 其他层由于之前遍历全部参数设置成了 False,所以不可训练。
    if param.requires_grad:
        trainable_count += 1
        print(f"【可训练层】:{name} ,  requires_grad = {param.requires_grad}")
    else:
        frozen_count += 1
        # 防止终端输出爆炸,只打印前 5 个冻结参数的名字做示范
        if frozen_count <= 5:
            print(f"【冻结层】:{name} ,  requires_grad = {param.requires_grad}")

print(f"\n========== 统计结果 ==========")
print(f"总共被冻结的参数切片数量:{frozen_count} 个")
print(f"总共保持可训练的参数切片数量:{trainable_count} 个")
print("================================\n")

# ============================================================
# 第五部分:微调训练模拟(展示冻结带来的巨大收益)
# ============================================================
# 准备工作:以CIFAR-10这个简单数据集为例做迁移学习训练
# CIFAR-10:包含飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车共10个分类,
# 每个类别6000张 32x32 彩色图像,非常适合演示微调效果

print(">>> 开始准备CIFAR-10数据集...(首次运行会自动化下载)")

transform = transforms.Compose([
    transforms.Resize(224),                # ResNet-18 标准输入尺寸为 224x224
    transforms.ToTensor(),                 # 将图片转换为PyTorch张量
    transforms.Normalize(mean=[0.485, 0.456, 0.406],   # ImageNet标准归一化参数
                         std=[0.229, 0.224, 0.225])
])

# 加载训练集(50000张图片,用于训练模型)
train_dataset = datasets.CIFAR10(root='./data', train=True,
                                 download=True, transform=transform)
# 加载验证集(10000张图片,用于验证模型效果,不参与梯度更新)
val_dataset = datasets.CIFAR10(root='./data', train=False,
                               download=True, transform=transform)

# 创建数据加载器(每次喂给模型一批图片,batch_size=32)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

print(">>> 数据加载完毕,准备进行微调训练...")

# 定义损失函数(交叉熵损失——多分类任务的标配)
criterion = nn.CrossEntropyLoss()
# 定义优化器(只优化模型中那些requires_grad=True的参数!!!)
# 这一行是关键——torch.optim的filter()方法会只把那些 requires_grad == True的参数扔进优化器
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()),
                             lr=0.001)

# 简单训练2个epoch来演示冻结的效果(虽然只有新加的全连接层在训练)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
print(f">>> 正在使用设备:{device} 进行训练演示...")

model.train()   # 设定模型为训练模式(某些层如Dropout、BatchNorm会有不同行为)
epochs = 2
for epoch in range(epochs):
    running_loss = 0.0
    for i, (images, labels) in enumerate(train_loader):
        images, labels = images.to(device), labels.to(device)

        # 前向传播:将图片输入模型,直接得到预测输出
        outputs = model(images)
        # 计算损失:预测值与真实标签之间的误差
        loss = criterion(outputs, labels)

        # 反向传播与优化:
        optimizer.zero_grad()   # 清空已有的梯度缓存
        loss.backward()         # 反向传播计算梯度(被冻结的层不计算梯度,此处加速度非常显著!)
        optimizer.step()        # 更新参数(只有解冻的那些参数更新!)

        running_loss += loss.item()
        if (i+1) % 200 == 0:    # 每200个批次打印一次
            print(f'Epoch [{epoch+1}/{epochs}], Batch [{i+1}/{len(train_loader)}], '
                  f'Loss: {running_loss/200:.4f}')
            running_loss = 0.0

print("\n########## 微调训练演示完毕! ##########")
print("你看,虽然模型只有最后一层分类层在训练,但损失在不断下降,说明冻结策略奏效了。")

3.1 代码详解(逐段拆解,一步步明明白白)

我们来梳理一下这段代码的执行流程,确保每个新手看起来都要通透!

阶段一:加载预训练模型。
代码使用了models.resnet18(pretrained=True)。这里的参数pretrained=True意味着你拿到的这个ResNet-18模型已经用ImageNet大赛中的1400多万张图像训练过了,权重已经是一组准确率极高的数值。迁移学习的第一步,就是把这一整个大礼包请进来。

阶段二:全体冻结。
核心代码for param in model.parameters(): param.requires_grad = False。它的含义非常直接:把这个ResNet里每一个、丝毫不出差的参数的梯度计算开关关掉。在神经网络中,一旦得知某个参数requires_grad=False,就会在loss.backward()(反向传播)这个环节坚决跳过此参数的计算,省时省力!

阶段三:替换分类层并解冻。
最后,我们把模型的最终分类层(model.fc)重新连接到了我们定制的任务上。代码model.fc = nn.Linear(num_features, 10),因为nn.Linear里实例化出来的参数的requires_grad默认值是True,所以旧层全冻、新层自动学习的效果就达到了。

阶段四:验证。
我们遍历了模型的所有命名参数,并且分别打印出了可训练切片数量和冻结切片数量。你运行时会发现,冻结的参数切片数量是好几百个,而可训练的仅仅是最后那几十个来自fc层的。

阶段五:精简训练演示。
我们用CIFAR-10数据集进行了两轮微调训练。请特别体会optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001)这行代码——做了filter过滤器,所以优化器只针对requires_grad=True的参数做梯度下降更新!损失在连续下降说明效果立竿见影。

四、TensorFlow/Keras中的模型冻结(一句话对比)

虽然上面使用了PyTorch实践,但在工业界同等流行的TensorFlow/Keras框架下,冻结模型同样无可挑剔地简单。

最经典的操作方法就是:

# 将预训练的基础模型的所有层进行遍历,并将每一层的trainable属性设置为False
base_model = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3),
                                               include_top=False,
                                               weights='imagenet')
base_model.trainable = False      # 一招致命!冻结base_model中的全部层!

# 然后在你自己的模型类中或者顺序模型里,把 base_model 当作第一层,
# 再往里面追加你想要拿来微调的新层(例如Dropout,Dense层等等)
model = tf.keras.Sequential([
    base_model,                   # 这部分已经冻结不变
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')   # 自己新增的分类层,它会自动可训练
])

# 编译并训练模型... 此时只有新增的全连接层在更新,冻结层不参与梯度下降!
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

留个心眼! 在TensorFlow/Keras中,如果你使用model.trainable = False递归冻结了整个模型,那么它下面的所有子层都会被自动冻结;而想要部分层冻结,部分可训练,就要精准地把想要冻结的那些层的trainable设成False。

代码简洁并不代表原理简单,恰恰验证了框架设计者的深思熟虑。

五、大模型时代下的“亲戚名词大扫盲”

理解冻结之后,我们把大模型领域的相关关键术语一次性理清楚。这些名词技术全是围绕“怎么少改参数、多出成绩”这个逻辑展开的。

5.1 微调

微调是整个领域最基础的动作。大模型厂商用海量数据预训练了一个巨大基底模型之后,你要让它适应你的行业,只需要拿你的小部分特定数据集在这基础上精修它的参数即可。

微调这个动作本身必然要用到模型冻结,否则把全部数千亿参数都重新训练一次,极度不现实。SFT(监督微调,Supervised Fine-tuning)就是最常见的一种方式——用标注好的优质指令数据去教模型按照特定对话格式作出回答。

5.2 PEFT:参数高效微调

既然大模型全量参数(例如GPT-3有1750亿参数)微调成本几乎无法承受,研究人员开发出了PEFT(Parameter-Efficient Fine-Tuning)这一大家族神器——几乎完全冻结基座模型,仅微调额外插入的小得惊人的附加参数来解决垂直任务。

PEFT的代表人物之一就是大名鼎鼎的LoRA(低秩自适应) :它在原始大模型权重旁边偷偷附加了两个可以训练的低维矩阵B和A(合起来就是B·A)。想象原本模型要修改一个大矩阵W,要改动亿万个数字;LoRA不修改W,而是用B×A来模拟对W的修改效果,冻结住W的本体不动。这样你的显存消耗降90%,用RTX 4090就能跑起微调!根据工程数据,GPT-3的0.1%的参数就能被LoRA微调,也就是仅需训练约1750万个参数。

另一个PEFT变种叫做Adapter Tuning,它是在原模型层与层之间嵌入了小巧的适配器模块,训练时只调适配器参数量(不到模型全重量的1%)。

5.3 知识蒸馏:“师生传承”

有些场景下最终部署的硬件(比如手机)根本跑不动千亿参数的大模型,这时候就需要一个学生模型(轻量级) 跟着一个教师模型(重型) 学习。教师模型产出软标签(预测概率分布),带着这种表达能力去学徒模型,学生因此学到的不是死分类而是类别之间的深层关联含义。

这种传承模式最终让微型模型也能拥有大模型80%~90%的水平,而参数量只有教师模型的十分之一或者更少。

5.4 模型剪枝:简单粗暴去冗余

深度神经网络的一大特点就是过参数化,意思是模型训练完会发现里面超过一半的权重接近零,没什么用!剪枝技术就是无情的园丁修剪枯枝——把这些不重要的权重给砍掉。删除后模型变瘦、变快,精度损失微乎其微。剪枝可以做到90%以上压缩率,天然和模型冻结原理上相辅相成。一般做完整流程会先剪枝再微调恢复精度。

5.5 量化:给小数字降降精度

原来的模型参数大多数是32位浮点数(FP32),你把它降成8位整数(INT8)后计算就快得多。这个名叫量化的过程能让存储变成原来的四分之一,在CPU、边缘设备上加速明显。把蒸、剪、量三种手段组合使用,甚至可以实现模型缩到原来的十六分之一的效果。

六、模型冻结到底为什么是通用纲领?

看到这里你已经明白了,虽然新技术层出不穷(PEFT、LoRA、Adapter等),它们执行起来的秘密武器就是模型冻结。下面这张总结会让你明白冻结在多维度上的巨大威力:

技术维度

硬核效果数据分析

技术机制

计算资源

冻结大部分特征层,显存占用降低80%以上

requires_grad=False

阻断反向传播计算图

训练速度

训练时间从几天压缩到几小时

梯度计算和参数更新直线减少

模型性能

对于小数据任务,提升测试集精度6%以上

冻结层保留了原始的教科书级特征提取器

防止灾难性遗忘

实现预训练知识100%完整迁移

旧参数不可修改,核心经验永不丢失

硬件门槛

甚至可以在单张T4 GPU上微调LLAMA 7B

冻结权重无需存储诸多的梯度中间缓存

七、终章总结

不要被眼花缭乱的新名词吓倒。模型冻结是深度学习中一个极其实用而精妙的思想:

冻结就是择其要而存其精,让那些普适的、通用的、已经足够优秀的特征知识原封不动地传承下来,把宝贵的新数据资源和训练力量倾注在任务相关的末梢环节上。

它就像电脑游戏里的“装备继承系统”:把神装的基础属性完美继承过来,自己精雕细琢地强化最后10%的关键词缀。在这个大模型横行的时代,学会冻结模型、用好迁移学习和高效微调,是任何一个AI开发者打出击穿天花板的必备内功心法。

只要掌握param.requires_grad=False或layer.trainable=False这一句话,再懂其中逻辑,你就已经走在通往顶尖AI巨匠的最短路径上。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐