正确冻结PyTorch模型层需遍历子模块所有参数设requires_grad=False,而非仅设weight;冻结后须调用model.train()以确保BN层正常更新,并在构建optimizer时显式筛选可训练参数。

冻结 PyTorch 模型层的正确写法是 requires_grad = False
直接设 model.layer.weight.requires_grad = False 是常见误区——它只冻结单个参数,漏掉 bias;更稳妥的是遍历整个子模块。冻结本质是关闭梯度计算,不是“禁用层”。torch.no_grad() 完全不适用,那是推理用的上下文管理器,和训练时的参数更新无关。
推荐做法:
- 对指定模块(如
model.encoder)调用for param in module.parameters(): param.requires_grad = False - 冻结后务必调用
model.train(),否则 BatchNorm 层会卡在 eval 模式,导致统计量不更新 - 冻结后记得检查:打印
[p.requires_grad for p in model.encoder.parameters()][:3]确认是否全为False
微调时 optimizer 只应包含 requires_grad == True 的参数
如果把所有参数都传给 torch.optim.Adam(model.parameters(), ...),即使某些 requires_grad 为 False,optimizer 内部仍会为其分配状态(如 Adam 的 exp_avg),浪费显存且可能引发隐式错误。PyTorch 不会跳过这些参数的 state 初始化。
安全写法:
立即学习“Python免费学习笔记(深入)”;
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
- 显式筛选:
filter(lambda p: p.requires_grad, model.parameters()) - 或按模块组织:
optim.Adam([{'params': model.classifier.parameters()}]),只传解冻部分 - 切忌在冻结后还用
model.parameters()构造 optimizer,尤其在多卡DDP下容易因参数状态不一致报错
model.eval() 和 model.train() 在微调中必须手动切换
冻结层 ≠ 关闭训练模式。BatchNorm 和 Dropout 层的行为由 model.training 属性控制,与 requires_grad 无关。若冻结了带 BN 的 backbone,但没调 model.train(),BN 层会沿用 running_mean/std,导致输出漂移甚至 NaN。
典型错误场景:
- 验证时用了
model.eval(),但训练循环末尾忘记切回model.train() - 只对 classifier 调
classifier.train(),而 backbone 仍处于 eval 模式 - 使用
torch.compile()后,某些自定义 forward 中混用if self.training:逻辑,但全局模式未同步
加载预训练权重后冻结再修改分类头,顺序不能颠倒
先改结构(如替换 model.fc = nn.Linear(512, 10)),再加载权重,最后冻结 —— 这个顺序最容易出问题。新 fc 层没有预训练权重,若此时执行 load_state_dict(..., strict=False),旧权重不会自动映射到新层,而冻结又发生在加载之后,结果就是新层参数被冻结、无法训练。
正确流程:
- 加载完整预训练模型(含原始 head)
- 执行冻结(
for param in model.features.parameters(): ...) - 再替换 head:
model.classifier = nn.Linear(...)—— 新层默认requires_grad=True - 最后构建 optimizer,只包含新 head 参数
最易忽略的是:替换 head 后没检查新参数是否真的可训练,尤其当模型封装在 nn.Sequential 或自定义 forward 中时,model.classifier 可能只是个名字,实际参数不在该属性下。

















