Majid Al-RaimiEstimating n-gram probabilities with maximum likelihood

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
Understood
0/5 concepts

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

  1. Compute bigram and general n-gram MLE estimates by hand from a padded corpus.
  2. Explain why dividing by the history count normalizes each conditional distribution.
  3. Read count and probability tables like the BeRP tables and convert between them.
  4. Compute a sentence probability with <s> and </s>, in both linear and log space.
  5. 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:

P(wn∣wn−1)=C(wn−1 wn)∑wC(wn−1 w)P(w_n \mid w_{n-1}) = \frac{C(w_{n-1}\, w_n)}{\sum_{w} C(w_{n-1}\, w)}
Equation 3.10: normalize by every bigram that starts with the same history word.

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:

P(wn∣wn−1)=C(wn−1 wn)C(wn−1)P(w_n \mid w_{n-1}) = \frac{C(w_{n-1}\, w_n)}{C(w_{n-1})}
Equation 3.11: the bigram MLE. Numerator, the pair. Denominator, the history word alone.
The row after I: am was seen twice and do once. Divided by C(I) = 3, the two bars become one bar of length 1 split at 2/3.

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?

The sum over all w of C(w_(n-1) w) equals C(w_(n-1)), so dividing by it makes each conditional distribution sum to 1. Dividing by the total bigram count gives a joint probability P(w_(n-1), w_n), not a conditional one.

Quick check

Why does the bigram MLE divide by C(w_(n-1))?

Doing MLE by hand on I am Sam, then for any N

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>
The padded I am Sam corpus (slide 19).

Worked example

Bigram MLE on I am Sam

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Result

    P(<s> I am Sam </s>) = 1/9, about 0.111. Four factors for three words, because the end token is predicted too.
HistoryC(history)Bigrams that start with itMLE estimates
<s>3<s> I: 2, <s> Sam: 1P(I | <s>) = 2/3, P(Sam | <s>) = 1/3
I3I am: 2, I do: 1P(am | I) = 2/3, P(do | I) = 1/3
am2am Sam: 1, am </s>: 1P(Sam | am) = 1/2, P(</s> | am) = 1/2
Sam2Sam </s>: 1, Sam I: 1P(</s> | Sam) = 1/2, P(I | Sam) = 1/2
do, not, like, green, eggs, and, ham1 eachExactly one continuation each, ending with ham </s>Each is 1/1 = 1
The I am Sam counts as a tally: one row per history word

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.

InteractiveBigram count-table builder
Counts C(row word, column word)
C(row)IamSam</s>donotlikegreeneggsandham
<s>320100000000
I302001000000
am200110000000
Sam210010000000
do100000100000
not100000010000
like100000001000
green100000000100
eggs100000000010
and100000000001
ham100010000000
MLE P(column | row) = count / C(row)
rowIamSam</s>donotlikegreeneggsandham
<s>2/301/300000000
I02/3001/3000000
am001/21/20000000
Sam1/2001/20000000
do000001/100000
not0000001/10000
like00000001/1000
green000000001/100
eggs0000000001/10
and00000000001/1
ham0001/10000000
  1. P(I | <s>) = 2/3
  2. P(am | I) = 2/3
  3. P(Sam | am) = 1/2
  4. P(</s> | Sam) = 1/2
Sentences3in corpus
P(sentence)0.111product
ln P-2.197sum of logs

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):

P(wn∣wn−N+1:n−1)=C(wn−N+1:n−1  wn)C(wn−N+1:n−1)\begin{aligned} &P(w_n \mid w_{n-N+1:n-1}) \\ &\quad = \frac{C(w_{n-N+1:n-1}\; w_n)}{C(w_{n-N+1:n-1})} \end{aligned}
Equation 3.12: numerator, the whole N-word sequence. Denominator, its first N - 1 words.

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.

1/3 and 1/2. The denominators count the history word: C(I) = 3 and C(am) = 2.

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)iwanttoeatchinesefoodlunchspend
i (2533)5827090002
want (927)2060816651
to (2417)204686206211
eat (746)0020162420
chinese (158)100008210
food (1093)1501501400
lunch (341)20000100
spend (278)10100000
BeRP bigram counts: row word followed by column word. The unigram count of each row word is in brackets (SLP3 figure 3.1)

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:

EstimateBigram countRow word countProbability
P(want | i)82725330.33
P(to | want)6089270.66
P(eat | to)68624170.28
P(food | chinese)821580.52
P(lunch | eat)427460.056
P(spend | to)21124170.087
P(i | want)29270.0022
Count over unigram count gives the bigram probability
History (row)iwanttoeatchinesefoodlunchspend
i0.0020.3300.00360000.00079
want0.002200.660.00110.00650.00650.00540.0011
to0.0008300.00170.280.0008300.00250.087
eat000.002700.0210.00270.0560
chinese0.006300000.520.00630
food0.01400.01400.000920.003700
lunch0.005900000.002900
spend0.003600.003600000
BeRP bigram probabilities P(column | row), each count divided by its row word's unigram count (SLP3 figure 3.2)
All 64 cells rest dim; on activation only the bigrams BeRP actually saw light up, brightest for i to want, want to to, to to eat and chinese to food. Half the block stays dark.

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).

827 / 2533 = 0.33 and 82 / 158 = 0.52.

Recall

Why does row i of the count table not sum to 2533?

Only 8 of the 1446 possible next words are shown, and </s> is missing, so the row is a partial view of the full row, which does sum to C(i).

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:

P(⟨s⟩ i want english food ⟨/s⟩)=P(i∣⟨s⟩) P(want∣i)×P(english∣want)×P(food∣english)×P(⟨/s⟩∣food)\begin{aligned} &P(\langle s\rangle\ \text{i want english food}\ \langle/s\rangle) \\ &\quad = P(\text{i} \mid \langle s\rangle)\, P(\text{want} \mid \text{i}) \\ &\quad\quad \times P(\text{english} \mid \text{want}) \\ &\quad\quad \times P(\text{food} \mid \text{english}) \\ &\quad\quad \times P(\langle/s\rangle \mid \text{food}) \end{aligned}

Worked example

Scoring i want english food with BeRP bigrams

  1. 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.
  2. 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.
  3. Result

    P(<s> i want english food </s>) = 3.0855 x 10^-5, about 0.000031, the value SLP3 reports.
Each link's thickness is its bigram probability. want to english is a hairline at 0.0011, and the running product below drops by three orders of magnitude at that one link.

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?

Scaling up: log probabilities and longer n-grams

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):

p1×p2×p3×p4=exp⁡(log⁡p1+log⁡p2+log⁡p3+log⁡p4)\begin{aligned} &p_1 \times p_2 \times p_3 \times p_4 \\ &\quad = \exp(\log p_1 + \log p_2 \\ &\qquad + \log p_3 + \log p_4) \end{aligned}
Equation 3.13: add log probabilities while computing, and take exp only when you need to report a probability.

Worked example

The BeRP sentence in log space

  1. 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.
  2. 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.
  3. Convert back only at the end

    exp(-10.386) = 3.09 x 10^-5, the same 0.000031 as before.
  4. 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.
Top: on a linear 0 to 1 axis the product slides into 0 and becomes invisible. Bottom: on a natural log axis each factor is a visible step left, and the steps add to -10.386.
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))
Underflow and its log-space fix, runnable as is.

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>).

NContext wordsStart paddingFluencyPossible histories V^(N-1)Parameters V^N
2 (bigram)1<s>Local, often choppy1446about 2.09 x 10^6
3 (trigram)2<s> <s>Clearly better phrasesabout 2.09 x 10^6about 3.02 x 10^9
4 (4-gram)3<s> <s> <s>Very fluent where seenabout 3.02 x 10^9about 4.37 x 10^12
5 (5-gram)4<s> x 4Copies training text on small dataabout 4.37 x 10^12about 6.32 x 10^15
What each extra word of context costs, with V = 1446 as in BeRP

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.

-1.386 - 1.109 - 6.812 - 0.693 - 0.386 = -10.386, and exp(-10.386) is about 3.1 x 10^-5.

Recall

How many start symbols does a trigram model need, and why?

Two. The first word needs a two-word history, so it is scored as P(w_1 | <s> <s>), and the second word as P(w_2 | <s> w_1).

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