Perplexity › From Cross-Entropy to Perplexity
Language AI HandbookAll chapters Perplexity › From Cross-Entropy to PerplexityOne section · Sections in this chapter4 / 17 ↓
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
Cross-entropy is useful, but "2.7 bits per word" doesn't immediately convey how good a model is in intuitive terms. Is that good? Bad? It depends on the vocabulary size and the inherent predictability of the text. Comparing cross-entropy values across different models or tasks requires converting the number back into something more interpretable.
Perplexity solves this by converting bits back to a count, specifically the number of equally likely choices the model faces at each step. The transformation is simple:
where:
- : perplexity of the model on word sequence
- : cross-entropy between the true distribution and model distribution
Why raise 2 to the power of cross-entropy? Because entropy measures bits, and each bit represents a binary choice that doubles the number of possibilities. A cross-entropy of 3 bits means equally likely choices; 10 bits means choices. By exponentiating, we convert from the abstract "bits" unit back to the concrete "number of alternatives" unit.
Expanding this using our practical cross-entropy formula gives us the working definition of perplexity:
where:
- : total number of words in the test sequence
- : the -th word in the sequence
- : model's probability for word , given its context
Using properties of logarithms, we can derive an equivalent product form that makes the geometric mean interpretation explicit. The derivation proceeds as follows:
Step 1: Start with the exponential form:
Step 2: Move the inside using :
Step 3: Apply (the sum of logs equals the log of a product):
This shows that perplexity is the geometric mean of the inverse probabilities. Equivalently:
where denotes the -th root.
The geometric mean matters here for two important reasons. First, unlike the arithmetic mean, the geometric mean is sensitive to very small probabilities. A single word with near-zero probability will dramatically increase perplexity. If any , then , making . This is exactly what we want: a good language model should not assign tiny probabilities to words that occur in real text. Second, the geometric mean makes perplexity scale-independent in a useful way: what matters is the relative confidence of predictions, not the absolute probability values.
The perplexity of a language model on a test set is the inverse probability of the test set, normalized by the number of words. It can be interpreted as the weighted average number of choices the model faces at each step. Lower perplexity means the model is less "perplexed" by the test data, assigning higher probability to what occurs.
For language models that condition on context, like n-gram models, we use conditional probabilities. The formula above assumed each word probability was independent, but real language models predict each word based on its history. The proper perplexity formula for such models is:
where:
- : the test sequence of words
- : the probability of word given all preceding words, the model's conditional prediction
- : the -th root (geometric mean)
In practice, we work with log probabilities to avoid numerical underflow. When multiplying many small probabilities (each less than 1), the product quickly approaches zero, causing floating-point arithmetic to lose precision entirely. By working in log space, we convert products to sums:
This is numerically stable because we're adding log probabilities (negative numbers typically in the range -3 to -15 for typical words) rather than multiplying tiny probabilities (numbers like to that quickly underflow to zero).
def perplexity(test_tokens, model_log_prob):
"""
Calculate perplexity of a model on test data.
Args:
test_tokens: List of tokens from test corpus
model_log_prob: Function returning log2 P(token | context)
"""
log_prob_sum = 0.0
count = 0
for i, token in enumerate(test_tokens):
log_p = model_log_prob(token, i, test_tokens)
if log_p == float("-inf"):
return float("inf") # Zero probability encountered
log_prob_sum += log_p
count += 1
cross_ent = -log_prob_sum / count
return 2**cross_ent
def unigram_log_prob(token, position, tokens):
"""Return log2 P(token) for unigram model with smoothing."""
count = word_counts.get(token, 0)
vocab_size = len(word_counts) + 1
prob = (count + 1) / (total_words + vocab_size)
return np.log2(prob)Test corpus: the cat sat on the rug Perplexity: 7.31 Effective branching factor: ~7 choices per word
Now the interpretation is immediate: a perplexity around 6-7 means the model faces roughly the same uncertainty as choosing uniformly among 6-7 equally likely words at each position. For a simple unigram model on a small corpus, this is reasonable. The model has learned that some words (like "the") are much more common than others, so it's not completely in the dark about what comes next.