Perplexity › Cross-Entropy: The Foundation
Language AI HandbookAll chapters Perplexity › Cross-Entropy: The FoundationOne section · Sections in this chapter3 / 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
To measure how well a language model predicts text, we need a principled way to quantify "prediction quality." This is where information theory provides the perfect framework. The key insight is that prediction and compression are two sides of the same coin: if you can predict what comes next, you can compress it efficiently. Cross-entropy formalizes this connection, and perplexity translates it into an intuitive scale.
Let's build up to perplexity step by step, starting with the most fundamental question: how do we measure uncertainty?
Entropy: Quantifying Uncertainty
Imagine you're playing a word-guessing game. Your friend thinks of a word, and you have to guess it using only yes/no questions. How many questions do you need?
The answer depends entirely on how predictable the word is. If your friend always picks from {"cat", "dog"} with equal probability, you need exactly one question ("Is it cat?"). But if they pick from a thousand equally likely words, you need about 10 questions (since ). This number of questions is exactly what entropy measures: the minimum average number of yes/no questions needed to determine an outcome.
For a probability distribution over a vocabulary , entropy is:
where:
- : entropy of distribution , measured in bits when using
- : probability of word
- The sum runs over all words in the vocabulary
The formula might look abstract, but it captures a clear intuition. The term is called the surprisal or information content of word . It measures how many bits of information you gain by learning that occurred:
- A very likely word (say ) has surprisal bit
- An unlikely word (say ) has surprisal bits
- A certain word () has surprisal bits (no surprise at all)
Entropy is the expected surprisal: we weight each word's surprisal by how often it occurs, , and sum up. Rare words contribute more bits because they're more surprising when they occur, while common words contribute fewer because they're expected. Entropy captures the average information content across all outcomes.
The connection to coding theory is tight. If you want to design the most efficient binary code for transmitting words from this distribution, the optimal code assigns bits to word . Frequently occurring words get short codes (few bits), rare words get long codes (many bits). The entropy is the minimum average number of bits needed by any such scheme, a theoretical limit known as Shannon's source coding theorem. A language model that assigns better probabilities to common words and lower probabilities to rare ones is essentially learning a better compression scheme for language.
Let's see this in action with a simple two-word vocabulary:
import numpy as np
def entropy(probs):
"""Calculate entropy of a probability distribution."""
# Filter out zero probabilities to avoid log(0)
probs = np.array([p for p in probs if p > 0])
return -np.sum(probs * np.log2(probs))
# Different probability distributions over two words
distributions = [
([0.5, 0.5], "Equal probability"),
([0.9, 0.1], "Skewed (90/10)"),
([0.99, 0.01], "Very skewed (99/1)"),
([1.0, 0.0], "Certain"),
]Entropy for different distributions over {cat, dog}:
--------------------------------------------------
Equal probability H = 1.0000 bits
Skewed (90/10) H = 0.4690 bits
Very skewed (99/1) H = 0.0808 bits
Certain H = -0.0000 bitsNotice the pattern: when both words are equally likely, entropy is maximal at 1 bit. You need one yes/no question. As one word dominates, entropy drops because the outcome becomes more predictable. When the outcome is certain, entropy is zero: no questions needed, no uncertainty remains.

This gives us our first key insight: entropy measures the inherent unpredictability of a distribution. A good language model should have low entropy on real text because it can accurately predict what comes next. When a language model says "after 'the sky is', the next word is very likely 'blue'," it's operating in a low-entropy regime for that context, with very high certainty about the next word. When it encounters an ambiguous context where almost any continuation is plausible, it's in a high-entropy regime.
Cross-Entropy: Measuring Model Mismatch
Entropy tells us about the true distribution of language, but we don't have direct access to that. We only have our model's predictions. This is where cross-entropy enters the picture.
Cross-entropy asks: "If the true distribution is , but we use model to make predictions, how many bits do we need on average?" The answer is always at least as many as entropy itself, and usually more, because our model isn't perfect. We're encoding outcomes using a code designed for distribution , but the outcomes come from distribution .
where:
- : cross-entropy of relative to , measured in bits
- : true probability of word , from the actual data distribution
- : model's predicted probability of word
- : the vocabulary, the set of all possible words
- The sum computes the expected number of bits, weighting each word's encoding cost by its true frequency
The key relationship is: . Cross-entropy is always at least as large as entropy. This inequality holds because using the "wrong" distribution for encoding is never better than using the true distribution . The gap between them, called KL divergence, measures exactly how much our model's predictions differ from reality.
Think of it this way:
- Entropy : the minimum bits needed with a perfect model
- Cross-entropy : the bits needed with our actual model
- KL divergence : the "cost" of using an imperfect model
The KL divergence is sometimes called the "inefficiency" of the model. It represents the wasted bits that come from using instead of to design your encoding scheme. A model that perfectly captures the true distribution of language would have zero KL divergence and achieve the theoretical entropy minimum.
| Model Quality | Entropy H(P) | KL Divergence | Cross-Entropy |
|---|---|---|---|
| Perfect Model | 2.5 bits | 0.0 bits | 2.5 bits |
| Good Model | 2.5 bits | 0.5 bits | 3.0 bits |
| Average Model | 2.5 bits | 1.5 bits | 4.0 bits |
| Poor Model | 2.5 bits | 3.0 bits | 5.5 bits |
| Random Guessing | 2.5 bits | 5.0 bits | 7.5 bits |
In practice, we don't know the true distribution . Instead, we approximate it using the empirical distribution from our test data. If word appears times in a test corpus of words, we estimate:
where:
- : empirical probability of word (the "hat" notation indicates an estimate, not the true value)
- : count of how many times word appears in the test corpus
- : total number of words in the test corpus
When we substitute this empirical distribution into the cross-entropy formula, something elegant happens. Since each word in the corpus contributes equally to the empirical distribution (each occurrence contributes to the probability mass), the weighted sum simplifies to a plain average:
where:
- : cross-entropy between the empirical distribution and model
- : total number of words in the test corpus
- : the -th word in the test corpus
- : the probability our model assigns to word
In words: cross-entropy is the average negative log probability that our model assigns to each word in the test corpus. Lower is better because it means the model assigned higher probabilities to the words that appeared. A model that assigns probability 0.1 to every word contributes bits per word. A model that assigns probability 0.5 to every word contributes just 1 bit. The better the model, the closer its assigned probabilities are to 1.0 for words that occur, and the lower the cross-entropy.
Let's compute this for a simple unigram model:
def cross_entropy(test_tokens, model_probs):
"""
Calculate cross-entropy of a model on test data.
Args:
test_tokens: List of tokens from test corpus
model_probs: Function that returns P(token | context)
"""
log_prob_sum = 0.0
count = 0
for i, token in enumerate(test_tokens):
prob = model_probs(token, i, test_tokens)
if prob > 0:
log_prob_sum += np.log2(prob)
count += 1
else:
# Handle zero probability (model assigns impossible)
return float("inf")
return -log_prob_sum / count
# Simple unigram model for demonstration
train_corpus = """
the cat sat on the mat
the dog sat on the rug
the cat chased the dog
the dog chased the cat
""".lower().split()
from collections import Counter
word_counts = Counter(train_corpus)
total_words = len(train_corpus)
def unigram_prob(token, position, tokens):
"""Return unigram probability P(token)."""
count = word_counts.get(token, 0)
# Add-1 smoothing to handle unseen words
vocab_size = len(word_counts) + 1 # +1 for unknown
return (count + 1) / (total_words + vocab_size)
# Test on held-out data
test_corpus = "the cat sat on the rug".split()Test corpus: the cat sat on the rug Cross-entropy: 2.8692 bits per word
A cross-entropy around 2.7 bits per word indicates moderate predictability. For comparison, a uniform distribution over our vocabulary of about 8 unique words would give bits. Our unigram model does slightly better because it learned that some words (like "the") are more common than others.
The cross-entropy tells us how many bits on average our model needs to encode each word. This number has a concrete interpretation: if we used our model's probabilities to design a compression scheme, each word would require about this many bits on average. The better the model, the fewer bits needed, and the better the compression.