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

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

这种模式可以让梯度玩出更多花样,比如说梯度累加(gradient accumulation)

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

for i, (image, label) in enumerate(train_loader):
    # 1. input output
    pred = model(image)
    loss = criterion(pred, label)

    # 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,(image, label) in enumerate(train_loader):
    # 1. input output
    pred = model(image)
    loss = criterion(pred, label)

    # 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
  1. 获取 loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;
  2. loss.backward() 反向传播,计算当前梯度;
  3. 多次循环步骤 1-2,不清空梯度,使梯度累加在已有梯度上;
  4. 梯度累加了一定次数后,先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,可以尝试调节下。

<think>好的,我现在需要帮助用户解决关于PyTorch梯度反向传播的实现方法和应用场景的问题。首先,我得仔细看看用户的问题历史和相关引用,确保回答准确。 用户之问了关于张量赋值操作的区别,现在转向梯度反向传播。他们引用了三篇资料,涉及梯度计算、反向传播和优化方法。用户现在想了解如何实现梯度反向层以及应用案例。我需要结合这些引用,特别是引用2和3,它们提到了backward()方法和链式法则的应用。 首先,我需要解释PyTorch的自动微分机制,即autograd。这部分要提到张量的requires_grad属性,以及计算图的构建。然后,详细说明backward()方法的作用,可能还要举例说明如何使用它进行梯度计算,比如引用2中的例子。此外,要提到梯度清零的重要性,防止梯度累积,这在优化循环中很关键。 接下来是应用场景,用户可能想知道在哪些情况下需要手动处理梯度反向传播。比如自定义层、复杂网络结构、梯度裁剪等。这部分需要结合引用3中的链式法则和优化过程,说明在实现这些功能时如何应用反向传播。 然后,实现案例部分,用户需要一个具体的例子。线性回归是个好选择,因为它简单且能展示整个流程。需要展示如何定义模型、损失函数,执行向和反向传播,以及优化步骤。这里要引用到引用1中的数据处理部分,可能提到transforms.ToTensor,但用户的问题更关注梯度反向,所以重点放在模型和训练循环上。 还要注意用户提到的“梯度反向层”,可能是指自定义的反向传播步骤,比如在复杂模型中手动计算梯度。比如在案例二中,展示如何在不使用自动梯度的情况下手动计算,这可以引用引用2中的backward()用法,但这里需要展示手动实现的方式。 最后,常见问题部分需要解答用户可能遇到的疑惑,比如梯度清零的原因,如何检查梯度,以及in-place操作的影响。这部分要确保解释清晰,帮助用户避免常见错误。 检查是否需要补充更多细节,比如计算图的具体工作原理,或者更多应用场景的例子。同时,确保语言简洁,结构清晰,符合用户要求的步骤化解答。确保所有PyTorch代码正确,特别是张量操作和梯度处理的步骤。最后生成的相关问题要围绕梯度反向传播的深入应用和常见问题展开,帮助用户进一步学习。</think>在PyTorch中,梯度反向传播通过**自动微分系统(autograd)**实现,其核心是`backward()`方法与计算图机制。以下是具体实现方法与应用场景的详细分析: --- ### 一、梯度反向传播的实现原理 #### 1. **计算图构建** PyTorch通过动态计算图追踪张量操作: ```python import torch x = torch.tensor([2.0], requires_grad=True) y = x ** 2 + 3 * x # 自动构建计算图 ``` - `requires_grad=True`:标记需要计算梯度的张量 - 每次操作生成新的计算节点(如乘法、加法) #### 2. **反向传播触发** 调用`backward()`自动计算梯度: ```python y.backward() # 触发反向传播 print(x.grad) # 输出梯度值:tensor([7.]) ``` - 梯度计算公式:$\frac{dy}{dx} = 2x + 3|_{x=2}=7$[^2] #### 3. **梯度累积与清零** ```python optimizer = torch.optim.SGD([x], lr=0.1) optimizer.zero_grad() # 必须手动清零梯度 y.backward() optimizer.step() ``` - 梯度默认会累积,训练时需在每次迭代清零[^3] --- ### 二、核心应用场景 #### 1. **自定义网络层** ```python class CustomLayer(torch.nn.Module): def forward(self, x): return x * torch.sigmoid(x) # 自定义向计算 def backward(self, grad_output): # 手动定义反向传播规则(PyTorch通常自动处理) sig = torch.sigmoid(self.x) return grad_output * (sig * (1 - sig) * self.x + sig) ``` #### 2. **梯度裁剪** ```python torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) ``` - 防止梯度爆炸,常用于RNN训练 #### 3. **二阶优化** ```python # 需要计算Hessian矩阵的部分场景 grad1 = torch.autograd.grad(y, x, create_graph=True) grad2 = torch.autograd.grad(grad1, x) ``` --- ### 三、实现案例:线性回归 #### 案例1:基础实现 ```python # 数据准备 X = torch.randn(100, 1) y = 3 * X + 2 + torch.randn(100,1)*0.1 # 模型定义 w = torch.randn(1, requires_grad=True) b = torch.randn(1, requires_grad=True) # 训练循环 for epoch in range(100): pred = X @ w + b loss = ((pred - y)**2).mean() loss.backward() # 自动计算梯度 with torch.no_grad(): # 禁用梯度追踪 w -= 0.01 * w.grad b -= 0.01 * b.grad w.grad.zero_() b.grad.zero_() ``` #### 案例2:自定义反向流程 ```python class ManualBackprop(torch.autograd.Function): @staticmethod def forward(ctx, input): ctx.save_for_backward(input) return input * 2 @staticmethod def backward(ctx, grad_output): input, = ctx.saved_tensors return grad_output * 0.5 # 手动定义梯度规则 x = torch.tensor([4.0], requires_grad=True) y = ManualBackprop.apply(x) y.backward() print(x.grad) # 输出tensor([0.5]) ``` --- ### 四、关键机制解析 | 机制 | 作用 | |---------------------|----------------------------------------------------------------------| | `requires_grad` | 标记需要计算梯度的张量 | | `backward()` | 从标量输出反向传播梯度,非标量需传入`gradient`参数[^2] | | `torch.no_grad()` | 上下文管理器,禁用梯度计算(用于推理或参数更新) | | `detach()` | 从计算图中分离张量,阻止梯度传播 | --- ### 五、常见问题解答 1. **为什么需要`zero_grad()`?** 梯度默认会累积,多次`backward()`会导致梯度叠加,影响优化方向[^3]。 2. **如何检查梯度是否正确?** 可通过数值梯度验证: ```python from torch.autograd import gradcheck gradcheck(func, inputs) # 返回布尔值验证梯度正确性 ``` 3. **`in-place`操作的影响** 修改具有梯度的张量值会破坏计算图,导致梯度错误(如`x += 1`应改为`x = x + 1`)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值