Chapter 3 showed that word frequencies are wildly uneven. Zipf’s law guarantees a handful of words dominate every corpus, while a long tail barely occurs at all.
This chapter turns that raw material into something a machine can compare. We need a numerical representation of a document, and a way to measure when two documents are alike.
The construction is the foundation of information retrieval. Its two ideas come back in almost every later chapter. Weight the words by how informative they are. Measure similarity as an angle.
Start with the simplest representation there is. Fix the vocabulary of the corpus. Represent a document as a vector with one entry per vocabulary word, recording how often that word appears.
This is the bag of words. It keeps word counts and throws word order away completely, so “dog bites man” and “man bites dog” get the same vector.
That is a real loss. It is also tolerable for many tasks, where topic matters more than syntax. In exchange we get a document as a point in an -dimensional space, which is a form we can do arithmetic on.
Keeping only presence or absence, rather than counts, gives the binary incidence matrix of classical Boolean retrieval . Counts are the richer choice.
The matrix has one row per term and one column per document, so it holds cells. That number gets large very fast.
Take a million documents over half a million distinct terms. The matrix has cells. Even at one bit each, that is gigabytes. An average document contains perhaps a thousand distinct terms, so about cells are non-zero. The density is per cent.
The sparsity is not an accident of this example. It is Zipf’s law again. Most types are rare, so most rows are nearly empty.
So nobody stores the matrix. For each term you store the list of documents that contain it, which is the inverted index, and the empty cells cost nothing. The same argument returns for the co-occurrence matrices of Chapter 7.
The bit matrix has a second problem, and it is the one that motivates the rest of this chapter. A document that mentions caesar once and one that mentions it forty times look identical, so every matching document is equally good. Boolean retrieval cannot rank.
Raw counts bring the trouble Chapter 3 predicted. The most frequent word in a document is almost always the or of. Those words occur everywhere and say nothing about what the document is about.
So a representation dominated by its largest counts is dominated by its least informative words. We need to weight each term by how much it discriminates. The classic solution has two parts.
The first part measures how important a term is within a document. That is its term frequency. It can be the raw count . It can be the count normalised by document length, , so long documents do not win by sheer size. It can be a dampened log form, , so the tenth occurrence counts for less than the second.
The damping deserves a sentence of justification, because it looks arbitrary. Relevance does not grow linearly with occurrences. A document that says cricket twenty times is more about cricket than one that says it twice. It is not ten times more about cricket.
| raw | at | ||
|---|---|---|---|
| 1 | 1 | 0.001 | 1.000 |
| 2 | 2 | 0.002 | 1.301 |
| 10 | 10 | 0.010 | 2.000 |
| 100 | 100 | 0.100 | 3.000 |
| 1000 | 1000 | 1.000 | 4.000 |
A thousandfold spread in raw counts becomes a fourfold spread in weight. The logarithm turns multiplicative differences into additive ones, which is the same trick that made Zipf’s law a straight line in Chapter 3. The exists so that a single occurrence scores rather than .
Length normalisation and log damping fix different problems, and they are often used together.
But term frequency alone still rewards the. So we multiply it by a second factor, measuring how informative the term is across the whole collection. That is the inverse document frequency:
where is the number of documents in the collection, and is how many of them contain term .
Note what counts. It counts documents, not occurrences. A term used five hundred times in one document still has . Since always, the ratio is at least and the idf is never negative.
Equation (4.1) behaves exactly as we want. A term appearing in every document has , so . It is useless for telling documents apart, and its weight is annihilated. A term appearing in few documents has a small and so a large idf.
The logarithm is not a fudge factor. Pick a document from the collection at random. The chance that it contains is . So
which is the self-information of the event “this document contains ” . The weight is a count of bits of surprise, in whatever base the logarithm uses.
That reading explains the behaviour at both ends. A term you fully expected to see carries no information, and scores zero. A term you did not expect carries a lot, and scores high.
The logarithm also tames the scale. Without it, ranges over five orders of magnitude, and one rare word would dominate every score it touched. The base of the logarithm only rescales every weight by a constant, so it never changes a ranking. Use base by hand and natural logs in code.
Implementations rarely use Equation (4.1) unmodified. Two variants are common:
The
in Equation (4.7) guards against a
query term that appears in no document at all, where the plain formula
divides by zero. The trailing
stops a term present in every document from collapsing the entire vector
to zero. This is what scikit-learn computes by default. Its
numbers will not match a hand computation unless you ask for the
unsmoothed form.
Equation (4.8) goes negative for any term in more than half the collection, which is a stronger punishment of the head. It is the ancestor of BM25, developed in Chapter 20.
All three variants agree on the ordering of terms. They differ only in how hard they push the head down.
The product of the two factors is tf-idf:
It is high only when a term occurs often in this document but rarely across the collection. That is precisely the profile of a word that characterises the document.
This answers a puzzle Chapter 3 left open. At bottom, idf is a machine for undoing Zipf’s law. It discounts the frequent words that the frequency distribution guarantees you will have.
Numbers make the mechanism concrete. Take a collection of documents. The word moon occurs in of them, so
Take a token document where moon appears times. Its length normalised term frequency is . Its tf-idf is therefore .
Now take the, which appears in all documents. Its idf is . So its tf-idf is no matter how many times it occurs in the document.
The weighting has silenced the most frequent word in the language and amplified the one that signals the topic. It did so with nothing but a logarithm of a ratio of counts.
Try it yourself.
code/worked_examples/weighting.pyprints this table and the PMI one from Chapter 7. It adds a word that occurs once and still outscores the.
Now that documents and words are weighted vectors, “how similar are these two?” becomes a geometric question.
The natural first guess is the dot product . It has a flaw. It grows with the lengths of the vectors, so a long document scores high similarity with everything, purely by having large entries.
The fix is to divide the magnitudes out. Measure the angle between the vectors instead of their lengths. That is cosine similarity:
It is when the vectors point the same way, meaning maximally similar. It is when they are orthogonal, meaning no shared terms. It is negative only when they point oppositely, which is rare for count vectors, whose entries are never negative.
Cosine is the standard measure of similarity in this book. It compares word vectors in Chapter 6, embeddings in Chapter 9, and retrieved passages in Chapter 20.
The unnormalised dot product is not discarded, though. The attention mechanism at the heart of every transformer is built on exactly that operation. Chapter 16 puts it to a different use.
Euclidean distance is the third obvious choice. It is sensitive to magnitude, so it is rarely used for comparing these vectors directly.
There is one case where all three agree. For unit length vectors,
so ranking by cosine and ranking by Euclidean distance give the same order. Vector databases exploit exactly this. Normalise once at indexing time, then use whichever operation the hardware does faster.
The moon example weighted one term. This section takes four documents all the way from raw text to a ranking. Every number is small enough to check by hand.
The collection is documents:
the moon orbits the earth |
|
the earth orbits the sun |
|
the telescope shows the moon |
|
the sun is a star |
Ten distinct terms, so the matrix is . Each cell is .
| term | ||||
|---|---|---|---|---|
| the | 2 | 2 | 2 | 1 |
| moon | 1 | 0 | 1 | 0 |
| orbits | 1 | 1 | 0 | 0 |
| earth | 1 | 1 | 0 | 0 |
| sun | 0 | 1 | 0 | 1 |
| telescope | 0 | 0 | 1 | 0 |
| shows | 0 | 0 | 1 | 0 |
| is | 0 | 0 | 0 | 1 |
| a | 0 | 0 | 0 | 1 |
| star | 0 | 0 | 0 | 1 |
By raw count, the is the most important word in all four documents. That is the problem this chapter exists to solve.
| term | |||
|---|---|---|---|
| the | 4 | 1.00 | 0.000 |
| moon, orbits, earth, sun | 2 | 2.00 | 0.301 |
| telescope, shows, is, a, star | 1 | 4.00 | 0.602 |
The is in every document, so and its weight is exactly zero.
One caveat, and it is an honest one. In this collection a has , so it scores the highest idf available. idf is a statistic of a collection, not a judgement about language, and with it is measuring almost nothing. Give it a hundred thousand documents and a falls to zero alongside the.
| term | ||||
|---|---|---|---|---|
| the | 0.000 | 0.000 | 0.000 | 0.000 |
| moon | 0.301 | 0.000 | 0.301 | 0.000 |
| orbits | 0.301 | 0.301 | 0.000 | 0.000 |
| earth | 0.301 | 0.301 | 0.000 | 0.000 |
| sun | 0.000 | 0.301 | 0.000 | 0.301 |
| telescope | 0.000 | 0.000 | 0.602 | 0.000 |
| shows | 0.000 | 0.000 | 0.602 | 0.000 |
| is | 0.000 | 0.000 | 0.000 | 0.602 |
| a | 0.000 | 0.000 | 0.000 | 0.602 |
| star | 0.000 | 0.000 | 0.000 | 0.602 |
| 0.5214 | 0.5214 | 0.9031 | 1.0854 |
The entire first row is zero. Two documents that share only the now share nothing at all.
Take and . Each has three non-zero entries of , and they share two of them, orbits and earth:
That is exactly , two shared terms out of three equally weighted ones. Doing the same for and , which share only moon, gives .
Table 1.1 scores all six pairs twice, once on raw counts and once on tf-idf.
| pair | on raw tf | on tf-idf |
|---|---|---|
| 0.8571 | 0.6667 | |
| 0.7143 | 0.1925 | |
| 0.5071 | 0.1601 | |
| 0.3381 | 0.0000 | |
| 0.5714 | 0.0000 | |
| 0.3381 | 0.0000 |
Read the row for . Raw counts call them similar. The only word they share is the. Under tf-idf their cosine is exactly zero, which is the right answer.
Now read against . Raw counts separate them by a factor of . tf-idf separates them by a factor of . The ranking was already correct. The weighting made it decisive, and a retrieval system needs decisive.
A query is just a short document. Score each document by adding the tf-idf of the query terms it contains:
For the query “the moon”:
| document | raw count | tf-idf score |
|---|---|---|
| 3 | 0.3010 | |
| 3 | 0.3010 | |
| 2 | 0.0000 | |
| 1 | 0.0000 |
Raw counts rank third with a score of , ahead of . Neither document mentions the moon. tf-idf sends both to exactly zero, and keeps the correct tie between and .
Notice what that means for the user. The word the contributed nothing to the score, so typing it cost nothing. Weighting is what lets people write queries in ordinary English.
Try it yourself.
python3 weighting.py corpusprints every table in this section. Add a fifth document mentioning the moon and watch move, taking every weight in the matrix with it.
Two ideas leave this chapter and never depart.
Weight terms by informativeness, so the frequent words Zipf’s law forces on us do not drown the signal. Measure similarity by angle, so comparison is about direction, not magnitude.
Together, tf-idf and cosine turn a corpus into a searchable, comparable space. They were the engine of information retrieval for decades.
But notice what they assume. Every word is still an independent dimension, orthogonal to every other. So cat and dog stay exactly as unrelated as cat and the. tf-idf weights the axes. It does not make similar words share them.
Repairing that is the next leap. We want cat and dog to have nearby directions, not perpendicular ones. That takes us to the distributional vectors of Chapter 6, and then to the learned embeddings of Chapter 9. The measuring stick, cosine, comes along unchanged. What improves is the space it measures.
Manning, Raghavan and Schütze’s Introduction to Information Retrieval is the definitive modern treatment of tf-idf weighting, the vector space model and cosine similarity. Manning and Schütze cover the incidence matrix and the statistical foundations. BM25, the probabilistic descendant of tf-idf, is developed in Chapter 20.
idf by hand. In a collection of documents, term appears in documents, term in , and term in all . Compute in base for each. Rank them by informativeness. In one sentence, explain why ’s value is what it is.
tf-idf silences the stopword. A token document contains data times, and data appears in of documents. The same document contains the times, and the appears in all . Compute the length normalised tf-idf of each. Which word does the weighting say the document is about? Why is the raw count the wrong guide?
Cosine versus dot product. Let and over the vocabulary . Compute the dot product and the cosine similarity of Equation (4.4). These two vectors describe documents with identical word proportions but different lengths. Which measure recognises them as the same? Why is that the property you want when comparing documents?
Why orthogonal axes are not enough. Under a tf-idf bag of words representation, compute the cosine similarity between a document containing only cat and one containing only dog. Is the value what a human would judge? Explain how this motivates the distributional vectors of Chapter 6, where cat and dog are no longer perpendicular.
idf is bits. Using Equation (4.2), state in one sentence what means about the probability that a random document contains . Then say why changing the base of the logarithm cannot change a ranking.
Refit with the smoothed idf. Rebuild Section 1.4 using Equation (4.7) instead. Which cells change? Does the query ranking change? Explain why the answer to those two questions differs.
Damping changes the winner. Take a document of tokens containing cricket times and a document of tokens containing it times. Rank them by raw count, by , and by . Report the three rankings and say which one you would ship.
Cosine and Euclidean. Normalise and to unit length, then verify Equation (4.5) numerically. Construct a pair of unnormalised vectors for which cosine and Euclidean distance rank a third vector differently.
Count the storage. A collection has documents and terms, and an average document holds distinct terms. Compute the size of the dense bit matrix and the number of non-zero cells. What fraction of the matrix would an inverted index actually store?