14+ J
📐 Page 06 von 8 · ~25 Min

TF-IDF – die Mathematik der Relevanz 📐

Das erste echte Ranking-Modell. Aus zwei einfachen Ideen – „wie oft kommt das Wort vor" und „wie selten ist das Wort" – entstand jahrzehntelang der Standard. Wir gehen tief: log-TF, Vector Space Model, Cosine Similarity, Varianten und die Schwächen, die zu BM25 führten.

📊 TF-Varianten 🧮 IDF Smoothing 📐 Vector Space 📏 Cosine Similarity
roboter drucker Doc A Doc B Query θ Dokumente als Vektoren

01Recap: die TF-IDF-Idee

Zwei Zahlen, die multipliziert werden – und dahinter steckt richtig viel Theorie.

tfidf(t, d) = tf(t, d) × idf(t) Wie oft das Wort im Dokument vorkommt (TF) × wie selten es global ist (IDF)

02TF-Varianten – nicht alle gleich

In der Praxis benutzt man fast nie das rohe TF. Hier vier Varianten und wann sie sinnvoll sind.

VarianteFormelIdee
Raw tf(t,d) = count(t in d) Naivste Form. 10 Vorkommen = 10x so wichtig (linear).
Boolean tf(t,d) = 1 if t in d else 0 Nur „enthalten ja/nein". Für kleine Documente OK.
Log-Normalized tf(t,d) = 1 + log(count) Sättigung mit log. Das 100. Vorkommen bringt weniger als das 10.
Double-Norm 0.5 tf = 0.5 + 0.5 × (count/max_count) Normiert auf 0.5-1.0. Schwächt lange Documente nicht.
📊
Praxis-Tipp: log(1+count) ist der pragmatische Standard. Es bestraft Keyword-Stuffing, ohne ganz binär zu werden – ein direkter Vorläufer der BM25-Sättigung.

03IDF – und warum „smoothed"?

Die rohe Formel ist instabil. Mit einem kleinen Trick wird sie robust.

Klassisch (Naive IDF)

idf(t) = log( N / df(t) ) N = Gesamtzahl Docs · df(t) = Dokumente die t enthalten

Problem: Was, wenn df(t) = 0 (Wort kommt nirgends vor)? Division durch Null. Oder df(t) = N (in allen Docs)? log(1) = 0 – das Wort hat exakt 0 Wert (kann praktisch sein).

Smoothed (Standard in Praxis)

idf(t) = log( (N + 1) / (df(t) + 1) ) + 1 +1 vermeidet Division durch Null und log(0). +1 am Ende hält IDF positiv.

Probabilistic IDF (führt zu BM25)

idf(t) = log( (N - df(t) + 0.5) / (df(t) + 0.5) ) Diese Form benutzt BM25. Theoretisch hergeleitet aus dem probabilistischen Retrieval-Modell.

04Das Vector Space Model

Jedes Dokument ist ein Vektor. Jede Suche auch. Ähnlichkeit = Winkel zwischen ihnen.

Hier wird's mathematisch elegant. Stell dir vor, dein Index hat V Wörter. Jedes Dokument ist ein Vektor mit V Dimensionen. Jeder Eintrag im Vektor = TF-IDF des entsprechenden Wortes im Dokument.

Mini-Beispiel mit 3 Wörtern

Doc A

roboter: 2.4
drucker: 1.1
motor: 0.0

Vektor: (2.4, 1.1, 0.0)

Doc B

roboter: 0.6
drucker: 3.2
motor: 0.0

Vektor: (0.6, 3.2, 0.0)

Query

roboter: 1.0
drucker: 0.0
motor: 0.0

Vektor: (1.0, 0, 0)

Welcher Doc ist „ähnlicher" zur Query? Doc A – weil sein Vektor in eine ähnlichere Richtung zeigt.

05Cosine Similarity – Winkel statt Abstand

Der Standard, um Ähnlichkeit zwischen Vektoren zu messen.

cos(d, q) = ( d · q ) / ( ||d|| × ||q|| ) Skalarprodukt geteilt durch das Produkt der Vektor-Längen. Ergibt einen Wert zwischen -1 und 1.
📏
Warum nicht Euklidischer Abstand? Lange Dokumente haben automatisch grosse Vektor-Längen. Mit Euklid wären sie immer „weiter weg" als kurze, selbst bei gleichem Inhalt. Cosine ignoriert die Länge und misst nur die Richtung – genau, was wir wollen.

Konkretes Beispiel rechnen

Doc A = (2.4, 1.1, 0.0), Query = (1.0, 0, 0):

d·q = 2.4×1.0 + 1.1×0 + 0×0 = 2.4
||d|| = √(2.4² + 1.1² + 0²) = 2.64
||q|| = 1.0
cos = 2.4 / (2.64 × 1.0) 0.91

Sehr ähnlich. Doc A ist ein guter Treffer.

06Schwächen von TF-IDF (→ BM25)

Warum trotz Eleganz die Suchcommunity weitergebaut hat.

  1. Lineares TF: 100x ein Wort = 100x wichtig. Spam-Anfälligkeit. Log-TF hilft, ist aber nicht perfekt.
  2. Dokumentlänge ignoriert: Lange Docs haben mehr TF-Vorkommen → tendieren zu höheren Scores. Cosine kompensiert teilweise – aber nicht ideal.
  3. Keine Sättigung: Ab welchem Punkt bringt's nichts mehr? TF-IDF hat keinen klaren „enough"-Punkt.
  4. Wort-Position ignoriert: Wort im Titel = gleich wichtig wie Wort am Ende des Footers.
  5. Kein semantisches Verständnis: „Auto" und „PKW" sind völlig verschiedene Wörter aus TF-IDF-Sicht.
🎯
BM25 löst die Punkte 1-3 elegant mit zwei Parametern (k₁ und b). Die Punkte 4 & 5 brauchen entweder spezielle Tricks (Feld-Boosts, BM25F) oder einen ganz anderen Ansatz: Embeddings (Page 08).

07TF-IDF in Python – scikit-learn

In 10 Zeilen ein funktionierender TF-IDF-Search-Index.

PYTHON from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity docs = [ "Lunolabs baut Roboter und 3D-Drucker", "3D-Drucker drucken Bauteile aus PLA", "Roboter brauchen Sensoren und Motoren", ] vec = TfidfVectorizer() matrix = vec.fit_transform(docs) # shape: (3 docs × V terms) query = vec.transform(["roboter motor"]) scores = cosine_similarity(query, matrix).flatten() for i, score in sorted(enumerate(scores), key=lambda x: -x[1]): print(f"score={score:.3f} - {docs[i]}") # => score=0.612 - Roboter brauchen Sensoren und Motoren # => score=0.291 - Lunolabs baut Roboter und 3D-Drucker # => score=0.000 - 3D-Drucker drucken Bauteile aus PLA

Das ist produktionsreifer Code. Für kleine bis mittlere Sammlungen (~Mio Docs) macht das den Job hervorragend – mit ein paar Zeilen.

🎯Key Takeaways