ICS 582Lecture 03Part 03
Estimating n-gram probabilities with maximum likelihood
Relative-frequency (MLE) estimation for bigrams and general n-grams, worked on the I am Sam toy corpus and the Berkeley Restaurant Project tables, plus why we compute in log space and when longer n-grams pay off.
- Concepts
- 5
- Slides
- 17-28
- Reading
- 30 min
Why this part matters
Part 02 reduced a sentence probability to a product of short conditional probabilities. This part answers the question that leaves open: where do the numbers come from? The answer is the simplest estimator in statistics, counting and dividing, and every later idea in the lecture (perplexity, sampling, smoothing, Kneser-Ney, stupid backoff) is built on these relative frequencies.
It is also the most reliably examined skill in the chapter. You will be handed a tiny corpus or a count table and asked for a bigram probability and a sentence probability, and the marks are lost on two details: which count goes in the denominator, and whether the <s> and </s> factors were included. Real toolkits such as KenLM then store everything as base 10 logarithms, which is the last idea here.
By the end you can
- Compute bigram and general n-gram MLE estimates by hand from a padded corpus.
- Explain why dividing by the history count normalizes each conditional distribution.
- Read count and probability tables like the BeRP tables and convert between them.
- Compute a sentence probability with <s> and </s>, in both linear and log space.
- Explain underflow and the tradeoffs of trigram to 5-gram models with their start padding.
Suppose the word "Chinese" occurs 400 times in a corpus of one million words. What probability should a unigram model give it? The obvious answer is 400 / 1,000,000 = 0.0004, and that obvious answer has a name: it is the maximum likelihood estimate. Among every possible value you could pick, 0.0004 is the one that makes the observed data, 400 occurrences in a million draws, most probable. SLP3 is careful about what this does and does not mean: it is not a claim that the "true" probability of Chinese in all English is 0.0004. It is the best fit to this one training set.
Stated generally, maximum likelihood chooses the parameters of a model M that maximize P(T | M), the probability of the training set T. For a language model made of n-gram probabilities, the maximizing parameters turn out to be relative frequencies: count how often something happened and normalize, meaning divide by a total so the results lie between 0 and 1 and sum to 1.
From counts to a conditional probability
A Bigram model needs P(w_n | w_(n-1)): given the previous word, how likely is each next word? The relative frequency is the count of the pair divided by the total count of all pairs that start with the same previous word. That is SLP3 equation 3.10:
The denominator looks awkward, but it collapses. Walk through the corpus and look at every occurrence of the word w_(n-1). Each occurrence is immediately followed by exactly one word, so it starts exactly one bigram. Summing the bigram counts over all possible followers therefore just counts the occurrences of w_(n-1) itself. The only occurrence that could break this is a word at the very end of a sentence, which has no follower, and closing every sentence with </s> fixes this too: the last real word is followed by the end token, so it too starts a bigram. The result is equation 3.11:
It helps to see what the wrong denominator would give. If you divided C(w_(n-1) w_n) by the total number of bigrams N in the corpus, you would get the joint probability P(w_(n-1), w_n): how often this particular pair occurs among all pairs. That is a perfectly good number, but it answers a different question. All the joint values together sum to 1, while what theChain rule of probability needs is a distribution over next words for a fixed history.
Recall
Why must the denominator be C(w_(n-1)) and not the total number of bigrams?
Quick check
Why does the bigram MLE divide by C(w_(n-1))?
The best way to make equation 3.11 automatic is to run it once by hand on a corpus small enough to hold in your head. SLP3 uses three sentences from Dr. Seuss, each wrapped in sentence boundary tokens:
<s> I am Sam </s>
<s> Sam I am </s>
<s> I do not like green eggs and ham </s>Worked example
Bigram MLE on I am Sam
Count every history word
Count each word as it appears in the history position, that is, every token except </s>. <s> occurs 3 times (once per sentence), I occurs 3 times, am 2, Sam 2, and every other word once.Count the bigrams that start with each history
After <s>: I twice, Sam once. After I: am twice, do once. After am: Sam once, </s> once. After Sam: </s> once, I once. Check: each history's followers add up to its own count, 2 + 1 = 3 and 1 + 1 = 2.Divide each pair by its history count
P(I | <s>) = 2/3 = 0.67, P(Sam | <s>) = 1/3 = 0.33, P(am | I) = 2/3 = 0.67, P(do | I) = 1/3 = 0.33, P(</s> | Sam) = 1/2 = 0.5, P(Sam | am) = 1/2 = 0.5.Score a whole sentence
P(<s> I am Sam </s>) = P(I | <s>) x P(am | I) x P(Sam | am) x P(</s> | Sam) = 2/3 x 2/3 x 1/2 x 1/2.Result
P(<s> I am Sam </s>) = 1/9, about 0.111. Four factors for three words, because the end token is predicted too.
| History | C(history) | Bigrams that start with it | MLE estimates |
|---|---|---|---|
| <s> | 3 | <s> I: 2, <s> Sam: 1 | P(I | <s>) = 2/3, P(Sam | <s>) = 1/3 |
| I | 3 | I am: 2, I do: 1 | P(am | I) = 2/3, P(do | I) = 1/3 |
| am | 2 | am Sam: 1, am </s>: 1 | P(Sam | am) = 1/2, P(</s> | am) = 1/2 |
| Sam | 2 | Sam </s>: 1, Sam I: 1 | P(</s> | Sam) = 1/2, P(I | Sam) = 1/2 |
| do, not, like, green, eggs, and, ham | 1 each | Exactly one continuation each, ending with ham </s> | Each is 1/1 = 1 |
Now try it on your own data. The builder below pads every line, fills the count matrix and the MLE matrix, and scores any sentence as a product and as a sum of natural logs. Load the I am Sam preset and check the numbers above, then type a sentence containing a Bigram the corpus never saw and watch the product collapse to zero.
| C(row) | I | am | Sam | </s> | do | not | like | green | eggs | and | ham |
|---|---|---|---|---|---|---|---|---|---|---|---|
| <s>3 | 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| I3 | 0 | 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| am2 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Sam2 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| do1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| not1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| like1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| green1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| eggs1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| and1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| ham1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| row | I | am | Sam | </s> | do | not | like | green | eggs | and | ham |
|---|---|---|---|---|---|---|---|---|---|---|---|
| <s> | 2/3 | 0 | 1/3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| I | 0 | 2/3 | 0 | 0 | 1/3 | 0 | 0 | 0 | 0 | 0 | 0 |
| am | 0 | 0 | 1/2 | 1/2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Sam | 1/2 | 0 | 0 | 1/2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| do | 0 | 0 | 0 | 0 | 0 | 1/1 | 0 | 0 | 0 | 0 | 0 |
| not | 0 | 0 | 0 | 0 | 0 | 0 | 1/1 | 0 | 0 | 0 | 0 |
| like | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/1 | 0 | 0 | 0 |
| green | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/1 | 0 | 0 |
| eggs | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/1 | 0 |
| and | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/1 |
| ham | 0 | 0 | 0 | 1/1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
- P(I | <s>) = 2/3
- P(am | I) = 2/3
- P(Sam | am) = 1/2
- P(</s> | Sam) = 1/2
Sentence probability 0.111, log probability -2.197
Highlighted cells are the bigrams the sentence uses. A teal factor is an unseen bigram: MLE gives it zero, the whole product collapses to zero, and its log is minus infinity.
From bigrams to any N
Nothing in the recipe depends on the history being one word. For an n-gram model, the Markov assumption keeps the last N - 1 words as the history, and the estimate is the count of the full n-gram divided by the count of its prefix (equation 3.12):
For a Trigram, P(Sam | I am) is C(I am Sam) / C(I am). In the toy corpus that is 1/2: "I am" occurs twice, followed once by Sam and once by </s>. The price of the longer history is Sparsity. With a vocabulary of V words there are V^(N-1) possible histories, and each needs enough occurrences for its row of fractions to mean anything. The rows get thinner fast.
Recall
From the I am Sam corpus, compute P(do | I) and P(Sam | am), and say what each denominator counts.
Quick check
In the I am Sam corpus, what is the trigram MLE estimate of P(do | <s> I)?
In the Berkeley Restaurant Project corpus, the word i occurs 2533 times and the pair "i want" occurs 827 times. So the maximum likelihood estimate is P(want | i) = 827 / 2533 = 0.33: a third of the time a user of this system says i, the next word is want. That single division is all there is to reading a real Bigram table.
BeRP was a spoken question-answering system from the 1990s that answered questions about a database of restaurants in Berkeley, California. Its transcripts are exactly the sort of text an assistant hears: can you tell me about any good cantonese restaurants close by, i'm looking for a good place to eat breakfast. SLP3 uses 9332 sentences with a vocabulary of V = 1446 and shows an 8 by 8 slice of the bigram table for eight hand-picked words.
| History (row) | i | want | to | eat | chinese | food | lunch | spend |
|---|---|---|---|---|---|---|---|---|
| i (2533) | 5 | 827 | 0 | 9 | 0 | 0 | 0 | 2 |
| want (927) | 2 | 0 | 608 | 1 | 6 | 6 | 5 | 1 |
| to (2417) | 2 | 0 | 4 | 686 | 2 | 0 | 6 | 211 |
| eat (746) | 0 | 0 | 2 | 0 | 16 | 2 | 42 | 0 |
| chinese (158) | 1 | 0 | 0 | 0 | 0 | 82 | 1 | 0 |
| food (1093) | 15 | 0 | 15 | 0 | 1 | 4 | 0 | 0 |
| lunch (341) | 2 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| spend (278) | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
Read the table as row then column: the row is the history and the column is the next word. The 827 in row i, column want means want followed i 827 times. To turn counts into probabilities, divide every cell in a row by that row word's unigram count. Row i is divided by 2533, row want by 927, row to by 2417, and so on:
| Estimate | Bigram count | Row word count | Probability |
|---|---|---|---|
| P(want | i) | 827 | 2533 | 0.33 |
| P(to | want) | 608 | 927 | 0.66 |
| P(eat | to) | 686 | 2417 | 0.28 |
| P(food | chinese) | 82 | 158 | 0.52 |
| P(lunch | eat) | 42 | 746 | 0.056 |
| P(spend | to) | 211 | 2417 | 0.087 |
| P(i | want) | 2 | 927 | 0.0022 |
| History (row) | i | want | to | eat | chinese | food | lunch | spend |
|---|---|---|---|---|---|---|---|---|
| i | 0.002 | 0.33 | 0 | 0.0036 | 0 | 0 | 0 | 0.00079 |
| want | 0.0022 | 0 | 0.66 | 0.0011 | 0.0065 | 0.0065 | 0.0054 | 0.0011 |
| to | 0.00083 | 0 | 0.0017 | 0.28 | 0.00083 | 0 | 0.0025 | 0.087 |
| eat | 0 | 0 | 0.0027 | 0 | 0.021 | 0.0027 | 0.056 | 0 |
| chinese | 0.0063 | 0 | 0 | 0 | 0 | 0.52 | 0.0063 | 0 |
| food | 0.014 | 0 | 0.014 | 0 | 0.00092 | 0.0037 | 0 | 0 |
| lunch | 0.0059 | 0 | 0 | 0 | 0 | 0.0029 | 0 | 0 |
| spend | 0.0036 | 0 | 0.0036 | 0 | 0 | 0 | 0 | 0 |
Now look at the zeros. These eight words were chosen because they cohere, they are the words of "i want to eat chinese food" plus two neighbors, and still 32 of the 64 cells are zero. The full table has 1446 x 1446, about 2.09 million, cells, filled from only 9332sentences, so the overwhelming majority of the full table must be zero. That is Sparsity seen directly, and some of those zeros (chinese to and lunch to are both zero, yet "chinese to go" is ordinary English, while a zero for i lunch reflects grammar) are accidents of a small sample rather than facts about English.
Recall
Using the BeRP tables, compute P(want | i) and P(food | chinese).
Recall
Why does row i of the count table not sum to 2533?
With a table in hand, scoring a sentence is mechanical. The Chain rule of probability writes the sentence probability as a product of next-word probabilities, and the Markov assumption cuts each history down to one word. For "i want english food", with the boundary tokens included, that is five factors:
Worked example
Scoring i want english food with BeRP bigrams
Collect the five factors
P(i | <s>) = 0.25, P(want | i) = 0.33 (from the table), P(english | want) = 0.0011, P(food | english) = 0.5, P(</s> | food) = 0.68.Multiply left to right
0.25 x 0.33 = 0.0825, then x 0.0011 = 0.0000908, then x 0.5 = 0.0000454, then x 0.68 = 0.0000309.Result
P(<s> i want english food </s>) = 3.0855 x 10^-5, about 0.000031, the value SLP3 reports.
Four of the five factors lie between 0.25 and 0.68. The fifth, 0.0011, is what makes the sentence improbable: the running product falls from 0.0825 to about 0.00009 at that single step. A sentence probability is dominated by its rarest transition.
What the numbers know
The table was built by counting, yet it already encodes several kinds of knowledge. Some is syntactic: after to, the high-probability words are verbs (P(eat | to) = 0.28), and after eat SLP3 notes that what follows is usually a noun or an adjective (lunch, chinese). Some is about the task: sentences tend to start with i, and "i want" is how people talk to an assistant. Some is cultural: the corpus makes Chinese food much more probable than English food, compare P(english | want) = 0.0011 with P(chinese | want) = 0.0065.
Quick check
A five-word sentence is scored with a bigram model using both boundary tokens. How many factors are multiplied?
Multiply 400 probabilities of 0.1 in Python and the answer is exactly 0.0. The true value, 10^-400, is far below the smallest positive normalized double, 2.2 x 10^-308, and even below the smallest subnormal, about 5 x 10^-324, so the hardware rounds it to zero. Four hundred tokens is a paragraph, not a book. Yet the logarithm of the same product is simply 400 x ln 0.1 = -921.03, an unremarkable number.
This failure is called numerical underflow, and SLP3 gives it as the reason language models always work with log probabilities: probabilities are at most 1, so the more of them you multiply, the smaller the product gets. The identity that saves us is that the log of a product is the sum of the logs (equation 3.13):
Worked example
The BeRP sentence in log space
Take the natural log of each factor
ln 0.25 = -1.386, ln 0.33 = -1.109, ln 0.0011 = -6.812, ln 0.5 = -0.693, ln 0.68 = -0.386.Add them
-1.386 - 1.109 - 6.812 - 0.693 - 0.386 = -10.386. The rare bigram is still visible: it contributes two thirds of the total.Convert back only at the end
exp(-10.386) = 3.09 x 10^-5, the same 0.000031 as before.Result
ln P = -10.386. In base 10 the same sum is -4.511 and in base 2 it is -14.984; all three describe the same probability.
import math, sys
print(0.1 ** 400)
print(400 * math.log(0.1))
print(sys.float_info.min, math.ulp(0.0))
factors = [0.25, 0.33, 0.0011, 0.5, 0.68]
log_p = sum(math.log(p) for p in factors)
print(log_p, math.exp(log_p))The script prints 0.0, then -921.034..., then 2.2250738585072014e-308 and 5e-324, then -10.386 and 3.0855e-05. Note that math.log is the natural log unless you pass a base.
Longer context
With enough data, a bigram history is too short. Production systems use Trigram, 4-gram and 5-gram models, and the recipe is equation 3.12 again. The first words of a sentence need a full-length history, so a model of order N prepends N - 1 start tokens. For a trigram that is two, and the first word is scored as P(I | <s> <s>).
| N | Context words | Start padding | Fluency | Possible histories V^(N-1) | Parameters V^N |
|---|---|---|---|---|---|
| 2 (bigram) | 1 | <s> | Local, often choppy | 1446 | about 2.09 x 10^6 |
| 3 (trigram) | 2 | <s> <s> | Clearly better phrases | about 2.09 x 10^6 | about 3.02 x 10^9 |
| 4 (4-gram) | 3 | <s> <s> <s> | Very fluent where seen | about 3.02 x 10^9 | about 4.37 x 10^12 |
| 5 (5-gram) | 4 | <s> x 4 | Copies training text on small data | about 4.37 x 10^12 | about 6.32 x 10^15 |
The tradeoff is direct. Each extra word of context makes predictions more specific and the generated text more fluent, but multiplies the number of possible histories by V, so each history is seen less often and the table fills with zeros. On a small corpus a high-order MLE model has almost one continuation per history and simply copies training sentences, the Overfitting that Part 06 shows with Shakespeare samples.
At web scale the balance shifts. Google's Web 1T 5-gram corpus counted n-grams over about one trillion tokens, with 13,588,391 unigram types and 1,176,470,663 distinct five-grams. Brants and colleagues in 2007 trained on two trillion tokens, about 300 billion n-grams, with the stupid backoff scheme you will meet in Part 07. Infini-gram (2024) drops the fixed N entirely: it indexes five trillion tokens with suffix arrays and computes counts for an n-gram of any length at query time. Toolkits like KenLM make large models fit in memory by storing log10 probabilities, quantizing them to 4 to 8 bits, hashing words to 64-bit integers, storing n-grams in reverse tries, and pruning rare n-grams.
Recall
Compute ln P(<s> i want english food </s>) from the five factors and convert it back.
Recall
How many start symbols does a trigram model need, and why?
Quick check
In float64, what does multiplying 400 probabilities of 0.1 give?
Quick check
How many <s> pseudo-words does a 4-gram model prepend?
Recap
If you remember nothing else
- MLE is count then normalize: P(w_n | w_{n-1}) = C(w_{n-1} w_n) / C(w_{n-1}). It maximizes the likelihood of the training data, not the true probability.
- The sum over w of C(w_{n-1} w) equals C(w_{n-1}), so each conditional row sums to 1. Dividing by the total number of bigrams would give a joint probability instead.
- I am Sam gives P(I | <s>) = 2/3, P(Sam | <s>) = 1/3, P(am | I) = 2/3, P(do | I) = 1/3 and P(</s> | Sam) = 1/2, and P(<s> I am Sam </s>) = 1/9.
- The general n-gram MLE divides the count of the full n-gram by the count of its (N-1)-word prefix. The number of possible histories is V^(N-1), so sparsity grows quickly with N.
- BeRP has 9332 sentences and V = 1446. P(want | i) = 827/2533 = 0.33, and even among 8 hand-picked coherent words half the cells are zero.
- P(<s> i want english food </s>) = 0.25 x 0.33 x 0.0011 x 0.5 x 0.68, about 0.000031. One rare bigram dominates the result.
- Bigram statistics encode local syntax, task habits and cultural facts, all from one corpus and domain.
- Store and add log probabilities: p1 ... pk = exp(sum of log pi). SLP3 uses ln, KenLM and ARPA files use log10, and perplexity work often uses log2. The base never changes a ranking.
- Trigrams pad with <s> <s>. Longer n-grams are more fluent but sparser and larger, and on small corpora they copy the training data.
Sources
- Speech and Language Processing, 3rd ed. draft, chapter 3: N-gram Language ModelsBookJurafsky and Martin, StanfordSections 3.1.2 and 3.1.3: MLE, equations 3.10 to 3.13, the I am Sam and BeRP examples, log probabilities and large-scale models.(opens in a new tab)
- The Berkeley Restaurant ProjectPaperJurafsky et al., ICSLP 1994 (ISCA Archive)(opens in a new tab)
- Large Language Models in Machine TranslationPaperBrants, Popat, Xu, Och and Dean, EMNLP-CoNLL 2007Two trillion tokens, 300 billion n-grams, stupid backoff.(opens in a new tab)
- Infini-gram: Scaling Unbounded n-gram Language Models to a Trillion TokensPaperLiu, Min, Zettlemoyer, Choi and Hajishirzi, 2024 (arXiv)(opens in a new tab)
- KenLM: Faster and Smaller Language Model QueriesPaperHeafield, WMT 2011(opens in a new tab)
- KenLM READMEDocsKenneth HeafieldAs is the custom in language modeling, all probabilities are log base 10.(opens in a new tab)
- Web 1T 5-gram Version 1 (LDC2006T13)DocsLinguistic Data Consortium(opens in a new tab)
- sys.float_infoDocsPython documentation(opens in a new tab)
- math: log, exp and ulpDocsPython documentation(opens in a new tab)
- nltk.lm: MLE language models and logscoreDocsNLTK documentation(opens in a new tab)