Tokenwerk · Das LLM-Lehrbuch

Kapitel 15 · II · Von Zahlen zu Sprache · 7 Minuten

Embeddings und geteilte Repräsentationen

Aus einer Adresse wird ein lernbarer Vektor. Warum die Dimensionen keine von Hand benannten Eigenschaften brauchen.

Eine Token-ID ist zunächst nur eine Zeilennummer

Stell dir eine Tabelle mit drei Zeilen und jeweils zwei Zahlen vor:

ID Token Erste Komponente Zweite Komponente
0 Katze 0.2 0.7
1 Hund 0.4 0.6
2 Sofa -0.3 0.1

Zur ID 1 schlagen wir [0.4, 0.6] nach. Diese Zahlenliste heißt Embedding. Die Zahlen sind hier nur ein erfundenes Rechenbeispiel. In einem echten Modell beginnen sie als kleine Zufallswerte und werden beim Training verändert. Wir legen nicht von Hand fest, dass die erste Zahl „Tierart“ oder die zweite „Größe“ bedeuten soll.

Die ID selbst ist keine Messung: Hund ist durch ID 1 weder größer noch besser als Katze mit ID 0. Die trainierbare Zahlenliste schafft erst eine Darstellung, mit der das Netz sinnvoll weiterrechnen kann. Ihre Länge heißt Embeddingdimension oder Breite D, hier D = 2.

Ein One-Hot-Vektor wäre eine Liste, die nur an der gesuchten ID eine 1 und sonst Nullen enthält: für ID 1 also [0,1,0]. Eine solche Auswahl mit der Tabelle zu multiplizieren ergibt dieselbe Zeile. Praktisch schlagen wir die Zeile direkt nach und sparen uns diese lange Nullenliste.

Die Embeddingtabelle

Eine Embeddingtabelle enthält für jeden Token einen Vektor mit DD Komponenten. Sie hat Form [V,D][V,D]. Für die ID 42 liest das Modell Zeile 42. Es ist kein Suchvorgang über Bedeutungen, sondern ein Indexzugriff. Die Vektoren starten zufällig und werden beim Training angepasst.

python
import torch
from torch import nn
embedding = nn.Embedding(100, 16)
ids = torch.tensor([[4, 12, 4]])
x = embedding(ids)
assert x.shape == (1, 3, 16)
assert torch.equal(x[0, 0], x[0, 2])

Die beiden Vorkommen der ID 4 liefern an dieser Stelle denselben Vektor. Sie werden erst später durch Positionen und Kontext unterschiedlich repräsentiert.

Warum IDs keine semantische Ordnung besitzen

Stelle dir vor, „Hund“ hat ID 10 und „Katze“ ID 900. Der numerische Abstand 890 ist bedeutungslos. Der Abstand ihrer gelernten Vektoren kann dagegen für bestimmte Aufgaben relevant werden. Ein Embedding ersetzt eine diskrete Adresse durch eine trainierbare Zahlenliste. „Diskret“ heißt hier: eine von festgelegten IDs. Die Einträge der Liste können dagegen Zwischenwerte wie 0,2 oder 0,21 annehmen. Mit diesen Listen können wir die bereits bekannten Multiplikationen und Additionen ausführen.

Du könntest diesen Zugriff als Multiplikation eines One-Hot-Vektors mit einer Tabelle darstellen. Ein One-Hot-Vektor hat an genau einer Position eine Eins. Das Produkt wählt genau eine Tabellenzeile aus. Die tatsächliche Implementierung spart den riesigen überwiegend nullwertigen Vektor und benutzt direkt den Index.

Keine vorgegebenen Bedeutungsschubladen

Die Dimension 0 bedeutet nicht automatisch „Tier“, Dimension 1 „Farbe“ und Dimension 2 „Freundlichkeit“. Verschiedene Koordinatensysteme können dieselbe Funktion realisieren, wenn die folgenden Transformationen angepasst werden. Manchmal lassen sich Richtungen interpretieren; daraus folgt nicht, dass jeder Eintrag eine feste menschliche Eigenschaft kodiert.

Ähnliche Kontexte können ähnliche Repräsentationen fördern, aber „semantisch ähnlich“ ist kein garantiertes Resultat jeder Ebene und jeder Distanzmetrik. Ein kleines Byte-Modell lernt zum Beispiel zunächst Zeichen- und Wortstückmuster. Es erhält von uns keine Tabelle mit Beziehungen zwischen allen Begriffen.

Eine Vorschau: dieselbe ID in anderem Kontext

Das Eingabeembedding von „Bank“ bleibt bei derselben Token-ID gleich. In einem späteren Netz wird diese Liste zusammen mit vorherigem Text weiterverarbeitet. Bei „Geld bei der Bank“ und „sitzen auf der Bank“ kann dadurch eine unterschiedliche innere Darstellung entstehen. Einen solchen Zwischenwert nennen wir Hidden State, also internen Zustand.

Wie das Netz früheren Text einbezieht, erklären wir erst im Attentionkapitel. Für dieses Kapitel genügt: Der anfängliche Tabellenzugriff ist immer derselbe; die nachfolgende Verarbeitung kann Kontext berücksichtigen.

Eine Projektion in die nächste Verteilung

Angenommen, die weitere Verarbeitung liefert die Liste h = [1,2]. Für drei mögliche Tokens verwenden wir die Zeilen [1,0], [0,1] und [1,1] einer Gewichtstabelle. Ihre Skalarprodukte mit h ergeben 1, 2 und 3. Ohne Bias sind unsere Logits also z = [1,2,3]. Softmax könnte daraus als Nächstes eine Verteilung machen.

Allgemein schreiben wir z=Wouth+bz=W_{out}h+b. h ist die Eingabeliste mit D Komponenten. W_out ist eine Tabelle mit V Zeilen und D Spalten. Jede Zeile erzeugt einen Logit; b ist eine Biasliste mit V Einträgen, die anschließend addiert wird. z ist die fertige Liste von V Logits. „out“ steht für Ausgabe.

In Fachtexten begegnet dir dafür die Schreibweise h∈RDh\in\mathbb{R}^D. Das Zeichen ∈ liest du „ist ein Element von“ und ℝ bezeichnet die reellen Zahlen. Zusammen heißt es hier nur: h ist eine Liste mit D reellwertigen Komponenten. RV\mathbb{R}^V bezeichnet entsprechend die Listenlänge V der Ausgabe. Im Computer werden diese Zahlen durch ein endliches Zahlenformat angenähert.

Bei Weight Tying teilen Eingabeembedding und Ausgabeprojektion dieselbe Parameter-Matrix. In PyTorch sieht das so aus:

python
self.embed = nn.Embedding(V, D)
self.head = nn.Linear(D, V, bias=False)
self.head.weight = self.embed.weight

Die gemeinsame Matrix wird in beiden Rollen trainiert. Das spart Parameter und macht zwei Rollen strukturell verwandt. Es ist keine Garantie, dass jede Architektur mit Tying besser wird. Im Lehrprojekt hält es kleine Modelle übersichtlich.

Vertiefung: Ähnlichkeit zweier Listen messen

Vergleiche a = [1,0] mit b = [2,0]. Beide zeigen in dieselbe Richtung, aber b ist doppelt so lang. Das Skalarprodukt ist 2. Die Länge eines Vektors berechnen wir als Wurzel aus der Summe seiner Komponentenquadrate: a hat Länge √(1²+0²) = 1, b hat Länge √(2²+0²) = 2.

Teilen wir das Skalarprodukt durch beide Längen, erhalten wir 2/(1×2) = 1. Diese Kosinusähnlichkeit beträgt für gleiche Richtung 1, für entgegengesetzte Richtung −1 und für rechtwinklige Richtungen 0. Du brauchst dafür zunächst keine Winkel auszurechnen.

A¨hnlichkeit(a,b)=a⋅b∥a∥∥b∥.\text{Ähnlichkeit}(a,b)=\frac{a\cdot b}{\lVert a\rVert\lVert b\rVert}.

a und b sind die verglichenen Vektoren. Der Punkt bedeutet Skalarprodukt; die doppelten senkrechten Striche bedeuten die jeweilige Vektorlänge. Der Nenner multipliziert beide Längen. Hat eine Liste Länge null, ist diese Division nicht definiert. Das Experiment zeigt dann keinen gültigen Ähnlichkeitswert an.

Ein anderes Maß ist die euklidische Distanz, der gewöhnliche Abstand: Bilde die Unterschiede pro Komponente, quadriere sie, addiere sie und ziehe die Wurzel. Der Abstand von [1,0] zu [2,0] ist 1. Beide Listen haben also gleiche Richtung, aber nicht dieselbe Position.

Das Experiment verwendet frei einstellbare zweidimensionale Listen. Es sind keine gelernten Embeddings eines echten Sprachmodells. Selbst ähnliche gelernte Vektoren beweisen nicht automatisch gleiche Bedeutung; das hängt von Training und Messmethode ab.

Was Embeddings lernen müssen

Mit der Embeddingtabelle teilen alle Vorkommen einer ID Parameter. Der Gradient eines Trainingsschritts kann deshalb Information aus vielen Positionen in dieselbe Zeile tragen. Andere Modellteile sind ebenfalls geteilt: Derselbe Transformer-Block verarbeitet jede Zeitposition, nicht ein eigenes Netz pro Position.

Das ist ein grundlegender Vorteil gegenüber einer Sammlung voneinander unabhängiger Regeln. Parameter-Sharing verknüpft Beispiele, verlangt aber auch Kompromisse: Ein Wortstück kann in vielen Bedeutungen vorkommen. Der Kontextweg muss diese Fälle später auseinanderhalten.

Eine kleine Parameterrechnung

Bei V=4096V=4096 und D=256D=256 enthält die Tabelle 1.048.576 Parameter. In Float32 sind das etwa vier MiB Gewichtsdaten. Zwei Tabellen wären acht MiB. Die Trainingsspeichermenge ist größer, weil Ableitungen und Optimizer-Zustände hinzukommen. Rechne deshalb nicht „Embedding passt in RAM, also passt das Training“.