Skip to main content
This tutorial compares a classic lexical retriever (TF-IDF / BM25 via Whoosh) with a lightweight semantic retriever using Sentence Transformers. The aim is to demonstrate how semantic search can surface conceptually related documents (for example, documents about “motor diagnostics”) even when the query uses different wording (for example, “engine troubleshooting”). What you’ll learn:
  • How to run a simple Whoosh keyword search.
  • How to embed text with a Sentence Transformers model and rank by cosine similarity.
  • How the two approaches differ in practice and how to compare them side-by-side.

Prerequisites

Install the required Python packages (run in a notebook or virtual environment):
If you plan to run this in a production-like environment, use a dedicated virtual environment and pin package versions to ensure reproducibility.

Corpus: small, focused, intentionally non-overlapping

We use a tiny corpus of six short documents and titles to make the difference between keyword and semantic retrieval obvious:

1) Keyword search (Whoosh — lexical retrieval)

Build a temporary Whoosh index storing title and content fields, then run a keyword query. The example query is "engine troubleshooting".
Example lexical output:
Explanation: The lexical retriever returns the exact-match document containing the words “engine troubleshooting”. Documents that convey the same concept but use different words (for example, “motor diagnostics”) do not appear because they lack lexical overlap with the query.

2) Semantic retrieval (Sentence Transformers + cosine similarity)

Embed the documents and the query using a Sentence Transformers model (all-MiniLM-L6-v2), then rank documents by cosine similarity.
Example semantic-ranking output:
Explanation: Semantic search returns several related documents beyond the exact lexical match. Because embeddings capture conceptual similarity, “Motor Diagnostics Checklist” and “Valve Timing Problems” appear as relevant even though they do not share exact wording with the query.

3) Side-by-side comparison (TF-IDF vs Semantic)

Normalize the results into DataFrames and merge them to compare ranks and scores across both methods. The Whoosh/lexical search may return only exact matches, while the semantic search gives a ranked list for all documents.
Example merged result (conceptual):
The NaNs indicate documents not returned by the lexical keyword search.
Practical pattern: use a hybrid pipeline. First retrieve a broad candidate set quickly (lexical methods like TF-IDF/BM25 or a fast ANN index), then re-rank that subset with a semantic model for better precision. This balances speed, recall, and semantic coverage.

Quick comparison: Lexical vs Semantic vs Hybrid

Tips for experimentation and scaling

  • Try larger Sentence Transformers models for improved semantic quality at the cost of latency.
  • For bigger corpora, use an approximate nearest neighbor (ANN) index (e.g., FAISS, Annoy, HNSW) to retrieve candidate embeddings efficiently.
  • Consider normalization strategies (L2-normalization vs. no normalization) depending on your similarity metric and model outputs.
  • When using Whoosh in production, evaluate BM25 configuration and tokenization for your domain language.

Summary

  • Lexical retrievers like Whoosh excel at exact lexical matches and are low-latency and interpretable.
  • Semantic retrieval using sentence embeddings recovers conceptually related documents even with different surface wording.
  • A hybrid system (lexical retrieval for recall + semantic re-ranking for precision) is a practical production pattern that often yields the best results.
You can reuse the notebook snippets above to experiment with different models, retrieval thresholds, or corpora to see how lexical and semantic methods compare in your domain.

Watch Video

Practice Lab