PyTorch中冻结卷积滤波器子集:梯度置零后权重仍变化求解答
这是个很常见的PyTorch训练细节问题,我来帮你拆解分析:
问题1:为什么梯度置零后权重仍会改变?
最核心的原因大概率和你使用的优化器特性有关,尤其是当你用了带动量的优化器(比如SGD with momentum、Adam、RMSprop等)时:
- 这类优化器的更新逻辑不是直接用当前梯度乘以学习率来调整权重,而是会累积历史梯度的动量。比如SGD带动量的更新公式是:
哪怕你把当前梯度强行设为0,只要之前迭代累积的动量v = momentum * v + lr * gradient weight = weight - vv不为0,权重还是会被这个动量值推动产生变化。 - 另外可以快速排查:你是否在
loss.backward()之后、设置梯度为0之前,有其他操作偷偷修改了梯度?或者你的损失函数有没有附加L2正则项(很多优化器默认会加),正则项的梯度可能在你置零后又被计算了?不过从你的代码来看,动量导致的概率最高。
你可以做个验证测试:临时把优化器换成纯SGD(不带momentum)再跑一轮,如果权重不再变化,那就能坐实是动量的问题了。
问题2:冻结卷积滤波器子集的更可靠方案
手动置零梯度的方法容易受优化器特性干扰,这里有几个更稳定的替代方案:
方案1:设置目标参数的requires_grad=False
直接让PyTorch跳过这些滤波器的梯度计算,从根源上阻止它们被更新:
# 先获取要冻结的滤波器索引 freeze_indices = temp_5.values # 确保整个卷积层的权重默认是可训练的 im_net.features[25].weight.requires_grad = True # 逐个设置目标滤波器的权重不需要梯度 for idx in freeze_indices: im_net.features[25].weight[idx].requires_grad = False
注意:哪怕你的优化器是在设置requires_grad之前初始化的也没关系——PyTorch的优化器在执行step()时会自动跳过requires_grad=False的参数。之后如果需要解冻这些滤波器,再把它们的requires_grad设回True即可。
方案2:用优化器参数分组实现冻结
把模型参数拆成两组,给需要冻结的组设置lr=0,这样即使有梯度,更新时也不会改变权重:
# 拆分参数为「待更新」和「待冻结」两组 params_to_update = [] params_to_freeze = [] for name, param in im_net.named_parameters(): if name == "features.25.weight": # 创建掩码区分需要冻结和更新的滤波器 mask = torch.ones(param.shape[0], dtype=torch.bool, device=device) mask[freeze_indices] = False # 切片加入对应分组 params_to_update.append(param[mask]) params_to_freeze.append(param[~mask]) else: # 其他参数默认加入待更新组 params_to_update.append(param) # 初始化优化器,给冻结组设置lr=0 optimizer = torch.optim.SGD([ {'params': params_to_update, 'lr': 0.001}, {'params': params_to_freeze, 'lr': 0} ], momentum=0.9)
这个方案适合你不想修改requires_grad状态的场景,比如之后需要灵活调整不同组的学习率时。
方案3:手动恢复权重(备选,不推荐)
在优化器更新后,把冻结的滤波器权重强制恢复到更新前的状态:
# 更新前先保存冻结部分的权重 frozen_weights_backup = im_net.features[25].weight[freeze_indices].detach().clone() # 执行优化器更新 optimizer.step() # 恢复冻结部分的权重 im_net.features[25].weight[freeze_indices] = frozen_weights_backup
这个方法比较繁琐,每次都要克隆张量,会额外占用内存,所以优先推荐前两个方案。
内容的提问来源于stack exchange,提问作者Hongjun Choi
相关产品推荐
相关产品推荐

