Backpropagation
Wie der Fehler am Ende eines Netzes bis zum ersten Gewicht zurückwandert, warum das effizient möglich ist und was dabei im Speicher liegen bleibt.
Die Idee
Am Ende einer langen Fertigungskette liegt ein fehlerhaftes Teil. Sie gehen die Kette rückwärts durch und fragen an jeder Station: Wie viel von diesem Fehler kommt von hier? Wer viel beigetragen hat, muss sich am stärksten korrigieren.
Backpropagation macht genau das, nur mit Rechenschritten statt Stationen.
Von Hand nachgebaut
import numpy as np
def relu(x): return np.maximum(0, x)
def relu_grad(x): return (x > 0).astype(float)
rng = np.random.default_rng(0)
X = rng.normal(size=(64, 8)); y = rng.normal(size=(64, 1))
W1 = rng.normal(0, 0.1, (8, 32)); b1 = np.zeros(32)
W2 = rng.normal(0, 0.1, (32, 1)); b2 = np.zeros(1)Und die Schleife selbst. Jede Zeile im Rückwärtsteil ist genau ein Glied der Kettenregel.
for schritt in range(300):
# Vorwaerts - die Zwischenwerte z1 und a1 muessen aufgehoben werden.
z1 = X @ W1 + b1; a1 = relu(z1)
z2 = a1 @ W2 + b2
verlust = float(((z2 - y) ** 2).mean())
# Rueckwaerts - jede Zeile ist ein Glied der Kettenregel.
dz2 = 2 * (z2 - y) / len(X)
dW2 = a1.T @ dz2; db2 = dz2.sum(0)
da1 = dz2 @ W2.T
dz1 = da1 * relu_grad(z1)
dW1 = X.T @ dz1; db1 = dz1.sum(0)
for p, g in ((W1, dW1), (b1, db1), (W2, dW2), (b2, db2)):
p -= 0.05 * g
if schritt % 100 == 0:
print(schritt, round(verlust, 4))Typische Fehler
- Aktivierungen im Vorwärtsdurchlauf überschreiben und im Rückwärtsweg vermissen.
- Batch-Dimension bei
sum(0)vergessen, wodurch die Bias-Gradienten falsch werden. - Den Gradienten nicht durch die Batch-Größe teilen, wodurch die Lernrate von
|B|abhängt. no_gradbeim Auswerten vergessen, wodurch der Graph unnötig aufgebaut wird.
Warum rückwärts und nicht vorwärts
Bei n = 7e9 Parametern und m = 1 Verlustwert ist das der Unterschied zwischen
sieben Milliarden Durchläufen und einem. Das ist der ganze Grund, warum es
Backpropagation heißt und nicht Forwardpropagation.
Der Speicherbedarf
Durchgerechnet für b = 8, s = 4096, h = 4096, a = 32 in bfloat16:
der lineare Anteil liegt bei rund 268 MB je Schicht mit k₁ ≈ 2, der
quadratische Anteil bei 8 × 32 × 4096² × 2 Byte ≈ 8,6 GB je Schicht. Der
zweite Term dominiert vollständig, und genau deshalb ist FlashAttention, das
diese Matrix nie vollständig materialisiert, kein Detail, sondern die
Voraussetzung für lange Kontexte. Siehe
Das Kontextfenster, technisch.
Gradient Checkpointing
Speichert man nur jede √L-te Schicht von L Schichten und berechnet den Rest
neu, sinkt der Aktivierungsspeicher von O(L) auf O(√L) bei etwa 30 Prozent
Mehrrechenzeit. Bei 80 Schichten heißt das: statt 80 gespeicherter Schichten nur
noch 9. Das ist der Unterschied zwischen „passt nicht" und „passt".
Passende Kurse und Quellen
Deep Learning Specialization
Fünf Kurse von den Grundlagen neuronaler Netze bis zu Sequenzmodellen. Ausführlich, mit Programmierübungen, und in Teilen älter als die aktuelle Praxis.
Für alle mit Programmierkenntnissen, die den Bereich vollständig durcharbeiten wollen.
Neuronale Netze, visuell erklärt
Von der einzelnen Gewichtung über Gradientenabstieg bis zum Aufmerksamkeitsmechanismus. Die beste verfügbare Anschauung für das, was die Formeln beschreiben.
Vor dem ersten Lehrbuch anzusehen, nicht danach. Spart Wochen an Verwirrung.
PyTorch-Tutorials
Die offiziellen Anleitungen, von der ersten Tensoroperation bis zum verteilten Training. Kurz, lauffähig und laufend aktualisiert.
Für den Einstieg in die Bibliothek, in der der Großteil der Forschung geschrieben ist.