web-dev-qa-db-fra.com

Pourquoi devons-nous appeler explicitement zero_grad ()?

Pourquoi devons-nous mettre à zéro explicitement les gradients dans PyTorch? Pourquoi les gradients ne peuvent-ils pas être mis à zéro lorsque loss.backward() est appelée? Quel scénario est servi en conservant les gradients sur le graphique et en demandant à l'utilisateur de mettre à zéro explicitement les gradients?

38
Wasi Ahmad

Nous devons explicitement appeler zero_grad() car, après loss.backward() (lorsque les gradients sont calculés), nous devons utiliser optimizer.step() pour poursuivre la descente du gradient. Plus précisément, les gradients ne sont pas automatiquement mis à zéro car ces deux opérations, loss.backward() et optimizer.step(), sont séparées et optimizer.step() nécessite les gradients juste calculés.

De plus, parfois, nous devons accumuler le gradient entre certains lots; pour ce faire, nous pouvons simplement appeler backward plusieurs fois et optimiser une fois.

40
danche

J'ai un cas d'utilisation pour la configuration actuelle de PyTorch.

Si l'on utilise un réseau neuronal récurrent (RNN) qui fait des prédictions à chaque étape, on peut vouloir avoir un hyperparamètre qui permet d'accumuler des gradients dans le temps. Le fait de ne pas mettre à zéro les gradients à chaque pas de temps permet d'utiliser la rétropropagation dans le temps (BPTT) de manière intéressante et originale.

Si vous souhaitez plus d'informations sur les BPTT ou les RNN, consultez l'article Tutoriel sur les réseaux de neurones récurrents, Partie 3 - Rétropropagation dans le temps et gradients de disparition ou L'efficacité déraisonnable des réseaux de neurones récurrents .

5
twrichar

Laisser les dégradés en place avant d'appeler .step() est utile au cas où vous souhaiteriez accumuler le dégradé sur plusieurs lots (comme d'autres l'ont mentionné).

Il est également utile pour après appeler .step() au cas où vous souhaiteriez implémenter l'élan pour SGD, et diverses autres méthodes peuvent dépendre des valeurs du gradient de la mise à jour précédente.

2

Il y a un cycle à PyTorch:

  • Transférer lorsque nous obtenons la sortie ou y_hat De l'entrée,
  • Calcul de la perte où loss = loss_fn(y_hat, y)
  • loss.backward Lorsque nous calculons les gradients
  • optimizer.step Lorsque nous mettons à jour les paramètres

Ou en code:

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()

Si nous ne supprimions pas les dégradés après le optimizer.step, Qui est l'étape appropriée ou juste avant les backward() les gradients suivants s'accumuleraient. Voici un exemple montrant l'accumulation:

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() n'a aucun moyen de le spécifier.

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

Parmi toutes les options que vous pouvez spécifier, il n'y a aucun moyen de mettre à zéro les dégradés manuellement. Comme ceci dans le mini exemple précédent:

w.grad.zero_()

Il y a eu une discussion sur la façon de faire zero_grad() à chaque fois avec backward() (évidemment les gradients précédents) et de garder les notes avec preserve_grads=True , mais cela n'a jamais a pris vie.

2
prosti