Topic 1.3
TF-IDF by Hand
In one line
TF-IDF scores a passage by how often each query word appears in it (term frequency), weighted by how rare that word is across all passages (inverse document frequency). It ranks results, and it has two famous flaws.
Think of it like this
Finding who in a room is talking about bicycles. If everyone says "the" all the time, hearing "the" tells you nothing. If only one person says "helmet", and says it three times, you listen to that person. Words that few people use are strong clues; words that everyone uses are noise. TF-IDF is that instinct as arithmetic.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Term frequency (tf)
- How many times a term appears in one passage.
- Document frequency (df)
- In how many passages the term appears at least once.
- IDF
- A weight that is high for rare terms and near zero for terms that are everywhere: ln(N / df).
- Natural logarithm (ln)
- The logarithm with base e (about 2.718). It turns big ratios into small, gentle numbers.
- Keyword stuffing
- Repeating a word many times to win a ranking.
- Length bias
- Longer passages score higher just because they contain more words.
Step by step
01Step 1: count what is in each passage
Tokenise every passage (Topic 1.1) and keep the word list. tf is words.count(term); df is how many lists contain the term.
words_of = {doc_id: tokenize(text) for doc_id, text in HANDBOOK}
def df(term):
return sum(1 for words in words_of.values() if term in words)02Step 2: weight words by rarity
idf = ln(N / df). Print it for a few words, as the example does, and see the pattern: the more passages a word is in, the lower its weight. A word in all passages would be exactly 0.
import math
def idf(term):
return math.log(N / df(term))03Step 3: score and rank
For each passage, add tf * idf for every distinct query term. Terms missing from the whole collection (df = 0) are skipped, since N / 0 is undefined. Sort best first.
04Step 4: read the result
D8 wins with 4.16, D10 is second with 2.08 because of the single word cost, and the rest score 0. The ranking is right here, but look at what carried it: a single rare word.
05Step 5: build a passage that cheats
The second example adds a passage that is the word pass eight times. Its score is 5.55, higher than the genuine passage. Raw counts have no ceiling.
06Step 6: build a passage that is just long
Passage C is 45 words with three mentions. It outranks the focused 4-word passage A. Normalising by length (the last column) fixes C but not B, so the formula itself must change.
Try it yourself
- 1
Predict the idf
In the first example,
lockis in 2 of 8 passages. Predictln(8 / 2)on a calculator, then check the printed value. Then add the wordchargeto the list and explain why its df is 0 and what happens if you ask for its idf. - 2
A new query
Change the query to
helmet under 16. Which passage wins and by how much? Why doesundercarry weight even though it sounds like a small word? - 3
Cheat on purpose
In the second example make passage B ten words of
passinstead of eight. How does its tf-idf change? Now make A saypasstwice. Does the genuine passage ever catch the stuffed one?
Code & diagrams
Natural log in all three languages (math.log, Math.log). Eight handbook passages: D1, D2, D3, D4, D6, D8, D10, D12.
Expected output
idf (N = 8):
the df=5 idf=0.470
a df=4 idf=0.693
bike df=2 idf=1.386
lock df=2 idf=1.386
helmet df=1 idf=2.079
monthly df=1 idf=2.079
query: monthly pass cost
D8 4.159
D10 2.079
D1 0.000Passages A, B and C are made up to expose the flaws. D1 to D3 are handbook passages that make the collection large enough for idf to be above zero.
Expected output
df=3 of 6 idf=0.693
A tf=1 words=4 tf-idf=0.693 per word=0.173
B tf=8 words=8 tf-idf=5.545 per word=0.693
C tf=3 words=45 tf-idf=2.079 per word=0.046Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Turn the weight upside down
In the Python tab of the first example, change math.log(N / df(word)) to math.log(df(word) / N) and run.
Myth vs fact
Myth
A word that appears more often is always more important.
Fact
Only up to a point. Repetition inside one passage is cheap to fake, and a word that appears in every passage tells you nothing about which one to pick.
Myth
IDF is a property of the word.
Fact
It is a property of the word in your collection. monthly is rare in a bike handbook and common in a banking one, so the same word has different weights in different indexes.
Myth
TF-IDF is what modern search engines use.
Fact
Most moved to BM25, which repairs TF-IDF's flaws. TF-IDF is the simpler stepping stone.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
IDF depends on collection statistics, so it changes as documents are added and removed. Engines that shard an index can compute IDF per shard, which gives slightly different scores for the same document depending on where it lives. Some offer a mode that first collects global statistics.
- ▸
There are many TF-IDF variants: log-scaled tf (
1 + ln(tf)) to soften repetition, smoothed idf (ln(1 + N/df)) so a word in every passage still counts a little, and length normalisation of the vector. They are attempts to patch the two flaws you just saw, which BM25 handles in a principled way. - ▸
A term that is in every passage gets IDF 0 here and so can never help or hurt. A query made only of such terms gives all passages score 0 and the order is arbitrary. Your
retrievemust handle "nothing scored above zero" explicitly, and Phase 0's refusal rule is how AskDocs does it.
Remember this
- 1
Why this exists. The index from Topic 1.2 can say *which* passages contain the words, but not which is *best*. Counting shared words (Phase 0) treats
theandhelmetas equal, and that is why the login question went wrong. Ranking needs two ideas: how much a word shows up in this passage, and how much that word is worth at all. - 2
Term frequency (TF) is the number of times a term occurs in a passage. A passage that says
passthree times is probably more about passes than one that says it once. In this topic TF is the raw count. - 3
Document frequency (DF) is the number of passages that contain the term at least once. Inverse document frequency (IDF) turns that into a weight:
idf = ln(N / df), whereNis the number of passages andlnis the natural logarithm. A word in every passage gets weight 0; a word in one passage of eight gets about 2.08. In the example,the(in 5 of 8 passages) is worth 0.47,ais worth 0.69,bikeandlock1.39, andhelmetandmonthly2.08. Rarity is a property of the collection, not of one passage. - 4
The score of a passage is the sum, over the query's terms, of
tf * idf. Formonthly pass costover eight handbook passages, D8 scores 4.16 (it hasmonthlyandpass, each worth 2.08), D10 scores 2.08 (it only hascost, in "no extra cost") and every passage without these words scores 0. Notice thatcostsin D8 does not count forcost: without stemming (Topic 1.1) they are different terms. - 5
Flaw one: repetition wins. Passage B in the second example is just the word
passeight times. It scores 5.55, eight times higher than the clean passage A ("Monthly pass: 25 dollars.", 0.69), although it says nothing. Raw counts reward stuffing a word, and early web search engines were exploited this way. - 6
Flaw two: long passages win. Passage C is 45 words about many things and mentions
passthree times. It scores 2.08 and beats A, which is entirely about the pass. More words means more chances to contain the query. Dividing the count by the passage length (the last column) fixes C, which falls to 0.05, but B is still on top at 0.69. Both flaws need a better formula, and that is BM25 (Topic 1.4). - 7
Where TF-IDF is still used. BM25 replaced it as the ranking function in search engines, but TF-IDF remains useful for extracting a passage's most characteristic words and as features for simple machine-learning models. Understanding it is the fastest route to understanding BM25: the same two ingredients, repaired.
Explain it without notes
Define TF, DF and IDF, and explain why IDF is high for rare words.
Why does a repeated word win under raw TF-IDF, and why is that a problem?
Why does a long passage tend to outrank a short focused one, and does dividing by length fully fix it?
What happens to IDF when a term is in every passage, and when it is in none?
Practice
Write idf(n, df) using the natural logarithm and print df=1, df=4 and df=8 for n = 8, each to 3 decimals.
Given three passages, rank them for the query late with raw tf * idf: a = "late fee late", b = "late fee", c = "monthly pass". Print each id with its score to 3 decimals.
Trade-offs
- ↔
Raw tf: simple and transparent, but rewards stuffing and long passages.
- ↔
Log-scaled tf or length division: softer, but ad hoc fixes. BM25 does both properly.
- ↔
A hand-made weighting scheme: fine for learning, but for production use an engine's tuned BM25 (Topic 1.7).
Done when you can
Done when you can compute tf, df and idf for a word in a small collection by hand.
Done when you can explain why
theis worth less thanhelmet.Done when you can demonstrate keyword stuffing with a three-line example.
Done when you can name TF-IDF's two flaws and what BM25 changes.