wake-up-neo.com

Was bedeutet der Parameter retain_graph in der Methode backward () der Variablen?

Ich gehe durch das Tutorial für Neuronale Übertragung und bin verwirrt über die Verwendung von retain_variable (veraltet, jetzt bezeichnet als retain_graph). Das Codebeispiel zeigt:

class ContentLoss(nn.Module):

    def __init__(self, target, weight):
        super(ContentLoss, self).__init__()
        self.target = target.detach() * weight
        self.weight = weight
        self.criterion = nn.MSELoss()

    def forward(self, input):
        self.loss = self.criterion(input * self.weight, self.target)
        self.output = input
        return self.output

    def backward(self, retain_variables=True):
        #Why is retain_variables True??
        self.loss.backward(retain_variables=retain_variables)
        return self.loss

Von die Dokumentation

retain_graph (bool, optional) - Wenn False, wird das zur Berechnung des Grads verwendete Diagramm freigegeben. Beachten Sie, dass in fast allen Fällen das Setzen dieser Option auf "True" nicht erforderlich ist und häufig effizienter umgangen werden kann. Der Standardwert ist create_graph.

Also, indem Sie retain_graph= True, wir geben den für das Diagramm zugewiesenen Speicher beim Rückwärtsdurchlauf nicht frei. Was ist der Vorteil dieses Gedächtnisses, warum brauchen wir es?

21
jvans

@cleros ist ziemlich auf den Punkt über die Verwendung von retain_graph=True. Im Wesentlichen werden alle erforderlichen Informationen zur Berechnung einer bestimmten Variablen gespeichert, damit wir sie rückwärts weitergeben können.

Ein anschauliches Beispiel

enter image description here

Angenommen, wir haben ein oben gezeigtes Berechnungsdiagramm. Die Variablen d und e sind die Ausgabe und a ist die Eingabe. Beispielsweise,

import torch
from torch.autograd import Variable
a = Variable(torch.Rand(1, 4), requires_grad=True)
b = a**2
c = b*2
d = c.mean()
e = c.sum()

wenn wir d.backward() tun, ist das in Ordnung. Nach dieser Berechnung wird der Teil des Diagramms, der d berechnet, standardmäßig freigegeben, um Speicherplatz zu sparen. Wenn wir also e.backward() ausführen, wird die Fehlermeldung angezeigt. Um e.backward() auszuführen, müssen wir den Parameter retain_graph In d.backward() auf True setzen, d.h.

d.backward(retain_graph=True)

Solange Sie retain_graph=True In Ihrer Rückwärtsmethode verwenden, können Sie jederzeit rückwärts vorgehen:

d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!

Weitere nützliche Diskussion finden Sie hier .

Ein echter Anwendungsfall

Im Moment ist ein realer Anwendungsfall das Lernen mit mehreren Aufgaben, bei denen Sie mehrere Verluste haben, die sich möglicherweise auf verschiedenen Ebenen befinden. Angenommen, Sie haben 2 Verluste: loss1 Und loss2 Und sie befinden sich in verschiedenen Ebenen. Um die Steigung von loss1 Und loss2 Unabhängig voneinander auf das lernbare Gewicht Ihres Netzwerks zurückzusetzen. Sie müssen retain_graph=True In der backward() -Methode im ersten zurückpropagierten Verlust verwenden.

# suppose you first back-propagate loss1, then loss2 (you can also do the reverse)
loss1.backward(retain_graph=True)
loss2.backward() # now the graph is freed, and next process of batch gradient descent is ready
optimizer.step() # update the network parameters
32
jdhao

Dies ist eine sehr nützliche Funktion, wenn Sie mehr als einen Ausgang eines Netzwerks haben. Hier ist ein komplett durchdachtes Beispiel: Stellen Sie sich vor, Sie möchten ein zufälliges Faltungsnetzwerk aufbauen, dem Sie zwei Fragen stellen können: Enthält das Eingabebild eine Katze und enthält das Bild ein Auto?

Eine Möglichkeit, dies zu tun, besteht darin, ein Netzwerk zu haben, das die Faltungsebenen teilt, aber zwei parallele Klassifizierungsebenen hat, die folgen (verzeihen Sie mein schreckliches ASCII Grafik, aber es sollen drei Konvolutionsebenen folgen) durch drei vollständig verbundene Schichten (eine für Katzen und eine für Autos):

                    -- FC - FC - FC - cat?
Conv - Conv - Conv -|
                    -- FC - FC - FC - car?

Wenn wir ein Bild haben, auf dem wir beide Zweige betreiben möchten, können wir dies beim Training des Netzwerks auf verschiedene Arten tun. Zuerst (was hier wahrscheinlich das Beste ist, um zu veranschaulichen, wie schlecht das Beispiel ist) berechnen wir einfach einen Verlust für beide Einschätzungen und summieren den Verlust und propagieren dann zurück.

Es gibt jedoch ein anderes Szenario, in dem wir dies nacheinander tun möchten. Zuerst wollen wir durch einen Zweig und dann durch den anderen backpropen (ich hatte diesen Anwendungsfall schon einmal, daher ist er nicht vollständig erfunden). In diesem Fall werden durch Ausführen von .backward() in einem Diagramm auch alle Gradienteninformationen in den Faltungsschichten zerstört, und die Faltungsberechnungen des zweiten Zweigs (da dies die einzigen sind, die mit dem anderen Zweig geteilt werden) enthalten kein a Grafik mehr! Das bedeutet, dass Pytorch beim Backprop-Versuch durch den zweiten Zweig einen Fehler auslöst, da es keinen Graphen findet, der die Eingabe mit der Ausgabe verbindet! In diesen Fällen können wir das Problem lösen, indem wir das Diagramm einfach beim ersten Rückwärtsdurchlauf beibehalten. Der Graph wird dann nicht verbraucht, sondern nur beim ersten Rückwärtsdurchlauf, bei dem er nicht beibehalten werden muss.

BEARBEITEN: Wenn Sie das Diagramm bei allen Rückwärtsdurchläufen beibehalten, werden die impliziten Diagrammdefinitionen, die an die Ausgabevariablen angehängt sind, niemals freigegeben. Vielleicht gibt es auch hier einen nützlichen Fall, aber mir fällt keiner ein. Im Allgemeinen sollten Sie also sicherstellen, dass der letzte Rückwärtsdurchlauf den Speicher freigibt, indem Sie die Diagramminformationen nicht beibehalten.

Was passiert bei mehreren Rückwärtsläufen? Wie Sie vermutet haben, sammelt Pytorch Farbverläufe, indem Sie sie direkt hinzufügen (zu der Eigenschaft/parameters .grad Einer Variablen). Dies kann sehr nützlich sein, da das wiederholte Durchlaufen eines Stapels und das gleichzeitige Verarbeiten der Farbverläufe am Ende den gleichen Optimierungsschritt ausführt wie das vollständige stapelweise Aktualisieren (bei dem nur alle Farbverläufe als zusammengefasst werden) Gut). Während eine vollständige Stapelaktualisierung mehr parallelisiert werden kann und daher im Allgemeinen vorzuziehen ist, gibt es Fälle, in denen die Stapelberechnung entweder sehr, sehr schwierig zu implementieren oder einfach nicht möglich ist. Mit dieser Akkumulation können wir uns jedoch immer noch auf einige der stabilisierenden Eigenschaften von Nice verlassen, die das Dosieren mit sich bringt. (Wenn nicht auf die Leistungssteigerung)

11
cleros