N-gram Language Models › Computing Sequence Probabilities
Language AI HandbookAll chapters N-gram Language Models › Computing Sequence ProbabilitiesOne section · Sections in this chapter6 / 15 ↓
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
With n-gram probabilities estimated, we can compute the probability of any sequence. We combine the chain rule decomposition with the Markov approximation to get:
where:
- : the total number of words in the sequence
- : the n-gram order (2 for bigrams, 3 for trigrams)
- : the -th word in the sequence
- : the context of words immediately preceding
- : multiply all conditional probabilities together
For a bigram model (), this simplifies to:
where:
- : the probability of the first word given the start-of-sentence marker
- : the probability of each subsequent word given only the single preceding word
Log Probabilities and Numerical Stability
Multiplying many small probabilities leads to numerical underflow: numbers become too small to represent in floating-point arithmetic. A sentence of 300 words, each with probability 0.1, gives , close to the minimum representable double-precision float near . Longer sentences or lower-probability words make this worse. Taking logarithms converts products to sums, keeping values in a manageable range.
Instead of computing a product of probabilities:
we compute a sum of log probabilities:
where:
- : the natural logarithm function (base )
- : sum over all words in the sequence
- : the log probability of each bigram conditional, always a negative number since
This transformation works because of the logarithm's fundamental property: . While individual probabilities might be 0.01 or smaller, their log values are manageable negative numbers (for example, ). To recover the original probability if needed, compute .
import math
def sequence_probability(sentence, bigram_counts, unigram_counts, log=True):
"""Calculate the probability of a sentence using bigram model."""
toks = ["<s>"] + sentence.lower().split() + ["</s>"]
if log:
log_prob = 0.0
for i in range(1, len(toks)):
w1, w2 = toks[i - 1], toks[i]
prob = bigram_probability(w1, w2, bigram_counts, unigram_counts)
if prob == 0:
return float("-inf")
log_prob += math.log(prob)
return log_prob
else:
prob = 1.0
for i in range(1, len(toks)):
w1, w2 = toks[i - 1], toks[i]
prob *= bigram_probability(w1, w2, bigram_counts, unigram_counts)
return prob
# Test sentences
test_sentences = [
"the cat sat on the mat",
"the dog sat on the mat",
"the cat chased the dog",
"the fish ate the cat",
]
results = [
(
s,
sequence_probability(
s, bigram_counts_marked, unigram_counts_marked, log=True
),
)
for s in test_sentences
]Sentence Log-Probabilities (bigram model): ------------------------------------------------------------ log P = -3.47 "the dog sat on the mat" log P = -3.47 "the cat sat on the mat" log P = -inf "the cat chased the dog" log P = -inf "the fish ate the cat"


The visualization confirms why log probabilities are essential. With an average per-word probability of 0.1, raw probabilities approach the limits of floating-point representation at around 300 words. Log probabilities decrease linearly and remain tractable for sequences of any length.
The Zero Probability Problem
What happens when we encounter an n-gram that never appeared in training?
# Test with a sentence containing unseen bigrams
unseen_sentence = "the bird flew over the house"
unseen_tokens = ["<s>"] + unseen_sentence.lower().split() + ["</s>"]
# Check each bigram
bigram_analysis = []
for i in range(1, len(unseen_tokens)):
w1, w2 = unseen_tokens[i - 1], unseen_tokens[i]
bigram = (w1, w2)
count = bigram_counts_marked.get(bigram, 0)
prob = bigram_probability(
w1, w2, bigram_counts_marked, unigram_counts_marked
)
bigram_analysis.append((w1, w2, count, prob))Analyzing: "the bird flew over the house" ------------------------------------------------------- (<s>, the): count=4, P=1.000 [OK] (the, bird): count=0, P=0.000 [UNSEEN] (bird, flew): count=0, P=0.000 [UNSEEN] (flew, over): count=0, P=0.000 [UNSEEN] (over, the): count=0, P=0.000 [UNSEEN] (the, house): count=0, P=0.000 [UNSEEN] (house, </s>): count=0, P=0.000 [UNSEEN] Total probability: 0.0
A single unseen bigram makes the entire sequence probability zero. This is the zero probability problem: perfectly valid sentences receive zero probability just because our training data did not contain every possible word combination. The sentence "the bird flew over the house" is entirely plausible English, but our tiny corpus knows nothing about birds or flying. The MLE estimate assigns probability zero to any n-gram not seen in training, and since sequence probability is a product of these terms, any zero drives the whole product to zero.
The deeper issue is that zero probability is semantically wrong, in addition to being numerically inconvenient. A language model that assigns zero probability to valid English sentences fundamentally misrepresents the distribution of language. A well-calibrated language model should assign some small but non-zero probability to any grammatical sequence. This brittleness motivates smoothing techniques, covered in the next chapter. Raw MLE estimates are too brittle for real applications since any test sentence containing an unseen n-gram collapses to zero probability regardless of its plausibility.