问题
Pytorch训练模型时由于需要冻结部分预训练模型的参数,所以想打印下需训练部分梯度是否有变化,但出现了反向传播前后梯度完全相同,但每次迭代的梯度不同的情况。按常理来说,在optimizer.zero_grad()后模型梯度应该清零,然后反向传播再计算梯度。
代码和解决方法
原代码:
''' 前向传播代码'''
if torch.cuda.is_available():
opt.zero_grad()
grad_before_training = [p.grad for p in model.parameters() if p.requires_grad]
#print("grad_before_training:", grad_before_training)
loss = torch.square((result["bit"] / frame_pixel_num - target_bitrate) / target_bitrate)[0]
loss.backward()
opt.step()
grad_after_training = [p.grad for p in model.parameters() if p.requires_grad]
#print("grad_after_training:", grad_after_training)
params_updated = False
#print(torch.sum(grad_before_training[0]), torch.sum(grad_after_training[0]))
for grad_before, grad_after in zip(grad_before_training, grad_after_training):
#print(grad_before, grad_after)
if torch.any(grad_before != grad_after):
params_updated = True
if params_updated:
print("模型参数已更新")
else:
print("模型参数未更新")
打印出来结果一直是“模型参数未更新”,原因是grad_before_training与模型参数的梯度是指向同一块内存,loss.backward()后模型参数的梯度更新,grad_before_training也对应更新。
可以在反向传播前后分别打印模型参数的梯度,可以发现反向传播前是0,反向传播后都是新的梯度了。
所以其实这里模型的参数是正常更新的,只是判断更新的代码逻辑出了问题。
解决方法
这里需要创建梯度的副本给grad_before_training:
grad_before_training = [p.grad.clone() for p in model.parameters() if p.requires_grad]