Viele Neuronen rechnen mit vielen Zahlen
Du hast ein kleines Netz mit zwei Eingaben und zwei inneren Neuronen von Hand berechnet. In einem größeren Netz möchten wir dieselben Rechnungen für viele Eingaben ordentlich aufschreiben und gemeinsam ausführen. Dafür brauchen wir zunächst nur Listen und Tabellen.
Eine einzelne Zahl, beispielsweise 3, nennen wir Skalar. Eine geordnete Zahlenliste wie [2,1] heißt Vektor. Eine rechteckige Zahlentabelle heißt Matrix. Der Oberbegriff Tensor erlaubt beliebig viele solcher Ordnungsrichtungen. Ein Tensor muss also nichts Mysteriöses sein: Für uns ist er ein geordnetes Zahlenfeld mit einer bekannten Form.
Die Form beschreibt die Organisation, nicht den Inhalt
Die Liste [2,1] enthält zwei Zahlen und hat die Form [2]. Die Tabelle [[1,1],[-1,2]] hat zwei Zeilen und zwei Spalten; ihre Form ist [2,2]. Die Form sagt nicht, welche Werte darin stehen, sondern wie viele Werte entlang jeder Achse angeordnet sind. Eine Achse ist hier eine Richtung im Zahlenfeld, etwa die Zeilen- oder Spaltenrichtung.
Ein Stapel aus drei Tabellen mit je zwei Zeilen und vier Spalten hätte Form [3,2,4]. Drei Zahlen in dieser Formbeschreibung bedeuten drei Achsen. Sie bedeuten nicht, dass nur drei Zahlen gespeichert sind. Insgesamt enthält der Stapel 3×2×4=24 Zahlen.
Unser kleines Netz als Tabelle
Die Gewichte der zwei versteckten Neuronen lassen sich in eine gemeinsame Tabelle schreiben:
| Neuron | Gewicht für erste Eingabe | Gewicht für zweite Eingabe | Bias |
|---|---|---|---|
| Erstes | 1 | 1 | −2 |
| Zweites | −1 | 2 | 1 |
Für die Eingaben [2,1] berechnen wir je Zeile die bekannte Neuronensumme. Erste Zeile: 2×1+1×1−2=1. Zweite Zeile: 2×(−1)+1×2+1=1. Danach wenden wir ReLU auf beide Ergebnisse an.
Eine Matrixmultiplikation ist eine organisierte Methode, viele solcher gewichteten Summen zusammen auszurechnen. Sie fügt unserer Neuronenrechnung keine neue Bedeutung hinzu. Sie fasst sie nur effizient zusammen.
Zuerst eine einzelne gewichtete Summe
Nimm zwei gleich lange Listen: [1,2,3] und [4,0,−1]. Multipliziere die jeweils zusammengehörigen Zahlen und addiere die Ergebnisse: 1×4+2×0+3×(−1)=1. Diese Rechnung heißt Skalarprodukt. Das Ergebnis ist eine einzelne Zahl, also ein Skalar.
Mit kurzen Namen schreiben wir:
Hier sind a und b die beiden vollständigen Listen. Der Punkt zwischen den Listennamen bezeichnet ihr Skalarprodukt. Zwischen einzelnen Zahlen bezeichnet er die gewöhnliche Multiplikation. Welche Bedeutung gemeint ist, ergibt sich aus den beteiligten Objekten.
Ein großes Skalarprodukt bedeutet nicht automatisch „gleiche Bedeutung“. Es hängt auch davon ab, wie groß die Komponenten sind. Den Unterschied zwischen Zahlenähnlichkeit und Bedeutung betrachten wir später bei den gelernten Textvektoren.
Eine Matrixmultiplikation Schritt für Schritt
Wir wollen die Eingabeliste [1,2] in zwei Ausgaben umrechnen. Die erste Ausgabe soll 1×3+2×5=13 sein. Die zweite soll 1×4+2×6=16 sein. Die benötigten Gewichte können wir so organisieren:
import torch
x = torch.tensor([[1., 2.]])
w = torch.tensor([[3., 4.], [5., 6.]])
print(x @ w) # tensor([[13., 16.]])torch.tensor erzeugt aus den Listen ein Zahlenfeld. Die Punkte in 1. und 2. kennzeichnen Fließkommazahlen, mit denen auch Nachkommastellen darstellbar sind. Das Zeichen @ bedeutet in Python Matrixmultiplikation.
Für jede Ausgabespalte nehmen wir ihre Gewichte, multiplizieren sie mit den Eingaben und addieren. Die Formrechnung dazu lautet [1,2] @ [2,2] → [1,2]: eine Eingabezeile mit zwei Zahlen wird zu einer Ausgabezeile mit zwei Zahlen. Die innere Zahl 2 muss zusammenpassen, weil genauso viele Gewichte wie Eingabekomponenten erforderlich sind.
Nicht mit elementweiser Multiplikation verwechseln
Bei zwei gleich geformten Zahlenfeldern multipliziert a * b nur die jeweils gleich positionierten Elemente. Es summiert sie nicht. a @ b bildet dagegen gewichtete Summen entlang einer passenden Achse. Elementweise heißt immer: jede Stelle für sich.
Auch das Hochstellen von T in einer Formel kann neu wirken: bedeutet Transponieren. Zeilen und Spalten werden vertauscht. Das ist keine Potenz. Es wird oft benötigt, weil dieselben Gewichte je nach Schreibkonvention anders angeordnet sind.
Warum PyTorch-Gewichte manchmal gedreht erscheinen
nn.Linear(2,3) bezeichnet eine Schicht mit zwei Eingaben und drei Ausgaben. Jede der drei Ausgaben braucht zwei Gewichte. PyTorch speichert diese Gewichte als drei Zeilen mit jeweils zwei Zahlen, also Form [3,2].
from torch import nn
layer = nn.Linear(2, 3)
x = torch.tensor([[2., 1.]])
y = layer(x)
print(layer.weight.shape) # torch.Size([3, 2])
print(y.shape) # torch.Size([1, 3])shape ist der englische Name für die Form. Die Schicht erledigt die nötige Transposition intern. Sie erzeugt hier zufällige Anfangsgewichte; du solltest daher keine bestimmte gelernte Vorhersage erwarten.
Die Formen, die später im Sprachmodell vorkommen
Ein Batch ist eine Gruppe gleichzeitig verarbeiteter Beispiele. Eine Sequenz ist eine geordnete Folge, später also eine Textfolge. Jedes Textstück bekommt nach einer noch zu erklärenden Übersetzung einen Zahlenvektor. Für diese wiederkehrenden Größen verwenden wir vier Namen:
| Name | Bedeutung | Kleines Beispiel |
|---|---|---|
| B | Anzahl der Beispiele im Batch | 2 Texte |
| T | Anzahl der verarbeiteten Positionen pro Text | 3 Positionen |
| D | Anzahl der Zahlen im Vektor jeder Position | 4 Zahlen |
| V | Anzahl möglicher Textbausteine im Wörterverzeichnis des Modells | zunächst nur als spätere Größe |
Die Form [B,T,D]=[2,3,4] bedeutet dann zwei Texte, je drei Positionen und je vier Zahlen pro Position. Sie enthält 24 Zahlen. Diese Buchstaben sind Namen für Größen, keine Operationen.
Hilfsoperationen im Code
Reshape bedeutet: dieselben Zahlen anders gruppieren, etwa zwölf Zahlen in drei Vierergruppen. Die Anzahl der Zahlen muss gleich bleiben. Transpose vertauscht Achsen. Broadcasting bedeutet: Ein kleineres Zahlenfeld wird bei einer Operation passend wiederverwendet, etwa derselbe Bias für jede Eingabezeile.
Das sind praktische Werkzeuge, aber du solltest immer wissen, welche Rolle jede Achse hat. Im späteren Attentionkapitel brauchen wir zusätzlich eine Kopf-Achse. Du musst sie jetzt noch nicht auswendig verfolgen.
Die wichtigste Arbeitsgewohnheit
Schreibe vor einer Operation in Worten auf, was eine Zeile und eine Spalte bedeuten. Prüfe dann die Form. Ein Programm kann mit passenden Zahlenmaßen laufen und trotzdem die falschen Rollen vermischen. „Zwei Texte mit drei Positionen“ ist etwas anderes als „drei Texte mit zwei Positionen“, obwohl beide sechs Positionen enthalten.