PyTorch中在反向传播前为什么要手动将梯度清零?


作者:Pascal
链接:https://www.zhihu.com/question/303070254/answer/573037166
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这种模式可以让梯度玩出更多花样,比如说梯度累加(gradient accumulation)。gradient_accumulation_steps通过累计梯度来解决本地显存不足问题。

传统的训练函数,一个batch是这么训练的:

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()
  1. 获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;
  2. optimizer.zero_grad() 清空过往梯度;
  3. loss.backward() 反向传播,计算当前梯度;
  4. optimizer.step() 根据梯度更新网络参数

简单的说就是进来一个batch的数据,计算一次梯度,更新一次网络

使用梯度累加是这么写的:

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.1 loss regularization
    loss = loss/accumulation_steps   
    # 2.2 back propagation
    loss.backward()
    # 3. update parameters of net
    if((i+1)%accumulation_steps)==0:
        # optimizer the net
        optimizer.step()        # update parameters of net
        optimizer.zero_grad()   # reset gradient

获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;

loss.backward() 反向传播,计算当前梯度;

多次循环步骤1-2,不清空梯度,使梯度累加在已有梯度上;

梯度累加了一定次数后,先optimizer.step() 根据累计的梯度更新网络参数,然后optimizer.zero_grad() 清空过往梯度,为下一波梯度累加做准备;

总结来说:梯度累加就是,每次获取1个batch的数据,计算1次梯度,梯度不清空,不断累加,累加一定次数后,根据累加的梯度更新网络参数,然后清空梯度,进行下一次循环。

一定条件下,batchsize越大训练效果越好,梯度累加则实现了batchsize的变相扩大,如果accumulation_steps为8,则batchsize ‘变相’ 扩大了8倍,是我们这种乞丐实验室解决显存受限的一个不错的trick,使用时需要注意,学习率也要适当放大。

更新1:关于BN是否有影响,之前有人是这么说的:

As far as I know, batch norm statistics get updated on each forward pass, so no problem if you don’t do .backward() every time.

BN的估算是在forward阶段就已经完成的,并不冲突,只是accumulation_steps=8和真实的batchsize放大八倍相比,效果自然是差一些,毕竟八倍Batchsize的BN估算出来的均值和方差肯定更精准一些。

更新2:根据 @李韶华的分享,可以适当调低BN自己的momentum参数

bn自己有个momentum参数:x_new_running = (1 - momentum) * x_running + momentum * x_new_observed. momentum越接近0,老的running stats记得越久,所以可以得到更长序列的统计信息

我简单看了下PyTorch 1.0的源码:https://github.com/pytorch/pytorch/blob/162ad945902e8fc9420cbd0ed432252bd7de673a/torch/nn/modules/batchnorm.py#L24,BN类里面momentum这个属性默认为0.1,可以尝试调节下。

今天仍然还是在复现论文,编码。

上午:调bug,发现程序运行的贼慢,调了半天发现人家的代码跟实验室的GPU环境不太搭,看来得按图索骥,重新写。

下午,代码:照抄+理解了一部分代码,目前还没有动到要自己修改的代码部分。难顶。

光复现了,没有跟上论文阅读,知识面太窄。


文章作者: CarlYoung
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CarlYoung !
 上一篇
warmup_proportion预热学习率的作用 warmup_proportion预热学习率的作用
作者:EO_eaf6链接:https://www.jianshu.com/p/19a4abfcd835来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 学习率(learning rate)是神经网络训练中最
下一篇 
跑实验中遇到的Bug 跑实验中遇到的Bug
model.to(device)十分缓慢跑eeqa这个项目,运行到model.to(device)这行代码的时候,十分缓慢。按理来说,以往的项目model很快就能加载到GPU上的。 后来经过谷歌搜索之后,发现可能的原因是:eeqa这个项目使
2021-02-21 CarlYoung
  目录