Je lis la documentation de PyTorch et trouve 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 de savoir quels sont les arguments 0.1, 1.0 et 0.0001 du tenseur des gradients. La documentation n'est pas très claire à ce sujet.
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 de la fonction prend) ni la dimension des paramètres.
Pour bien comprendre cela, j'ai créé plusieurs exemples proches 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])
Comme vous pouvez le voir, je suppose que dans le premier exemple, notre fonction est y=3*a + 2*b*b + torch.log(c)
et les paramètres sont des tenseurs avec trois éléments à l'intérieur.
Mais il y a une autre option:
Exemple 2:
import torch
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)
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(a.grad) # tensor(3.3003)
print(b.grad) # tensor(4.4004)
print(c.grad) # tensor(1.1001)
La gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
est l'accumulateur.
L'exemple suivant fournirait des résultats identiques.
Exemple 3:
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)
gradients = torch.FloatTensor([0.1])
y.backward(gradients,retain_graph=True)
gradients = torch.FloatTensor([1.0])
y.backward(gradients,retain_graph=True)
gradients = torch.FloatTensor([0.0001])
y.backward(gradients)
print(a.grad) # tensor(3.3003)
print(b.grad) # tensor(4.4004)
print(c.grad) # tensor(1.1001)
Comme vous le savez peut-être, le calcul du système PyTorch Autograd est équivalent au produit Jacobien.
Si vous avez une fonction, comme nous l'avons fait:
y=3*a + 2*b*b + torch.log(c)
Jacobian serait [3, 4*b, 1/c]
. Cependant, ce Jacobian n’est pas ainsi que PyTorch fait les choses pour calculer les gradients à un moment donné.
Pour la fonction précédente, PyTorch ferait par exemple δy/δb
, Pour b=1
Et b=1+ε
Où ε est petit. Il n'y a donc rien de tel que les mathématiques symboliques impliquées.
Si vous n'utilisez pas de dégradés dans y.backward()
:
Exemple 4
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 obtiendrez simplement le résultat à un moment donné, en fonction de la façon dont vous définissez vos tenseurs a
, b
, c
initialement.
Faites attention à la façon dont vous initialisez votre a
, b
, c
:
Exemple 5:
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=True
, Vous obtiendrez des résultats différents à chaque fois.
De plus, les gradients de note sont comme des accumulateurs, donc remettez-les à zéro si nécessaire.
Exemple 6:
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, je voulais juste énoncer quelques termes utilisés par PyTorch:
PyTorch crée un graphe de calcul dynamique lors du calcul des gradients. Cela ressemble beaucoup à un arbre.
Ainsi, vous entendrez souvent les feuilles de cet arbre sont des tenseurs d'entrée et le racine est tenseur de sortie .
Les gradients sont calculés en traçant le graphique de la racine à la feuille et en multipliant chaque dégradé de la même manière à l'aide de la règle de chaîne .
Pour les réseaux de neurones, nous utilisons généralement loss
pour évaluer dans quelle mesure le réseau a appris à classer l'image d'entrée (ou d'autres tâches). Le terme loss
est généralement une valeur scalaire. Afin de mettre à jour les paramètres du réseau, nous devons calculer le gradient de loss
par rapport aux paramètres, qui est en réalité leaf node
Dans le graphe de calcul (au fait, ces paramètres sont principalement le poids et le biais de diverses couches telles que convolution, linéaire, etc.).
Selon la règle de la chaîne, afin de calculer le gradient de loss
wrt sur un nœud feuille, nous pouvons calculer la dérivée de loss
par rapport à une variable intermédiaire et le gradient de la variable intermédiaire wrt à la variable feuille, faire un produit scalaire et résumer tout cela.
Les arguments gradient
de la méthode backward()
de Variable
est utilisée pour calculer une somme pondérée de chaque élément d'une variable par rapport à Variable feuille . Ce poids est simplement le dérivé de final loss
par rapport à chaque élément de la variable intermédiaire.
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 print
est
2 0 0 0
[torche.Tenseur Float de taille 1x4]
qui est exactement la dérivée de z_1 w.r.t à x.
Le résultat de la seconde print
est:
0 2 0 0
[torche.Tenseur Float de taille 1x4]
qui est la dérivée de z_2 w.r.t à x.
Maintenant, si vous utilisez un poids de [1, 1, 1, 1] pour calculer la dérivée de z w.r.t à x, le résultat est 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx
. Donc, pas surprenant, le résultat de la 3ème print
est:
2 2 2 2
[torche.Tenseur Float de taille 1x4]
Il convient de noter que le vecteur de poids [1, 1, 1, 1] est exactement dérivé de loss
w.r.t à z_1, z_2, z_3 et z_4. Le dérivé de loss
w.r.t à x
est calculé 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 4ème print
est identique à celle du 3ème print
:
2 2 2 2
[torche.Tenseur Float de taille 1x4]
Typiquement, votre graphique de calcul a une sortie scalaire qui dit loss
. Ensuite, vous pouvez calculer le gradient de loss
w.r.t. les poids (w
) par loss.backward()
. Où l'argument par défaut de backward()
est 1.0
.
Si votre sortie comporte plusieurs valeurs (par exemple, loss=[loss1, loss2, loss3]
), Vous pouvez calculer les gradients de perte w.r.t. les poids par 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)/dw
simultanément.
Ici, la sortie de forward (), c.-à-d. Y est un vecteur 3.
Les trois valeurs sont les gradients à la 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, en particulier si y fait partie d'un réseau plus grand.
Pour par exemple. si x est l'entrée, y = [y1, y2, y3] est une sortie intermédiaire 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 à reculer sont
[dz/dy1, dz/dy2, dz/dy3]
puis backward () calcule dz/dx