N-gram language models
Derive the Markov n-gram approximation, build an MLE bigram table by counting, and compute sentence probabilities.
A language model assigns a probability to every sequence of tokens. That probability is the central object of NLP: generate text by sampling from it, score a candidate translation by evaluating it, measure how surprised a model is by a new document with it. Before neural networks, the canonical parameterization was the n-gram model[1, 2]: simple, interpretable, and fast to build, but with fundamental structural limits that motivate every architecture that follows.
The autoregressive factorization
The chain rule of probability factors any joint distribution over a sequence into a product of conditionals:
This factorization is exact, no approximation so far. The challenge is representing , which in principle conditions on the entire prefix .
The Markov approximation
An n-gram model truncates the conditioning context to the last tokens:
This is the th-order Markov assumption. The special cases:
- (unigram): , no context, just a bag-of-words distribution.
- (bigram): , conditions on one previous token.
- (trigram): , two-token context.
Maximum-likelihood estimation
Given a corpus , let count how often the sequence appears. The MLE bigram probability is:
Training is just counting. There are no parameters to learn by gradient descent.
To handle sentence boundaries, prepend and append to each sentence. This lets the model assign probability to "the sentence ends here" and ensures the joint distribution over sequences of any length normalizes to 1.
A worked bigram
Toy corpus (three sentences):
Selected bigram counts:
| pair | count |
|---|---|
| 2 | |
| 1 | |
| 2 | |
| 1 | |
| 1 |
Bigram MLE table (partial):
Probability of "Sam I am":
The three structural limits
1. Sparsity. Most n-grams never appear, even in billion-token corpora. The MLE assigns them probability zero, so any test sentence containing an unseen bigram gets probability zero and infinite cross-entropy loss.
2. Parameter explosion. For vocabulary size and order , the table has entries. At and : entries, impossible to store or estimate.
3. Fixed range. The Markov assumption guarantees that any dependency spanning more than tokens is invisible. The model can never resolve long-distance coreference or syntactic agreement.
These limits are the structural motivation for neural language models.
Perplexity
Perplexity is the standard metric for comparing language models on held-out text:
It equals the exponentiated average per-token cross-entropy loss. Lower is better: a perfect model (assigns probability 1 to every correct next token) has . Smoothed trigrams on news text achieve ; modern LLMs achieve .
import numpy as np
from collections import Counter, defaultdict
corpus = [
["<bos>", "I", "am", "Sam", "<eos>"],
["<bos>", "Sam", "I", "am", "<eos>"],
["<bos>", "I", "do", "not", "like", "green", "eggs", "<eos>"],
]
# Count unigrams and bigrams
unigrams = Counter()
bigrams = Counter()
for sent in corpus:
for w in sent:
unigrams[w] += 1
for a, b in zip(sent, sent[1:]):
bigrams[(a, b)] += 1
def bigram_prob(a, b):
return bigrams[(a, b)] / unigrams[a] if unigrams[a] > 0 else 0.0
# Score a test sentence
test = ["<bos>", "Sam", "I", "am", "<eos>"]
log_prob = sum(
np.log(bigram_prob(a, b) + 1e-30)
for a, b in zip(test, test[1:])
)
n_tokens = len(test) - 1 # predict all but <bos>
ppl = np.exp(-log_prob / n_tokens)
print(f"log p = {log_prob:.3f}, perplexity = {ppl:.2f}")The zero-probability problem is immediate: any unseen bigram sends log probability to .
Exercises
Exercise 1. Using the toy corpus with and markers, compute the bigram MLE probability of the sentence "I am Sam" by hand.
Solution
Step 1. Collect the needed counts. The contexts that matter: (three sentences), (I appears three times, heading the bigrams I-am, I-am, I-do), , .
Step 2. Read off the four bigram probabilities for the path :
The bigram occurs once out of two am tokens; Sam ends one sentence ("I am Sam") out of its two occurrences.
Step 3. Multiply:
Exercise 2. A bigram model with vocabulary size has at most free parameters. Show that the bigram rows are not all independent: how many genuinely free numbers does the model carry, given that each row must sum to 1?
Solution
Step 1. There are possible context tokens , so conditional rows, each a distribution over the next tokens. Naively that is numbers.
Step 2. Each row is a probability distribution, so its entries are constrained by . That one linear constraint removes one degree of freedom per row: a row over outcomes has only free numbers.
Step 3. With rows each carrying free numbers, the model has
free parameters. The count is still , which is the parameter-explosion limit: the normalization constraint shaves off only a lower-order term.
Exercise 3 (code). Build a bigram model from the toy corpus and verify that every context row sums to 1 (a valid conditional distribution), and that .
Solution
import numpy as np
from collections import Counter, defaultdict
corpus = [
["<bos>", "I", "am", "Sam", "<eos>"],
["<bos>", "Sam", "I", "am", "<eos>"],
["<bos>", "I", "do", "not", "like", "green", "eggs", "<eos>"],
]
unigrams = Counter(w for sent in corpus for w in sent)
bigrams = Counter((a, b) for sent in corpus for a, b in zip(sent, sent[1:]))
# Group next-token probabilities by context
rows = defaultdict(dict)
for (a, b), c in bigrams.items():
rows[a][b] = c / unigrams[a]
# Every context that starts a bigram must define a proper distribution.
# <eos> never starts a bigram, so it has no row.
for a, dist in rows.items():
total = sum(dist.values())
assert np.isclose(total, 1.0), (a, total)
assert np.isclose(rows["I"]["am"], 2 / 3)
print("rows valid; p(am | I) =", round(rows["I"]["am"], 4))Each context row normalizes to 1 because its bigram counts sum to the context's count as a left token, and "am" follows "I" in two of the three I-contexts.
The next lesson introduces smoothing to fix the zero-probability problem.
References
- [1]Jurafsky, D., & Martin, J. H. (2024). Speech and Language Processing (3rd ed. draft). N-gram language models, the Markov assumption, and perplexity.↩
- [2]Manning, C. D., & Schütze, H. (1999). Foundations of Statistical Natural Language Processing. MIT Press. Statistical n-gram estimation and evaluation.↩