Semantische Suche vs. Keyword-Suche
Auf dieser Seite
Lernziele
- Die Grenzen der Keyword-Suche verstehen
- Entdecken, wie die semantische Suche diese Probleme löst
- Eine einfache semantische Suche implementieren
Die Grenzen der Keyword-Suche
Klassische Suchverfahren wie TF-IDF, BM25 oder Elasticsearch basieren auf lexikalischer Übereinstimmung: Sie suchen nach den exakten Wörtern der Abfrage innerhalb der Dokumente. Dieser Mechanismus ist schnell, vorhersehbar und so zuverlässig, solange der Nutzer das gleiche Vokabular verwendet wie die Dokumente. Das Problem ist jedoch, dass dies fast nie der Fall ist.
Der folgende kleine, kommentierte Korpus zeigt die vier typischen Situationen, die bei der Abfrage „how to feed a puppy” auftreten:
# Keyword-Suche
query = "how to feed a puppy"
# ✅ Gefunden: enthält dieselben Wörter
doc_a = "How to feed a 3-month-old puppy"
# ❌ Nicht gefunden: andere Wörter, gleiche Bedeutung
doc_b = "Feeding the young dog"
# ❌ Nicht gefunden: Umschreibung
doc_c = "Dietary regimen for newborn puppies"
# ⚠️ Falschpositiv: gleiche Wörter, andere Bedeutung
doc_d = "How to feed a startup project like a puppy"
Nur das erste Dokument wird zurückgegeben, obwohl das zweite und dritte die Frage ebenso gut beantworten. Schlimmer noch: Das vierte rangiert hoch, lediglich weil es Wörter mit der Abfrage teilt, obwohl es sich mit Unternehmensführung befasst. Synonyme, Umschreibungen und Mehrdeutigkeit: Die lexikalische Suche scheitert bei allen drei Punkten.
Semantische Suche
Die semantische Suche vergleicht die Bedeutung von Texten, nicht deren Wörter. Sie kodiert den Korpus einmal und für alle, kodiert die Abfrage, wenn sie eingeht, und rangiert die Dokumente nach Ähnlichkeit.
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(text: str) -> np.ndarray:
response = client.embeddings.create(
input=text,
model="text-embedding-3-large"
)
return np.array(response.data[0].embedding)
# Korpus
documents = [
"How to feed a 3-month-old puppy",
"Feeding the young dog",
"Dietary regimen for newborn puppies",
"How to feed a startup project like a puppy",
"The basics of Italian cuisine",
]
# Indizierung des Korpus
corpus_embeddings = np.array([embed(doc) for doc in documents])
# Suche
query = "how to feed a puppy"
query_embedding = embed(query)
# Ähnlichkeiten berechnen
similarities = corpus_embeddings @ query_embedding
# Nach Relevanz sortieren
results = sorted(
enumerate(similarities), key=lambda x: x[1], reverse=True
)
for idx, score in results:
print(f"{score:.4f} | {documents[idx]}")
Erwartetes Ergebnis:
0.9123 | How to feed a 3-month-old puppy
0.8756 | Dietary regimen for newborn puppies
0.8421 | Feeding the young dog
0.4532 | How to feed a startup project like a puppy
0.1245 | The basics of Italian cuisine
Die drei relevanten Dokumente belegen die Top-3-Positionen, trotz völlig anderer Formulierung, und das Falschpositiv sinkt auf 0,45 – deutlich unter der Schwelle, die es an die Spitze bringen würde. Das Modell verstand, dass „puppy” hier als Vergleich dient und nicht als Subjekt.
Strukturierte Gegenüberstellung
| Kriterium | Keywords (BM25) | Semantisch (Embeddings) |
|---|---|---|
| Synonyme | ❌ Scheitert | ✅ Funktioniert |
| Umschreibungen | ❌ Scheitert | ✅ Funktioniert |
| Mehrsprachigkeit | ❌ Unmöglich | ✅ Möglich |
| Rechtschreibfehler | ❌ Scheitert | ✅ Toleriert |
| Exakte Übereinstimmung | ✅ Ausgezeichnet | ⚠️ Manchmal ungenau |
| Eigennamen, Codes | ✅ Ausgezeichnet | ⚠️ Kann verwirren |
| Geschwindigkeit (großer Korpus) | ✅ Sehr schnell | ⚠️ Benötigt einen Index |
| Kosten | ✅ Kostenlos | ⚠️ API-Aufrufe |
Wann wendet man was an?
Das Lesen der Tabelle ergibt eine einfache Regel: Je näher eine Abfrage an einen Identifier heranreicht, desto geeigneter wird die lexikalische Suche. Eine Produktreferenz wie XR-4471-B, ein Datum, eine exakte Kategorie oder ein seltener Eigennamen sollte wortwörtlich gesucht werden; ein Embedding-Modell neigt dazu, diese Referenz mit seinen Nachbarn zu verwechseln. Dies ist auch die Standardwahl, wenn der Korpus massiv ist und das Embedding-Budget null beträgt.
Die semantische Suche übernimmt die Führung, sobald sich der Nutzer in natürlicher Sprache ausdrückt. Eine FAQ, Kundensupport, technische Dokumentation, die von Nicht-Spezialisten konsultiert wird, ein mehrsprachiger Korpus, bei dem die Frage auf Französisch und die Antwort auf Englisch verfasst ist, oder sogar eine vage, explorative Abfrage: In all diesen Fällen ist die Lücke zwischen dem Vokabular des Nutzers und dem der Dokumente genau das, was Embeddings zu überbrücken wissen.
Da diese beiden Profile sich ergänzen und nicht konkurrieren, besteht die beste Antwort oft darin, beide Ansätze zu kombinieren, um alle Fälle abzudecken. Dies ist das Thema der Lektion 9.
Implementierung einer vollständigen semantischen Suche
In der Praxis kapseln Sie Indizierung und Abfrage in einer kleinen Klasse ein. Dies vermeidet die Neuberechnung der Korpus-Vektoren für jede Abfrage – der häufigste Fehler von Anfängern.
class SemanticSearch:
def __init__(self, model: str = "text-embedding-3-large"):
self.client = OpenAI()
self.model = model
self.documents: list[dict] = []
self.embeddings: np.ndarray | None = None
def index(self, documents: list[dict]):
"""Indiziert eine Liste von Dokumenten {id, text, ...}."""
self.documents = documents
texts = [d["text"] for d in documents]
response = self.client.embeddings.create(
input=texts,
model=self.model
)
vectors = sorted(response.data, key=lambda x: x.index)
self.embeddings = np.array(
[v.embedding for v in vectors], dtype=np.float32
)
def search(self, query: str, k: int = 5) -> list[dict]:
"""Sucht nach den k relevantesten Dokumenten."""
query_emb = self.client.embeddings.create(
input=query,
model=self.model
).data[0].embedding
similarities = self.embeddings @ np.array(query_emb)
top_k = np.argsort(similarities)[-k:][::-1]
return [
{**self.documents[i], "score": float(similarities[i])}
for i in top_k
]
# Nutzung
engine = SemanticSearch()
engine.index([
{"id": 1, "text": "How to install Python on Windows"},
{"id": 2, "text": "Guide to configuring the Python environment"},
{"id": 3, "text": "The basics of the Python language"},
])
results = engine.search("set up Python on my PC")
for r in results:
print(f"[{r['score']:.3f}] {r['text']}")
Testen Sie diesen Motor mit der Abfrage „set up Python on my PC”: Keine dieser fünf Wörter erscheint in Dokument 1, das sich mit „installing Python on Windows” befasst. Es sollte jedoch an erster Stelle rangieren. Ändern Sie dann die Abfrage, um über das „configuring the environment” zu sprechen, und beobachten Sie, wie die Rangliste sich auf Dokument 2 verschiebt – Sie haben nun eine minimale Testumgebung, um das Verhalten Ihres Indexes zu überprüfen, bevor Sie ihn ernsthaft befüllen.
Zusammenfassung
- Die Keyword-Suche scheitert bei Synonymen und Umschreibungen
- Die semantische Suche vergleicht Bedeutungen über Embeddings
- Jeder Ansatz hat seine Stärken: Eine hybride Suche kombiniert beide
- Eine einfache semantische Engine kann in wenigen Dutzend Zeilen gebaut werden