Skip to main content
In this lesson we compare classic BM25 retrieval with true semantic search (using sentence-transformers), then build a simple hybrid ranker that blends both signals. This example is compact, corrected, and runnable in a Jupyter-friendly notebook. BM25 is a token-statistics method that excels at matching exact terms and weighting important tokens, but it can miss synonyms, paraphrases, and deeper meaning. A bi-encoder (sentence-transformers) maps queries and documents to vectors and uses cosine similarity to retrieve semantically similar items. A hybrid approach combines both strengths and often yields more robust retrieval for real applications. Quick links:

Installation

Install the required Python packages (Jupyter-friendly):

Overview: how the demo works

  1. Prepare a small document corpus and example queries.
  2. Tokenize documents for BM25 and initialize the BM25 index.
  3. Encode documents with a sentence-transformer to produce normalized embeddings.
  4. For each query:
    • Get BM25 scores (token overlap / importance).
    • Get semantic scores (cosine similarity with embeddings).
    • Normalize both score vectors to [0,1] and combine them with a weighted linear blend: hybrid = alpha * semantic + (1-alpha) * bm25.
  5. Compare the top-k results for BM25, semantic, and hybrid.

Corpus, queries, and imports

Prepare BM25 and SentenceTransformer embeddings

Helper: show results for a query (BM25, semantic, and weighted hybrid)

Run the demo for all queries (default semantic weight alpha=0.8)

Discussion and example behavior

  • BM25 relies on token overlap and term importance. It may favor documents that share surface words with the query (even if the meaning differs).
  • SentenceTransformer returns semantically similar results by embedding meaning, so it better handles synonyms and paraphrases (e.g., mapping “2FA” to “two-factor authentication”).
  • The hybrid approach blends both signals using alpha. Values:
    • alpha > 0.5 favors semantic matching.
    • alpha < 0.5 favors BM25.
  • Normalizing both score arrays to [0,1] before combining allows a simple weighted linear blend that is robust to differing score scales. The small epsilon (1e-9) prevents division-by-zero for degenerate score distributions.
The image shows a Jupyter notebook interface comparing retrieval results using BM25, Semantic (SentenceTransformer), and a hybrid method for different queries related to HbA1c and sick leave policy. The results include top-k entries for each query with associated scores.

Sample (cleaned) output for illustration

Tuning guidance

Tune alpha and experiment with different sentence-transformer models (for example, all-MiniLM-L6-v2 for speed or multi-qa-MiniLM-L6-cos-v1 for QA-style retrieval). Use a labeled validation set (queries with known correct docs) to measure precision/recall and choose alpha and model for your dataset. If BM25 returns many identical scores (common in small corpora), the semantic signal typically helps; if semantic matching over-generalizes in your domain, increase BM25 weight.

Switching the embedding model (example)

Quick comparison

Final notes

  • This demo uses a very small toy corpus for illustration. On larger corpora you’ll get more stable BM25 distributions and richer semantic matches.
  • Keep the retrieval pipeline configurable: alpha, k, and model selection should be part of your evaluation loop.
  • Validate hybrid weighting with representative queries and metrics (e.g., recall@k, MRR) before deploying to production.
References and further reading:

Watch Video