Suchen nach Bedeutung
Kurz gesagt: Die Vektorsuche wandelt Text in einen Vektor aus Zahlen (ein Embedding) um und findet die Datensätze, die der Bedeutung nach am nächsten liegen, über die Kosinus-Ähnlichkeit.
Problem
„Etwas zum Frühstück“
Ein Kunde tippt in die Suche des Shops „etwas zum Frühstück“ ein. Das Wort steht in keinem Produktnamen:
Die Volltextsuche aus der vorigen Lektion findet nur die Bewertung „Das Vollkornbrot ist wunderbar zum Frühstück.“ Dabei sind Milch, ein Brötchen, Kaffee und Banitsa genau das, was der Kunde will. Er sucht nach Bedeutung, nicht nach einem Wort. Synonyme, Übersetzungen und „etwas zum …“ fängt man mit Wörtern nicht.
Die Idee
Ein Text als Punkt
Ein Sprachmodell (ein Embedding-Modell) wandelt einen Text in einen Vektor um: eine Liste von Zahlen. Es ist so trainiert, dass Texte mit naher Bedeutung nahe Vektoren ergeben. „Milch“, „Brötchen“ und „etwas zum Frühstück“ liegen nah beieinander, und „Waschpulver“ weit weg.
Echte Modelle liefern von ein paar hundert bis zu ein paar tausend Zahlen, und keine davon hat einen Namen. Damit du siehst, was passiert, sind die Vektoren hier ausgedacht und haben nur vier benannte Dimensionen: Frühstück, süß, Kochen und Reinigen. Die Tabelle product_vectors hält den Vektor jedes Produkts als JSON-Array:
Das Brötchen ist sehr „Frühstück“ und ein wenig „süß“. Die Kartoffeln sind nur „Kochen“ und das Waschpulver nur „Reinigen“.
Genauer, für Neugierige
Der Vektor wird einmal berechnet, wenn das Produkt hinzukommt oder sich seine Beschreibung ändert, und in der Datenbank gehalten. Die Frage des Kunden wird bei jeder Suche mit demselben Modell in einen Vektor umgewandelt. Die beiden Vektoren müssen vom selben Modell stammen: Vektoren verschiedener Modelle lassen sich nicht vergleichen. Es gibt offene Modelle (zum Beispiel aus der Familie sentence-transformers, die auf einem gewöhnlichen Computer laufen) und bezahlte Dienste.
Die Idee
Wie nah: der Kosinus
Zwei Vektoren sind nah, wenn sie in dieselbe Richtung zeigen. Das Maß ist die Kosinus-Ähnlichkeit: 1 heißt dieselbe Richtung, 0 heißt nichts gemeinsam. Sie wird so berechnet: Multipliziere die Zahlen an denselben Stellen und addiere sie (das Skalarprodukt), dann teile durch die Längen der beiden Vektoren.
json_each entfaltet ein JSON-Array zu Zeilen: key ist die Stelle und value die Zahl. Der Weißkäse gegen die Frage „etwas zum Frühstück“ ([1, 0.2, 0.1, 0], so wie das Modell sie liefern würde):
Die Summe der letzten Spalte ist 0,66. Geteilt durch die Längen (0,849 für den Käse und 1,025 für die Frage) ergibt das 0,759. Dasselbe für alle Produkte, vom nächsten abwärts:
WITH q(v) liefert den Vektor der Frage. Die zwei json_each werden über die Stelle verbunden, und GROUP BY addiert die Produkte für jeden Eintrag. Keines dieser Produkte hat das Wort „Frühstück“ im Namen.
Probier es aus
Etwas Süßes zum Frühstück
Das Modell hat die Frage „etwas Süßes zum Frühstück“ in den Vektor [0.7, 0.9, 0, 0] umgewandelt. Finde die drei nächsten Produkte (die Spalten name und similarity, auf 0.001 gerundet), vom nächsten abwärts.
Lösung
WITH q(v) AS (SELECT '[0.7, 0.9, 0, 0]')
SELECT p.name,
ROUND(SUM(a.value * b.value)
/ (sqrt(SUM(a.value * a.value)) * sqrt(SUM(b.value * b.value))), 3) AS similarity
FROM product_vectors pv
JOIN products p ON p.id = pv.product_id
CROSS JOIN q
JOIN json_each(pv.embedding) a
JOIN json_each(q.v) b ON b.key = a.key
GROUP BY p.id, p.name
ORDER BY similarity DESC
LIMIT 3;Dieselbe Abfrage mit '[0.7, 0.9, 0, 0]' in WITH q(v). Mit ORDER BY similarity ohne DESC kommen die entferntesten heraus: die Reinigungs- und Küchenprodukte, mit Ähnlichkeit 0. Und der Vektor von „etwas zum Frühstück“ ergibt Kaffee und Banitsa, die nicht süß sind.
Die Idee
Filtern vor der Sortierung
Die Suche nach Bedeutung hat oft auch gewöhnliche Bedingungen: unter einem Preis, nur auf Lager, nur in einer Kategorie. Die Bedingung gehört in WHERE, also vor GROUP BY, ORDER BY und LIMIT. Nimmst du erst die nächsten drei und filterst dann, kann nichts übrig bleiben: Es sind Kaffee für 14,90 €, Banitsa für 2,50 € und Milch für 1,60 €.
Genauer, für Neugierige
Vektorindizes (die nächste Karte) haben genau dieses Problem. Die ungefähre Suche liefert zum Beispiel die nächsten 100, und kommt der Filter danach, während die günstigen Produkte nicht unter diesen 100 sind, ist das Ergebnis leer. Deshalb haben Vektordatenbanken Filter, die während der Suche angewendet werden, und pgvector hat Einstellungen wie hnsw.ef_search und iterative Scans (hnsw.iterative_scan).
Übung
Günstig und ähnlich
1. Der Kunde hat „bis 1,50 €“ gewählt.
Finde die drei Produkte, die „etwas zum Frühstück“ ([1, 0.2, 0.1, 0]) am nächsten liegen, mit einem Preis unter 1,50 € (die Spalten name und similarity), vom nächsten abwärts.
Lösung
WITH q(v) AS (SELECT '[1, 0.2, 0.1, 0]')
SELECT p.name,
ROUND(SUM(a.value * b.value)
/ (sqrt(SUM(a.value * a.value)) * sqrt(SUM(b.value * b.value))), 3) AS similarity
FROM product_vectors pv
JOIN products p ON p.id = pv.product_id
CROSS JOIN q
JOIN json_each(pv.embedding) a
JOIN json_each(q.v) b ON b.key = a.key
WHERE p.price < 1.5
GROUP BY p.id, p.name
ORDER BY similarity DESC
LIMIT 3;WHERE p.price < 1.5 vor GROUP BY. Nimmst du erst die nächsten drei und filterst dann nach dem Preis, bleibt keines übrig: Der Kaffee, die Banitsa und die Milch liegen über 1,50 €.
2. Die Produktseite zeigt „ähnliche Produkte“. Der Vektor der Frage ist der Vektor des Produkts selbst.
Finde die drei Produkte, die dem Weißkäse (Produkt 7) am ähnlichsten sind, ohne den Käse selbst (die Spalten name und similarity), vom nächsten abwärts.
Lösung
WITH q(v) AS (SELECT embedding FROM product_vectors WHERE product_id = 7)
SELECT p.name,
ROUND(SUM(a.value * b.value)
/ (sqrt(SUM(a.value * a.value)) * sqrt(SUM(b.value * b.value))), 3) AS similarity
FROM product_vectors pv
JOIN products p ON p.id = pv.product_id
CROSS JOIN q
JOIN json_each(pv.embedding) a
JOIN json_each(q.v) b ON b.key = a.key
WHERE p.id <> 7
GROUP BY p.id, p.name
ORDER BY similarity DESC
LIMIT 3;WITH q(v) AS (SELECT embedding FROM product_vectors WHERE product_id = 7) und WHERE p.id <> 7. Ohne die Bedingung geht der erste Platz an den Käse selbst, mit Ähnlichkeit 1.
Die Idee
Millionen Vektoren: ein ungefährer Index
Die Abfrage vergleicht die Frage mit jedem Produkt. Der Plan zeigt SCAN p:
Bei 23 Produkten mit je 4 Zahlen ist das nichts. Bei 10 Millionen Dokumenten mit je 1536 Zahlen ist jede Suche 15 Milliarden Multiplikationen. Ein gewöhnlicher Index hilft nicht: Er sortiert nach einem Wert, während „nah“ bei Vektoren in hunderten Richtungen gleichzeitig gilt.
Deshalb nutzen Vektordatenbanken ungefähre Indizes (ANN, approximate nearest neighbours). Am weitesten verbreitet ist HNSW: ein Graph, in dem jeder Vektor auf mehreren Ebenen mit seinen nahen verbunden ist. Die Suche beginnt auf der obersten, dünnen Ebene und geht nach unten, immer zu näheren Nachbarn. Sie findet die nächsten in Millisekunden, verpasst aber manchmal einen davon. Der Anteil der wirklich nächsten, den der Index findet, ist der Recall, und er wird gegen die Geschwindigkeit abgestimmt.
Die Idee
Wo die Vektoren leben
Am einfachsten ist es, wenn die Vektoren neben den Daten liegen. pgvector ist eine Erweiterung von PostgreSQL:
CREATE EXTENSION vector;CREATE TABLE product_vectors ( product_id integer PRIMARY KEY REFERENCES products(id), embedding vector(384));CREATE INDEX ON product_vectors USING hnsw (embedding vector_cosine_ops);
-- <=> ist die Kosinus-Distanz (1 minus die Ähnlichkeit)SELECT p.nameFROM product_vectors pv JOIN products p ON p.id = pv.product_idWHERE p.price < 1.5ORDER BY pv.embedding <=> $1LIMIT 3;Eigene Vektordatenbanken (Qdrant, Weaviate, Milvus, Pinecone) lohnen sich bei hunderten Millionen Vektoren oder wenn die Suche nach Bedeutung die Hauptaufgabe ist. Auch Elasticsearch, OpenSearch und Redis haben eine Vektorsuche.
Der häufigste Einsatz heute ist RAG (retrieval-augmented generation): Der Chatbot des Shops wandelt die Frage in einen Vektor um, findet die nächsten Stücke aus Beschreibungen, Bewertungen und Regeln und gibt sie dem Sprachmodell, damit es daraus antwortet und nicht aus dem Gedächtnis. Die Suche nach Bedeutung wird oft mit der Volltextsuche gemischt: Die Wörter fangen genaue Namen und Codes, und die Vektoren die Bedeutung.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Eine Auswertung in Schritten: WITH · Indizes · EXPLAIN: der Abfrageplan · Text durchsuchen
- Geht weiter in: Wie du eine Datenbank wählst
- Aufgaben zum Thema: Arten von Datenbanken