DL S005 · Probability and Information — Surprise, Entropy, Cross-Entropy
Why cross-entropy IS log-likelihood, why we use it everywhere in ML, and how to compute H(p, q) by hand. Part of the 'Deep Learning & LLMs From Scratch' 80-session self-study series.
🎯 Compute cross-entropy H(p, q) by hand for a 3-class example, show why minimising it is the same as maximising log-likelihood, and derive the softmax + cross-entropy gradient in three lines.
Series: Deep Learning & LLMs From Scratch — 80 sessions · Session 5 / 80 · Module M01 · ~2 hours
The story
Here's the puzzle: every time a modern neural network is trained on any classification task — MNIST, ImageNet, GPT pre-training, LLaMA fine-tuning, image-to-text, text-to-image — the loss function is called "cross-entropy". Same words. Same three lines of code. Why? What's so special about this particular loss?
The short answer: cross-entropy is not really a loss function at all. It is the negative log-likelihood of your data under your model. Minimising it means maximising the probability your model assigns to what actually happened. That framing — "make what happened more likely under your model" — is the philosophical bedrock of the whole field. Every language model, every classifier, every diffusion sampler, every RLHF reward model, all of it, is playing exactly this game.
To get there we need three ideas, in order: (1) surprise — how astonished should you be to see event x? (2) entropy — the average surprise of a distribution; the "how random is this really" number. (3) cross-entropy — the average surprise of the true distribution measured with your model's beliefs. That third one is the loss. When your model is perfect, cross-entropy equals entropy (a floor you can't get below). When it's wrong, cross-entropy is higher — and the gap is called the KL divergence, which measures how wrong you are.
None of this requires more than high-school algebra. It does require slowing down. Read every equation twice.
- Define surprise `I(x) = -log p(x)` and explain intuitively why log is the right function.
- Compute entropy H(p) for a small discrete distribution by hand.
- Compute cross-entropy H(p, q) between two distributions and confirm H(p, q) ≥ H(p).
- State the KL divergence formula and show that KL(p || q) ≥ 0, with equality iff p = q.
- Prove that cross-entropy loss = negative log-likelihood on one-hot labels.
- Derive that the gradient of softmax + cross-entropy is just (softmax_probs - one_hot_target).
Prerequisites
- Session 004 — you'll need the chain rule to derive the softmax gradient in §7.
- Comfort with
logandexp. That's it.
We do NOT assume prior probability theory. We rebuild it from four rules.
1 · Probability in four rules
To do everything in this session, we need four facts about probabilities.
- A probability is a real number in [0, 1]. p(x) = 0 means 'never'; p(x) = 1 means 'always'.
- The probabilities of all mutually exclusive outcomes sum to 1: Σ p(x) = 1.
- The probability of independent events happening together multiplies: p(A and B) = p(A) * p(B).
- Conditional: p(A | B) = p(A, B) / p(B). Everything else in Bayesian reasoning is algebra on this.
That's it. If you can hold those four facts, you have all the probability we need for this session.
1.1 The tiny worked example we'll use all session
A biased 3-sided die (yes, imagine a triangular pyramid) with outcomes \{cat, dog, fish\}:
p(cat) = 0.7
p(dog) = 0.2
p(fish) = 0.1
--------------
Σ = 1.0 ✅Simple. We'll compute entropy of this, then design a "model" q with different probabilities, and see the cross-entropy story unfold.
2 · Surprise — how astonished should I be?
Suppose I tell you the die came up fish. You should be more surprised than if I said cat, because fish is less likely. How much more?
Shannon proposed we quantify surprise as:
I(x) = -log p(x) (in bits if log₂; in nats if ln)Why -log?
- If
p(x) = 1(certain), thenI = 0. No surprise. ✅ - If
p(x) → 0(nearly impossible), thenI → ∞. Infinite surprise. ✅ - If two independent events both happen, surprise ADDS (because
-log(p * q) = -log p + -log q). ✅ (Independent events have independent surprises — this is exactly the property we want.) - It's the ONLY function (up to units) with all three properties. Shannon proved it.
Units. If you use log₂, surprise is in bits. If you use ln, it's in nats. In ML we use ln (base-e) because exp is smooth and easier to differentiate. Either is fine — it's a constant multiplier.
2.1 Compute the surprise for each outcome
Using ln:
I(cat) = -ln(0.7) ≈ 0.357 nats
I(dog) = -ln(0.2) ≈ 1.609 nats
I(fish) = -ln(0.1) ≈ 2.303 natsfish is much more surprising than cat. Feels right.
import numpy as np
p = np.array([0.7, 0.2, 0.1])
print(-np.log(p))
# [0.35667494 1.60943791 2.30258509]3 · Entropy — the average surprise of a distribution
Entropy H(p) is the expected surprise you feel when sampling from p:
H(p) = Σ p(x) * (-log p(x))
x
= -Σ p(x) log p(x)It's the average number of "surprise units" per sample. Equivalently — and this is Shannon's magic — it's the minimum average code length needed to transmit samples from p losslessly. High-entropy distributions are hard to compress; low-entropy ones easy.
3.1 Compute H(p) for our die
H(p) = 0.7 * 0.357 + 0.2 * 1.609 + 0.1 * 2.303
= 0.250 + 0.322 + 0.230
≈ 0.802 natsH = -(p * np.log(p)).sum()
print(H) # 0.80181855254333723.2 Two useful extreme cases
Maximum entropy: uniform distribution. For n outcomes, H = ln n. For our 3-class die, max entropy would be ln 3 ≈ 1.099 — happening when p = [1/3, 1/3, 1/3]. Uniform means "least informative"; there's no structure to compress.
Minimum entropy: certainty. If p = [1, 0, 0], entropy is 0. There's no surprise at all — you already know what will happen.
Our biased die sits in between (0.802 nats), closer to certainty than to uniform, which matches the intuition that cat is likely.
3.3 Entropy as "how much randomness"
Entropy is a scalar-valued randomness meter. A fair coin: H = ln 2 ≈ 0.693 nats. A weighted coin favouring heads 90%: H ≈ 0.325 nats. A cheating coin that always lands heads: H = 0. Entropy is the answer to "how much randomness is there really?" — a question that seems fuzzy until you write the formula down.
4 · Cross-entropy — the average surprise UNDER YOUR MODEL
Here's the setup. There's a true distribution p (nature). You (the ML engineer) have proposed a model distribution q — your best guess at p. Now nature draws samples from p. You react to each sample with your model's surprise level.
Cross-entropy is the average surprise, according to q, of samples drawn from p:
H(p, q) = -Σ p(x) log q(x)Note the asymmetry. The outer weighting is p (because that's what nature actually samples), but the log is log q (because that's what YOU believe about how surprising the outcome is).
4.1 Worked example
Say your model q is:
q(cat) = 0.5
q(dog) = 0.3
q(fish) = 0.2Compute:
H(p, q) = -[ 0.7 * ln(0.5) + 0.2 * ln(0.3) + 0.1 * ln(0.2) ]
= -[ 0.7 * (-0.693) + 0.2 * (-1.204) + 0.1 * (-1.609) ]
= -[ -0.485 - 0.241 - 0.161 ]
= 0.887 natsq = np.array([0.5, 0.3, 0.2])
Hpq = -(p * np.log(q)).sum()
print(Hpq) # 0.88736Compare to H(p) = 0.802. The cross-entropy is larger than the entropy. This is not an accident — it is a theorem.
4.2 The theorem — cross-entropy is always at least entropy
Gibbs' inequality: For any two distributions p and q over the same outcomes,
H(p, q) ≥ H(p)with equality iff p = q. Proof sketch: use ln(x) ≤ x - 1 on the ratio q/p. Do it once on paper; not needed daily.
Interpretation: Your model q costs you extra surprise, on average, compared to knowing the true p. That extra cost is a distance measure.
4.3 The gap has a name — KL divergence
KL(p || q) = H(p, q) - H(p) = Σ p(x) log(p(x) / q(x))KL(p || q) ≥ 0, and = 0 iff p = q. It's "how many extra nats of surprise you pay per sample by using your model q instead of the true p."
For our example:
KL(p || q) = 0.887 - 0.802 = 0.085 natsKL is asymmetric. KL(p||q) ≠ KL(q||p) in general. This matters: minimising KL(p||q) (forward KL) pushes q to cover all of p's support; minimising KL(q||p) (reverse KL) makes q collapse onto p's modes. Session 049 (variational autoencoders) and Session 058 (RLHF) both hinge on which direction of KL you pick.
5 · Cross-entropy = negative log-likelihood
This is the punchline. It is why cross-entropy is used everywhere in ML.
Setup. You have a dataset of N samples x₁, ..., x_N, drawn i.i.d. from the true (unknown) distribution p. Your model gives probabilities q_θ(x) parameterised by weights θ. You want to pick θ to make the data likely.
The likelihood of the whole dataset under your model:
L(θ) = Π q_θ(x_i)
iWe take the log (turns product into sum, keeps the argmax) and negate (turns "maximise" into "minimise" for gradient descent):
NLL(θ) = -Σ log q_θ(x_i)
iDivide by N (empirical average):
average_NLL = -(1/N) Σ log q_θ(x_i)Now here's the crucial observation. The empirical distribution p̂ puts mass 1/N on each observed data point. So:
average_NLL = -Σ p̂(x) log q_θ(x) = H(p̂, q_θ)
xCross-entropy of the empirical data distribution against your model IS the average negative log-likelihood. Minimising cross-entropy loss is literally maximum likelihood estimation. This is why we do it.
5.1 For one-hot classification labels
In classification, each example has a single true class y ∈ {1, ..., K}. That means p̂ for one example is [0, ..., 0, 1, 0, ..., 0] with the 1 in position y. Then:
H(p̂, q) = -Σ p̂_k log q_k = -log q_yCross-entropy loss on one example = -log(predicted probability of the correct class). That's it. That's the whole formula. When your model gives probability 1 to the correct class, loss = 0. When it gives probability 0, loss = ∞ (you were infinitely surprised). Beautiful, extreme, exactly what you want.
6 · Softmax — turning logits into probabilities
Networks output arbitrary real numbers ("logits"), not probabilities. We convert with the softmax:
softmax(z)_k = exp(z_k) / Σ_j exp(z_j)Properties:
- Output is nonnegative (because
exp > 0). - Sums to 1 (denominator is exactly the sum of numerators).
- Preserves order (bigger
z_k→ biggersoftmax_k). - Shift-invariant:
softmax(z) = softmax(z + c)for any constantc. Useful for numerical stability.
6.1 Worked example
Logits z = [2.0, 1.0, 0.1]:
exp(z) = [7.389, 2.718, 1.105]
sum = 11.212
softmax = [0.659, 0.242, 0.098]z = np.array([2.0, 1.0, 0.1])
p = np.exp(z) / np.exp(z).sum()
print(p) # [0.65900114 0.24243297 0.09856589]6.2 The log-sum-exp trick (why your loss doesn't overflow)
If z_k = 1000, exp(z_k) overflows to inf. Since softmax is shift-invariant, we subtract max(z) before exponentiating:
def softmax_stable(z):
z = z - z.max()
e = np.exp(z)
return e / e.sum()Every DL framework does this internally. If you write a custom softmax in a kernel and skip the shift, you will get NaNs at scale. It's the #1 cause of "loss suddenly became NaN at epoch 3" — Session 006 covers this in detail.
Take three logits, e.g. z = np.array([2.0, 1.0, 0.1]), and a true class y = 0. Implement two things: (1) the analytic gradient using s = softmax_stable(z); s[y] -= 1, and (2) a numeric gradient by perturbing each component of z with h = 1e-6 and re-evaluating the loss -log(softmax(z)[y]). Print both. They should agree to about eight decimal places. Then change y to 2 (the least-likely class) and re-run — watch which component of the gradient gets the big negative value, and why.
7 · The softmax + cross-entropy gradient — the punchline
Combine everything: given logits z and true class y, the loss is:
L = -log(softmax(z)_y) = -z_y + log Σ_j exp(z_j)We want ∂L/∂z_k. It's shorter than you'd think.
Let s_k = softmax(z)_k. Then:
∂L/∂z_k = s_k - 1[k == y]The gradient is just predicted_probs - one_hot_target. Three lines of code:
def softmax_ce_grad(z, y):
s = softmax_stable(z)
s[y] -= 1.0
return sThat's the entire gradient of softmax + cross-entropy. All the logs, all the exps, they cancel. It's the cleanest gradient in deep learning, and it's why softmax + cross-entropy is the default classification head everywhere.
7.1 Derivation (do this once on paper)
Do that derivation once. Ever. Then use the three-line implementation forever. This is the gradient every classification network uses on the last layer.
7.2 Numeric check
z = np.array([2.0, 1.0, 0.1])
y = 0
def L(z_):
s = softmax_stable(z_)
return -np.log(s[y])
# analytic
g_analytic = softmax_ce_grad(z, y)
# numeric
h = 1e-6
g_num = np.zeros_like(z)
for k in range(len(z)):
z_p = z.copy(); z_p[k] += h
z_m = z.copy(); z_m[k] -= h
g_num[k] = (L(z_p) - L(z_m)) / (2*h)
print("analytic:", g_analytic)
print("numeric :", g_num)
# analytic: [-0.34099886 0.24243297 0.09856589]
# numeric : [-0.34099886 0.24243296 0.09856589] ✅Match to 8 decimal places. Beautiful.
8 · Where this all shows up in the series
Every future session with a loss function uses this machinery. A partial map:
- S011 (MNIST in numpy) — you'll implement
softmax_ce_gradin exactly the three-line form above. - S014 (PyTorch autograd) —
F.cross_entropy(logits, targets)does all of this in one call, including the log-sum-exp trick. - S041 (training your GPT on Tiny Shakespeare) — the LM loss IS cross-entropy over vocabulary logits at every token position. Millions of softmaxes per batch.
- S051 (SFT) — same cross-entropy, on instruction data.
- S058 (RLHF) — KL divergence (§4.3) appears as a penalty keeping the policy close to a reference model. Same math.
The point: you learn probability once. You use it forever.
8b · Cross-entropy in the wild — next-token prediction and 2024–2025 alignment
Everything you just learned about cross-entropy is the loss that trains every language model on Earth. Let's connect the dots — and then meet the four 2023–2025 successors that reshape the objective without leaving log-probability land.
8b.1 · The one loss every LLM is trained on
A language model, unrolled, computes:
where is the logit vector over a vocabulary of size to . The training loss for a batch of sequences is literally the average cross-entropy:
logits = model(input_ids) # (B, T, V)
targets = input_ids[:, 1:] # shift by one
loss = F.cross_entropy(
logits[:, :-1].reshape(-1, V),
targets.reshape(-1),
)That's it. Llama 3.1 405B, GPT-4, Gemini 2, Claude 3.5, DeepSeek-V3 — they were all pretrained by minimizing exactly this expression over trillions of tokens. When you see "loss = 1.8 nats per token" in a training log, that is averaged — the number you learned to compute today, at scale.
Per-token perplexity is where is the mean cross-entropy in nats. A model at loss 1.8 nats has perplexity : on average, when it's predicting a token, it's about as uncertain as if it were picking uniformly from 6 candidates. Chinchilla-optimal 70B checkpoints hit ~1.9 nats on English. GPT-4 is estimated (from public leaks/eval) around ~1.5. Every 0.1 nat drop is a headline result.
8b.2 · DeepSeek-V3's multi-token prediction (Dec 2024)
DeepSeek-V3 added a small auxiliary head that predicts two tokens ahead in addition to the standard next-token cross-entropy. The training loss becomes:
Both are cross-entropies. The auxiliary loss pushes the model to encode information about not just the next token but the one after — which at inference time enables speculative decoding without a separate draft model. Every serious 2025 pretraining recipe is looking at this.
8b.3 · KL, DPO, KTO, ORPO, SimPO — the 2023–2025 preference tuning line
RLHF used to be a hairy PPO loop with a separate reward model. Then DPO (Rafailov et al., 2023) showed the entire objective can be rewritten as a simple classification loss over pairs of (preferred, dispreferred) responses:
Read through the tokens: this is (a) two log-probability ratios (log of two softmax outputs, i.e., cross-entropy-like quantities you already know), (b) a difference ("preferred should be higher than dispreferred"), (c) a sigmoid + log — which is binary cross-entropy, which is a special case of §5. So DPO is fundamentally cross-entropy applied to a ratio of log-probs. Everything you learned today generalizes directly.
Successors, all using the same log-prob primitives:
- KTO (Ethayarajh et al., 2024) — replaces the pairwise term with a Kahneman-Tversky value function; works on unpaired data.
- ORPO (Hong et al., 2024) — combines SFT and preference in one loss, no reference model.
- SimPO (Meng et al., 2024) — length-normalized log-probs, drops the reference model entirely.
All are variants of "push up log-prob of good, push down log-prob of bad". If you can read cross-entropy, you can read any of them.
8b.4 · Load-balancing loss in MoE (Mixtral, DeepSeek-V3)
Mixture-of-Experts models add an auxiliary loss to keep experts equally used:
where is the fraction of tokens routed to expert and is the average router probability for that expert. If routing is imbalanced (all tokens go to one expert), the product explodes; balanced routing minimizes it. Even MoE architecture has cross-entropy-adjacent auxiliaries under the hood.
8b.5 · Label smoothing — the tiny CE tweak with big effects
Instead of a one-hot target, use [0.1/K, ..., 0.9 + 0.1/K, ..., 0.1/K] — give a little probability mass to every class. Introduced by Szegedy et al., 2016 for Inception-v3, still standard practice in 2025 image models and machine translation. Effect: prevents the model from becoming over-confident (softmax outputs saturating at 1.0), which improves calibration and often improves generalization by 0.3–0.7%. In the CE formula §4 you just replace the delta target with a smoothed target — the loss is now . Same shape, softer target.
Further reading:
- Rafailov et al. "Direct Preference Optimization" (2023).
- Meng et al. "SimPO: Simple Preference Optimization with a Reference-Free Reward" (2024).
- DeepSeek "DeepSeek-V3 Technical Report" (Dec 2024).
- Sebastian Raschka's "LLM Training: RLHF and Its Alternatives" blog series.
9 · War stories
I had a text classifier stuck at random accuracy. Loss curves looked reasonable, but predictions were constant. Turned out I was accidentally passing one-hot labels of shape (batch, K) where PyTorch's F.cross_entropy expects integer class indices of shape (batch,). It was silently interpreting the one-hot vector as class indices and computing gibberish gradients.
Fix: always print y.shape and y.dtype at the top of your training loop. Class indices are torch.long, shape (B,). If you have one-hots, y = y.argmax(-1) first.
PyTorch's F.cross_entropy expects raw logits, not probabilities — it applies log-softmax internally (with the stability trick). I was doing F.cross_entropy(F.softmax(logits, dim=-1), y). Loss decreased but gradients were tiny and the model barely learned.
Fix: the layer BEFORE cross-entropy should output logits. No activation. Just raw linear output. Read the framework docs, always. This bug lives silently in a lot of tutorials.
Somewhere in a custom decoder I had -torch.log(probs.gather(-1, targets)). When the model was very confident and wrong, probs for the correct class hit ~1e-40, log returned -92, loss ballooned, one bad gradient step, weights blew up, NaN forever after.
Fix: never compute log(softmax(x)) naively; use F.log_softmax (or the log-sum-exp trick) so the log and exp cancel inside the formula. Also: gradient clipping (Session 024) is your seat-belt for this class of bug.
10 · Diagram — the whole picture in one figure
Read the arrow "true class = 0 → gradient is q - one_hot" until it feels obvious. That's the atom of every classifier ever built.
"Cross-entropy and log-loss and negative log-likelihood are three different loss functions. And minimising cross-entropy is a modelling choice — I could just as well use MSE on the softmax outputs."
For classification they are the same object under three names: H(p, q) = -Σ p log q with p one-hot collapses to -log q[correct], which is exactly the negative log-likelihood of the data under the model. And MSE on softmax outputs is not merely a worse choice — it is a differently-shaped choice, whose gradient is scaled by the softmax derivative and therefore vanishes precisely when the model is confidently wrong.
Because the three names arrive from three different fields — information theory, statistics, and ML engineering — each with its own derivation and notation, and nobody ever says out loud that they cancelled down to the same expression. And the MSE half of the myth is sticky for a better reason: MSE genuinely is the right loss when your output is a real number with Gaussian noise. It is the correct answer to the regression question, applied to a question it does not fit.
Three formulas, one number — and the gradient difference that matters:
import numpy as np
logits = np.array([2.0, 1.0, 0.1]); y = 0
q = np.exp(logits - logits.max()); q /= q.sum()
p = np.eye(3)[y]
print(-(p * np.log(q)).sum()) # cross-entropy H(p,q)
print(-np.log(q[y])) # negative log-likelihood
print(-np.log(q @ p)) # log-loss -> all identical
# gradient shape: CE is linear in the error, MSE is not
print(q - p) # dCE/dlogits -- never vanishes
print(2 * (q - p) * q * (1 - q)) # dMSE/dlogits -- dies when q -> 0 or 1Why is the gradient of softmax + cross-entropy exactly probs - onehot? Two nontrivial functions compose and the messy Jacobian just… disappears. That is not luck.
- 1Write the loss on logits directly:
L = -z[y] + logsumexp(z), sincelog softmax(z)[y] = z[y] - logsumexp(z).forced by · substituting softmax into the log turns a quotient into a difference, which is the whole reason log-softmax is a single fused op - 2The first term contributes
-1to the derivative with respect toz[y], and 0 to every other component.forced by · it is linear in a single coordinate - 3The derivative of
logsumexp(z)with respect toz[i]isexp(z[i]) / Σ exp(z[j])— which is the definition ofsoftmax(z)[i].forced by · differentiating a log gives 1/sum, and differentiating the exp inside gives back exp(z[i]) — the normalisation reassembles itself - 4Adding the two:
dL/dz[i] = softmax(z)[i] - 1[i == y], i.e.probs - onehot, with no Jacobian product anywhere.forced by · logsumexp is the convex conjugate of entropy, so softmax is literally its gradient — the two functions were inverse-paired all along - 5The reason the messy softmax Jacobian never appears is that it was cancelled by the log, not avoided by a trick.forced by · cross-entropy applies log to exactly the quantity softmax exponentiated
Therefore the pairing of softmax with cross-entropy is not conventional but structural: each is the other's natural partner, and their composition has a gradient that is linear in the error.
And note the prediction: because this gradient never saturates, a confidently-wrong example produces a gradient of magnitude near 1 and gets corrected hard — whereas under MSE the same example produces a gradient near zero and the model stays stuck. Verify it: set logits to [10, 0, 0] with true class 1, and compare q - p against 2(q-p)q(1-q). It also predicts the framework must fuse the two ops: pass raw logits to cross_entropy and you get this stable form, but call softmax then log yourself and you get log(0) as soon as a logit gap grows. That is why PyTorch's API takes logits and why passing it probabilities is a silent, well-known bug.
Define the surprise of an outcome as -log q(x): an event you assigned probability 1 costs nothing, and one you assigned probability 0 costs infinity. Entropy is the average surprise you would pay if your beliefs were correct; cross-entropy is the average you actually pay using beliefs q while reality draws from p.
The gap between them — KL divergence — is the surcharge for being wrong, and it is never negative. So minimising cross-entropy is minimising a bill whose floor is fixed by the data's own entropy. That floor is why your loss plateaus above zero on any noisy label set, and it is not a bug.
H(p,q) = H(p) + KL(p‖q). You cannot optimiseH(p)— it is the data's irreducible noise. All training does is shrink the KL term.- KL is asymmetric.
KL(p‖q)punishes assigning low probability to things that happen (mode-covering);KL(q‖p)punishes assigning probability to things that don't (mode-seeking). - Assigning probability 0 to something that occurs costs infinite loss. Every clamp, epsilon, and label-smoothing trick in your codebase exists to prevent that.
exp(loss)is perplexity — the effective number of options the model is choosing between. A far more interpretable number than the loss itself.
Fire this model the moment you see: a loss that plateaus well above zero · perplexity quoted in a language-model paper · a NaN loss right after a confident prediction · label_smoothing in a config · KL terms in a VAE or a distillation objective · anyone comparing losses across datasets as if the numbers were commensurable.
Your classifier is overconfident and its probabilities are not trustworthy downstream. Train with hard labels, add label smoothing, or fit a post-hoc temperature?
argmax — nothing downstream reads the probability valueargmax decisions completely unchanged while fixing calibration; essentially freeDecide by asking what actually consumes the output. If it is a threshold, a cost-sensitive decision, an ensemble weight, or a human reading a confidence, you need calibration and temperature scaling is the cheapest honest fix — try it before touching the training objective, because it cannot make accuracy worse.
Reach for label smoothing when the labels are the problem rather than the calibration, and be aware you have traded exactness for robustness. And in either case measure it: reliability curves and expected calibration error, not loss. Loss will not tell you your 0.99 predictions are right only nine times in ten.
11 · Retention scaffold
One-line summary (write it in your own words): _______________________________
Spaced review: Re-derive the softmax+CE gradient tomorrow from scratch. Revisit §8b (LLM CE + DPO) on day 7.
Next session (S006): SGD, momentum, Adam, and the numerical-stability tricks (log-sum-exp, gradient clipping, fp16 loss scaling) that keep production training runs alive.
Sticky note: s = softmax_stable(z); s[y] -= 1 — three lines, that's the whole softmax+CE gradient.
Recall — from memory
1. Define surprise I(x) and give one reason -log is the right function.
I(x) = -log p(x). Reason: independent events should have additive surprise, and -log(p*q) = -log p + -log q. Also: p=1 → I=0 (certain events not surprising), p→0 → I→∞.
2. What is the entropy of a uniform distribution over K outcomes?
H = log K. Maximum possible entropy for K outcomes. Everything else is lower.
3. State Gibbs' inequality.
H(p, q) ≥ H(p) with equality iff p = q. The "gap" is KL(p || q) ≥ 0.
4. In one-hot classification, what is the cross-entropy loss on a single example with true class y and predicted probabilities q?
-log q_y — the negative log of the probability your model assigned to the correct class. It's zero when q_y = 1 and infinity when q_y = 0.
5. What is ∂L/∂z_k for the combined softmax + cross-entropy loss?
s_k - δ_{ky} — the predicted probability minus 1 for the correct class, exactly the predicted probability for every other class. In code: s = softmax(z); s[y] -= 1.
Stretch — for one extra hour
Implement softmax_stable, ce_loss, and softmax_ce_grad from scratch. On a random batch (z: (32, 10), y: (32,)), verify your analytic gradient against a numerical gradient to 6 decimal places using central differences. Then compute the KL divergence between your model's average predictions and the true class marginals — a great sanity check on classifiers.
In your own words
Explain why we use cross-entropy loss for classification, in one sentence:
Spaced-review pointer
- From S004 — the chain rule is what we used to derive the softmax + CE gradient in §7.1. Redo that derivation without looking if you can.
- From S002 — the shape rules will bite the moment you generalise §7 to batches;
s.shape == y_onehot.shapeis your friend.
Next-session teaser
You now know (a) how to compute gradients (S004) and (b) what loss function to minimise (S005). Next session: the algorithm that actually turns those gradients into learning. We'll cover SGD, momentum, Adam, learning-rate intuition, and the numerical-stability tricks (log-sum-exp, fp16 clipping) that keep training from exploding at scale. After Session 006 you'll have the complete "chain rule + cross-entropy + gradient descent" trinity that underlies every trained neural network on earth.
What to bring back tomorrow — sticky note
- Diagram: the "logits → softmax → probs → -log at true class" flow from §10.
- Equation:
∂L/∂z_k = s_k - δ_{ky}— the entire gradient of softmax + CE, in one line. - Snippet:
s = softmax_stable(z); s[y] -= 1— done. That's the gradient. Three lines.
Previous: ← DL S004 · Derivatives, the Chain Rule, and Computation Graphs · Next: DL S006 · Optimization Intuition + Numerical Stability →