PyTorch冻结层权重需将对应Parameter的requires_grad设为False,须遍历parameters()而非直接设Module属性;optimizer应仅传入requires_grad=True的参数;加载预训练权重后需重新冻结;BN层需额外控制track_running_stats或eval()以避免统计量更新。

冻结指定层的权重(requires_grad=False)
PyTorch 中冻结某层参数的核心是把该层所有 Parameter 的 requires_grad 设为 False,这样反向传播时梯度不会更新它们。注意:仅设置 requires_grad=False 就够了,不需要手动清空 grad 或禁用 optimizer 步骤。
常见错误是只冻结模块但没递归处理子模块,比如对 model.layer3 调用 .requires_grad = False 无效——因为 layer3 是 nn.Module,不是 Parameter;必须遍历其 parameters()。
- 正确做法:
for param in model.layer3.parameters(): param.requires_grad = False - 批量冻结多个层:
for name, param in model.named_parameters(): if "layer1" in name or "layer2" in name: param.requires_grad = False - 冻结整个模型后解冻最后几层:
for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): # 假设 fc 是新分类头 param.requires_grad = True
微调时 optimizer 只接收可训练参数
如果冻结部分参数后仍把整个模型传给 torch.optim.Adam(model.parameters(), ...),虽然梯度不会计算,但 optimizer 内部仍会为这些参数维护状态(如 Adam 的 momentum_buffer),浪费显存且可能引发意外行为(尤其在加载预训练 state_dict 时)。
务必让 optimizer 只接收当前 requires_grad=True 的参数:
- 推荐写法:
optimizer = torch.optim.Adam( [p for p in model.parameters() if p.requires_grad], lr=1e-4 ) - 避免:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)—— 即使部分p.requires_grad=False,也会注册冗余状态 - 若使用
param_groups区分学习率(如 backbone 用小 lr,head 用大 lr),需确保每个 group 的params列表里只含requires_grad=True的参数,否则报错ValueError: some parameters have requires_grad=False
加载预训练权重后冻结的顺序很重要
顺序错误会导致冻结失效:先 load_state_dict(),再冻结,否则加载时会重置 requires_grad 状态(尤其当 checkpoint 里保存了优化器状态或自定义属性时)。
- 正确顺序:
model = torchvision.models.resnet18(pretrained=True) # 先冻结 for param in model.layer4.parameters(): param.requires_grad = False # 再加载(如有自定义权重) if custom_ckpt: model.load_state_dict(torch.load(custom_ckpt)) # 最后构建 optimizer optimizer = torch.optim.Adam(...) - 常见坑:
model.load_state_dict(...)后未重新检查requires_grad状态——某些 checkpoint 保存了完整模型,加载后所有参数默认requires_grad=True,之前的冻结被覆盖 - 验证是否真冻结:
sum(p.requires_grad for p in model.parameters()) # 应小于总参数量
BN 层在冻结时的行为差异
冻结卷积/线性层参数后,BatchNorm 层默认仍会更新 running_mean 和 running_var,这在微调小数据集时可能导致分布偏移。是否冻结 BN 需按场景判断:
- 保持 BN 统计量不变(常用):
model.eval() # 关闭 train mode,但会禁用 dropout,慎用
或更精确地:for m in model.modules(): if isinstance(m, nn.BatchNorm2d): m.eval() # 冻结统计量更新,但保留训练模式下的 affine 参数更新(如果 requires_grad=True) - 完全冻结 BN(包括 weight/bias):
for m in model.modules(): if isinstance(m, nn.BatchNorm2d): m.requires_grad_(False) - 只冻结统计量、允许 affine 更新(推荐 finetune 场景):
for m in model.modules(): if isinstance(m, nn.BatchNorm2d): m.track_running_stats = False # 关闭统计量更新 # 但 weight/bias 仍可训练(取决于其 requires_grad)
BN 的行为比线性层复杂,不显式控制 track_running_stats 或 eval(),很容易在验证时得到和训练时不一致的输出。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











