14+ J
🎯 Page 07 von 8 · ~30 Min

BM25 – das Standard-Ranking 🎯

Seit den 90ern der Algorithmus für Suche – immer noch der Default in Elasticsearch, Lucene, Solr und vielen Online-Stores. Wir leiten die Formel her, drehen an den Knöpfen (k₁, b), schauen Varianten (BM25F, BM25+), und bauen einen funktionierenden BM25-Ranker in Python.

🧮 Herleitung 🎚 k₁ & b tuning 📚 BM25F 🐍 Python
Vorkommen (TF) Score TF-IDF BM25 "genug ist genug"

01Wieso BM25 – und nicht einfach TF-IDF?

Beide messen, wie gut ein Dokument zu einer Anfrage passt. BM25 macht's einfach besser.

BM25 (Best Matching 25) ist das Resultat einer Forschungsreihe von Stephen Robertson & Karen Spärck Jones in den späten 80ern/90ern – probabilistisch hergeleitet (daher der „BM"-Name). Drei Probleme von TF-IDF, die es löst:

  1. TF-Sättigung: Mehr Vorkommen → mehr Score, aber mit abnehmender Wirkung. Spam-resistent.
  2. Dokumentlängen-Normalisierung: Lange Docs werden nicht künstlich bevorzugt.
  3. Tuning-Parameter: Mit k₁ und b kann man auf den eigenen Corpus optimieren.

02Die Formel – Schritt für Schritt

Sie wirkt erstmal wild. Aber jede Komponente ist einzeln verständlich.

Die volle BM25-Score-Formel

score(d, q) = Σt∈q idf(t) × [ tf(t,d) × (k₁ + 1) ] / [ tf(t,d) + k₁ × (1 - b + b × |d|/avgdl) ] |d| = Länge dieses Dokuments · avgdl = durchschnittliche Doc-Länge im Index

In drei Teilen

[ IDF ] × [ TF-Komponente ] Score = IDF × normalisierter TF-Beitrag, summiert über alle Query-Terms

Teil 1 · IDF: wie selten ist das Wort? (gleiche Idee wie bei TF-IDF, mit Smoothing)

idf(t) = log( (N - df(t) + 0.5) / (df(t) + 0.5) + 1 )

Teil 2 · TF-Komponente: das Herz von BM25.

tf(t,d) × (k₁ + 1) / [ tf(t,d) + k₁ × (1 - b + b × |d|/avgdl) ]

Setzt man b=0 und k₁→∞, wird das wieder zu linearer TF (klassisches TF-IDF). Setzt man k₁→0, wird's boolean (nur „ja/nein"). Die beiden Knöpfe interpolieren zwischen Extremen.

03Die beiden Knöpfe: k₁ und b

Stell dir BM25 als Mischpult vor. Zwei Regler – beide haben sehr unterschiedlichen Effekt.

k₁ · Sättigungs-Regler

Was er macht: Steuert, wie schnell der TF-Score „flach" wird.

  • k₁ = 0: Score ist boolean (Wort drin = 1, sonst 0)
  • k₁ = 1.2: Default in Lucene/Elastic. Sättigt schnell.
  • k₁ = 2.0: Lockerere Sättigung. Mehr TF zählt mehr.
  • k₁ → ∞: Praktisch linear (wie TF-IDF)

Tipp: Bei sehr kurzen Docs (Tweets) niedriger setzen. Bei langen Docs (Papers) etwas höher.

b · Längen-Normalisierung

Was er macht: Steuert, wie stark Dokumentlänge bestraft wird.

  • b = 0: Länge wird ignoriert
  • b = 0.75: Default. Mittelmaß.
  • b = 1.0: Voll-Normalisierung. Lange Docs werden stark bestraft.

Tipp: News-Sites mit ähnlich langen Artikeln → b niedriger. Mixed-Length-Corpus (Tweets + Bücher) → b höher.

🎛
Real-World Defaults: Elasticsearch nutzt k₁=1.2, b=0.75 – das hat sich über Jahre als universell „gut genug" erwiesen. Für Domain-spezifische Suchen kann man durch Tuning ~10-20% Genauigkeit gewinnen.

04Durchgerechnet: ein konkretes BM25-Beispiel

Wir berechnen den Score für ein Dokument – Schritt für Schritt.

Setup:

Schritt 1 – IDF:

idf = log( (1'000'000 - 10'000 + 0.5) / (10'000 + 0.5) + 1 ) = log(100) 4.6

Schritt 2 – Normalisierter Term:

Norm = 1 - 0.75 + 0.75 × (100/80) = 0.25 + 0.9375 = 1.1875

Schritt 3 – TF-Komponente:

TF-comp = 3 × (1.2 + 1) / (3 + 1.2 × 1.1875) = 6.6 / 4.425 1.49

Schritt 4 – Score:

score = 4.6 × 1.49 6.85

Bei einer Multi-Word-Query summieren wir Scores über alle Query-Terms.

05BM25 in Python – mit rank_bm25

Eine kleine, produktionsreife Library für BM25.

PYTHON from rank_bm25 import BM25Okapi import re def tok(t): return re.findall(r"[a-zäöü0-9]+", t.lower()) corpus = [ "Lunolabs baut Roboter und 3D-Drucker im Lab in Altendorf", "3D-Drucker drucken Bauteile aus PLA und PETG Filament", "Roboter brauchen Sensoren und Motoren um zu funktionieren", "Im Lunolabs Lab lernen Kids Programmieren mit Python", ] tokenized = [tok(d) for d in corpus] bm25 = BM25Okapi(tokenized, k1=1.2, b=0.75) query = tok("roboter motor") scores = bm25.get_scores(query) for i, s in sorted(enumerate(scores), key=lambda x: -x[1]): print(f"{s:.3f} {corpus[i]}") # => 2.451 Roboter brauchen Sensoren und Motoren um zu funktionieren # => 0.834 Lunolabs baut Roboter und 3D-Drucker im Lab in Altendorf # => 0.000 3D-Drucker drucken Bauteile aus PLA und PETG Filament # => 0.000 Im Lunolabs Lab lernen Kids Programmieren mit Python

Installiere mit pip install rank_bm25. Für kleine bis mittlere Datasets (Tausende Docs) ideal – schnell und ohne Setup.

06BM25-Varianten

Drei spannende Ableger – jeder mit eigenem Anwendungsfall.

BM25F – Felder unterschiedlich gewichten

In der Praxis besteht ein Dokument aus mehreren Feldern: Titel, Beschreibung, Body, Tags. BM25F erlaubt unterschiedliche Gewichte pro Feld – ein Treffer im Titel zählt z.B. 5× so viel wie im Body.

ELASTICSEARCH { "multi_match": { "query": "roboter", "fields": ["title^3", "description^2", "body"], "type": "best_fields" } }

BM25+ – verhindert Score-Null

BM25 kann theoretisch sehr lange Dokumente auf einen Score nahe Null drücken, auch wenn das Wort drin ist. BM25+ fügt einen kleinen Boost (δ ≈ 1.0) hinzu, damit jedes Vorkommen mindestens etwas zählt.

BM25L – Längen-toleranter

Speziell für Corpora mit sehr unterschiedlich langen Docs (Mix aus kurzen und langen). Modifiziert die Längen-Normalisierung leicht.

📚
In Elasticsearch: BM25 ist seit 5.0 (2016) der Default. Das "similarity": "BM25" ist überall im Hintergrund aktiv – ausser du wechselst explizit.

07Wann BM25 immer noch das Beste ist

Trotz allem KI-Hype: BM25 ist 2026 immer noch produktiv unterwegs. Hier wann.

✅ BM25 ist top, wenn …

  • Du Wörter exakt matchen willst (Codes, IDs, Namen)
  • Schnelligkeit und niedrige Kosten zählen
  • Erklärbarkeit wichtig ist (du kannst zeigen, warum ein Doc gewinnt)
  • Wenig oder kein Trainings-Daten verfügbar
  • Multi-Sprache ohne speziell trainierte Modelle

⚠️ BM25 reicht nicht, wenn …

  • Synonyme matchen müssen (Auto ↔ PKW)
  • Semantisches Verstehen nötig ist
  • Mehrsprachige Übersetzungs-Suche
  • Long-tail Queries mit Tippfehlern
  • Kontextuelle Suche („zeig mir die positive Bewertung")

→ Genau diese Fälle löst Embeddings + RAG – siehe Page 08.

🎯Key Takeaways