在反向传播前要手动将梯度清零?做实验说明

经常看到类似下面的代码

for i,(images,target) in enumerate(train_loader):
    # 1. input output
    images = images.cuda(non_blocking=True)
    target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
    outputs = model(images)
    loss = criterion(outputs,target)
 
    # 2. backward
    optimizer.zero_grad()   # reset gradient
    loss.backward()
    optimizer.step()            

这三条经常一起出现:梯度清零,反向传播求梯度,更新参数

    # 2. backward
    optimizer.zero_grad()   # reset gradient
    loss.backward()
    optimizer.step()  

很多资料说梯度不清零会累加,我一开始想的是:求出新梯度不就直接赋值给变量(覆盖原来变量的值吗?)怎么会累加呢?

然后我做了个实验验证了一下:

代码如下:

import torch
import torch.nn as nn
import copy
# 创建网络
net1 = nn.Linear(10, 1)
net2 = copy.deepcopy(net1)

# 创建优化器
optimizer1 = torch.optim.SGD(net1.parameters(), lr=0.01)
optimizer2 = torch.optim.SGD(net2.parameters(), lr=0.01)

# 定义输入
inputs1 = [-0.4507,  1.5306,  1.6414, -1.4976, -0.4955,  0.8670,  1.7535,  0.6661, -1.1307,  0.6601]
inputs1 = torch.Tensor(inputs1)
# print("1---inputs1",inputs1)

inputs2 = [-0.7951,  1.0476,  0.3908,  1.0368, -0.4870,  0.2596,  0.2631,  0.6549, -0.9619, -0.1810]
inputs2 = torch.Tensor(inputs2)
# print("1---inputs2",inputs2)

#定义标签
targets = [-0.4159]
targets = torch.Tensor(targets)
# print("1---targets",targets)


#网络1的第一个batch
outputs1 = net1(inputs1)
print("网络的第一次输出",outputs1)
print("网络第一次计算损失前的梯度",net1.weight.grad)
# 计算损失1
loss1_1 = torch.nn.functional.mse_loss(outputs1, targets)
print("网络的第一次损失值",loss1_1)
print("网络第一次计算损失后,反向传播前的梯度",net1.weight.grad)
# 反向传播1
loss1_1.backward() #反向传播后才产生梯度
# 打印参数的梯度
print("网络第一次反向传播后的梯度",net1.weight.grad)
optimizer1.step() #更新参数

#网络1的第二个batch
outputs2 = net1(inputs2)
print("网络的第二次输出",outputs2)
print("网络第二次计算损失前的梯度",net1.weight.grad)
# 计算损失2
loss1_2 = torch.nn.functional.mse_loss(outputs2, targets)
print("网络第二次损失",loss1_2)
print("网络第二次计算损失后,反向传播前的梯度",net1.weight.grad)
# 反向传播2
loss1_2.backward()
# 打印参数的梯度
print("网络1第二次反向传播后的梯度",net1.weight.grad)
optimizer1.step()



#第二个网络 batch1
outputs1 = net2(inputs1)
print("网络的第一次输出",outputs1)
print("网络第一次计算损失前的梯度",net2.weight.grad)
#计算损失
loss2_1 = torch.nn.functional.mse_loss(outputs1, targets)
print("网络第一次损失",loss2_1)
print("网络第一次计算损失后,反向传播前的梯度",net2.weight.grad)
# 反向传播1
optimizer2.zero_grad()     # 清零梯度
loss2_1.backward()
# 打印参数的梯度
print("网络2第一次反向传播后的梯度",net2.weight.grad)
optimizer2.step()          # 更新参数

# batch2
outputs2 = net2(inputs2)
print("网络2的第二次输出",outputs2)
print("网络2第二次计算损失前的梯度",net2.weight.grad)
loss2_2 = torch.nn.functional.mse_loss(outputs2, targets)
print("网络第二次损失",loss2_2)
print("网络第二次计算损失后,反向传播前的梯度",net2.weight.grad)
# 反向传播2
optimizer2.zero_grad()     # 清零梯度
loss2_2.backward()
# 打印参数的梯度
print("网络2第二次反向传播后的梯度",net2.weight.grad)
optimizer2.step()          # 更新参数


"""
D:\Anaconda\envs\pytch-gpu-38\python.exe E:\pycharm-project\niuke\back_ward_2.py 
网络的第一次输出 tensor([0.2260], grad_fn=<AddBackward0>)
网络第一次计算损失前的梯度 None
网络的第一次损失值 tensor(0.4120, grad_fn=<MseLossBackward>)
网络第一次计算损失后,反向传播前的梯度 None
网络第一次反向传播后的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])

网络的第二次输出 tensor([-0.5868], grad_fn=<AddBackward0>)
网络第二次计算损失前的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])
网络第二次损失 tensor(0.0292, grad_fn=<MseLossBackward>)
网络第二次计算损失后,反向传播前的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])

网络1第二次反向传播后的梯度 tensor([[-0.3069,  1.6070,  1.9736, -2.2769, -0.4697,  1.0243,  2.1612,  0.6313, -1.1229,  0.9093]])



网络的第一次输出 tensor([0.2260], grad_fn=<AddBackward0>)
网络第一次计算损失前的梯度 None
网络第一次损失 tensor(0.4120, grad_fn=<MseLossBackward>)
网络第一次计算损失后,反向传播前的梯度 None
网络2第一次反向传播后的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])

网络2的第二次输出 tensor([-0.5868], grad_fn=<AddBackward0>)
网络2第二次计算损失前的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])
网络第二次损失 tensor(0.0292, grad_fn=<MseLossBackward>)
网络第二次计算损失后,反向传播前的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])

网络2第二次反向传播后的梯度 tensor([[ 0.2717, -0.3580, -0.1335, -0.3543,  0.1664, -0.0887, -0.0899, -0.2238,  0.3287,  0.0619]])

Process finished with exit code 0


"""

把有用的结果拿出来分析一下:

"""

# a
网络第一次反向传播后的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])  


# b
网络1第二次反向传播后的梯度 tensor([[-0.3069,  1.6070,  1.9736, -2.2769, -0.4697,  1.0243,  2.1612,  0.6313, -1.1229,  0.9093]])



# a
网络2第一次反向传播后的梯度 tensor([[-0.5786,  1.9649,  2.1072, -1.9226, -0.6361,  1.1130,  2.2511,  0.8551, -1.4516,  0.8474]])

# c
网络2第二次反向传播后的梯度 tensor([[ 0.2717, -0.3580, -0.1335, -0.3543,  0.1664, -0.0887, -0.0899, -0.2238,  0.3287,  0.0619]])

"""

然后需要验证一下是否 a+c = b

然后

-0.5786 + 0.2717 = -0.3069 (yes)

1.9649 - 0.3580 = 1.6069  (1.6070 不太严谨的分析是浮点数计算的小误差,或者是小数点后面很多位计算完后的四舍五入)

2.1072 - 0.1335 = 1.9737 (1.9736 , 这个结果有点蚌埠住了,暂时忽略吧)

基本上是满足 a+c=b的

....................

总之验证了 梯度不清零就是会累加的

学了这个博客:

PyTorch中梯度为什么默认自动累加,在反向传播前要手动将梯度清零?_为什么backward()要将梯度归零-CSDN博客

渐渐懂了为什么要累加梯度了。

简单解释:

辣鸡显卡显存小,一个batch不能太大,那么累加梯度的话,就可以看作是显存小的显卡也完成了“一次性处理一个大的batch”的任务。

假如,一个batch 是 8,4个batch中间不清零,可以看作大batch为32。

我们极限一点,所有的batch都不清零,也就是整个数据集是一个大batch,

那么这样的好处是,辣鸡显卡也能一次处理多条数据的任务,batch size越大训练效果越好,梯度累加则实现了batchsize的变相扩大。

坏处是容易对这个训练集的数据过拟合,也就是这个训练集上表现很好,但是泛化性差了一些。

总结:

梯度不清零:逻辑上等价于处理batch size较大的数据,训练网络的结果好,但泛化性差一点

梯度清零:处理的是batch size 较小的数据,泛化性好。

如果有错误,请及时指正。

又是钻牛角尖折磨自己的一天!    /(ㄒoㄒ)/~~

参考博客:

浅谈pytorch中为什么要用 zero_grad() 将梯度清零 - Python技术站 (pythonjishu.com)icon-default.png?t=N7T8https://pythonjishu.com/znnjjrzcmypxjqh/

PyTorch中梯度为什么默认自动累加,在反向传播前要手动将梯度清零?_为什么backward()要将梯度归零-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/DragonGirI/article/details/107381375 

 

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值