Pytorch, was sind die Gradientenargumente?


112

Ich lese die Dokumentation von PyTorch durch und habe ein Beispiel gefunden, in dem sie schreiben

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

Dabei war x eine Anfangsvariable, aus der y konstruiert wurde (ein 3-Vektor). Die Frage ist, was sind die Argumente 0.1, 1.0 und 0.0001 des Gradiententensors? Die Dokumentation ist dazu nicht sehr klar.

Antworten:


15

Den Originalcode habe ich auf der PyTorch-Website nicht mehr gefunden.

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

Das Problem mit dem obigen Code gibt es keine Funktion basierend auf der Berechnung der Gradienten. Dies bedeutet, dass wir nicht wissen, wie viele Parameter (Argumente die Funktion akzeptiert) und welche Dimension von Parametern.

Um dies vollständig zu verstehen, habe ich ein Beispiel erstellt, das dem Original nahe kommt:

Beispiel 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])

Ich nahm an, dass unsere Funktion ist y=3*a + 2*b*b + torch.log(c)und die Parameter Tensoren mit drei Elementen im Inneren sind.

Sie können sich vorstellen, gradients = torch.FloatTensor([0.1, 1.0, 0.0001])dass dies der Akkumulator ist.

Wie Sie vielleicht hören, entspricht die Berechnung des PyTorch-Autograd-Systems dem Jacobian-Produkt.

Jacobian

Falls Sie eine Funktion haben, wie wir es getan haben:

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

Jacobian wäre [3, 4*b, 1/c]. Mit diesem Jacobi berechnet PyTorch jedoch nicht die Gradienten an einem bestimmten Punkt.

PyTorch verwendet die automatische Differenzierung im Vorwärts- und Rückwärtsmodus (AD) im .

Es gibt keine symbolische Mathematik und keine numerische Differenzierung.

Die numerische Differenzierung wäre zu berechnen δy/δb, für b=1und b=1+εwo ε klein ist.

Wenn Sie keine Farbverläufe verwenden in y.backward():

Beispiel 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.)

Sie werden das Ergebnis an einem Punkt einfach bekommen, je nachdem , wie Sie setzten Ihre a, b, cTensoren zunächst.

Seien Sie vorsichtig , wie Sie initialisieren Ihre a, b, c:

Beispiel 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])

Wenn Sie verwenden torch.empty()und nicht verwenden pin_memory=True, können Sie jedes Mal andere Ergebnisse erzielen.

Notenverläufe sind auch wie Akkumulatoren, also setzen Sie sie bei Bedarf auf Null.

Beispiel 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.)

Zuletzt einige Tipps zu Begriffen, die PyTorch verwendet:

PyTorch erstellt bei der Berechnung der Gradienten im Vorwärtsdurchlauf ein dynamisches Berechnungsdiagramm . Das sieht einem Baum sehr ähnlich.

So werden Sie oft die hören Blätter dieses Baumes sind Eingangs Tensoren und die Wurzel ist Ausgangs Tensor .

Farbverläufe werden berechnet, indem das Diagramm von der Wurzel bis zum Blatt verfolgt und jeder Farbverlauf mithilfe der Kettenregel multipliziert wird . Diese Multiplikation erfolgt im Rückwärtsdurchlauf.


Gute Antwort! Ich glaube jedoch nicht, dass Pytorch eine numerische Differenzierung vornimmt ("Für die vorherige Funktion würde PyTorch beispielsweise δy / δb für b = 1 und b = 1 + ε ausführen, wobei ε klein ist. Es handelt sich also nicht um eine symbolische Mathematik. ") - Ich glaube, es macht eine automatische Differenzierung.
max_max_mir

Ja, es wird AD oder automatische Differenzierung verwendet. Später habe ich AD weiter untersucht, wie in diesem PDF . Als ich diese Antwort einstellte, war ich jedoch nicht ganz informiert.
Prosti

Beispiel 2 gibt RuntimeError an: Nicht übereinstimmende Form: grad_output [0] hat die Form von torch.Size ([3]) und output [0] hat die Form von torch.Size ([]).
Andreas K.

@AndreasK., Sie hatten Recht, PyTorch hat kürzlich Tensoren mit der Größe Null eingeführt, und dies hatte Auswirkungen auf meine vorherigen Beispiele. Entfernt, da diese Beispiele nicht entscheidend waren.
Prosti

100

Erläuterung

Bei neuronalen Netzen wird normalerweise lossbewertet, wie gut das Netzwerk gelernt hat, das Eingabebild (oder andere Aufgaben) zu klassifizieren. Der lossBegriff ist normalerweise ein Skalarwert. Um die Parameter des Netzwerks zu aktualisieren, müssen wir den Gradienten von losswrt zu den Parametern berechnen , der sich tatsächlich leaf nodeim Berechnungsdiagramm befindet (diese Parameter sind übrigens meistens das Gewicht und die Vorspannung verschiedener Schichten wie Convolution, Linear und demnächst).

Gemäß der Kettenregel losskönnen wir , um den Gradienten von wrt zu einem Blattknoten zu berechnen, die Ableitung von losswrt einer Zwischenvariablen und den Gradienten von intermediärer Variable wrt zu der Blattvariablen berechnen, ein Punktprodukt erstellen und alle diese zusammenfassen.

Die gradientArgumente eines Variable‚s backward()Verfahren wird verwendet , um eine gewichtete Summe jedes Element einer Variable WRT dem CALCULATE Blatt Variable . Dieses Gewicht ist nur die Ableitung des lossEndes jedes Elements der Zwischenvariablen.

Ein konkretes Beispiel

Nehmen wir ein konkretes und einfaches Beispiel, um dies zu verstehen.

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)    

In dem obigen Beispiel wird das Ergebnis der ersten printIS

2 0 0 0
[torch.FloatTensor der Größe 1x4]

Das ist genau die Ableitung von z_1 wrt zu x.

Das Ergebnis der zweiten printist:

0 2 0 0
[torch.FloatTensor der Größe 1x4]

Das ist die Ableitung von z_2 wrt zu x.

Wenn Sie nun ein Gewicht von [1, 1, 1, 1] verwenden, um die Ableitung von z wrt zu x zu berechnen, ist das Ergebnis 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx. Kein Wunder also, dass die Ausgabe von 3rd printlautet:

2 2 2 2
[Fackel.FloatTensor der Größe 1x4]

Es ist zu beachten, dass der Gewichtsvektor [1, 1, 1, 1] genau von losswrt zu z_1, z_2, z_3 und z_4 abgeleitet ist. Die Ableitung von losswrt to xwird berechnet als:

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

Die Ausgabe von 4th printist also dieselbe wie die von 3rd print:

2 2 2 2
[Fackel.FloatTensor der Größe 1x4]


1
Nur ein Zweifel, warum berechnen wir x.grad.data für Gradienten für Verlust oder z.
Priyank Pathak

7
Vielleicht habe ich etwas verpasst, aber ich habe das Gefühl, dass die offizielle Dokumentation das gradientArgument wirklich besser hätte erklären können . Danke für deine Antwort.
Protagonist

3
@jdhao „Es ist zu beachten , dass der Gewichtsvektor [1, 1, 1, 1]ist genau das Derivat von lossWRT z_1, z_2, z_3und z_4.“ Ich denke, diese Aussage ist wirklich der Schlüssel zur Antwort. Wenn man sich den Code des OP ansieht, ist ein großes Fragezeichen, woher diese willkürlichen (magischen) Zahlen für den Gradienten kommen. In Ihrem konkreten Beispiel halte ich es für sehr hilfreich, sofort auf die Beziehung zwischen dem [1, 0, 0 0]Tensor und der lossFunktion hinzuweisen, damit man sehen kann, dass die Werte in diesem Beispiel nicht willkürlich sind.
a_guest

1
@smwikipedia, das ist nicht wahr. Wenn wir expandieren loss = z.sum(dim=1), wird es loss = z_1 + z_2 + z_3 + z_4. Wenn Sie einfache lossBerechnungen kennen, wissen Sie, dass die Ableitung von wrt to z_1, z_2, z_3, z_4ist [1, 1, 1, 1].
JDHAO

1
Ich liebe dich. Meinen Zweifel gelöst!
Black Jack 21

45

Normalerweise hat Ihr Rechengraph eine skalare Ausgabe, sagt loss. Dann können Sie den Gradienten lossder Gewichte ( w) durch berechnen loss.backward(). Wo das Standardargument von backward()ist 1.0.

Wenn Ihre Ausgabe mehrere Werte hat (z. B. loss=[loss1, loss2, loss3]), können Sie die Verlustgradienten für die Gewichte berechnen loss.backward(torch.FloatTensor([1.0, 1.0, 1.0])).

Wenn Sie verschiedene Verluste mit Gewichten oder Wichtigkeiten versehen möchten, können Sie diese verwenden loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001])).

Dies bedeutet, -0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dwgleichzeitig zu berechnen .


1
"Wenn Sie verschiedenen Verlusten Gewichte oder Wichtigkeiten hinzufügen möchten, können Sie loss.backward (torch.FloatTensor ([- 0.1, 1.0, 0.0001])) verwenden." -> Dies ist wahr, aber etwas irreführend, da der Hauptgrund, warum wir bestehen, grad_tensorsnicht darin besteht, sie unterschiedlich zu wiegen, sondern Gradienten für jedes Element der entsprechenden Tensoren sind.
Aerin

27

Hier ist die Ausgabe von forward (), dh y, ein 3-Vektor.

Die drei Werte sind die Gradienten am Ausgang des Netzwerks. Sie werden normalerweise auf 1,0 gesetzt, wenn y die endgültige Ausgabe ist, können aber auch andere Werte haben, insbesondere wenn y Teil eines größeren Netzwerks ist.

Zum Beispiel. Wenn x die Eingabe ist, ist y = [y1, y2, y3] eine Zwischenausgabe, die zur Berechnung der endgültigen Ausgabe verwendet wird. z,

Dann,

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

Hier sind also die drei Werte für rückwärts

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

und dann berechnet backward () dz / dx


5
Danke für die Antwort, aber wie ist das in der Praxis nützlich? Ich meine, wo brauchen wir [dz / dy1, dz / dy2, dz / dy3] außer hartcodiertem Backprop?
Hi15

Ist es richtig zu sagen, dass das angegebene Gradientenargument der Gradient ist, der im letzten Teil des Netzwerks berechnet wurde?
Khanetor
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.