InterviewPrepKit

Home / Coding / Machine Learning Coding / Neural Net Internals / Softmax Cross-Entropy Loss and Gradient

Softmax Cross-Entropy Loss and Gradient

hard 00:00
Solving tips
  • Compute the stable softmax once and reuse it for both the loss and the gradient — the gradient is just probabilities minus one-hot, divided by n.
  • For the loss, index the softmax with the true-class column per row instead of forming the full one-hot matrix; use np.arange(n) as the row index alongside y.
  • The clean gradient (softmax - onehot)/n is exactly why classifiers pair softmax with cross-entropy: the exp and log cancel so no exp/log term survives in the backward pass.

Implement the fused softmax cross-entropy used at the output of essentially every classifier. Given a batch of logits and the integer labels, return both the scalar loss and the gradient with respect to the logits. Interviewers ask for the two together because the gradient collapses to a strikingly simple form once you push the softmax and the cross-entropy through the chain rule, and knowing that form is the difference between a slow, unstable backward pass and the one real frameworks ship.

Definition

Let p = softmax(logits) be the row-wise softmax, so p[i] is a probability distribution over the k classes for example i. Cross-entropy for one example with true label y[i] is:

L_i = -log(p[i, y[i]])

The batch loss is the mean over the n examples:

L = (1/n) * sum_i -log(p[i, y[i]])

The gradient of this mean loss with respect to the logits has a famously clean closed form:

dL/d(logits) = (p - onehot(y)) / n

where onehot(y) is the (n, k) matrix that is 1 at column y[i] in row i and 0 elsewhere. In other words, subtract 1 from the softmax probability of the correct class in each row, then divide the whole matrix by n.

Task

Complete softmax_cross_entropy(logits, y) so it returns the tuple (loss, grad):

  • loss is a Python float: the mean cross-entropy over the batch.
  • grad is an np.ndarray of shape (n, k): the gradient of loss with respect to logits, equal to (softmax(logits) - onehot(y)) / n.

Use a numerically stable softmax (subtract the per-row maximum before exponentiating). Do not call scipy.special.softmax, any library softmax, or any cross-entropy helper.

Example

logits = np.array([[1.0, 2.0, 3.0],
                   [1.0, 2.0, 3.0]])
y = np.array([2, 0])

loss, grad = softmax_cross_entropy(logits, y)

loss
# -> 1.4076059644443804

grad
# -> array([[ 0.04501529,  0.12236424, -0.16737952],
#           [-0.45498471,  0.12236424,  0.33262048]])

Both rows have softmax [0.0900, 0.2447, 0.6652]. Row 0’s label is class 2 (the confident, correct one) so its loss term -log(0.6652) = 0.4076 is small; row 1’s label is class 0 (the unlikely one) so -log(0.0900) = 2.4076 is large. The mean is 1.4076. Each gradient row is the softmax with 1 subtracted at the true-class column, divided by n = 2.

Constraints

  • logits is a 2-D array of shape (n, k) with 1 <= n and 2 <= k.
  • y is a 1-D integer array of shape (n,) with every entry in [0, k-1].
  • Logits may be large in magnitude; the loss and gradient must never contain inf or nan.
  • Use vectorized NumPy with broadcasting and integer indexing, not Python loops over rows.

Write your solution, then hit Run tests to check it — or get a mock grade from the AI coach.

The coach remembers this session — revise your code and ask again, and it grades your progress. It gives hints, not the answer.
Report a bug