Почему мы должны явно вызывать zero_grad()?

Почему мы должны явно обнулять градиенты в PyTorch? Почему нельзя loss.backward() градиенты при loss.backward()? Какой сценарий выполняется, если оставить градиенты на графике и попросить пользователя явно обнулить градиенты?

Ответ 1

Нам явно нужно вызвать zero_grad() потому что после loss.backward() (когда вычисляются градиенты) нам нужно использовать optimizer.step() для продолжения спуска градиента. Более конкретно, градиенты не обнуляются автоматически, потому что эти две операции, loss.backward() и optimizer.step(), разделены, а optimizer.step() требует только что вычисленных градиентов.

Кроме того, иногда нам нужно накапливать градиент между некоторыми партиями; чтобы сделать это, мы можем просто вызвать backward несколько раз и оптимизировать один раз.

Ответ 2

У меня есть вариант использования для текущей настройки в PyTorch.

Если вы используете рекуррентную нейронную сеть (RNN), которая делает прогнозы на каждом шаге, вам может потребоваться гиперпараметр, позволяющий накапливать градиенты во времени. Не обнуление градиентов на каждом временном шаге позволяет использовать обратное распространение во времени (BPTT) интересными и новыми способами.

Если вам нужна дополнительная информация о BPTT или RNN, см. Статью " Учебник по рекуррентным нейронным сетям", часть 3 "Обратное распространение во времени и исчезающие градиенты" или "Неоправданная эффективность рекуррентных нейронных сетей".

Ответ 3

Оставлять градиенты на месте перед вызовом .step() полезно, если вы хотите накапливать градиент в нескольких пакетах (как уже упоминали другие).

Это также полезно после вызова .step() если вы хотите реализовать импульс для SGD, и различные другие методы могут зависеть от значений из предыдущего градиента обновления.

Ответ 4

В PyTorch есть цикл:

  • Вперед, когда мы получим вывод или y_hat из ввода,
  • Расчет потерь, где loss = loss_fn(y_hat, y)
  • loss.backward когда мы вычисляем градиенты
  • optimizer.step когда мы обновляем параметры

Или в коде:

for mb in range(10): # 10 mini batches
    y_pred = model(x)
    loss = loss_fn(y_pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

Если мы не очистим градиенты после optimizer.step, который является подходящим шагом или непосредственно перед тем, как будут накапливаться следующие backward() градиенты. Вот пример, показывающий накопление:

import torch
w = torch.rand(5)
w.requires_grad_()
print(w) 
s = w.sum() 
s.backward()
print(w.grad) # tensor([1., 1., 1., 1., 1.])
s.backward()
print(w.grad) # tensor([2., 2., 2., 2., 2.])
s.backward()
print(w.grad) # tensor([3., 3., 3., 3., 3.])
s.backward()
print(w.grad) # tensor([4., 4., 4., 4., 4.])

loss.backward() не имеет никакого способа указать это.

torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

Из всех опций, которые вы можете указать, нет никакого способа обнулить градиенты вручную. Вот так в предыдущем мини-примере:

w.grad.zero_()

Были некоторые дискуссии о том, чтобы каждый раз выполнять zero_grad() с помощью backward() (очевидно, предыдущих градиентов) и сохранять грады с preserve_grads=True, но это никогда не воплощалось в жизнь.