Pytorch, quels sont les arguments du gradient


112

Je lis la documentation de PyTorch et j'ai trouvé un exemple où ils écrivent

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

où x était une variable initiale, à partir de laquelle y a été construit (un vecteur 3). La question est, quels sont les arguments 0,1, 1,0 et 0,0001 du tenseur de gradients? La documentation n'est pas très claire à ce sujet.

Réponses:


15

Le code original que je n'ai plus trouvé sur le site Web de PyTorch.

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

Le problème avec le code ci-dessus, il n'y a pas de fonction basée sur quoi calculer les gradients. Cela signifie que nous ne savons pas combien de paramètres (arguments la fonction prend) et la dimension des paramètres.

Pour bien comprendre cela, j'ai créé un exemple proche de l'original:

Exemple 1:

a = torch.tensor([1.0, 2.0, 3.0], requires_grad = True)
b = torch.tensor([3.0, 4.0, 5.0], requires_grad = True)
c = torch.tensor([6.0, 7.0, 8.0], requires_grad = True)

y=3*a + 2*b*b + torch.log(c)    
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients,retain_graph=True)    

print(a.grad) # tensor([3.0000e-01, 3.0000e+00, 3.0000e-04])
print(b.grad) # tensor([1.2000e+00, 1.6000e+01, 2.0000e-03])
print(c.grad) # tensor([1.6667e-02, 1.4286e-01, 1.2500e-05])

J'ai supposé que notre fonction est y=3*a + 2*b*b + torch.log(c)et que les paramètres sont des tenseurs avec trois éléments à l'intérieur.

Vous pouvez penser gradients = torch.FloatTensor([0.1, 1.0, 0.0001])à ce que c'est l'accumulateur.

Comme vous pouvez l'entendre, le calcul du système d'autogradation PyTorch est équivalent au produit Jacobien.

Jacobien

Au cas où vous auriez une fonction, comme nous l'avons fait:

y=3*a + 2*b*b + torch.log(c)

Jacobien serait [3, 4*b, 1/c]. Cependant, ce Jacobien n'est pas la façon dont PyTorch fait les choses pour calculer les gradients à un certain point.

PyTorch utilise la différenciation automatique (AD) en mode passe avant et arrière en tandem.

Il n'y a pas de mathématiques symboliques impliquées et aucune différenciation numérique.

La différenciation numérique serait de calculer δy/δb, pour b=1et b=1+εoù ε est petit.

Si vous n'utilisez pas de dégradés dans y.backward():

Exemple 2

a = torch.tensor(0.1, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(0.1, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward()

print(a.grad) # tensor(3.)
print(b.grad) # tensor(4.)
print(c.grad) # tensor(10.)

Vous simplement obtenir le résultat à un point, en fonction de la façon dont vous définissez votre a, b, ctenseurs initialement.

Soyez prudent lorsque vous initialisez votre a, b, c:

Exemple 3:

a = torch.empty(1, requires_grad = True, pin_memory=True)
b = torch.empty(1, requires_grad = True, pin_memory=True)
c = torch.empty(1, requires_grad = True, pin_memory=True)

y=3*a + 2*b*b + torch.log(c)

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)

print(a.grad) # tensor([3.3003])
print(b.grad) # tensor([0.])
print(c.grad) # tensor([inf])

Si vous utilisez torch.empty()et n'utilisez pas, pin_memory=Truevous pouvez avoir des résultats différents à chaque fois.

De plus, les dégradés de notes sont comme des accumulateurs, alors mettez-les à zéro si nécessaire.

Exemple 4:

a = torch.tensor(1.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(1.0, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward(retain_graph=True)
y.backward()

print(a.grad) # tensor(6.)
print(b.grad) # tensor(8.)
print(c.grad) # tensor(2.)

Enfin, quelques conseils sur les conditions d'utilisation de PyTorch:

PyTorch crée un graphe de calcul dynamique lors du calcul des gradients en passe avant. Cela ressemble beaucoup à un arbre.

Ainsi, vous entendrez souvent que les feuilles de cet arbre sont des tenseurs d'entrée et la racine est un tenseur de sortie .

Les dégradés sont calculés en traçant le graphique de la racine à la feuille et en multipliant chaque dégradé de la manière en utilisant la règle de la chaîne . Cette multiplication se produit lors de la passe arrière.


Très bonne réponse! Cependant, je ne pense pas que Pytorch fasse une différenciation numérique ("Pour la fonction précédente, PyTorch ferait par exemple δy / δb, pour b = 1 et b = 1 + ε où ε est petit. Donc, il n'y a rien de tel que les mathématiques symboliques impliquées. ") - Je crois qu'il fait une différenciation automatique.
max_max_mir

Oui, il utilise AD, ou différenciation automatique, plus tard, j'ai étudié AD plus loin comme dans ce PDF , cependant, lorsque j'ai défini cette réponse, je n'étais pas tout à fait informé.
prosti

Par exemple, l'exemple 2 donne RuntimeError: Mismatch in shape: grad_output [0] a une forme de torch.Size ([3]) et la sortie [0] a une forme de torch.Size ([]).
Andreas K.

@AndreasK., Vous aviez raison, PyTorch a récemment introduit des tenseurs de taille zéro et cela a eu un impact sur mes exemples précédents. Supprimé car ces exemples n'étaient pas cruciaux.
prosti

100

Explication

Pour les réseaux de neurones, nous utilisons généralement losspour évaluer dans quelle mesure le réseau a appris à classer l'image d'entrée (ou d'autres tâches). Le lossterme est généralement une valeur scalaire. Afin de mettre à jour les paramètres du réseau, nous devons calculer le gradient de losswrt aux paramètres, qui se trouve leaf nodeen fait dans le graphe de calcul (au fait, ces paramètres sont principalement le poids et le biais de diverses couches telles que Convolution, Linear et bientôt).

Selon la règle de la chaîne, afin de calculer le gradient de losswrt à un nœud feuille, nous pouvons calculer la dérivée de losswrt une variable intermédiaire et le gradient de la variable intermédiaire par rapport à la variable feuille, faire un produit scalaire et additionner tout cela.

Les gradientarguments de Variablela backward()méthode a sont utilisés pour calculer une somme pondérée de chaque élément d'une variable par rapport à la variable feuille . Ce poids n'est que la dérivée de la valeur finale de losschaque élément de la variable intermédiaire.

Un exemple concret

Prenons un exemple concret et simple pour comprendre cela.

from torch.autograd import Variable
import torch
x = Variable(torch.FloatTensor([[1, 2, 3, 4]]), requires_grad=True)
z = 2*x
loss = z.sum(dim=1)

# do backward for first element of z
z.backward(torch.FloatTensor([[1, 0, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_() #remove gradient in x.grad, or it will be accumulated

# do backward for second element of z
z.backward(torch.FloatTensor([[0, 1, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# do backward for all elements of z, with weight equal to the derivative of
# loss w.r.t z_1, z_2, z_3 and z_4
z.backward(torch.FloatTensor([[1, 1, 1, 1]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# or we can directly backprop using loss
loss.backward() # equivalent to loss.backward(torch.FloatTensor([1.0]))
print(x.grad.data)    

Dans l'exemple ci-dessus, le résultat du premier printest

2 0 0 0
[torch.FloatTensor de taille 1x4]

qui est exactement la dérivée de z_1 par rapport à x.

Le résultat du second printest:

0 2 0 0
[torch.FloatTensor de taille 1x4]

qui est la dérivée de z_2 par rapport à x.

Maintenant, si vous utilisez un poids de [1, 1, 1, 1] pour calculer la dérivée de z par rapport à x, le résultat est 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx. Donc, sans surprise, la sortie de 3rd printest:

2 2 2 2
[torch.FloatTensor de taille 1x4]

Il convient de noter que le vecteur de poids [1, 1, 1, 1] est exactement dérivé de losswrt en z_1, z_2, z_3 et z_4. La dérivée de losswrt to xest calculée comme suit:

d(loss)/dx = d(loss)/dz_1 * dz_1/dx + d(loss)/dz_2 * dz_2/dx + d(loss)/dz_3 * dz_3/dx + d(loss)/dz_4 * dz_4/dx

Ainsi, la sortie du 4e printest la même que celle du 3e print:

2 2 2 2
[torch.FloatTensor de taille 1x4]


1
juste un doute, pourquoi calculons-nous x.grad.data pour les gradients de perte ou z.
Priyank Pathak

7
J'ai peut-être manqué quelque chose, mais je pense que la documentation officielle aurait vraiment pu gradientmieux expliquer l' argument. Merci pour votre réponse.
protagoniste

3
@jdhao « Il convient de noter que le vecteur de poids [1, 1, 1, 1]est exactement dérivé de lossWRT à z_1, z_2, z_3et z_4. » Je pense que cette déclaration est vraiment la clé de la réponse. Quand on regarde le code de l'OP, un grand point d'interrogation est d'où viennent ces nombres arbitraires (magiques) pour le dégradé. Dans votre exemple concret, je pense qu'il serait très utile de souligner tout de suite la relation entre le [1, 0, 0 0]tenseur par exemple et la lossfonction afin que l'on puisse voir que les valeurs ne sont pas arbitraires dans cet exemple.
a_guest

1
@smwikipedia, ce n'est pas vrai. Si nous nous développons loss = z.sum(dim=1), cela deviendra loss = z_1 + z_2 + z_3 + z_4. Si vous connaissez le calcul simple, vous saurez que la dérivée de losswrt à z_1, z_2, z_3, z_4est [1, 1, 1, 1].
jdhao

1
Je t'aime. J'ai résolu mon doute!
Black Jack 21

45

En règle générale, votre graphe de calcul a une sortie scalaire dit loss. Ensuite, vous pouvez calculer le gradient de losswrt les poids ( w) par loss.backward(). Où l'argument par défaut de backward()est 1.0.

Si votre sortie a plusieurs valeurs (par exemple loss=[loss1, loss2, loss3]), vous pouvez calculer les gradients de perte par rapport aux poids loss.backward(torch.FloatTensor([1.0, 1.0, 1.0])).

De plus, si vous souhaitez ajouter des poids ou des importances à différentes pertes, vous pouvez utiliser loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001])).

Cela signifie calculer -0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dwsimultanément.


1
"si vous voulez ajouter des poids ou des importances à différentes pertes, vous pouvez utiliser loss.backward (torch.FloatTensor ([- 0.1, 1.0, 0.0001]))." -> C'est vrai mais quelque peu trompeur car la raison principale pour laquelle nous passons grad_tensorsn'est pas de les peser différemment mais ce sont des gradients pour chaque élément des tenseurs correspondants.
Aerin

27

Ici, la sortie de forward (), c'est-à-dire y est un vecteur 3.

Les trois valeurs sont les gradients en sortie du réseau. Ils sont généralement définis sur 1.0 si y est la sortie finale, mais peuvent également avoir d'autres valeurs, surtout si y fait partie d'un plus grand réseau.

Pour par exemple. si x est l'entrée, y = [y1, y2, y3] est une sortie intermédiaire qui est utilisée pour calculer la sortie finale z,

Ensuite,

dz/dx = dz/dy1 * dy1/dx + dz/dy2 * dy2/dx + dz/dy3 * dy3/dx

Alors ici, les trois valeurs à remonter sont

[dz/dy1, dz/dy2, dz/dy3]

puis backward () calcule dz / dx


5
Merci pour la réponse mais en quoi est-ce utile dans la pratique? Je veux dire où avons-nous besoin de [dz / dy1, dz / dy2, dz / dy3] autre que le backprop à codage en dur?
salut15

Est-il correct de dire que l'argument gradient fourni est le gradient calculé dans la dernière partie du réseau?
Khanetor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.