BM25 › The BM25 Formula

Michael BrenndoerferUpdated March 21, 202615 min read
Language AI HandbookAll chapters
Part 2 of 67Classical Text Representations
Chapter 14BM25 › The BM25 Formula
Reading section 4 of 14 · The BM25 Formula · 15 minSection 4 of 14 · 15 min

Book contents

Section 4 of 14 · 15 min read

BM25 › The BM25 FormulaOne section · Sections in this chapter4 / 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

The full BM25 formula looks intimidating at first glance, but every piece exists for a reason. Rather than presenting it all at once, let's build it up from the three core problems any good retrieval function must solve:

  1. Which terms matter? Not all words are equally informative. "The" appears everywhere; "quantum" is rare and specific.
  2. How much does repetition help? If a document mentions your query term once, that's good. But is mentioning it 100 times really 100 times better?
  3. How do we handle document length? A 10,000-word document naturally contains more vocabulary than a 100-word document. Should we penalize it for that?

BM25 addresses each of these problems with a specific component. Let's examine them one by one, then see how they combine into the complete formula.

Problem 1: Which Terms Matter? The IDF Component

Consider searching for "the cat sat on the mat." The word "the" appears in virtually every English document. Finding it tells you almost nothing. But "mat"? That's much more specific.

The Inverse Document Frequency (IDF) quantifies this intuition. A term that appears in many documents gets a low IDF; a rare term gets a high IDF. BM25 uses a specific IDF formulation derived from its probabilistic foundations:

IDF(t)=log⁡N−n(t)+0.5n(t)+0.5\text{IDF}(t) = \log \frac{N - n(t) + 0.5}{n(t) + 0.5}

where:

  • NN: total number of documents in the collection
  • n(t)n(t): number of documents containing term tt
  • The +0.5+0.5 terms provide smoothing to avoid division by zero and extreme values

This formulation differs from the standard log⁡(N/n(t))\log(N/n(t)) IDF you might have seen in TF-IDF. The N−n(t)N - n(t) in the numerator comes directly from the probabilistic derivation, representing the number of documents that don't contain the term. Intuitively, the formula computes a log-odds ratio: the numerator approximates the probability that a relevant document doesn't contain term tt, while the denominator approximates the probability that a non-relevant document does contain it.

For very common terms appearing in more than half the documents, this IDF becomes negative. The formula effectively penalizes ubiquitous terms rather than just ignoring them. If a term appears in 80% of documents, matching on it might hurt your score slightly, pushing you to focus on more discriminative terms. Standard TF-IDF simply assigns a low positive weight to common terms; BM25's probabilistic derivation says such terms should actively count against a match.

Let's visualize how IDF varies with document frequency:

In[3]:
Code
import numpy as np

# Simulate a collection of 10,000 documents
N = 10000

# Document frequencies ranging from 1 to N
doc_freqs = np.arange(1, N + 1)

# Calculate BM25 IDF
bm25_idf = np.log((N - doc_freqs + 0.5) / (doc_freqs + 0.5))

# Standard IDF for comparison
standard_idf = np.log(N / doc_freqs)
Out[4]:
Visualization
Line plot comparing BM25 IDF and standard IDF curves, showing BM25 IDF going negative after 50% document frequency.
BM25 IDF compared to standard IDF across different document frequencies. BM25's IDF becomes negative for terms appearing in more than half the documents, actively penalizing ubiquitous terms. The red dashed line marks the 50% crossover point where a term's IDF drops to zero.

The plot reveals an important difference: standard IDF is always positive, merely approaching zero for common terms. BM25's IDF crosses zero at exactly 50% document frequency and becomes increasingly negative beyond that. This means stopwords like "the" or "a" that appear in nearly every document will have strongly negative IDF, effectively penalizing documents that match only on these terms. In practice, this is why BM25 implementations often include a floor at zero or simply filter out negative-IDF terms before scoring.

Problem 2: How Much Does Repetition Help? The Saturation Effect

If a document mentions "machine learning" once, that's evidence of relevance. What about ten times? A hundred times?

Raw term frequency treats each occurrence equally: ten mentions = ten times the score. But this creates problems. A document that repeats a term obsessively (keyword stuffing) would dominate results. More fundamentally, there's a diminishing returns effect: the first mention tells you the document is about the topic; additional mentions provide less and less new information. The gap in relevance between a document that mentions a term once and a document that mentions it twice is much larger than the gap between 20 and 21 mentions.

BM25 captures this with a saturation function:

f(t,D)⋅(k1+1)f(t,D)+k1\frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1}

where:

  • f(t,D)f(t, D): frequency of term tt in document DD
  • k1k_1: saturation parameter controlling how quickly the function approaches its asymptote

The full formula includes length normalization in the denominator, which we'll add shortly.

Term Frequency Saturation

The principle that additional occurrences of a query term in a document provide diminishing returns for relevance. The first few occurrences matter a lot; the hundredth occurrence adds almost nothing.

To understand why this formula creates saturation, consider the mathematical behavior. When f(t,D)=0f(t, D) = 0 (term doesn't appear), the formula yields 0. When f(t,D)=1f(t, D) = 1 (term appears once), we get (1⋅(k1+1))/(1+k1)=1(1 \cdot (k_1 + 1))/(1 + k_1) = 1. As f(t,D)f(t, D) grows very large, both the numerator and denominator grow at the same rate, so the ratio approaches (k1+1)(k_1 + 1), an asymptote that cannot be exceeded no matter how many times the term appears.

You can verify this limit directly: as f→∞f \to \infty,

f(k1+1)f+k1→f(k1+1)f=k1+1\frac{f(k_1 + 1)}{f + k_1} \to \frac{f(k_1 + 1)}{f} = k_1 + 1

So the maximum possible contribution of any single query term, no matter how often it appears, is bounded by k1+1k_1 + 1 times the IDF weight. This cap makes BM25 resistant to keyword stuffing in a way that TF-IDF is not.

The intuition is this: the first occurrence of a term is strong evidence of relevance. Each additional occurrence provides weaker and weaker evidence, until eventually more occurrences barely matter at all.

The parameter k1k_1 controls how quickly you approach this ceiling:

  • k1=0k_1 = 0: Instant saturation. Any non-zero term frequency gives the same score (like the Binary Independence Model)
  • k1→∞k_1 \to \infty: No saturation. Score is linear in term frequency (like raw TF-IDF)
  • k1≈1.2k_1 \approx 1.2 to 2.02.0: The empirically validated sweet spot for most collections

Let's visualize this saturation effect:

In[5]:
Code
import numpy as np

# Term frequencies to plot
tf_values = np.arange(0, 21)

# Different k1 values
k1_values = [0.5, 1.2, 2.0, 5.0]

# Calculate saturation curves (without length normalization for clarity)
saturation_curves = {}
for k1 in k1_values:
    scores = (tf_values * (k1 + 1)) / (tf_values + k1)
    saturation_curves[k1] = scores
Out[6]:
Visualization
Line plot showing four saturation curves with different k1 values, all starting at zero and asymptotically approaching their maximum values.
BM25 saturation curves for different $k_1$ values. Each curve rises steeply at first and then flattens toward its asymptote at $k_1 + 1$. With $k_1 = 0.5$, the curve flattens by term frequency 5. With $k_1 = 5$, the curve is nearly linear over this range, making it more sensitive to repeated occurrences.

The visualization reveals the saturation behavior clearly. With k1=0.5k_1 = 0.5, the curve flattens quickly: by term frequency 5, you've captured most of the possible score. With k1=5.0k_1 = 5.0, the curve is nearly linear over this range, meaning repetition continues to help. The default value of k1=1.2k_1 = 1.2 provides a principled middle ground: the first few occurrences matter a lot, but the score doesn't keep climbing indefinitely.

Problem 3: How Do We Handle Document Length?

Here's a subtle but important issue. Imagine two documents about machine learning:

  • Document A: A focused 200-word abstract that mentions "neural network" twice
  • Document B: An extensive 10,000-word survey that mentions "neural network" twenty times

Which is more relevant to a query about neural networks? Raw term frequency would strongly favor Document B. But that's not necessarily right. Document B mentions the term more often simply because it's longer. The density of the term, its frequency relative to document length, might be a better signal.

But even this isn't the full story. Sometimes a long document has more relevant content: a detailed guide to neural networks should rank above a tweet that mentions the phrase once. The right approach is somewhere between ignoring length entirely and fully normalizing for it. BM25 lets you tune this tradeoff explicitly.

BM25 addresses this through length normalization, which adjusts term frequency based on how a document's length compares to the collection average:

1−b+b⋅∣D∣avgdl1 - b + b \cdot \frac{|D|}{\text{avgdl}}

where:

  • bb: length normalization parameter (typically between 0 and 1)
  • ∣D∣|D|: length of document DD (number of terms)
  • avgdl\text{avgdl}: average document length across the collection

This expression appears in the denominator of the BM25 formula, multiplied by k1k_1. Let's trace through what happens for different document lengths:

  • Average-length document (∣D∣=avgdl|D| = \text{avgdl}): The ratio ∣D∣/avgdl=1|D|/\text{avgdl} = 1, so the expression becomes 1−b+b⋅1=11 - b + b \cdot 1 = 1. No adjustment.
  • Longer document (∣D∣>avgdl|D| > \text{avgdl}): The ratio exceeds 1, so the expression is greater than 1. This increases the denominator, reducing the score.
  • Shorter document (∣D∣<avgdl|D| < \text{avgdl}): The ratio is less than 1, so the expression is less than 1. This decreases the denominator, boosting the score.
Length Normalization

A technique to prevent longer documents from having an unfair advantage in retrieval. BM25 normalizes term frequency by comparing document length to the collection average, with the strength of normalization controlled by the parameter bb.

The parameter bb controls how aggressively we normalize:

  • b=0b = 0: No length normalization. The expression always equals 1, and document length is ignored entirely.
  • b=1b = 1: Full length normalization. A document twice the average length has its term frequencies effectively halved.
  • b≈0.75b \approx 0.75: The typical default. This provides moderate normalization that accounts for length without removing its effect.

The intuition behind b=0.75b = 0.75 is that document length is partially informative. Some of the extra length in a long document reflects relevant content; some of it is just verbosity. The 0.75 setting is calibrated to discount verbosity while retaining some of the signal from long, content-rich documents.

Let's see how length normalization affects scoring:

In[7]:
Code
# Fixed term frequency
tf = 5
k1 = 1.2

# Document lengths as multiples of average
length_ratios = np.linspace(0.2, 3.0, 50)

# Different b values
b_values = [0.0, 0.25, 0.5, 0.75, 1.0]

# Calculate scores for each b
length_curves = {}
for b in b_values:
    normalization = 1 - b + b * length_ratios
    scores = (tf * (k1 + 1)) / (tf + k1 * normalization)
    length_curves[b] = scores
Out[8]:
Visualization
Line plot showing five curves demonstrating how document length affects BM25 score for different b parameter values.
Effect of length normalization parameter $b$ on BM25 scores for a fixed term frequency of 5. With $b = 0$ scores are flat regardless of length. With $b = 1$ a document three times average length sees its score drop by roughly half. The default $b = 0.75$ provides moderate penalization for longer documents.

The visualization confirms our intuition. When b=0b = 0, document length doesn't matter at all: all curves are flat. With b=1b = 1, a document that's three times average length gets significantly penalized. The default b=0.75b = 0.75 provides a reasonable middle ground, acknowledging that longer documents might naturally contain more occurrences without completely ignoring the length difference.

Putting It All Together: The Complete Formula

Now we can assemble the complete BM25 formula. For each query term, we multiply three components:

  1. IDF: How informative is this term?
  2. Saturated TF: How often does it appear (with diminishing returns)?
  3. Length adjustment: Normalized by document length

The final formula sums these contributions across all query terms:

BM25(D,Q)=∑t∈QIDF(t)⋅f(t,D)⋅(k1+1)f(t,D)+k1⋅(1−b+b⋅∣D∣avgdl)\text{BM25}(D, Q) = \sum_{t \in Q} \text{IDF}(t) \cdot \frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}

where:

  • DD: the document being scored
  • QQ: the query (set of query terms)
  • tt: a term in the query
  • IDF(t)\text{IDF}(t): inverse document frequency of term tt
  • f(t,D)f(t, D): frequency of term tt in document DD
  • k1k_1: saturation parameter (typically 1.2 to 2.0)
  • bb: length normalization parameter (typically 0.75)
  • ∣D∣|D|: length of document DD (number of terms)
  • avgdl\text{avgdl}: average document length in the collection

Each component addresses one of our original problems. The IDF weights rare terms more heavily. The saturation function ensures diminishing returns from repetition. The length normalization factor in the denominator adjusts for document size. Together, they create a scoring function that has worked well across decades of information retrieval research.

This formula does not include query term frequency. The original BM25 paper includes a query-side term frequency component as well (sometimes called BM25 with query term weighting), but in practice most implementations drop it. For typical short web queries, query terms appear at most once, making the query TF component degenerate to a constant. For longer queries, you could re-add it, but the gain is usually marginal.

Parameter Interaction: A Heatmap View

The parameters k1k_1 and bb interact in interesting ways. Let's visualize how different combinations affect the score for a document with a fixed term frequency:

In[9]:
Code
# Create parameter grids
k1_range = np.linspace(0.1, 3.0, 50)
b_range = np.linspace(0.0, 1.0, 50)
K1, B = np.meshgrid(k1_range, b_range)

# Fixed parameters for visualization
tf = 3  # Term frequency
doc_len = 150  # Document length
avgdl = 100  # Average document length
idf = 2.5  # IDF of a moderately rare term

# Calculate BM25 scores for each (k1, b) combination
scores = idf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (doc_len / avgdl)))
Out[10]:
Visualization
Heatmap showing BM25 scores varying with k1 on x-axis and b on y-axis, with warmer colors indicating higher scores.
BM25 score heatmap showing the interaction between $k_1$ and $b$ parameters for a document 1.5x average length with TF=3. Higher $k_1$ increases scores because term frequency contributes more before saturation. Higher $b$ decreases scores because the longer document incurs a stronger length penalty. The star marks the default parameters.

The heatmap reveals how the parameters affect the score. Moving right (higher k1k_1) increases scores because term frequency contributes more. Moving up (higher bb) decreases scores because our document is longer than average and gets penalized more heavily. The default parameters sit in a region that balances these effects, but the optimal choice depends on your specific collection and retrieval goals.