CH NEO-ZÜRICH AUSGABE
WETTER · KLAR 11°C
BLEND DES TAGES · 07/ROGUE
EST. 2027
DIE AEC CYBER MORGENZEITUNG

PAZ Kaffi

DESIGN · ABBRUCH · KOFFEIN · DEPESCHE
AUSGABE 1027 · 27 October 2026
SENDUNG 04:42 MEZ
2'400 BOGEN GEDRUCKT
LESEZEIT · 47 MIN
Backpropagation in 51 Zeilen Python – und befrage sie, wie viel eine Wand schief steht
Akademie
FRAME · 06:50
27-10-2026

Backpropagation in 51 Zeilen Python – und befrage sie, wie viel eine Wand schief steht

Bau eine kleine Autograd-Engine in Python, dann passe die Fassaden-Schieflage an: 2.08 mm pro Meter aus sechs Laser-Messungen.

Jedes neuronale Netzwerk, von dem du dieses Jahr gehört hast, lernt mit einem Trick. Es läuft die Kettenregel rückwärts durch einen Graphen von Zahlen und stellt jeder Zahl eine Frage: Wenn ich dich leicht anstupse, um wie viel würde sich der Fehler bewegen? Dieser Trick heisst Backpropagation. Du kannst ihn an einem Nachmittag in reines Python bauen, ohne etwas zu installieren, dann auf eine Fassade zeigen und fragen, um wie viel sie schief steht.

Die Idee ist älter als die meisten Büro-Server. Seppo Linnainmaa beschrieb reverse-mode automatic differentiation in seiner Master-These von 1970 in Helsinki, veröffentlicht in BIT 1976. Rumelhart et al. machten es zum Motor neuronaler Netzwerke in »Learning representations by back-propagating errors«, Nature 323, Oktober 1986. Neu ist, wie klein und lesbar die Lehr-Engine geworden ist.

Das Tool: micrograd, von Andrej Karpathy, auf GitHub seit 13. April 2020 unter der MIT-Lizenz. Der README sagt, es ist »a tiny Autograd engine«, das »backpropagation (reverse-mode autodiff) over a dynamically built DAG« implementiert und »potentially useful for educational purposes« ist. Die Engine hat 94 Zeilen, und ihre Tests prüfen jeden Gradienten gegen PyTorch als Referenz. Eine Lehr-Engine so klar zu schreiben ist echte Ingenieursarbeit: sie macht aus einem 1986er Paper der Nature etwas, das ein Designer auf einem Bildschirm lesen kann. Unten bauen wir micrograds Kernidee in 51 Zeilen neu und setzen sie auf eine Wand an.

←HEUTE: Im 2026 passt die Loop, die ein GPT trainiert, in eine Datei, die du bei einem Kaffee lesen kannst.
→3012: In Zürich-3012 öffnet sich das Plain-Text-Skript noch immer. Die versiegelten Black Boxes ihrer Zeit nicht.
Angelpunkt: Ein Modell, das du lesen kannst, ist eines, das du prüfen und reparieren kannst.

Setup: keine Installation, nur Python 3. Der Testlauf mit beiden Dateien unter Python 3.10 und Python 3.9, der Version im Rhino-8-ScriptEditor, zeigt identische Ergebnisse.

mkdir tiny-grad && cd tiny-grad
python --version
# tiny_grad.py und lean.py in diesen Ordner speichern, dann:
python lean.py
# optional: das Original lesen (engine.py hat 94 Zeilen)
git clone https://github.com/karpathy/micrograd

Erste Schritte:

  1. Die Engine unten als tiny_grad.py speichern.
  2. Im gleichen Ordner diesen Check ausführen, der klein genug ist, um von Hand zu prüfen:
    from tiny_grad import Value
    a, b = Value(2.0), Value(3.0)
    c = a * b + a          # c = 2*3 + 2 = 8
    c.backward()
    print(c.data, a.grad, b.grad)   # 8.0 4.0 2.0
    Es gibt 8.0 4.0 2.0 aus.
  3. Warum ist a.grad 4 und nicht 3? Die Variable a wird zweimal benutzt, in a*b und in +a, darum addieren sich ihre beiden Beiträge auf: 3 vom Multiplizieren plus 1 vom Addieren. Das erklärt das wichtigste Symbol in der Engine.

Der Code, erklärt: zuerst die Engine, dann die Wand.

# tiny_grad.py - eine kleine autograd Engine, micrograd-stil
class Value:
    def __init__(self, data, parents=(), op=""):
        self.data = data          # die Zahl selbst
        self.grad = 0.0           # d(loss)/d(diese Zahl), gefüllt durch backward()
        self._parents = parents   # die Values, aus denen diese gemacht wurde
        self._backward = lambda: None
        self.op = op

    def __add__(self, other):
        other = other if isinstance(other, Value) else Value(other)
        out = Value(self.data + other.data, (self, other), "+")
        def _backward():
            self.grad += out.grad          # d(a+b)/da = 1
            other.grad += out.grad         # d(a+b)/db = 1
        out._backward = _backward
        return out

    def __mul__(self, other):
        other = other if isinstance(other, Value) else Value(other)
        out = Value(self.data * other.data, (self, other), "*")
        def _backward():
            self.grad += other.data * out.grad   # d(a*b)/da = b
            other.grad += self.data * out.grad   # d(a*b)/db = a
        out._backward = _backward
        return out

    def __pow__(self, n):                        # n ist eine normale Zahl
        out = Value(self.data ** n, (self,), f"**{n}")
        def _backward():
            self.grad += n * self.data ** (n - 1) * out.grad
        out._backward = _backward
        return out

    def __neg__(self): return self * -1
    def __sub__(self, other): return self + (-other)
    def __radd__(self, other): return self + other
    def __rmul__(self, other): return self * other

    def backward(self):
        order, seen = [], set()
        def visit(v):                  # topologisches Sortieren: Eltern vor Kindern
            if v not in seen:
                seen.add(v)
                for p in v._parents:
                    visit(p)
                order.append(v)
        visit(self)
        self.grad = 1.0                # d(loss)/d(loss) = 1
        for v in reversed(order):      # geh vom Fehler zurück zu den Eingaben
            v._backward()

Zeilen 2–8 — die Zahl, die sich erinnert: Eine Value ist eine Klasse, eine Vorlage für Objekte, die Daten halten und wissen, wie sie sich verhalten. Diese umhüllt einen einfachen Float (data), fügt einen grad-Slot hinzu, der bei Null beginnt, und notiert in _parents, aus welchen Values sie gemacht wurde. So baut sich der Graph – Zahlen, verbunden durch die Operationen, die sie hervorgebracht haben – von selbst auf, während du normale Arithmetik betreibst.

Zeilen 10–26 — lokale Ableitungen: Jede Operation kennt nur ihre eigene Steigung. Bei a + b ist die Ableitung nach jedem Input 1, daher wird der Gradient direkt weitergeleitet. Bei a * b ist die Ableitung nach a gleich b und umgekehrt. Mit out.grad zu multiplizieren ist die Kettenregel: Die Steigung einer Funktionskette ist das Produkt ihrer Steigungen. Schau jetzt auf +=. Der klassische Anfängerfehler ist, = zu schreiben: Eine Zahl, zweimal benutzt wie a oben, hat dann ihren zweiten Pfad, der den ersten überschreibt, und der Gradient ist stillschweigend falsch. Wenn dein Check von den ersten Schritten nicht 4.0 für a.grad ausgibt, such nach einem =.

Zeilen 28–38 — Potenz und syntaktischer Zucker: __pow__ wendet die Schulregel an: Die Ableitung von xn ist n·xn−1. Die vier Einzeiler lassen Pythons Operatoren in beide Richtungen arbeiten. __radd__ zählt, weil sum() mit der normalen Zahl 0 startet und 0 + Value nur funktioniert, wenn Value von rechts addiert werden kann.

Zeilen 40–51 — der Rückwärtsgang: Ein topologisches Sortieren ist eine Ordnung, in der jede Zahl nach den Zahlen kommt, aus denen sie gebaut wurde. visit erzeugt es durch Rekursion, eine Funktion, die sich selbst auf den Eltern aufruft. Der Fehler bekommt einen Gradienten von 1.0, weil er eins-zu-eins mit sich selbst ändert, und die Loop geht die Ordnung rückwärts durch, jeder Knoten gibt seinen Gradienten an seine Eltern weiter. Bis ein Knoten sein _backward ausführt, hat alles, das auf ihm aufgebaut ist, bereits seinen Anteil hinzugefügt.

Nun die AEC-Aufgabe. Eine Wand, sechs Laser-Messungen, eine Frage.

# lean.py - wie viel lehnt sich diese Wand? fit offset = a*height + b
from tiny_grad import Value

heights = [0.5, 2.0, 4.0, 6.5, 9.0, 11.5]       # m über dem Boden
offsets = [3.0, 7.0, 10.0, 16.0, 21.0, 26.0]    # mm aus dem Lot (Laser)

a, b = Value(0.0), Value(0.0)                   # Schief (mm/m) und Basisversatz (mm)
lr = 0.004                                      # Lernrate: Schrittgrösse

for step in range(2001):
    loss = sum(((a * h + b) - o) ** 2 for h, o in zip(heights, offsets)) * (1 / len(heights))
    a.grad, b.grad = 0.0, 0.0                   # vergesse die Gradienten des letzten Schritts
    loss.backward()                             # fülle a.grad und b.grad
    a.data -= lr * a.grad                       # geh bergab
    b.data -= lr * b.grad
    if step % 500 == 0:
        print(f"step {step:4d}  loss {loss.data:8.3f}  a {a.data:5.2f} mm/m  b {b.data:5.2f} mm")

print(f"lean: {a.data:.2f} mm per metre, {a.data * 11.5:.1f} mm over the 11.5 m wall")

Zeilen 4–5 — die Daten: sechs Versätze aus dem Lot, gemessen in Höhen auf einer 11,5-m-Wand. Diese Zahlen sind illustrativ, erfunden für dieses Tutorial, nicht aus einer echten Vermessung.

Zeilen 7–11 — das Modell und der Fehler: Wir raten eine gerade Linie, offset = a·height + b, wo a die Schieflage in mm pro Meter und b der Versatz an der Basis ist. Der Fehler ist der mittlere quadratische Fehler: wie falsch die aktuelle Linie ist, als eine Zahl. Die Lernrate lr ist die Grösse jedes Schritts bergab.

Zeilen 12–15 — der Vier-Zeilen-Trainingsschritt: Nullsetzen der Gradienten, weil sie sonst mit += von Schritt zu Schritt sich aufsummieren würden. Die PyTorch-»Autograd mechanics«-Notizen beschreiben die gleiche Akkumulation, darum rufen PyTorch-Nutzer optimizer.zero_grad() auf. Dann füllt backward() a.grad und b.grad. Ein Gradient ist, wie sehr sich der Fehler ändert, wenn du diese Zahl anstupst; sein Vorzeichen zeigt bergauf, daher geht Subtraktion bergab. Die tatsächliche Ausgabe:

step    0  loss  255.167  a  0.86 mm/m  b  0.11 mm
step  500  loss    0.237  a  2.14 mm/m  b  1.70 mm
step 1000  loss    0.146  a  2.09 mm/m  b  2.09 mm
step 1500  loss    0.139  a  2.08 mm/m  b  2.20 mm
step 2000  loss    0.139  a  2.08 mm/m  b  2.23 mm
lean: 2.08 mm per metre, 23.9 mm over the 11.5 m wall

Die Gegenprobe: NumPys exakte Least-Squares-Anpassung, np.polyfit(heights, offsets, 1), gibt a = 2.075 mm/m und b = 2.247 mm. Gradientenabstieg wanderte zur gleichen Antwort, die die analytische Lösung gibt. Das ist der Sinn, es selbst zu bauen: Du kannst der Engine trauen, weil du sie prüfen kannst.

Atelier: Stell dir ein 12-Personen-Atelier in Zürich vor, das eine alte Fassade vor einer Sanierung vermisst, mit Laser-Versätzen pro Geschoss, die als Tabellenkalkulation ankommen, die keiner richtig liest. Die Loop, die ein GPT trainiert, ist die gleiche Loop, die diese Schieflage anpasst, und sie ist kurz genug, um im Projektordner Platz zu haben. Am Montag: tiny_grad.py und lean.py neben den Vermessungsdaten speichern, die realen Pro-Geschoss-Messwerte eintauschen und als Team vereinbaren, dass keine Schieflage-Zahl das Büro verlässt, bis sie np.polyfit entspricht.

Wenn du in Grasshopper arbeitest, besitzt du bereits einen Optimierer: Galapagos, David Ruttens Evolutionslöser. Rutten schrieb 2011, dass die Aufgabe des Lösers darin besteht, »den höchsten Gipfel in dieser Landschaft zu finden«, und Galapagos tut das, indem es Kandidaten-Antworten züchtet und abtastet, ohne dass eine Steigung nötig ist. Gradientenabstieg ist ein anderes Werkzeug, nicht besser: es liest die Steigung, wo es steht. Der Kompromiss ist klar: Gradientenabstieg braucht ein differenzierbares Modell und eine sinnvolle Schrittgrösse, während ein Evolutionslöser seine Freiheit durch viele weitere Evaluationen erkauft. Das Lesen von Steigungen ist warum differenzierbare Formfindung existiert, von Rafael Pastranas JAX FDM (»Crafted with care at Princeton University«) bis Pastrana et al.s 2023-Papier in Computer-Aided Design über constrained Formfindung mit automatic differentiation, das einen ETH-Zürich-Ko-Autor hat.

Hack: Erhöhe die Schrittgrösse, bis der Abstieg überschiesst, und sag es voraus, bevor du es ausführst. Ändere eine Zeile in lean.py:

lr = 0.03                                       # war 0.004

Erwartet, dass die Schritte zu gross sind. Echte Ausgabe:

step    0  loss  255.167  a  6.47 mm/m  b  0.83 mm
step    1  loss  822.216  a -5.18 mm/m  b -0.56 mm
step    2  loss 2656.461  a 15.77 mm/m  b  2.04 mm
step    3  loss 8589.489  a -21.93 mm/m  b -2.53 mm

Dann bricht Python mit OverflowError: (34, 'Result too large') ab. Jeder Schritt springt über das Tal und landet höher auf der anderen Seite, darum wechselt a jedes Mal das Vorzeichen. Die Wand bewegte sich nicht. Die Mathematik tat es. Zur Kontrolle: Tests mit lr 0.01 und 0.02 konvergieren zu 2.08 mm/m, während lr 0.001 zu zaghaft ist und nach 2.000 Schritten immer noch bei 2.14 mm/m sitzt.

Aus meiner Perspektive waren die Dateien, die überdauerten, die Klartextdateien. Zwei kleine .py-Dateien lassen sich noch lange nach dem Ablauf heutiger Lizenzen öffnen.

Zum Lernen:

Speichere die zwei Dateien heute, brich die Lernrate einmal absichtlich, und behalte den Ordner.

GEMELDET AUS
MIT-UNTERZEICHNER
PAZ Academy
VERTRAUEN
HIGH
NACHDRUCKE
© PAZ - PARAMETRIC ACADEMY ZURICH · ALLE RECHTE VORBEHALTEN

PAZ Kaffi · interdisziplinäre Redaktionsarbeit, geleitet von der PAZ Academy

⚑ FEHLER MELDEN · KORREKTUR EINSENDEN
◂ ZURÜCK ZUR TITELSEITE · PAZ KAFFI

© 2026 PAZ Academy.