Embeddings: Bedeutung als Vektoren
Kurz gesagt: Ein Embedding ist ein Vektor aus Zahlen für ein Wort oder einen Text, sodass Texte mit naher Bedeutung nahe Vektoren haben, und die Nähe wird mit dem Kosinus des Winkels gemessen.
Problem
Suche nach Wörtern
Der Support-Bot hat eine Basis fertiger Antworten: je einen Text für Lieferung, Rückgabe und Zahlung. Der Kunde schreibt eine Frage, und der Bot soll den Text finden, der sie beantwortet.
// die Antwortbasis des Supportsconst DOCS = [ { topic: "lieferung", text: "die lieferung dauert bis zu zwei tagen" }, { topic: "rückgabe", text: "gib die ware innerhalb von 14 tagen zurück und erhalte das geld" }, { topic: "zahlung", text: "zahle mit karte oder per nachnahme" },];
// die Fragen der Kundenconst QUESTIONS = [ "wann kommt mein paket an", "warum wurde meine abbuchung abgelehnt",];Ein Kollege sucht nach gemeinsamen Wörtern: Je mehr Wörter der Frage im Text stehen, desto besser.
// wie viele Wörter der Frage auch im Text stehenfunction common(question, text) { const ws = text.split(" "); return question.split(" ").filter((word) => ws.includes(word)).length;}for (const q of QUESTIONS) { console.log(q); let best = null; let top = 0; for (const d of DOCS) { const n = common(q, d.text); console.log(` ${d.topic.padEnd(10)}${n}`); if (n > top) { top = n; best = d; } } console.log(" → " + (best ? best.topic : "keiner"));}wann kommt mein paket an lieferung 0 rückgabe 0 zahlung 0 → keinerwarum wurde meine abbuchung abgelehnt lieferung 0 rückgabe 0 zahlung 0 → keinerKeine der beiden Fragen findet etwas. „paket“ und „abbuchung“ kommen in keinem Text vor. Und selbst „zahle“ im Text und „zahlung“ wären für das Programm zwei verschiedene Wörter. Kunden fragen mit ihren eigenen Wörtern, und die Texte in der Basis sind mit anderen geschrieben.
Gebraucht wird ein Vergleich der Bedeutung, nicht der Buchstaben.
Die Idee
Bedeutung als Richtung
Ein Wort erkennt man an der Gesellschaft, in der es steht. „paket“ und „bestellung“ stehen mit denselben Wörtern („kurier“, „tagen“), also bedeuten sie etwas Nahes, obwohl ihre Buchstaben verschieden sind.
Das schreibt man mit Zahlen auf. Jedes Wort wird zu einem Vektor: einer Liste von Zahlen, eine für jede Achse. Wörter mit naher Bedeutung bekommen nahe Vektoren, also Pfeile, die fast in dieselbe Richtung zeigen. Ein solcher Vektor, der für Bedeutung steht, heißt Embedding.
In der Lektion sind die Achsen die acht häufigsten Wörter im Text des Shops, und die Zahl für jede Achse ist, in wie vielen Sätzen das Wort zusammen mit dem Wort der Achse steht. Echte Modelle lernen die Vektoren mit einem neuronalen Netz und haben Hunderte oder Tausende Achsen, aber die Idee ist dieselbe: nahe Bedeutung, nahe Richtung.
Sieh es dir an
Wörter als Vektoren
Kurze Wörter wie „die“, „der“ und „mit“ stehen überall und sagen nichts über das Thema. Sie sind keine Achsen und gehen nicht in die Vektoren ein:
// die kurzen Wörter, die überall vorkommen und nichts über das Thema sagenconst STOP = new Set([ "die", "der", "das", "den", "dem", "des", "ein", "einen", "eine", "einer", "deine", "deiner", "dir", "dich", "du", "und", "oder", "aber", "ist", "war", "wird", "wurde", "hat", "bei", "mit", "an", "in", "von", "zu", "zur", "ab", "bis", "vor", "nach", "ich", "bin", "sehr", "kann", "kannst", "per", "auf", "innerhalb", "zwei", "kam", "wann", "warum", "mein", "meine", "meinen",]);Der Raum: die Achsen und der Vektor eines Wortes. Der Vektor eines Textes ist die Summe der Vektoren seiner Wörter.
// die Wörter als Vektoren: eine Zahl für jede Achseclass Space { // die Achsen: die häufigsten Wörter, ohne die kurzen constructor(text, stop, n) { this.text = text; this.stop = stop; const freq = new Map(); for (const line of text) for (const w of line.split(" ")) if (!stop.has(w)) freq.set(w, (freq.get(w) ?? 0) + 1); this.vocab = [...freq.keys()]; this.axes = [...this.vocab] .sort((a, b) => freq.get(b) - freq.get(a)) .slice(0, n); }
// in wie vielen Sätzen das Wort zusammen mit dem Wort jeder Achse steht of(word) { const v = new Array(this.axes.length).fill(0); for (const line of this.text) { const ws = line.split(" "); if (!ws.includes(word)) continue; for (const w of ws) { const j = this.axes.indexOf(w); if (j >= 0 && w !== word) v[j]++; } } return v; }
// der Vektor eines Textes: die Summe der Vektoren seiner Wörter ofText(s) { const v = new Array(this.axes.length).fill(0); for (const w of s.split(" ")) { if (this.stop.has(w)) continue; this.of(w).forEach((x, j) => (v[j] += x)); } return v; }}const space = new Space(TEXT, STOP, 8);console.log("Achsen: " + space.axes.join(" "));for (const word of ["paket", "bestellung", "zahlung"]) console.log(word.padEnd(12) + `[${space.of(word).join(", ")}]`);Achsen: lieferung kurier ware bestellung tagen karte paket zahlungpaket [0, 1, 0, 0, 1, 0, 0, 0]bestellung [0, 2, 0, 0, 1, 0, 0, 0]zahlung [0, 0, 0, 0, 0, 3, 0, 0]„paket“ steht in einem Satz mit „kurier“ und in einem mit „tagen“. „bestellung“ steht zweimal mit „kurier“ und einmal mit „tagen“: Die Zahlen sind verschieden, die Richtung ist fast gleich. „zahlung“ steht nur mit „karte“: eine ganz andere Richtung.
Sieh es dir an
Ähnlichkeit: der Kosinus des Winkels
Wie nah zwei Vektoren sind, sagt der Winkel zwischen ihnen. Der Kosinus des Winkels ist 1, wenn sie in dieselbe Richtung zeigen, und 0, wenn sie keine Achse gemeinsam haben. Das Teilen durch die Längen sorgt dafür, dass nur die Richtung zählt.
// Kosinus des Winkels: 1 gleiche Richtung, 0 nichts gemeinsamfunction cosine(a, b) { let dot = 0; let na = 0; let nb = 0; for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; } if (na === 0 || nb === 0) return 0; return dot / (Math.sqrt(na) * Math.sqrt(nb));}const space = new Space(TEXT, STOP, 8);const v = space.of("paket");console.log(`paket und bestellung: ${cosine(v, space.of("bestellung")).toFixed(3)}`);console.log(`paket und zahlung: ${cosine(v, space.of("zahlung")).toFixed(3)}`);const others = space.vocab .filter((o) => o !== "paket") .sort((x, y) => cosine(v, space.of(y)) - cosine(v, space.of(x)));console.log("am nächsten zu paket:");for (const o of others.slice(0, 5)) console.log(` ${o.padEnd(12)}${cosine(v, space.of(o)).toFixed(3)}`);paket und bestellung: 0.949paket und zahlung: 0.000am nächsten zu paket: bestellung 0.949 lieferung 0.949 ware 0.707 fünf 0.707 erstattet 0.707„paket“ und „bestellung“ haben 0,949: fast dieselbe Richtung. Mit „zahlung“ sind es 0,000. Die Wörter, die „paket“ am nächsten sind, sind „bestellung“ und „lieferung“, beide 0,949. Danach kommen „ware“, „fünf“ und „erstattet“ mit je 0,707: In einem kleinen Text ziehen auch Zufallsnachbarn mit.
Probier es aus
Sag die Ausgabe voraus
Wie groß ist der Kosinus von [1, 1, 0] und [1, 0, 0]?
console.log(cosine([1, 1, 0], [1, 0, 0]).toFixed(2));Das Skalarprodukt ist 1. Die Längen sind √2 und 1, und 1 / √2 = 0,7071, gerundet 0,71. Der Winkel zwischen den beiden Vektoren ist 45°: Sie haben eine Achse gemeinsam, und der erste hat noch eine mehr.
Sieh es dir an
Suche nach Bedeutung
Jetzt werden die Frage und jeder Text der Basis zu Vektoren, und die Antwort ist der Text mit dem größten Kosinus:
const space = new Space(TEXT, STOP, 8);for (const q of QUESTIONS) { const v = space.ofText(q); console.log(q); let best = DOCS[0]; let top = -1; for (const d of DOCS) { const c = cosine(v, space.ofText(d.text)); console.log(` ${d.topic.padEnd(10)}${c.toFixed(3)}`); if (c > top) { top = c; best = d; } } console.log(` → ${best.topic}: ${best.text}`);}wann kommt mein paket an lieferung 0.738 rückgabe 0.510 zahlung 0.000 → lieferung: die lieferung dauert bis zu zwei tagenwarum wurde meine abbuchung abgelehnt lieferung 0.000 rückgabe 0.221 zahlung 0.894 → zahlung: zahle mit karte oder per nachnahmeDie erste Frage findet „lieferung“ mit 0,738, „rückgabe“ hat 0,510: „kommt“ steht im Text auch in „die ware kommt in der originalverpackung an“. Die zweite findet „zahlung“ mit 0,894: „abgelehnt“ steht in einem Satz mit „karte“ und „zahlung“, und der Zahlungstext hat „karte“ und „nachnahme“. Keine der beiden Fragen hat ein Wort gemeinsam mit ihrer Antwort.
Hier ist die erste Frage als Richtungen. Die Vektoren haben 8 Dimensionen und das Bild ist flach, darum zeigt es nur den Winkel jedes Textes zur Frage:
// Kosinus des Winkels: 1 gleiche Richtung, 0 nichts gemeinsamfunction cosine(a, b) { let dot = 0; let na = 0; let nb = 0; for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; } if (na === 0 || nb === 0) return 0; return dot / (Math.sqrt(na) * Math.sqrt(nb));}
const space = new Space(TEXT, STOP, 8);for (const q of QUESTIONS) { const v = space.ofText(q); console.log(q); let best = DOCS[0]; let top = -1; for (const d of DOCS) { const c = cosine(v, space.ofText(d.text)); console.log(` ${d.topic.padEnd(10)}${c.toFixed(3)}`); if (c > top) { top = c; best = d; } } console.log(` → ${best.topic}: ${best.text}`);}Schritt für Schritt, in Worten
- Die Frage „wann kommt mein paket an“ wird zu einem Vektor: [0, 2, 1, 0, 1, 0, 0, 0]. Das Bild behält nur den Winkel dazu.
- „zahlung“: Kosinus 0,000, Winkel 90°. Keine einzige gemeinsame Achse: Die Pfeile stehen im rechten Winkel.
- „rückgabe“: Kosinus 0,510, Winkel 59°. Einige gemeinsame Achsen, aber weit weg.
- „lieferung“: Kosinus 0,738, Winkel 42°. Der kleinste Winkel.
- Am nächsten liegt „lieferung“: „die lieferung dauert bis zu zwei tagen“. Kein einziges gemeinsames Wort mit der Frage.
Probier es aus
Ergänze
Der Kosinus teilt das Skalarprodukt durch die beiden Längen. Was kommt an die Stelle von ___?
return dot / (Math.sqrt(na) * Math.sqrt());nb, die Summe der Quadrate des zweiten Vektors. Ihre Wurzel ist die Länge des zweiten Vektors, so wie die Wurzel von na die Länge des ersten ist.
Probier es aus
Finde den Fehler
Ein Kollege rechnet die Ähnlichkeit schneller aus, bekommt aber 7 für die erste Frage und den Lieferungstext, und die Ähnlichkeit soll zwischen 0 und 1 liegen. In welcher Zeile ist der Fehler?
Wähle die Zeile mit dem Fehler.
Er gibt das Skalarprodukt zurück, ohne durch die Längen zu teilen. Dann hat ein langer Text mit vielen Wörtern einen großen Vektor und sammelt große Zahlen, auch wenn er woandershin zeigt. Es muss dot / (√na · √nb) sein: So bleibt nur die Richtung übrig.
Die Idee
Attention als Idee
In der Lektion hat „karte“ überall einen Vektor. Aber in „zahle mit karte“ und „die karte der stadt“ bedeutet das Wort Verschiedenes: eine Bankkarte und einen Stadtplan.
Echte Modelle lösen das mit Attention (Aufmerksamkeit). Jedes Token schaut auf die anderen Tokens im Text und mischt ein wenig von ihnen in seinen eigenen Vektor, mehr von denen, die für es wichtig sind. „karte“ neben „zahle“ nimmt von „zahle“, neben „stadt“ von „stadt“, und die beiden Vektoren werden verschieden. Das wiederholt sich durch viele Schichten, und so trägt der Vektor jedes Tokens seine Bedeutung in genau diesem Satz. Der Vektor des ganzen Textes wird aus ihnen berechnet.
Hier ist Attention nur eine Idee: Wie sie genau berechnet wird, gehört nicht zum Kurs.
Die Idee
Wann es wehtut
-
Das kleine Modell kennt Wörter, nicht Bedeutung. Ein Kunde will etwas zurückgeben, und die Suche schickt ihn zur Lieferung:
const space = new Space(TEXT, STOP, 8);for (const q of ["ich will die bestellung zurückgeben"]) {const v = space.ofText(q);console.log(q);let best = DOCS[0];let top = -1;for (const d of DOCS) {const c = cosine(v, space.ofText(d.text));console.log(` ${d.topic.padEnd(10)}${c.toFixed(3)}`);if (c > top) {top = c;best = d;}}console.log(` → ${best.topic}: ${best.text}`);}Ausgabe ich will die bestellung zurückgebenlieferung 0.775rückgabe 0.573zahlung 0.000→ lieferung: die lieferung dauert bis zu zwei tagen„will“ steht nicht im Text und sagt dem Modell nichts. „zurückgeben“ kommt nur in einem Satz vor, mit „ware“ und „tagen“; „zurück“ und „rückgabe“ sind für das Modell andere Wörter, so wie beim Vokabular in der Lektion Text in Stücken: Tokens. „bestellung“ steht im Text zweimal mit „kurier“ und einmal mit „tagen“. So wird der Vektor der Frage
[0, 2, 1, 0, 2, 0, 0, 0]und zeigt zur Lieferung: „lieferung“ gewinnt mit 0,775, und „rückgabe“, die richtige Antwort, ist mit 0,573 Zweite. Bei einem anderen Text würde dieselbe Frage woanders landen: Die Fehlgriffe hängen vom Text ab.Ein echtes Embedding-Modell ist mit einer riesigen Menge Text trainiert. Es weiß, dass „zurückgeben“ und „Rückgabe“ zusammengehören, und es sieht die ganze Frage, nicht nur einzelne Wörter. Es kann sich trotzdem irren, aber viel seltener. Wie oft es den richtigen Text findet, wird gemessen: Im Modul zur Suche nach Bedeutung ist das recall@k, der Anteil der Fragen, deren richtiger Text unter den ersten k gefundenen ist.
-
Nah heißt nicht gleich. Wörter mit gegenteiliger Bedeutung stehen oft in denselben Sätzen:
const space = new Space(TEXT, STOP, 8);for (const [x, y] of [["kostenlos", "kostet"], ["schnell", "spät"]])console.log(`${x} und ${y}: ${cosine(space.of(x), space.of(y)).toFixed(3)}`);console.log(`farbe: [${space.of("farbe").join(", ")}]`);Ausgabe kostenlos und kostet: 0.707schnell und spät: 0.000farbe: [0, 0, 0, 0, 0, 0, 0, 0]„kostenlos“ und „kostet“ haben 0,707, weil beide neben „lieferung“ stehen. Echte Modelle machen das besser, aber „kann“ und „kann nicht“ kommen trotzdem nahe beieinander heraus.
-
Ein unbekanntes Wort ist null. „farbe“ steht nicht im Text, und sein Vektor besteht nur aus Nullen. Eine Frage nur mit unbekannten Wörtern hat 0,000 zu allem, aber die Suche liefert trotzdem irgendeinen Text. Darum braucht man eine Schwelle: siehe die Übung.
-
Die Vektoren kommen aus dem Text. Ein kleiner Text liefert Zufallsnachbarn („fünf“ neben „paket“). Verschiedene Modelle liefern verschiedene Vektoren, die sich nicht miteinander vergleichen lassen: Die Basis und die Frage müssen dasselbe Modell benutzen.
-
Viele Texte, viele Vektoren. Bei Tausenden Dokumenten werden die Vektoren in einer Datenbank gehalten, die die nächsten schnell findet: Vektordatenbanken. Die Suche nach Bedeutung über die Dokumente des Shops ist später im Kurs ein ganzes Modul. Wie man ein Embedding-Modell auf einem Server betreibt und den Index aktuell hält, ist Aufgabe des Kurses ZeroToOps.
Genauer, für Neugierige
Echte Embeddings. Embedding-Modelle nehmen Text und geben Hunderte oder Tausende Zahlen zurück. Sie werden wie andere Modelle über eine API aufgerufen oder lokal betrieben, meist aus Python. Die Vektoren haben meist die Länge 1, und dann ist der Kosinus einfach das Skalarprodukt.
Die Nullen auf den Achsen. In der Lektion zählt ein Wort seine eigene Achse nicht mit. „zahlung“ steht viermal im Text, hat aber an der Achse „zahlung“ eine 0: Sein Vektor ist [0, 0, 0, 0, 0, 3, 0, 0], nur „karte“ zählt. Zählte ein Wort auch seine eigene Achse, wäre das die größte Zahl in seinem Vektor, und es würde alles zu sich ziehen: „paket“ und „bestellung“ würden von 0,949 auf 0,129 fallen. Beide Entscheidungen sind Kompromisse des kleinen Modells; echte Modelle haben keine Achsen, die Wörter sind.
Warum Kosinus und nicht Abstand. Der Abstand zwischen den Spitzen hängt auch von der Länge ab, und die Länge ist hier, wie viele Wörter der Text hat. Der Kosinus schaut nur auf die Richtung. Bei Vektoren der Länge 1 geben beide dieselbe Reihenfolge.
Die nächsten k. Die Suche in der Lektion ist k-NN aus der Lektion über die nächsten Nachbarn mit k = 1: Nur die Vektoren und das Maß der Nähe sind andere.
Übung
Nicht sicher ist eine Antwort
Löse es in deinem Editor.
Der Bot darf nicht zufällig antworten. Schreibe eine Funktion, die den nächsten Text nur zurückgibt, wenn der Kosinus mindestens 0,5 ist, und sonst „nicht sicher, ich gebe an einen Kollegen weiter“. Probiere sie mit den beiden Fragen aus der Lektion und mit „kann ich eine andere farbe haben“.
wann kommt mein paket an → die lieferung dauert bis zu zwei tagenwarum wurde meine abbuchung abgelehnt → zahle mit karte oder per nachnahmekann ich eine andere farbe haben → nicht sicher, ich gebe an einen Kollegen weiterDie dritte Frage hat 0,000 zu allem, und statt einer falschen Antwort gibt der Bot sie an einen Menschen weiter. Die Schwelle ist ein Kompromiss: Eine hohe Schwelle gibt Fragen weiter, auf die der Bot die Antwort kennt, und eine niedrige lässt falsche Antworten durch.
Weitere Aufgaben
- Mehr Achsen. Baue den Raum mit 16 Achsen. Wie ändern sich die Ähnlichkeiten aus der Lektion?
- Die ersten zwei. Gib für jede Frage die zwei nächsten Texte aus und wie weit der zweite vom ersten entfernt ist.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Daten, switch und Destrukturierung · Was maschinelles Lernen ist · Text in Stücken: Tokens · Das nächste Token vorhersagen
- Geht weiter in: Semantische Suche: die k nächsten · Dokumente in Abschnitte teilen (Chunking) · RAG: Antworten aus den Dokumenten · RAG bewerten: Wird der Abschnitt gefunden, und ist die Antwort belegt
- Aufgaben zum Thema: Wie ein Sprachmodell funktioniert