ist nicht eine weitere Erklärung der Kettenregel. Es ist eine Tour durch die weird Seite von Autograd – wo Gradienten Physik dienen, nicht nur Gewichte
Ich habe dieses Tutorial ursprünglich im ersten Jahr meines Doktoranden für mich selbst geschrieben und in Pytorch durch die Feinheiten von Gradientenberechnungen navigiert. Das meiste davon ist eindeutig mit Blick auf die Normal -Backpropagation konzipiert – und das ist in Ordnung, da das die meisten Menschen das brauchen.
Aber physikalisches neuronales Netzwerk (PINN) ist ein launisches Tier und braucht eine andere Artwork von Gradientenlogik. Ich habe einige Zeit damit verbracht, es zu füttern und dachte, es könnte sich lohnen, die Ergebnisse mit der Group zu teilen, insbesondere mit anderen Pinn -Praktizierenden – vielleicht spart es jemandem ein paar Kopfschmerzen. Aber wenn Sie noch nie von Pinns gehört haben, machen Sie sich keine Sorgen! Dieser Beitrag ist immer noch für Sie – besonders wenn Sie sich mit Dingen wie Gradienten und all dem lustigen Zeug befassen.
Grundlagen
Tensor In der Computerwelt bedeutet einfach ein mehrdimensionales Array, dh eine Reihe von Zahlen, die von einer oder mehreren Ganzzahlen indiziert werden. Um genau zu sein, gibt es auch nulldimensionale Tensoren, die nur einzelne Zahlen sind. Einige Leute sagen, dass Tensoren eine Verallgemeinerung von Matrizen auf mehr als zwei Dimensionen sind.
Wenn Sie bereits allgemeine Relativitätstheorie studiert haben, haben Sie vielleicht gehört, dass mathematische Tensoren solche Dinge wie kovariante und kontravariante Indizes haben. Aber vergessen Sie es – in Pytorch -Tensoren sind nur mehrdimensionale Arrays. Keine Finesse hier.
Blattzensor ist ein Tensor, der ein Blatt (im Sinne einer Graphentheorie) eines Berechnungsdiagramms ist. Wir werden uns diese unten ansehen, daher wird diese Definition etwas sinnvoller sein.
Der requires_grad Die Eigenschaft eines Tensors sagt Pytorch, ob er sich daran erinnern sollte, wie dieser Tensor in weiteren Berechnungen verwendet wird. Denken Sie vorerst an Tensoren mit requires_grad=True als Variablen, während Tensoren mit requires_grad=False als Konstanten.
Blatttensoren
Beginnen wir damit, ein paar Tensoren zu erstellen und ihre Eigenschaften zu überprüfen requires_grad Und is_leaf.
import torch
a = torch.tensor((3.), requires_grad=True)
b = a * a
c = torch.tensor((5.))
d = c * c
assert a.requires_grad is True and a.is_leaf is True
assert b.requires_grad is True and b.is_leaf is False
assert c.requires_grad is False and c.is_leaf is True
assert d.requires_grad is False and d.is_leaf is True # sic!
del a, b, c, d
a ist wie erwartet ein Blatt und b ist nicht daran, dass es ein Ergebnis einer Multiplikation ist. a ist so eingestellt, dass er einen Abschluss benötigt, additionally natürlich b erbt diese Eigenschaft.
c ist offensichtlich ein Blatt, aber warum d Ist ein Blatt? Der Grund d.is_leaf ist wahr aus einer bestimmten Konvention: alle Tensoren mit requires_grad auf false eingestellt werden als Blatttensoren wie per als Pytorchs Dokumentation:
Alle Tensoren, die haben
requires_graddas istFalsewird durch Konvention Blatttensoren sein.
Während mathematisch, d ist kein Blatt (da es sich aus einer anderen Operation ergibt, c * c), Gradientenberechnung wird sich niemals über sie erstrecken. Mit anderen Worten, es wird kein Derivat in Bezug auf c. Dies erlaubt d als Blatt behandelt werden.
Kurz gesagt, in Pytorch sind Blatttenoren beider:
- Direkt eingegeben (dh nicht von anderen Tensoren berechnet) und haben
requires_grad=True. Beispiel: Neuronale Netzwerkgewichte, die zufällig initialisiert werden. - Erfordern Sie überhaupt keine Gradienten, unabhängig davon, ob sie direkt eingegeben oder berechnet werden. In den Augen von Autograd sind dies nur Konstanten. Beispiele:
- Alle neuronalen Netzwerkeingabedaten,
- Ein Eingabebild nach mittlerer Entfernung oder anderen Operationen, bei denen nur Tensoren für nicht-Gradient-Erregung beteiligt sind.
Eine kleine Bemerkung für diejenigen, die mehr wissen wollen. Der requires_grad Eigenschaft wird wie hier illustriert vererbt:
a = torch.tensor((5.), requires_grad=True)
b = torch.tensor((5.), requires_grad=True)
c = torch.tensor((5.), requires_grad=False)
d = torch.sin(a * b * c)
assert d.requires_grad == any((x.requires_grad for x in (a, b, c)))
Code-Bemerkung: Alle Code-Snippets sollten in sich geschlossen werden, mit Ausnahme von Importen, die ich nur dann einbezieht, wenn sie zum ersten Mal erscheinen. Ich lasse sie fallen, um den Boilerplate -Code zu minimieren. Ich vertraue darauf, dass der Leser in der Lage sein wird, sich leicht um diese zu kümmern.
Abschlussretention
Ein separates Downside ist die Gradientenaufbewahrung. Alle Knoten im Berechnungsdiagramm, dh alle verwendeten Tensoren, haben Gradienten berechnet, wenn sie einen Abschluss benötigen. Es behalten jedoch nur Blatttensoren diese Gradienten. Dies ist sinnvoll, da Gradienten normalerweise zum Aktualisieren von Tensoren verwendet werden und nur Blatttensoren während des Trainings aktualisiert werden. Nicht-Blatt-Tensoren wie b Im ersten Beispiel werden nicht direkt aktualisiert; Sie verändern sich infolge von Änderungen in aso können ihre Gradienten verworfen werden. Es gibt jedoch Szenarien, insbesondere in physikalischen neuronalen Netzwerken (PINNs), in denen Sie möglicherweise die Gradienten dieser Zwischenzensoren beibehalten möchten. In solchen Fällen müssen Sie explizit Nicht-Blatt-Tensoren markieren, um ihre Gradienten zu halten. Mal sehen:
a = torch.tensor((3.), requires_grad=True)
b = a * a
b.backward()
assert a.grad shouldn't be None
assert b.grad is None # generates a warning
Sie haben wahrscheinlich gerade eine Warnung gesehen:
UserWarning: The .grad attribute of a Tensor that's not a leaf Tensor is being
accessed. Its .grad attribute will not be populated throughout autograd.backward().
Should you certainly need the .grad area to be populated for a non-leaf Tensor, use
.retain_grad() on the non-leaf Tensor. Should you entry the non-leaf Tensor by
mistake, be sure to entry the leaf Tensor as an alternative.
See github.com/pytorch/pytorch/pull/30531 for extra informations.
(Triggered internally at atensrcATen/core/TensorBody.h:491.)
Lasst es uns additionally beheben, indem wir erzwingen b seinen Gradienten beibehalten
a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad() # <- the distinction
b.backward()
assert a.grad shouldn't be None
assert b.grad shouldn't be None
Geheimnisse von Grad
Schauen wir uns nun den berühmten Absolventen selbst an. Was ist das? Ist es ein Tensor? Wenn ja, ist es ein Blatt -Tensor? Benötigt oder behält es Grad?
a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward()
assert isinstance(a.grad, torch.Tensor)
assert a.grad.requires_grad is False and a.grad.retains_grad is False and a.grad.is_leaf is True
assert b.grad.requires_grad is False and b.grad.retains_grad is False and b.grad.is_leaf is True
Scheinbar:
– – Grad selbst ist ein Tensor,
– – Grad ist ein Blattzensor,
– – Grad erfordert keinen Grad.
Beibehält es Grad? Diese Frage macht keinen Sinn, da sie überhaupt nicht einen Abschluss erfordert. Wir werden in einer Sekunde auf die Frage zurückkommen, dass der Abschluss ein Blatt -Tensor ist, aber jetzt werden wir ein paar Dinge testen.
Mehrfach rückwärts und retain_graph
Was wird passieren, wenn wir den gleichen Grad zweimal berechnen?
a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward()
attempt:
b.backward()
besides RuntimeError:
"""
RuntimeError: Making an attempt to backward via the graph a second time (or
immediately entry saved tensors after they've already been freed). Saved
intermediate values of the graph are freed whenever you name .backward() or
autograd.grad(). Specify retain_graph=True if you have to backward via
the graph a second time or if you have to entry saved tensors after
calling backward.
"""
Die Fehlermeldung erklärt alles. Dies sollte funktionieren:
a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward(retain_graph=True)
print(a.grad) # prints tensor((6.))
b.backward(retain_graph=True)
print(a.grad) # prints tensor((12.))
b.backward(retain_graph=False)
print(a.grad) # prints tensor((18.))
# b.backward(retain_graph=False) # <- right here we'd get an error, as a result of in
# the earlier name we didn't retain the graph.
Seite (aber wichtig) Hinweis: Sie können auch beobachten, wie sich der Gradient ansammelt a: Mit jeder Iteration wird es hinzugefügt.
Kraftvoll create_graph Argument
Wie erfordern ich einen Abschluss?
a = torch.tensor((5.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward(create_graph=True)
# Right here an fascinating factor occurs: now a.grad would require grad!
assert a.grad.requires_grad is True
assert a.grad.is_leaf is False
# Then again, the grad of b doesn't require grad, as beforehand.
assert b.grad.requires_grad is False
assert b.grad.is_leaf is True
Das obige ist sehr nützlich: a.grad Was mathematisch ( frac { partial b} { partial a} ) ist, ist keine Konstante (Blatt), sondern ein reguläres Mitglied des Rechendiagramms, das weiter verwendet werden kann. Wir werden diese Tatsache in Teil 2 verwenden.
Warum das b.grad erfordert keinen Abschluss? Weil Ableitung von b in Bezug auf b ist einfach 1.
Wenn der backward Fühlt sich jetzt nicht intuitiv für Sie, mach dir keine Sorgen. Wir werden bald zu einer anderen Methode namens Nomen Omen wechseln grad Dies ermöglicht es, die Zutaten der Derivate genau zu wählen. Vor zuvor zwei Seitennoten:
Randnotiz 1: Wenn Sie festlegen create_graph Für wahr ist es auch festgelegt retain_graph zu wahr (wenn nicht explizit eingestellt). Im Pytorch -Code sieht es genau so aus wie
Das:
if retain_graph is None:
retain_graph = create_graph
Randnotiz 2: Sie haben wahrscheinlich eine solche Warnung gesehen:
UserWarning: Utilizing backward() with create_graph=True will create a reference
cycle between the parameter and its gradient which may trigger a reminiscence leak.
We advocate utilizing autograd.grad when creating the graph to keep away from this. If
it's important to use this perform, be certain that to reset the .grad fields of your
parameters to None after use to interrupt the cycle and keep away from the leak.
(Triggered internally at C:cbpytorch_1000000000000worktorchcsrcautogradengine.cpp:1156.)
Variable._execution_engine.run_backward( # Calls into the C++ engine to
run the backward cross
Und wir werden den Rat folgen und verwenden autograd.grad Jetzt.
Ableitungen mit autograd.grad Funktion
Lassen Sie uns nun von der irgendwie hohen Ebene bewegen .backward() Methode zur niedrigeren Ebene grad Methode, die die Ableitung eines Tensors in Bezug auf einen anderen explizit berechnet.
from torch.autograd import grad
a = torch.tensor((3.), requires_grad=True)
b = a * a * a
db_da = grad(b, a, create_graph=True)(0)
assert db_da.requires_grad is True
In ähnlicher Weise wie bei backwarddie Ableitung von b in Bezug auf a kann als Funktion behandelt und weiter differenziert werden. Additionally mit anderen Worten die create_graph Die Flagge kann verstanden werden als: Halten Sie bei der Berechnung von Gradienten die Geschichte, wie sie berechnet wurden, damit sie als Nicht-Blatt-Tensoren behandeln, die Grad erfordern, und nutzen Sie weiter.
Insbesondere können wir Ableitung zweiter Ordnung berechnen:
d2b_da2 = grad(db_da, a, create_graph=True)(0)
# Facet be aware: the grad perform returns a tuple and the primary component of it's what we want.
assert d2b_da2.merchandise() == 18
assert d2b_da2.requires_grad is True
Wie bereits erwähnt: Dies ist eigentlich die wichtigste Eigenschaft, die es uns ermöglicht, mit Pytorch zu pinnieren.
Einpacken
Die meisten Tutorials über Pytorch -Gradienten konzentrieren sich auf die Backpropagation im klassischen überwachten Lernen. Dieser untersuchte eine andere Perspektive-eine, die von den Bedürfnissen von Pinns und anderen gradientenhungrigen Tieren geprägt ist.
Wir haben gelernt, welche Blätter im Pytorch -Dschungel sind, warum Gradienten nur für Blattknoten standardmäßig aufbewahrt werden und wie sie bei Bedarf für andere Tensoren beibehalten können. Wir haben gesehen, wie create_graph verwandelt Gradienten in differenzierbare Bürger der Autograd -Welt.
Es gibt jedoch noch viele Dinge zu entdecken-insbesondere, warum Gradienten nicht-Scalarar-Funktionen zusätzliche Sorgfalt erfordern, wie man Ableitungen zweiter Ordnung berechnet, ohne Ihren gesamten RAM zu verwenden elementweise Gradient.
Treffen wir uns additionally in Teil 2, wo wir uns genauer ansehen werden grad 👋
