Python · Course navigation

Cross-entropy loss and the softmax gradient

See why classification networks use cross-entropy, and verify its simple gradient numerically.

On this page
Before you start

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

Python
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))
Output
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
Guided walkthroughReady
1import math
2
3
4def softmax(values):
5 peak = max(values)
6 exps = [math.exp(v - peak) for v in values]
7 total = sum(exps)
8 return [e / total for e in exps]
9
10
11def cross_entropy(scores, target):
12 return -math.log(softmax(scores)[target])
13
14
15def sigmoid(z):
16 return 1 / (1 + math.exp(-z))
17
18
19scores = [2.0, 1.0, 0.1]
20target = 0
21p = softmax(scores)
22analytic = [p[i] - (1 if i == target else 0) for i in range(3)]
23numeric = []
24for i in range(3):
25 up = scores[:]
26 up[i] += 1e-6
27 down = scores[:]
28 down[i] -= 1e-6
29 numeric.append((cross_entropy(up, target) - cross_entropy(down, target)) / 2e-6)
30print(all(abs(a - n) < 1e-6 for a, n in zip(analytic, numeric)))
31print(round(cross_entropy(scores, 0), 4), round(cross_entropy(scores, 2), 4))
32y = sigmoid(-6)
33print(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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading