如何在Pytorch中进行渐变裁剪?

Gul*_*zar 12 python machine-learning gradient-descent pytorch

在pytorch中执行梯度剪切的正确方法是什么?

我有一个爆炸性的渐变问题,我需要解决这个问题。

Rah*_*hul 17

一个更完整的例子

optimizer.zero_grad()        
loss, hidden = model(data, hidden, targets)
loss.backward()

torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip)
optimizer.step()
Run Code Online (Sandbox Code Playgroud)

来源:https : //github.com/pytorch/pytorch/issues/309

  • 什么是 args.clip? (15认同)
  • 这只是遵循一种流行的模式,可以在 loss.backward() 和 optimizationr.step() 之间插入 torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip) (10认同)
  • @FarhangAmaji 来自“args”的“max_norm”(裁剪阈值)值(可能来自“argparse”模块) (6认同)
  • 为什么这样更完整?我看到更多的选票,但不太明白为什么这更好。你能解释一下吗? (3认同)

hkc*_*rex 13

如果您使用自动混合精度 (AMP),则需要在剪切之前执行更多操作,因为 AMP 会缩放渐变:

optimizer.zero_grad()
loss = model(data, targets)
scaler.scale(loss).backward()

# Unscales the gradients of optimizer's assigned params in-place
scaler.unscale_(optimizer)

# Since the gradients of optimizer's assigned params are unscaled, clips as usual:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)

# optimizer's gradients are already unscaled, so scaler.step does not unscale them,
# although it still skips optimizer.step() if the gradients contain infs or NaNs.
scaler.step(optimizer)

# Updates the scale for next iteration.
scaler.update()
Run Code Online (Sandbox Code Playgroud)

参考:https://pytorch.org/docs/stable/notes/amp_examples.html#gradient-clipping


Gul*_*zar 10

通读论坛讨论给出了这个:

clipping_value = 1 # arbitrary value of your choosing
torch.nn.utils.clip_grad_norm(model.parameters(), clipping_value)
Run Code Online (Sandbox Code Playgroud)

我确信它比仅此代码片段更深入。


a_g*_*est 10

clip_grad_norm(实际上,不建议使用它,而是在执行就地修改时clip_grad_norm_遵循尾随的更一致的语法),通过将传递给函数的所有参数进行连接来裁剪整个渐变_的范数,如从文档中可以看出:

范数是在所有梯度上一起计算的,就好像它们被串联到单个矢量中一样。渐变就地修改。

在您的示例中,看起来像您想要的clip_grad_value_,它具有类似的语法,并且可以就地修改渐变:

clip_grad_value_(model.parameters(), clip_value)
Run Code Online (Sandbox Code Playgroud)

另一种选择是注册一个向后挂钩。这将当前渐变作为输入,并可能返回一个张量,该张量将代替先前的渐变使用,即对其进行修改。每次计算完梯度后都会调用此钩子,即,在钩子注册后就无需手动裁剪:

for p in model.parameters():
    p.register_hook(lambda grad: torch.clamp(grad, -clip_value, clip_value))
Run Code Online (Sandbox Code Playgroud)

  • 这里值得一提的是,这两种方法并不等同。后一种注册钩子的方法绝对是大多数人想要的。这两种方法之间的区别在于,后一种方法在反向传播期间剪辑梯度,而第一种方法在整个反向传播发生后剪辑梯度。 (20认同)
  • @NikSp如果在反向传播过程中进行剪辑,则剪辑的梯度会传播到上游层。否则,原始梯度会向上游传播,这可能会使这些上游层的梯度饱和(如果在反向传播之后执行裁剪)。如果所有层的梯度在阈值(剪辑)处饱和,这可能会阻止收敛。 (10认同)
  • 为什么我们要在反向传播期间而不是之后修剪梯度?试图理解为什么后者比第一个更可取。 (7认同)