N-grams › N-gram Language Models
Language AI HandbookAll chapters N-grams › N-gram Language ModelsOne section · Sections in this chapter6 / 16 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
One of the most powerful applications of n-gram statistics is building language models, models that assign a probability to any sequence of words. A language model answers the question: how likely is this sentence? This capability underlies autocomplete, speech recognition, machine translation, and text generation.
The probability of a sequence of words can be written using the chain rule of probability:
where is the conditional probability of word given all preceding words. The problem with this exact formulation is that the history grows without bound as increases, making it impossible to estimate reliably from finite data.
The n-gram approximation solves this by applying the Markov assumption: the probability of the next word depends only on the last words, not the full history. For a bigram model (), the assumption is:
For a trigram model ():
In general, an n-gram model conditions on the preceding words:
The intuition is that most of the predictive signal about the next word comes from the immediately preceding few words. Knowing that the last two words were "machine learning" tells you a lot about what comes next. Knowing that the word 20 positions back was "the" tells you very little.
Maximum Likelihood Estimation
The parameters of an n-gram language model are the conditional probabilities . The simplest way to estimate these from training data is maximum likelihood estimation (MLE):
where:
- : the count of the full n-gram (the context words followed by ) in training data
- : the count of the context (the preceding words) alone
In plain language: the probability of word following a given context is the fraction of times that context in training data was followed by . If the bigram "machine learning" appears 100 times and the word "machine" appears 200 times total, then .
This estimate is intuitive and easy to compute. However, it assigns zero probability to any n-gram that did not appear in training data. Since any held-out text will contain many unseen n-grams, a pure MLE n-gram model assigns zero probability to real sentences, which makes it unusable for tasks like perplexity evaluation or speech recognition scoring.
Smoothing: Handling Unseen N-grams
Smoothing is the family of techniques used to assign non-zero probabilities to unseen n-grams by redistributing probability mass from seen n-grams to unseen ones. Every practical n-gram language model uses some form of smoothing.
Laplace (Add-one) Smoothing is the simplest approach. We add 1 to every n-gram count, including unseen ones:
where:
- : the observed bigram count
- : the observed unigram count for the context word
- : the vocabulary size (the number of word types)
The denominator increases by rather than by because we are adding 1 to the count of every possible next word, and there are of them. Think of it as: every word in the vocabulary is given one free observation following every context. This ensures no zero-probability outcomes.
The key insight is that adding to the denominator normalizes the distribution so that probabilities still sum to 1 over all possible next words. Without this correction, the probabilities would be inflated.
In practice, Laplace smoothing assigns far too much probability mass to unseen n-grams, which dilutes the probability of observed ones. More sophisticated methods like Kneser-Ney smoothing achieve much better results in practice by using a more careful accounting of how probability mass should be redistributed based on the diversity of contexts each word appears in. But Laplace smoothing is useful to understand because it captures the essential idea: every technique in the smoothing family is ultimately trying to solve the same problem of zero counts.
def build_bigram_lm(corpus_docs):
"""Build a bigram language model with Laplace smoothing."""
unigram_counts = Counter()
bigram_counts_lm = Counter()
for doc in corpus_docs:
toks = ["<s>"] + doc.lower().split() + ["</s>"]
for w in toks:
unigram_counts[w] += 1
for bigram in extract_ngrams(toks, 2):
bigram_counts_lm[bigram] += 1
vocab = set(unigram_counts.keys())
V = len(vocab)
def bigram_prob(word, context):
"""Laplace-smoothed bigram probability."""
bigram = (context, word)
numerator = bigram_counts_lm[bigram] + 1
denominator = unigram_counts[context] + V
return numerator / denominator
def sentence_log_prob(sentence):
"""Log probability of a sentence under the model."""
import math
toks = ["<s>"] + sentence.lower().split() + ["</s>"]
log_p = 0.0
for i in range(1, len(toks)):
p = bigram_prob(toks[i], toks[i - 1])
log_p += math.log(p)
return log_p
return bigram_prob, sentence_log_prob, vocab, V
bigram_prob, sentence_log_prob, lm_vocab, lm_V = build_bigram_lm(corpus)Bigram Language Model (Laplace smoothed):
Vocabulary size: 15
P(? | 'machine'):
P('learning' | 'machine') = 0.263158
P('translation' | 'machine') = 0.052632
P('is' | 'machine') = 0.052632
P(? | 'deep'):
P('learning' | 'deep') = 0.176471
P('sea' | 'deep') = 0.058824
P('blue' | 'deep') = 0.058824
P(? | 'i'):
P('love' | 'i') = 0.166667
P('hate' | 'i') = 0.055556
P('study' | 'i') = 0.111111
Sentence log-probabilities:
'I love machine learning': -8.534
'machine learning is powerful': -9.803
'deep learning is powerful': -10.203
'fish eat the sky purple': -16.536Notice how corpus-consistent sentences score much higher log-probability than nonsensical ones. The sentence "fish eat the sky purple" is grammatically unusual and uses words in combinations never seen in training, so even with Laplace smoothing its probability is much lower. This is exactly the behavior we want from a language model.