Majid Al-RaimiWeighting with tf-idf

ICS 582Lecture 04Part 05

Weighting with tf-idf

Why raw frequency is a poor representation, how log-scaled term frequency and inverse document frequency combine into tf-idf, and a full worked tf-idf table for the Shakespeare example.

Concepts
5
Slides
45-52
Reading
30 min
Understood
0/5 concepts

Why this part matters

Part 04 built count vectors and compared them with cosine. Those vectors have a flaw you can see the moment you look at real counts: the words with the biggest numbers are the, it and good, words that sit near everything and therefore say nothing. Any similarity computed from raw counts is dominated by them.

This part fixes the term-document side of that problem with tf-idf, the weighting that has been the baseline of information retrieval since the 1970s and still ships as the default sparse retriever and text feature extractor. Its descendant BM25 is the keyword half of most retrieval-augmented generation pipelines you will meet in research. Exams like this material because it is computable by hand: the difference between document frequency and collection frequency, and a tf-idf cell worked out from a count, are classic questions. The next part does the same job for term-term matrices with PPMI.

By the end you can

  1. Explain why raw co-occurrence counts over-weight frequent, uninformative words.
  2. Compute log-scaled term frequency and contrast the log10(count + 1) and 1 + log10(count) variants.
  3. Distinguish document frequency from collection frequency using Romeo and action.
  4. Compute idf with N = 37 and explain why a word in every document gets weight zero.
  5. Reproduce any cell of the slide 52 tf-idf table and explain how the choice of document changes the weights.

Build a Term-context matrix from a large corpus and look at the row for apricot. The context sugar has a healthy count there, and that is genuinely useful: apricots are sweet things you cook with sugar, and a word whose row also has a high sugar count, such as peach, is probably similar. Now look further along the same row. The contexts the, it and they have counts many times larger than sugar. They have similarly huge counts in the row for digital, for information, for every word in the vocabulary.

That is the paradox. Frequency is clearly informative, since co-occurring often is exactly how sugar earns its place. But frequency is not proportional to information. A Dot product sums products dimension by dimension, so a dimension where both vectors carry a count in the thousands swamps a dimension where both carry a count of twenty. Even after normalising by length, Cosine similarity on raw counts mostly measures how much two words share the ubiquitous dimensions, and every word shares those. What we want instead is a weight with two pressures in it: reward a term that is frequent here, and penalise a term that is frequent everywhere.

Two classic reweightings

Which reweighting you pick depends on which matrix you have. For a Term-document matrix, where columns are documents, the standard answer is tf-idf. Jurafsky and Martin describe it as "the product of two terms, the term frequency tf and the inverse document frequency idf", and note that the "-" is a hyphen, not a minus sign. The first factor rewards local frequency; the second penalises spread across the collection.

wt,d=tft,d×idftw_{t,d} = \mathrm{tf}_{t,d} \times \mathrm{idf}_t
tf-idf weight of term t in document d

For a term-term matrix, where both rows and columns are words, the standard answer is Pointwise mutual information. It compares how often two words actually appear together with how often they would if they were independent. Words like good and great earn a high PMI only if they meet more often than their individual frequencies predict, so a context like the that meets everything at the expected rate scores near zero. Part 06 develops PMI in full.

PMI(w1,w2)=log⁡2P(w1,w2)P(w1) P(w2)\mathrm{PMI}(w_1, w_2) = \log_2 \frac{P(w_1, w_2)}{P(w_1)\,P(w_2)}
Pointwise mutual information, written with the base 2 used from slide 54 onward
SchemeMatrix it suitsQuestion it answers
tf-idfTerm-documentIs this term distinctive for this document?
PMITerm-term (word-context)Do these two words co-occur more than chance would predict?
Two reweightings for two kinds of matrix

The idea behind the idf half is older than vector semantics. Karen Sparck Jones argued in 1972 that "matches on less frequent, more specific, terms are of greater value than matches on frequent terms", and proposed weighting each term by how few documents it appears in. Every term-weighting scheme in this lecture, PPMI included, is a variation on her insight.

Recall

Why does a cosine between two raw count vectors tend to be high for almost any pair of words?

Both vectors have their largest entries in the same ubiquitous dimensions (the, it, they, good). Those products dominate the dot product, so the cosine mostly reflects shared function-word contexts rather than meaning. Reweighting with tf-idf or PMI shrinks those dimensions.

Squashing term frequency with a logarithm

Suppose a word appears 0, 1, 9, 99 or 999 times in a document. Under the slides' formula those counts become a term frequency of 0, 0.301, 1, 2 and 3. A hundredfold increase in occurrences, from 9 to 999, adds only 2 to the weight.

Raw count to log-scaled term frequency, tf = log10(count + 1)

count 0
tf = log10(1) = 0
count 1
tf = log10(2) = 0.301
count 9
tf = log10(10) = 1
count 99
tf = log10(100) = 2
count 999
tf = log10(1000) = 3

The simplest term frequency would be the raw count itself, tf = count(t, d). The trouble is that significance does not grow linearly with repetition. Manning, Raghavan and Schütze put it plainly: "It seems unlikely that twenty occurrences of a term in a document truly carry twenty times the significance of a single occurrence." The second mention of battle in a play tells you a lot (this play has a battle in it); the hundredth mention adds far less. So we squash the count with a logarithm.

tft,d=log⁡10(count(t,d)+1)\mathrm{tf}_{t,d} = \log_{10}\big(\mathrm{count}(t,d) + 1\big)
Log-scaled term frequency used on the slides

The +1 is there because log 0 is undefined. Adding one before taking the log sends a count of zero to log10(1) = 0, which is exactly the weight an absent term should have, while barely changing large counts.

Grey ghosts show raw counts of 0, 9, 99 and 999 on a linear scale, where the first two are almost invisible. When active, the log-scaled tf bars rise to 0, 1, 2 and 3: each tenfold increase in count + 1 adds one equal step.

The other formula you will meet

The current SLP3 draft (now Chapter 11, on retrieval and RAG) and the IR book both use a slightly different squash. SLP3 notes in a footnote that log10(count + 1) is "this alternative formulation" used in its earlier editions, which is where the slides come from. Neither is an error; they are two members of the same sublinear family.

tft,d={1+log⁡10count(t,d)if count(t,d)>00otherwise\mathrm{tf}_{t,d} = \begin{cases} 1 + \log_{10} \mathrm{count}(t,d) & \text{if } \mathrm{count}(t,d) > 0 \\ 0 & \text{otherwise} \end{cases}
The variant in the current SLP3 draft and in IIR section 6.4.1

The two agree on the shape but not the numbers. Under the variant, a count of 1 gives 1 + log10 1 = 1 rather than 0.301, a count of 10 gives 2 rather than 1.041, and a count of 7 gives 1.845 rather than 0.903. scikit-learn offers a third version through sublinear_tf=True, which uses 1 + ln(tf) with the natural log.

Recall

Compute tf = log10(count + 1) for counts 0, 9 and 99.

0, 1 and 2. The +1 keeps a count of zero at a tf of zero, since log10 1 = 0.

Across the complete works of Shakespeare, the words Romeo and action each occur exactly 113 times. By raw volume they are identical. Yet every occurrence of Romeo sits in one play, Romeo and Juliet, while action is spread across 31 different plays. If you are handed a document and told it contains Romeo, you know which play it is. If you are told it contains action, you have learned almost nothing.

The two numbers in that story have names. The total number of times a term occurs across the whole collection is its Collection frequency, cf. The number of documents that contain the term at least once is its Document frequency, df_t. The IR book defines them exactly that way and concludes that for discriminating between documents it is "better to use a document-level statistic ... than to use a collection-wide statistic". SLP3 gives the reason: "Terms that occur in only a few documents are useful for discriminating those documents from the rest of the collection."

Wordcfdfidf
Romeo1131log10(37 / 1) = 1.57
action11331log10(37 / 31) = 0.077
Equal collection frequency, very different document frequency (idf computed with N = 37 plays)
A grid of 37 plays with 113 faint tokens scattered over it. When active, Romeo's 113 tokens collapse into a single play while action's 113 tokens spread across 31 plays: the same collection frequency, document frequencies of 1 and 31.

The Shakespeare pair is not a curiosity. The IR book finds the same pattern in the Reuters newswire collection, where try and insurance have almost the same collection frequency but very different document frequencies. Insurance clusters in the articles that are actually about insurance; try is sprinkled through everything.

Wordcfdf
try10,4228,760
insurance10,4403,997
Reuters RCV1, from IIR section 6.2.1

Recall

Romeo and action both have collection frequency 113. Give their df values and say which gets the larger idf with N = 37.

Romeo has df = 1 and action has df = 31. Romeo's idf is log10 37 = 1.57; action's is log10(37 / 31) = 0.077, about twenty times smaller.

Quick check

Romeo and action both occur 113 times across Shakespeare. Why does Romeo receive a much higher idf?

Treat each of Shakespeare's 37 plays as a document and walk down a ladder of words ordered by how many plays they appear in. Romeo is in one play, salad in two, Falstaff in four, forest in twelve, battle in twenty-one, wit in thirty-four, fool in thirty-six, and good and sweet in all thirty-seven. We want a weight that is large at the top of the ladder and vanishes at the bottom.

The ratio N / df_t of collection size to Document frequency does that: it is 37 for Romeo and 1 for good. Raw ratios grow too fast, though (a word in one document out of a million would get a ratio of a million), so we take the logarithm, exactly as we did for tf. The result is the inverse document frequency.

idft=log⁡10 ⁣(Ndft)\mathrm{idf}_t = \log_{10}\!\left(\frac{N}{\mathrm{df}_t}\right)
N is the number of documents in the collection
Worddfidf
Romeo11.57
salad21.27
Falstaff40.966 (slide prints 0.967)
forest120.489
battle210.246
wit340.037
fool360.012
good, sweet370
The idf ladder for Shakespeare, N = 37 plays (as in SLP3 section 11.1.2)

Two boundary values are worth memorising. The largest possible idf is log10 N, reached when a term occurs in a single document: log10 37 = 1.57 here. The smallest is 0, reached when a term occurs in every document, since log10(N / N) = log10 1 = 0. SLP3 says it directly: the lowest weight, 0, is "assigned to terms that occur in every document", words "like good or sweet". Notice that idf is not linear in df. Going from df = 1 to df = 2 costs 0.30, the same as going from df = 12 to df = 24: halving the spread always buys the same amount of weight.

Horizontal bars for idf = log10(37 / df), ordered by df. When active they draw in one after another, from 1.57 for Romeo down to almost nothing for fool and a bare dot at zero for good and sweet, which occur in all 37 plays.

Worked example

Checking the two ends of the ladder

  1. Romeo, df = 1

    idf = log10(37 / 1) = log10 37 = 1.568, which rounds to the slide's 1.57.
  2. fool, df = 36

    37 / 36 = 1.0278, so idf = log10 1.0278 = 0.0119, which rounds to 0.012.
  3. good, df = 37

    idf = log10(37 / 37) = log10 1 = 0.
  4. Result

    A word in one play is worth 1.57; a word missing from just one play is worth about 0.012, over a hundred times less; a word in every play is worth nothing.

What counts as a document?

Nothing in the formula says a document must be a play. SLP3 defines a document as "whatever unit of text the system indexes and retrieves (web pages, scientific papers, news articles, or even shorter passages like paragraphs)". It could be a Wikipedia article, a tweet, a paragraph, or a 300-token chunk in a RAG index. The choice is a modelling decision, and it changes everything downstream: N changes, every df changes, and so every idf changes.

Switch Shakespeare from plays to paragraphs and N grows into the thousands. good is no longer in every document, because most paragraphs do not contain it, so it gets a positive idf. A word like battle that clusters in a few scenes now looks rarer relative to the collection, and its weight rises. The right unit is the one that matches what you retrieve or compare: if your system returns passages, compute df over passages.

Recall

What happens to the idf values if you treat each paragraph as a document instead of each play?

N and every df change, so every idf changes. Words concentrated in a few passages gain relative weight, and a word like good that is in every play but not in every paragraph stops getting zero. The unit should match what you retrieve or compare.

Take battle in Julius Caesar. The play uses the word 7 times, so its term frequency is log10(7 + 1) = log10 8 = 0.903. battle appears in 21 of the 37 plays, so its idf is 0.246. Multiply: 0.903 × 0.246 = 0.222, which the slide prints as 0.22. That is one cell of the tf-idf matrix, and every other cell is computed the same way.

The rule is just the definition applied cell by cell: squash the count into Term frequency, look up the word's Inverse document frequency, multiply. The table below combines the raw counts on slide 52 with the idf column of slide 50 and shows the intermediate tf values the slide skips. All sixteen weights agree with the slide.

WordRaw countstf = log10(count + 1)idftf-idf
battle1, 0, 7, 130.301, 0, 0.903, 1.1460.2460.074, 0, 0.22, 0.28
good114, 80, 62, 892.061, 1.908, 1.799, 1.95400, 0, 0, 0
fool36, 58, 1, 41.568, 1.771, 0.301, 0.6990.01190.019, 0.021, 0.0036, 0.0083
wit20, 15, 2, 31.322, 1.204, 0.477, 0.6020.03670.049, 0.044, 0.018, 0.022
Four words in As You Like It, Twelfth Night, Julius Caesar and Henry V (values listed in that order)

Worked example

Two cells from the slide 52 table

  1. battle in Julius Caesar

    tf = log10 8 = 0.903, idf = log10(37 / 21) = 0.246, so w = 0.903 × 0.246 = 0.222 ≈ 0.22.
  2. fool in Henry V

    The count is 4, so tf = log10 5 = 0.699. fool is in 36 plays, so idf = 0.0119, and w = 0.699 × 0.0119 = 0.0083.
  3. Result

    battle in Julius Caesar outweighs fool in Henry V by a factor of about 27, although the raw counts differ only by 7 against 4. The difference comes almost entirely from idf.

What the table teaches

Look at the good row. It has the largest counts in the table, from 62 to 114, and its tf values are all around 2. Yet every tf-idf cell is 0, because good occurs in all 37 plays and its idf is log10 1 = 0. The word that dominated the raw vectors has been removed from the comparison without anyone writing a stop list.

Now look at battle. In raw counts it was a minor dimension, dwarfed by good in every play and by fool in the comedies. After weighting it is the largest value in every column where it appears, and it cleanly separates Julius Caesar (0.22) and Henry V (0.28), plays full of war, from the comedies As You Like It (0.074) and Twelfth Night (0). The IR book summarises the pattern: a weight is highest when a term occurs many times in a small number of documents, lower when it occurs fewer times or in many documents, and lowest when it occurs in virtually all documents.

A 4 by 4 grid of plays and words. Grey bars are raw counts, with the good row tallest. When active, the good row's tf-idf collapses to nothing while the battle cells for Julius Caesar and Henry V light up as the strongest weights.

The resulting document vectors are still sparse vectors of vocabulary length, mostly zeros, and you compare them with Cosine similarity exactly as in part 04. The difference is that the dimensions now carry information in proportion to how distinctive they are.

tf-idf in real systems

The formula is the same everywhere, but the details are not, and the differences matter when you reproduce a paper or debug a pipeline. scikit-learn's TfidfVectorizer by default uses raw counts for tf, a smoothed natural-log idf, ln((1 + n) / (1 + df)) + 1, and then L2-normalises each document vector. Because of the +1, a term in every document gets idf 1, not 0, so good would survive. The documentation's own example turns the vector [3, 0, 1.8473] into [0.8515, 0, 0.5243] after normalisation.

Sourcetfidfidf of a term in every document
Slides (SLP3 earlier editions)log10(count + 1)log10(N / df)0
SLP3 current draft, IIR1 + log10(count), or 0log10(N / df)0
scikit-learn defaultraw count (sublinear_tf=False)ln((1 + n) / (1 + df)) + 11
Three formulations you will meet

BM25 is the modern member of the family and the standard keyword retriever in search engines and hybrid RAG systems. It keeps idf but replaces log tf with a saturating function controlled by a parameter k, usually between 1.2 and 2, and normalises for document length with b = 0.75. Salton and Buckley's 1988 comparison of term-weighting schemes is the classic study of why these choices matter.

Recall

Compute the tf-idf of battle in Henry V (count 13, df 21, N 37).

tf = log10 14 = 1.146, idf = 0.246, so w = 1.146 × 0.246 = 0.282 ≈ 0.28.

Recall

Why does every cell in the good row of slide 52 become 0?

good occurs in all 37 plays, so df = N and idf = log10 1 = 0. Anything multiplied by zero is zero, however large its counts.

Quick check

With N = 37 and tf = log10(count + 1), what is the tf-idf of battle in Julius Caesar (count 7, df 21)?

Quick check

Why is every entry in the good row of the slide 52 tf-idf table equal to zero?

Quick check

In scikit-learn's default TfidfVectorizer, what idf does a term that appears in every document receive?

Recap

If you remember nothing else

  • Raw frequency is informative but dominated by ubiquitous words. tf-idf (term-document) and PMI (term-term) reweight counts.
  • The slides use tf = log10(count + 1), so 0 → 0, 9 → 1, 99 → 2. The current SLP3 draft and IIR use 1 + log10(count) for count > 0.
  • df counts documents, cf counts tokens. Romeo and action share cf 113 but have df 1 versus 31.
  • idf = log10(N / df). With N = 37 plays it ranges from 1.57 (Romeo) to 0 (good, sweet). The slides never state N.
  • w = tf × idf. Battle in Julius Caesar is log10 8 × 0.246 = 0.22, and the whole good row becomes 0.
  • A document is any unit you choose. Changing it changes N, df and every weight.
  • Libraries differ: scikit-learn uses ln((1 + n) / (1 + df)) + 1 with L2 normalization, so a term in every document keeps idf 1 instead of 0. BM25 adds tf saturation and length normalization.

Sources