

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: BM25
incomplete
2: Term Frequency Saturation
incomplete
3: Document Length Normalization
incomplete
4: BM25 Search
incomplete
This lesson's interactive features are locked, please to keep using them
Now let's tackle the second BM25 improvement: term frequency saturation. This prevents a single term from dominating search results just because it appears many many times.
In basic TF-IDF, a word that appears 100 times gets 10x more weight than a word that appears 10 times. That can create bad rankings:
Query: "bear hunting"
With basic TF, Document A gets a much higher score despite probably being less useful!
BM25 uses diminishing returns – after a certain point, additional occurrences matter less. Let's break down the formula:
tf_component = (tf * (k1 + 1)) / (tf + k1)
k1 is a tunable parameter that controls the diminishing return; a common value is 1.5. Here's how different TF values get saturated:
| Occurrences | Basic TF | BM25 TF (k1 = 1.5) |
|---|---|---|
| 1 | 1 | 1.0 |
| 2 | 2 | 1.4 |
| 5 | 5 | 1.9 |
| 10 | 10 | 2.2 |
| 20 | 20 | 2.3 |
Notice how BM25 grows much slower: the first few occurrences matter the most.
Implement the BM25 term frequency saturation formula in our InvertedIndex class. Use a k1 value of 1.5 to control the saturation effect.
BM25_K1 = 1.5
bm25_tf_parser = subparsers.add_parser(
"bm25tf", help="Get BM25 TF score for a given document ID and term"
)
bm25_tf_parser.add_argument("doc_id", type=int, help="Document ID")
bm25_tf_parser.add_argument("term", type=str, help="Term to get BM25 TF score for")
bm25_tf_parser.add_argument(
"k1", type=float, nargs="?", default=BM25_K1, help="Tunable BM25 K1 parameter"
)
print(f"BM25 TF score of '{args.term}' in document '{args.doc_id}': {bm25tf:.2f}")
Run and submit the CLI tests.