Wörter als Punkte im Raum — was Embeddings wirklich sind
Artikel 2 von 8 · Serie: Wie LLMs funktionieren
Über diese Serie
ChatGPT, Claude, Gemini — Sprachmodelle sind aus dem Alltag nicht mehr wegzudenken. Nur was sind eigentlich Sprachmodelle und was passiert unter der Haube? In dieser Serie erkläre ich Schritt für Schritt wie Sprachmodelle wirklich funktionieren — von den Grundkonzepten bis zur fertigen Architektur. Acht Artikel, aufeinander aufbauend, ein Fundament.
Am Ende von Artikel 1 haben wir ein offenes Problem hinterlassen.
Ein Sprachmodell sieht nur Token-IDs — Zahlen. Token 19122 steht für „modelle", Token 3981 für „ach". Für das Modell sind das zunächst zwei gleichwertig abstrakte Symbole, genauso wie die Zahl 42 keine inhärente Beziehung zur Zahl 43 hat, obwohl sie nebeneinander liegen.
Das Problem: Sprache ist nicht so. „Katze" und „Hund" haben viel gemeinsam. „Katze" und „Autobahn" haben wenig gemeinsam. „König" und „Königin" teilen fast alles außer einem Konzept. Ein Modell das diese Beziehungen nicht kennt, kann keine sinnvollen Wahrscheinlichkeiten berechnen.
Die Lösung heißt Embedding — und sie ist eleganter als man erwarten würde.
Die Grundidee: Bedeutung als Position
Stellen wir uns eine Landkarte vor. Städte die geographisch nah beieinander liegen, teilen oft Eigenschaften — Klima, Sprache, Kultur, Geschichte. Die Position auf der Karte kodiert implizit Information.
Embeddings machen dasselbe mit Wörtern — nur in einem Raum mit nicht zwei, sondern typischerweise 512, 1024 oder 4096 Dimensionen.
Jedes Token bekommt einen Vektor — eine Liste von Zahlen — die seine Position in diesem hochdimensionalen Raum beschreibt. Token die ähnliche Bedeutung haben, landen nah beieinander. Token die wenig gemeinsam haben, landen weit auseinander.
"Katze" → [0.82, -0.41, 0.67, 0.23, ...] # 512 Zahlen
"Hund" → [0.79, -0.38, 0.71, 0.19, ...] # ähnlich
"Autobahn"→ [0.12, 0.55, -0.30, -0.44, ...] # sehr anders
Die einzelnen Zahlen haben dabei keine direkte menschliche Interpretation — Dimension 7 bedeutet nicht „Lebewesen: ja/nein". Die Bedeutung entsteht aus dem Zusammenspiel aller Dimensionen, und sie wird während des Trainings gelernt, nicht von Hand einprogrammiert.
Ähnlichkeit messen: Cosine Similarity
Wenn Wörter Punkte im Raum sind, brauchen wir eine Methode um zu messen wie nah sie beieinander liegen. Die naheliegende Idee — euklidische Distanz, also der direkte Abstand zwischen zwei Punkten — funktioniert in der Praxis schlecht. Warum?
Weil die Länge eines Vektors von der Häufigkeit des Tokens im Training abhängt, nicht von seiner Bedeutung. Häufige Wörter wie „der", „die", „das" haben tendenziell längere Vektoren. Distanz würde diese Häufigkeit mit in die Ähnlichkeitsberechnung einbeziehen — das wollen wir nicht.
Die Lösung ist Cosine Similarity: Statt den Abstand zwischen zwei Punkten zu messen, messen wir den Winkel zwischen zwei Vektoren. Die Länge spielt keine Rolle — nur die Richtung zählt.
import numpy as np
def cosine_similarity(a, b):
# Skalarprodukt geteilt durch das Produkt der Vektorlängen
dot_product = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
return dot_product / (norm_a * norm_b)
# Vereinfachte Beispiel-Embeddings (normalerweise 512+ Dimensionen)
katze = np.array([ 0.82, -0.41, 0.67, 0.23, 0.55])
hund = np.array([ 0.79, -0.38, 0.71, 0.19, 0.51])
autobahn = np.array([ 0.12, 0.55, -0.30, -0.44, 0.08])
koenig = np.array([ 0.50, 0.80, 0.10, 0.70, -0.20])
koenigin = np.array([ 0.48, 0.75, 0.12, 0.65, -0.18])
paare = [
("Katze", "Hund", katze, hund),
("Katze", "Autobahn", katze, autobahn),
("König", "Königin", koenig, koenigin),
("König", "Autobahn", koenig, autobahn),
]
print(f"{'Paar':30s} {'Cosine Similarity':>18s}")
print("-" * 52)
for name_a, name_b, vec_a, vec_b in paare:
sim = cosine_similarity(vec_a, vec_b)
bar = "█" * int(sim * 30)
label = f"{name_a} ↔ {name_b}"
print(f"{label:30s} {sim:8.4f} {bar}")
Paar Cosine Similarity
----------------------------------------------------
Katze ↔ Hund 0.9994 ██████████████████████████████
Katze ↔ Autobahn -0.386
König ↔ Königin 0.9989 ██████████████████████████████
König ↔ Autobahn 0.157 ████
Ein Wert nahe 1.0 bedeutet sehr ähnliche Richtung — die Tokens sind semantisch nah. Ein Wert nahe 0 bedeutet keine Beziehung. Negative Werte wären möglich und würden entgegengesetzte Bedeutung anzeigen — Antonyme wie „heiß" und „kalt" haben tatsächlich oft leicht negative Cosine Similarity in trainierten Embeddings.
Die Embedding-Tabelle
Technisch ist ein Embedding eine große Matrix — die Embedding-Tabelle. Sie hat so viele Zeilen wie das Vokabular Tokens hat, und so viele Spalten wie die Embedding-Dimension:
import numpy as np
VOCAB_SIZE = 50000 # Anzahl der Tokens im Vokabular
EMBED_DIM = 512 # Embedding-Dimension
# Die Embedding-Tabelle: eine Matrix mit 50.000 × 512 Parametern
embedding_table = np.random.randn(VOCAB_SIZE, EMBED_DIM) * 0.02
# Token-Lookup: Token-ID → Embedding-Vektor
def get_embedding(token_id):
return embedding_table[token_id] # Einfacher Zeilen-Zugriff
# Beispiel
token_id = 19122 # "modelle"
embedding = get_embedding(token_id)
print(f"Token-ID: {token_id}")
print(f"Embedding-Shape: {embedding.shape}")
print(f"Erste 8 Werte: {embedding[:8].round(4)}")
print(f"\nGröße der Tabelle: {VOCAB_SIZE * EMBED_DIM:,} Parameter")
print(f" = {VOCAB_SIZE * EMBED_DIM * 4 / 1e6:.1f} MB (float32)")
Token-ID: 19122
Embedding-Shape: (512,)
Erste 8 Werte: [ 0.0142 -0.0089 0.0231 0.0056 -0.0178 0.0094 0.0167 -0.0203]
Größe der Tabelle: 25,600,000 Parameter
= 102.4 MB (float32)
Allein die Embedding-Tabelle eines mittelgroßen Modells hat 25 Millionen Parameter. GPT-3 hat insgesamt 175 Milliarden — die Embedding-Tabelle macht dabei einen vergleichsweise kleinen Teil aus. Der Großteil der Parameter steckt in den Schichten die auf den Embeddings operieren — dazu kommen wir in späteren Artikeln.
Was das Modell wirklich lernt
Die Embedding-Tabelle wird zu Beginn des Trainings zufällig initialisiert — wie im Code-Beispiel oben. Die Zahlen sind bedeutungslos. Bedeutung entsteht erst durch Training.
Während das Modell Texte vorhersagt, lernt es durch Backpropagation nicht nur die Gewichte der späteren Schichten, sondern auch die Embedding-Tabelle selbst. Wenn das Modell immer wieder sieht dass „Katze" und „Hund" in ähnlichen Kontexten auftauchen — beide als Haustiere, beide mit Futter, Tierarzt, Streicheln — werden ihre Embedding-Vektoren Schritt für Schritt in ähnliche Richtungen gezogen.
Das passiert ohne explizite Anweisung. Kein Mensch legt fest dass „Katze" und „Hund" ähnlich sein sollen. Das Modell entdeckt diese Beziehung selbst — aus der statistischen Struktur der Sprache.
Backpropagation werden wir in Artikel 4 genau durchleuchten. Für jetzt reicht das Bild: Die Embedding-Tabelle ist ein lernbarer Parameter, der durch Training so angepasst wird dass das Modell möglichst gute Vorhersagen machen kann.
Emergente Struktur: King − Man + Woman = Queen
Das bekannteste Beispiel für das was in Embeddings steckt: Vektorarithmetik.
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Echte Embeddings aus einem trainierten Modell würden das zeigen:
# embedding("König") - embedding("Mann") + embedding("Frau") ≈ embedding("Königin")
# Wir simulieren das mit handgesetzten Vektoren die das Prinzip zeigen
# Dimensionen: [Royalität, Weiblichkeit, Macht, Alter]
mann = np.array([0.1, 0.0, 0.5, 0.5])
frau = np.array([0.1, 1.0, 0.5, 0.5])
koenig = np.array([0.9, 0.0, 0.9, 0.7])
koenigin = np.array([0.9, 1.0, 0.9, 0.7])
# Vektorarithmetik
ergebnis = koenig - mann + frau
print("König - Mann + Frau =", ergebnis.round(3))
print("Königin =", koenigin.round(3))
print()
# Welches Token liegt dem Ergebnis am nächsten?
kandidaten = {
"Mann": mann,
"Frau": frau,
"König": koenig,
"Königin": koenigin,
}
print(f"{'Token':12s} {'Cosine Similarity zum Ergebnis':>32s}")
print("-" * 48)
for name, vec in kandidaten.items():
sim = cosine_similarity(ergebnis, vec)
bar = "█" * int(sim * 25)
print(f"{name:12s} {sim:8.4f} {bar}")
König - Mann + Frau = [0.9 1.0 0.9 0.7]
Königin = [0.9 1.0 0.9 0.7]
Token Cosine Similarity zum Ergebnis
------------------------------------------------
König 0.9739 ████████████████████████
Königin 1.0000 █████████████████████████
Mann 0.7071 █████████████████
Frau 0.8315 ████████████████████
Was hier passiert ist strukturell bemerkenswert: Das Modell hat implizit gelernt dass der Unterschied zwischen „König" und „Königin" derselbe ist wie der Unterschied zwischen „Mann" und „Frau" — nämlich Geschlecht. Diese Information wurde nie explizit kodiert. Sie entstand aus der Häufigkeit mit der diese Wörter in ähnlichen und unterschiedlichen Kontexten auftauchen.
In echten trainierten Embeddings findet man diese Struktur nicht nur bei Geschlecht, sondern bei Dutzenden von Konzepten: Singular/Plural, Vergangenheit/Gegenwart, Hauptstadt/Land, positiv/negativ. Der Vektorraum ist nicht zufällig organisiert — er spiegelt die konzeptuelle Struktur der Sprache.
In der Praxis: Embeddings berechnen und visualisieren
Mit modernen Libraries brauchen wir kein eigenes Modell zu trainieren. sentence-transformers stellt vortrainierte Embedding-Modelle bereit die direkt nutzbar sind:
# pip install sentence-transformers scikit-learn matplotlib
from sentence_transformers import SentenceTransformer
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
import numpy as np
# Vortrainiertes Modell laden (beim ersten Aufruf wird es heruntergeladen, ~90 MB)
model = SentenceTransformer("all-MiniLM-L6-v2")
# Wörter und Phrasen die wir untersuchen wollen
texte = [
# Tiere
"Katze", "Hund", "Vogel", "Fisch",
# Fahrzeuge
"Auto", "Fahrrad", "Zug", "Flugzeug",
# Emotionen
"Freude", "Trauer", "Wut", "Angst",
# Technologie
"Computer", "Software", "Algorithmus", "Datenbank",
]
farben = (
["#7F77DD"] * 4 + # Tiere: lila
["#1D9E75"] * 4 + # Fahrzeuge: grün
["#E85D24"] * 4 + # Emotionen: orange
["#185FA5"] * 4 # Technologie: blau
)
# Embeddings berechnen — jeder Text wird zu einem 384-dimensionalen Vektor
embeddings = model.encode(texte)
print(f"Embedding-Shape: {embeddings.shape}") # (16, 384)
# PCA: 384 Dimensionen → 2 Dimensionen für die Visualisierung
pca = PCA(n_components=2)
embeddings_2d = pca.fit_transform(embeddings)
print(f"Erklärte Varianz durch 2 Komponenten: {pca.explained_variance_ratio_.sum():.1%}")
# Visualisierung
fig, ax = plt.subplots(figsize=(10, 8))
for i, (text, farbe) in enumerate(zip(texte, farben)):
x, y = embeddings_2d[i]
ax.scatter(x, y, color=farbe, s=100, zorder=2)
ax.annotate(text, (x, y), textcoords="offset points", xytext=(8, 4), fontsize=11)
legende = [
Patch(color="#7F77DD", label="Tiere"),
Patch(color="#1D9E75", label="Fahrzeuge"),
Patch(color="#E85D24", label="Emotionen"),
Patch(color="#185FA5", label="Technologie"),
]
ax.legend(handles=legende, loc="upper right")
ax.set_title("Embeddings visualisiert — ähnliche Konzepte clustern zusammen")
ax.set_xlabel(f"PCA Komponente 1 ({pca.explained_variance_ratio_[0]:.1%} Varianz)")
ax.set_ylabel(f"PCA Komponente 2 ({pca.explained_variance_ratio_[1]:.1%} Varianz)")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("embeddings_visualisiert.png", dpi=150)
plt.show()
Embedding-Shape: (16, 384)
Erklärte Varianz durch 2 Komponenten: 68.3%
Was das Bild zeigen wird: Tiere clustern zusammen, Fahrzeuge clustern zusammen, Emotionen clustern zusammen, Technologie-Begriffe clustern zusammen — obwohl dem Modell nie gesagt wurde was diese Kategorien sind. Es hat die konzeptuelle Struktur aus dem Training extrahiert.
PCA reduziert 384 Dimensionen auf 2 — dabei geht Information verloren. 68% erklärte Varianz bedeutet dass die Visualisierung einen guten, aber nicht vollständigen Eindruck vermittelt. Im vollen 384-dimensionalen Raum sind die Cluster noch klarer getrennt.
Cosine Similarity in der Praxis
Mit echten Embeddings lässt sich sofort praktisch arbeiten:
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("all-MiniLM-L6-v2")
# Semantic Search: Welcher Satz passt am besten zur Anfrage?
anfrage = "Wie funktioniert maschinelles Lernen?"
dokumente = [
"Neuronale Netze lernen durch Anpassung von Gewichten.",
"Das Wetter in München ist heute sonnig.",
"Gradient Descent minimiert die Verlustfunktion.",
"Die Bundesliga startet in die neue Saison.",
"Backpropagation berechnet Gradienten durch die Schichten.",
"Ein gutes Rezept braucht frische Zutaten.",
]
anfrage_emb = model.encode(anfrage, convert_to_tensor=True)
dokument_emb = model.encode(dokumente, convert_to_tensor=True)
# Cosine Similarity zwischen Anfrage und allen Dokumenten
scores = util.cos_sim(anfrage_emb, dokument_emb)[0]
# Sortiert nach Relevanz
ranking = sorted(zip(scores.tolist(), dokumente), reverse=True)
print(f"Anfrage: '{anfrage}'\n")
print(f"{'Score':8s} Dokument")
print("-" * 70)
for score, doc in ranking:
print(f"{score:6.4f} {doc}")
Anfrage: 'Wie funktioniert maschinelles Lernen?'
Score Dokument
----------------------------------------------------------------------
0.7823 Neuronale Netze lernen durch Anpassung von Gewichten.
0.7541 Backpropagation berechnet Gradienten durch die Schichten.
0.7218 Gradient Descent minimiert die Verlustfunktion.
0.1834 Ein gutes Rezept braucht frische Zutaten.
0.1621 Das Wetter in München ist heute sonnig.
0.1204 Die Bundesliga startet in die neue Saison.
Das ist der Kern von Semantic Search, RAG-Systemen und vielen anderen KI-Anwendungen — kein Keyword-Matching, sondern Bedeutungsvergleich im Vektorraum.
Das Problem das bleibt
Embeddings sind ein enormer Fortschritt gegenüber rohen Token-IDs. Aber sie haben eine fundamentale Schwäche: Sie sind statisch.
Jedes Token hat genau einen Embedding-Vektor — unabhängig vom Kontext in dem es auftaucht. Das Wort „Bank" hat dasselbe Embedding egal ob es um eine Sitzgelegenheit oder ein Geldinstitut geht. Das Wort „schlägt" hat dasselbe Embedding in „er schlägt den Ball" und „das Herz schlägt".
Sprache ist aber kontextabhängig. Die Bedeutung von „Bank" hängt von allem ab was davor und danach steht. Ein statisches Embedding kann das nicht abbilden.
Was wir brauchen, ist ein Mechanismus der Embeddings in Abhängigkeit vom Kontext transformiert — der aus dem statischen Embedding von „Bank" ein kontextuelles Embedding macht das im Finanzkontext anders aussieht als im Park.
Das ist die Aufgabe neuronaler Netze — und damit beginnt Artikel 3.
Alle Artikel der Serie
- Das nächste Wort — wie Sprachmodelle funktionieren
- Wörter als Punkte im Raum — was Embeddings wirklich sind ← dieser Artikel
- Neuronale Netze von Grund auf (erscheint demnächst)
- Backpropagation — wie ein Modell lernt (erscheint demnächst)
- Kontext und RNNs — warum Reihenfolge zählt (erscheint demnächst)
- Attention — der Mechanismus der alles veränderte (erscheint demnächst)
- Der Transformer — die vollständige Architektur (erscheint demnächst)
- Fine-Tuning — vom Basismodell zum Assistenten (erscheint demnächst)
Serie: Wie LLMs wirklich funktionieren · rotecodefraktion.de