BM25 › BM25 vs TF-IDF: An Empirical Comparison
Language AI HandbookAll chapters BM25 › BM25 vs TF-IDF: An Empirical ComparisonOne section · Sections in this chapter9 / 14 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Larger test corpus
test_corpus = [
"Machine learning algorithms learn patterns from data",
"Deep learning uses neural networks with many layers",
"Natural language processing enables computers to understand text",
"Computer vision allows machines to interpret images",
"Reinforcement learning trains agents through rewards and penalties",
"Supervised learning requires labeled training examples",
"Unsupervised learning finds patterns without labels",
"Transfer learning reuses knowledge from one task to another",
"Neural networks are inspired by biological neurons",
"Gradient descent optimizes model parameters iteratively",
"Backpropagation computes gradients for neural network training",
"Convolutional networks excel at image recognition tasks",
"Recurrent networks process sequential data like text",
"Attention mechanisms help models focus on relevant inputs",
"Transformers revolutionized natural language processing",
"BERT provides contextual word embeddings for NLP tasks",
"GPT generates coherent text using autoregressive modeling",
"Word embeddings represent words as dense vectors",
"Sentiment analysis determines the emotional tone of text",
"Named entity recognition identifies people places and organizations",
]
# Build BM25 index
bm25_index = BM25Index(k1=1.2, b=0.75)
bm25_index.index(test_corpus)
# Build TF-IDF index
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(test_corpus)
def tfidf_search(query, top_k=5):
"""Search using TF-IDF cosine similarity."""
query_vec = tfidf_vectorizer.transform([query])
similarities = cosine_similarity(query_vec, tfidf_matrix)[0]
top_indices = similarities.argsort()[::-1][:top_k]
return [
(idx, similarities[idx]) for idx in top_indices if similarities[idx] > 0
]Now let's compare rankings for several queries:
comparison_queries = [
"neural network training",
"text processing language",
"learning from examples",
"image recognition deep",
]
comparison_results = []
for query in comparison_queries:
bm25_results = bm25_index.search(query, top_k=5)
tfidf_results = tfidf_search(query, top_k=5)
comparison_results.append(
{"query": query, "bm25": bm25_results, "tfidf": tfidf_results}
)Query: "neural network training" ====================================================================== BM25 Results: 1. [6.2469] Backpropagation computes gradients for neural network t... 2. [2.1479] Supervised learning requires labeled training examples... 3. [1.6279] Neural networks are inspired by biological neurons... TF-IDF Results: 1. [0.6237] Backpropagation computes gradients for neural network t... 2. [0.2194] Supervised learning requires labeled training examples... 3. [0.1636] Neural networks are inspired by biological neurons... Query: "text processing language" ====================================================================== BM25 Results: 1. [5.0717] Natural language processing enables computers to unders... 2. [4.5748] Transformers revolutionized natural language processing... 3. [1.3142] Recurrent networks process sequential data like text... TF-IDF Results: 1. [0.5659] Natural language processing enables computers to unders... 2. [0.5165] Transformers revolutionized natural language processing... 3. [0.1519] Recurrent networks process sequential data like text... Query: "learning from examples" ====================================================================== BM25 Results: 1. [3.6137] Supervised learning requires labeled training examples... 2. [2.8361] Machine learning algorithms learn patterns from data... 3. [2.5437] Transfer learning reuses knowledge from one task to ano... TF-IDF Results: 1. [0.4163] Supervised learning requires labeled training examples... 2. [0.3313] Machine learning algorithms learn patterns from data... 3. [0.2836] Transfer learning reuses knowledge from one task to ano... Query: "image recognition deep" ====================================================================== BM25 Results: 1. [4.6189] Convolutional networks excel at image recognition tasks... 2. [2.4534] Deep learning uses neural networks with many layers... 3. [1.9145] Named entity recognition identifies people places and o... TF-IDF Results: 1. [0.4320] Convolutional networks excel at image recognition tasks... 2. [0.2347] Deep learning uses neural networks with many layers... 3. [0.1689] Named entity recognition identifies people places and o...
The rankings often agree on the top result, but there are subtle differences in how the methods order subsequent results. BM25's saturation effect means that a document with many occurrences of a query term doesn't dominate as much as it would with raw TF-IDF. The length normalization also helps ensure shorter, focused documents aren't overwhelmed by longer ones. Notice how the score scales differ significantly: BM25 scores can exceed 1.0, while TF-IDF cosine similarities are bounded between 0 and 1.
Let's visualize the score distributions:
# Get all scores for a specific query
query = "neural network learning"
bm25_all_scores = []
tfidf_all_scores = []
query_terms = bm25_index.tokenize(query)
query_vec = tfidf_vectorizer.transform([query])
tfidf_sims = cosine_similarity(query_vec, tfidf_matrix)[0]
for doc_id in range(len(test_corpus)):
bm25_score_val = bm25_index.score_document(doc_id, query_terms)
bm25_all_scores.append(bm25_score_val)
tfidf_all_scores.append(tfidf_sims[doc_id])

BM25 scores tend to have a wider dynamic range with clearer separation between relevant and non-relevant documents. TF-IDF cosine similarities are bounded between 0 and 1, which can make it harder to distinguish between moderately relevant documents. This separation is important in practice: when you're taking the top-10 results out of millions, you want the scoring function to clearly distinguish the winner from the also-rans.