Cross-entropy loss and the softmax gradient
See why classification networks use cross-entropy, and verify its simple gradient numerically.
On this page
Softmax and derivatives.
- See why classification networks use cross-entropy, and verify its simple gradient numerically.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A classifier with softmax outputs produces a probability for each class. The loss that fits this setting is cross-entropy: for the correct class c, the loss is −log p_c. It is small when the model assigns a high probability to the right class and grows without bound as that probability approaches zero. Minimizing it is equivalent to maximum-likelihood training of the model.
Combined with softmax, cross-entropy has a very clean gradient with respect to the scores: ∂L/∂zᵢ = pᵢ − yᵢ, the predicted probability minus the one-hot target. The program verifies this against a finite-difference estimate for each score. The first loss is lower for class 0 (the highest-scoring class) than for class 2, which the model thinks is unlikely.
The last two numbers show why cross-entropy is preferred with sigmoid or softmax outputs. Suppose a sigmoid unit is confidently wrong: its score is −6 and the target is 1. The gradient of squared error with respect to the score is multiplied by the sigmoid's slope, which is almost zero in the saturated region, so it is only −0.0025 and learning barely moves. The cross-entropy gradient is y − t = −0.9975, large exactly when the error is large.
Code example
import math
def softmax(values):
peak = max(values)
exps = [math.exp(v - peak) for v in values]
total = sum(exps)
return [e / total for e in exps]
def cross_entropy(scores, target):
return -math.log(softmax(scores)[target])
def sigmoid(z):
return 1 / (1 + math.exp(-z))
scores = [2.0, 1.0, 0.1]
target = 0
p = softmax(scores)
analytic = [p[i] - (1 if i == target else 0) for i in range(3)]
numeric = []
for i in range(3):
up = scores[:]
up[i] += 1e-6
down = scores[:]
down[i] -= 1e-6
numeric.append((cross_entropy(up, target) - cross_entropy(down, target)) / 2e-6)
print(all(abs(a - n) < 1e-6 for a, n in zip(analytic, numeric)))
print(round(cross_entropy(scores, 0), 4), round(cross_entropy(scores, 2), 4))
y = sigmoid(-6)
print(round((y - 1) * y * (1 - y), 4), round(y - 1, 4))True 0.417 2.317 -0.0025 -0.9975
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
import math def softmax(values): peak = max(values) exps = [math.exp(v - peak) for v in values] total = sum(exps) return [e / total for e in exps] def cross_entropy(scores, target): return -math.log(softmax(scores)[target]) def sigmoid(z): return 1 / (1 + math.exp(-z)) scores = [2.0, 1.0, 0.1]target = 0p = softmax(scores)analytic = [p[i] - (1 if i == target else 0) for i in range(3)]numeric = []for i in range(3): up = scores[:] up[i] += 1e-6 down = scores[:] down[i] -= 1e-6 numeric.append((cross_entropy(up, target) - cross_entropy(down, target)) / 2e-6)print(all(abs(a - n) < 1e-6 for a, n in zip(analytic, numeric)))print(round(cross_entropy(scores, 0), 4), round(cross_entropy(scores, 2), 4))y = sigmoid(-6)print(round((y - 1) * y * (1 - y), 4), round(y - 1, 4))Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The softmax cross-entropy gradient is just the predicted probabilities minus the one-hot target. | — | — | |
| The finite-difference gradient agrees with the formula for every score. | — | True | |
| A loss of 0.417 when the true class is the likeliest, and 2.317 when it is the unlikeliest. | — | True 0.417 2.317 | |
| A confidently wrong sigmoid: the squared-error gradient is only −0.0025 (saturated), but the cross-entropy gradient is −0.9975. | — | True 0.417 2.317 -0.0025 -0.9975 |
Behind the scenes
How Python processes this code
Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.
Cost and common mistakes
Evaluating softmax cross-entropy for k classes is O(k). Its gradient is also O(k).
- Computing log(softmax(z)) in two separate steps can underflow to log(0). Libraries combine them as a log-sum-exp for stability.
- Do not apply softmax twice: loss functions that take raw scores (logits) already include it.
- Cross-entropy is for classification. For predicting real numbers, mean squared error is the usual choice.
Try it yourself
What is the loss if the model gives the correct class probability 0.5?
Reveal answer and reasoning
−ln 0.5 ≈ 0.693. The loss is the negative natural logarithm of the probability given to the correct class.