PyTorch中交替冻结参数不同部分时梯度钩子失效问题及非优化器重定义的解决方案咨询
嗨,这个问题我之前做阶段性参数微调的时候也碰到过,确实钩子的方式有时候会藏着一些不易察觉的坑,而重定义优化器又会丢失Adam这类优化器的动量、二阶矩等关键状态,直接影响收敛效率。咱们来一步步拆解问题,再给出更稳妥的解决方案。
首先分析你的钩子方案为什么失效:
你已经正确移除了第一个钩子并注册了第二个,但从输出结果看,第二次训练中本该被冻结的后3个参数还是更新了。这大概率不是钩子注册的问题,而是Adam优化器的内部状态机制在起作用——Adam会维护每个参数的动量(exp_avg)和平方梯度累积(exp_avg_sq),即使你当前的梯度被钩子置为0,之前迭代中累积的动量依然会驱动参数更新(尤其是当你没有重置优化器状态的时候)。而重定义优化器会清空这些状态,所以钩子的效果才显现出来,但这显然不是我们想要的。
接下来是不需要重定义优化器的解决方案,这里推荐两种最实用的:
方案一:直接手动修改梯度(最直观可靠)
放弃钩子,在反向传播完成后、优化器更新前,直接手动将不需要更新的参数梯度置为0。这种方式绕开了钩子的潜在问题,而且完全保留优化器的状态。
修改你的代码如下:
import torch torch.manual_seed(12345) # 简单线性回归案例 N = 10 p = 5 k = 2 assert k <= p X = torch.randn(N, p) y = torch.randn(N) beta = torch.nn.Parameter(torch.randn(p)) print(f'beta before training: {beta}') # 只初始化一次优化器,保留状态 optimizer = torch.optim.Adam([beta], lr=0.01) # 第一次训练:冻结前k个参数 optimizer.zero_grad() y_pred = X @ beta loss = torch.nn.functional.mse_loss(y_pred, y) loss.backward() # 手动将前k个参数的梯度置为0 beta.grad[:k] = 0.0 optimizer.step() print(f'beta after first training: {beta}') # 第二次训练:冻结后p-k个参数 optimizer.zero_grad() y_pred = X @ beta loss = torch.nn.functional.mse_loss(y_pred, y) loss.backward() # 手动将后p-k个参数的梯度置为0 beta.grad[k:] = 0.0 optimizer.step() print(f'beta after second training: {beta}')
运行后你会发现,第二次训练中后3个参数完全没有更新,前2个正常更新,完美符合你的需求,同时优化器的动量等状态全程保留。
方案二:拆分参数组动态调整(适合更复杂场景)
如果你的模型参数更多、拆分逻辑更复杂,可以把参数分成两个组,通过动态调整参数组的lr来实现冻结,这种方式更结构化:
# 初始化时将参数拆分为两组 param_groups = [ {'params': beta[:k], 'lr': 0.01}, {'params': beta[k:], 'lr': 0.01} ] optimizer = torch.optim.Adam(param_groups, lr=0.01) # 第一次训练:冻结前k组(设置lr为0) for group in optimizer.param_groups[:1]: group['lr'] = 0.0 # 训练步骤:zero_grad -> forward -> loss -> backward -> step # 第二次训练:冻结后p-k组,恢复前k组的lr for group in optimizer.param_groups[:1]: group['lr'] = 0.01 for group in optimizer.param_groups[1:]: group['lr'] = 0.0 # 训练步骤同上
不过这种方式需要注意:设置lr=0时,优化器依然会累积动量(只是不更新参数),而手动置梯度为0的话,动量不会累积(因为梯度为0时,Adam的动量更新项也为0)。所以如果想要完全模拟“冻结”(既不更新参数也不累积动量),方案一更合适。
最后再总结一下:钩子的方式虽然灵活,但在和Adam这类有状态的优化器配合时,容易因为优化器的内部状态导致预期外的更新。而手动修改梯度的方式直接、透明,完全保留优化器的训练状态,是解决这类交替冻结问题的最优选择。
内容来源于stack exchange

