Topic 1.4
BM25 by Hand
In one line
BM25 repairs TF-IDF: repeated words add less and less to the score (saturation, controlled by k1), and long passages are scaled down against the average length (controlled by b). It is the default keyword ranker of search engines.
Think of it like this
Reading reviews of a restaurant. The first review that says "delicious" convinces you. The tenth one saying "delicious" adds very little. And a one-line review that says "great food" is stronger evidence than the same two words buried in a fifty-line essay about someone's holiday. BM25 builds in both feelings: repetition has diminishing returns, and the same words count for more in a short text.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- BM25
- A keyword ranking formula with term-frequency saturation and length normalisation.
- k1
- The saturation knob. It caps how much repeating a term can add. Common default 1.2.
- b
- The length-normalisation knob, from 0 (ignore length) to 1 (full). Common default 0.75.
- Saturation
- Diminishing returns: each extra occurrence adds less than the one before.
- Average length (avg_len)
- The mean number of terms per passage in the collection.
- Okapi
- The research system where BM25 was developed. The name "Best Matching 25" is its 25th weighting attempt.
Step by step
01Step 1: gather the collection numbers
Compute the number of passages N (12), each passage's length, and the average length (19.417). These are the only global numbers BM25 needs besides each term's df.
02Step 2: idf for each query term
Use ln(1 + (N - df + 0.5) / (df + 0.5)). The example prints the (df 8, idf 0.425), and monthly, pass, cost (df 1, idf 2.159). what and does are not in any passage, so they contribute nothing.
def idf(term):
return math.log(1 + (N - df(term) + 0.5) / (df(term) + 0.5))03Step 3: the term score
For a term that occurs tf times in a passage of length len, multiply idf by the saturating, length-aware factor. Write it as a function that takes the term and the passage's word list.
def term_score(term, words):
tf = words.count(term)
if tf == 0:
return 0.0
norm = tf + K1 * (1 - B + B * len(words) / AVG_LEN)
return idf(term) * tf * (K1 + 1) / norm04Step 4: sum over the query's terms
The passage score is the sum of the term scores. Use each distinct query term once (a word repeated in the question is not worth more). Sort best first; break ties by passage order.
05Step 5: open up the winner
The example breaks D8's score into parts: the 0.468, monthly 2.381, pass 2.381. Add them: 5.230, and 5.231 before rounding. Being able to explain a score term by term is the main debugging tool for keyword search.
06Step 6: see saturation and length in isolation
The second example removes idf and the data, and prints only the passage-dependent factor. Read the saturation table down a column (diminishing returns) and the length table along a row (the effect of b).
07Step 7: check against the lab
Run the search lab below with Rank by set to Keyword. Its BM25 column shows 5.23 for D8, 2.43 for D10 and 0.64 for D3, the numbers you just computed. They are the same formula, constants and tokeniser.
Try it yourself
- 1
Predict a saturation value
Without running anything, compute
factor(4, 1.0, 1.2, 0.75)by hand from the formula, then add it to the first table's loop to check. Is it between the values for 3 and 5? - 2
Turn the knobs
In the first example change
K1, B = 1.2, 0.75toK1, B = 0.0, 0.75(no repetition benefit) and thenK1, B = 1.2, 0.0(no length effect). Which scores move, and by how much? - 3
Explain a score
Ask the example about D10 instead of D8 (
words_of[9]). Which two terms produce its 2.43, and why istheworth more in D10 than in D8? - 4
Lab
In the search lab, change a passage so it repeats "pass" many times. Does it overtake the real pass passage under Keyword ranking? Try the same under Vector ranking.
Code & diagrams
Same formula and constants as the search lab's keyword ranker: k1 = 1.2, b = 0.75, idf = ln(1 + (N - df + 0.5) / (df + 0.5)).
Expected output
N=12 average length=19.417 words
the df=8 idf=0.425
monthly df=1 idf=2.159
pass df=1 idf=2.159
cost df=1 idf=2.159
top 3: D8=5.231, D10=2.430, D3=0.642
D8 has 15 words
the tf=1 part=0.468
monthly tf=1 part=2.381
pass tf=1 part=2.381The factor is the part of the term score that depends on the passage. Multiply it by idf to get the full term score.
Expected output
saturation, a passage of average length
tf raw k1=0.5 k1=1.2 k1=3.0
1 1 1.000 1.000 1.000
2 2 1.200 1.375 1.600
3 3 1.286 1.571 2.000
5 5 1.364 1.774 2.500
8 8 1.412 1.913 2.909
20 20 1.463 2.075 3.478
limits: 1.5, 2.2, 4.0 (that is k1 + 1)
length, tf=2 and k1=1.2
len/avg b=0 b=0.75 b=1
0.5 1.375 1.600 1.692
1.0 1.375 1.375 1.375
2.0 1.375 1.073 1.000
4.0 1.375 0.746 0.647Try: Press Search and look at the BM25 column with Rank by set to Keyword (BM25): D8 should show 5.23, the flat-tyre passage 2.43 and the password passage 0.64, as in the example. Then change the question to just "pass" and predict the new scores before you press Search. Then try "monthly pass monthly pass": a repeated word in the question should not change anything.
Goals
- The monthly pass passage ranks first
Press Search. Then change one thing (a word in the question, a passage, the ranker) and compare the scores.
Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Drop the 1 from the idf logarithm
In the Python tab of the first example, change math.log(1 + (N - df(term) + 0.5) / (df(term) + 0.5)) to math.log((N - df(term) + 0.5) / (df(term) + 0.5)) and run.
Myth vs fact
Myth
BM25 scores are probabilities or percentages.
Fact
They are unbounded positive numbers that depend on the query and the collection. They order results for one query. They say nothing like "87 percent relevant".
Myth
The defaults k1 = 1.2 and b = 0.75 are always best.
Fact
They are good starting points. Short, uniform passages (like FAQ entries) often want a smaller b; tuning them needs an evaluation set (Phase 9).
Myth
BM25 understands the question.
Fact
It only sees which terms match. login and password are as unrelated to it as login and banana. That is the vocabulary mismatch Phases 2 and 3 address.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Some libraries drop the constant
(k1 + 1)from the numerator. Every score is multiplied by the same constant, so the ranking is unchanged but the numbers differ. Never compare raw scores between two implementations; compare rankings. - ▸
BM25 scores add across terms, so a passage matching many weak terms can beat one matching a single very strong term. For queries with exact identifiers, boost or pre-filter on the identifier (Topic 1.5).
- ▸
Length normalisation assumes similar-sized passages. If your index mixes tweets and manuals,
bcannot be right for both. Chunking (Phase 4) creates passages of controlled size so BM25's assumption roughly holds. - ▸
BM25F extends BM25 to several fields (title, body) by combining their term frequencies before saturation, which is usually better than adding two separate BM25 scores as Topic 1.5 does for simplicity.
Remember this
- 1
Why this exists. Topic 1.3 ended with two flaws: a stuffed passage wins, and a long passage wins. BM25 ("Best Matching 25", from the Okapi research system) was designed to fix both with two tunable knobs. It has been the standard way to rank by keywords for decades, which is why this course uses it as the baseline.
- 2
The formula. For one query term
tand one passage:idf(t) * tf * (k1 + 1) / (tf + k1 * (1 - b + b * len / avg_len)). The passage's score is the sum over the query's distinct terms. Heretfis the term's count in the passage,lenthe passage's word count,avg_lenthe average over the collection, andk1 = 1.2,b = 0.75are the common defaults. One number: fortf = 1,len = 15,avg_len = 19.417andidf = 2.159, the length part is1 - 0.75 + 0.75 * 15 / 19.417 = 0.829, the bottom is1 + 1.2 * 0.829 = 1.995, and the score is2.159 * 1 * 2.2 / 1.995 = 2.38. That ismonthlyin D8, as you will see below. - 3
The IDF form. BM25 uses
idf = ln(1 + (N - df + 0.5) / (df + 0.5)). Compared with TF-IDF'sln(N / df)it is smoothed, and the1 +inside the logarithm keeps it positive for every word. Without it, a word in more than half of the passages would get a negative weight and *lower* a passage's score. The break-it below showstheat -0.64. - 4
**Saturation (
k1).** Look at the first table. Withk1 = 1.2, a term occurring once contributes a factor of 1.000, twice 1.375, five times 1.774, twenty times 2.075. The factor can never exceedk1 + 1 = 2.2. Raw TF-IDF would give 1, 2, 5 and 20. Smallk1saturates quickly (at 0.5 the limit is 1.5), largek1is nearly linear (at 3.0 twenty mentions give 3.478). Sok1answers: how fast does repetition stop mattering? - 5
**Length normalisation (
b).** Thelen / avg_lenratio shrinks the score of long passages and boosts short ones. In the second table (term twice,k1 = 1.2),b = 0ignores length (1.375 whatever the length),b = 1scales fully (a passage 4 times the average drops to 0.647), andb = 0.75sits between (0.746). A passage of exactly average length is unaffected byb. - 6
A full example by hand. In the twelve handbook passages the average length is 19.417 words. For
What does the monthly pass cost?,monthly,passandcosteach occur in one passage (idf 2.159) andthein eight (idf 0.425);whatanddoesoccur nowhere. D8 has 15 words, shorter than average. Itsmonthlyandpasseach contribute 2.381 andthe0.468, which add up to 5.231. D10 is next with 2.430 (it hascostandthe) and D3 third with 0.642 (onlythe). The search lab on this page prints the same numbers, which is the point: there is no hidden magic in the keyword column. - 7
What the number means. A BM25 score is not a probability and not a percentage. It depends on the query (a query with more or rarer words scores higher) and on the collection, so scores from different questions cannot be compared: D2 scores 10.39 for the holidays question while D8 scores 5.23 for the pass question, and both are right answers. Use scores to order passages for one query, and be careful with fixed thresholds across queries (Topic 8.4).
Explain it without notes
Write the BM25 score for one term and explain each part.
What does k1 control, and what is the maximum value of the saturating factor?
What does b control, and what do b = 0 and b = 1 mean?
Why does the 1 + inside BM25's idf matter?
Why can't you compare BM25 scores between two different queries?
Practice
Write idf(n, df) with BM25's form and print it for n = 12 and df = 1, 8 and 12, to 3 decimals.
Write factor(tf, k1=1.2) for a passage of average length (len = avg_len, so the length part is 1) and print it for tf = 1, 2, 5.
Trade-offs
- ↔
Small
k1: repetition stops mattering quickly, good against stuffing, weaker for long documents where frequency is informative. - ↔
Large
b: strongly favours short passages. Smallb: lets long passages win. Tune it on your own questions. - ↔
BM25 vs vectors: BM25 is exact, explainable and cheap; vectors understand paraphrase and cost more. Phase 6 combines them.
Done when you can
Done when you can compute one term's BM25 contribution from the formula with a calculator.
Done when you can reproduce the lab's keyword scores for the monthly pass question.
Done when you can explain
k1andbwith a table of numbers, not only words.Done when you can say why BM25 scores from different queries cannot be compared.