Loss Functions Mathematical Deep Dive

P6.nn-foundations.05 · Audience: it-ml · Prerequisites: Loss Functions and the Learning Objective

Mathematical derivation of cross-entropy from MLE, MSE from the Gaussian noise assumption, the softmax+CE combined gradient, and label smoothing. Step through the four derivations one at a time (keep a pencil at hand), then work the pencil-and-paper exercises, NumPy programming tasks, and 5-question self-test below.

Prerequisite: Loss Functions and the Learning Objective. This page assumes familiarity with logarithms, probability distributions, and basic calculus (chain rule). All derivations are self-contained.

Step 1 / 4Cross-Entropy from Maximum Likelihood

Goal: prove that minimising cross-entropy is equivalent to maximum likelihood estimation for a classifier.

Setup. Suppose the correct label for example ii is class ki{1,,K}k^*_i \in \{1, \dots, K\}. The model assigns probability pkip_{k^*_i} to the correct class (via softmax).

Step 1 — Likelihood of one example. The categorical likelihood of observing label kik^*_i given the model's probabilities is:

L(θ;i)=pkiL(\theta; i) = p_{k^*_i}

Step 2 — Log-likelihood over the training set. Assuming examples are independent, the joint log-likelihood is:

(θ)=ilogpki\ell(\theta) = \sum_i \log p_{k^*_i}

Step 3 — Maximise log-likelihood ≡ minimise negative log-likelihood.

argmaxθ(θ)=argminθ(ilogpki)=argminθiCEi\arg\max_\theta \ell(\theta) = \arg\min_\theta \Big(-\sum_i \log p_{k^*_i}\Big) = \arg\min_\theta \sum_i \mathrm{CE}_i

where CEi=logpki\mathrm{CE}_i = -\log p_{k^*_i} is the per-example cross-entropy loss.

Step 4 — Average over nn examples (the batch loss).

CE=1ni(logpki)\mathrm{CE} = \frac{1}{n} \sum_i \big(-\log p_{k^*_i}\big)

Conclusion: minimising cross-entropy is exactly MLE for the categorical distribution — the model is trained to assign the highest possible probability to the correct class label. ∎

ⓘ Concept — why not use accuracy as the loss?
Accuracy (fraction of correct predictions) is not differentiable — it changes only when the argmax of the probabilities flips, so its gradient is zero almost everywhere. Cross-entropy, by contrast, is differentiable everywhere pk>0p_{k^*} > 0: even when the prediction is correct (argmax =k= k^*), the gradient still pushes the model toward higher confidence. This continuity is what makes gradient descent possible.
LCE=1ni=1nlogpki\mathcal{L}_{\text{CE}} = -\frac{1}{n}\sum_{i=1}^{n} \log p_{k^*_i}

Part 2 — Exercises

Work these on paper or in the scratch console at the bottom of the page. Graded numeric practice on these formulas lives on the module 03 practice rung — here each exercise carries its worked solution behind a reveal, so check yourself as you go.

Maths exercises (pencil and paper)

M1 — Binary cross-entropy from MLE. Consider binary classification (K=2K = 2, y{0,1}y \in \{0, 1\}). The model outputs a scalar y^=p(y=1x)\hat{y} = p(y = 1 \mid x).

  1. Write the likelihood of observing label yy given y^\hat{y} as a single expression using y^\hat{y} and (1y^)(1 - \hat{y}) without an if-else.
  2. Take the negative log-likelihood for one example to obtain BCE(y^,y)\mathrm{BCE}(\hat{y}, y).
  3. Verify: for y=1y = 1 and y^1\hat{y} \to 1, BCE 0\to 0; for y=1y = 1 and y^0\hat{y} \to 0, BCE \to \infty.
  4. Show that BCE is a special case of CE with K=2K = 2 classes.
Show answer — M1
  1. Likelihood: L=y^y(1y^)1yL = \hat{y}^{\,y} \cdot (1-\hat{y})^{1-y} — takes value y^\hat{y} when y=1y = 1, and (1y^)(1-\hat{y}) when y=0y = 0.
  2. BCE=logL=ylogy^(1y)log(1y^)\mathrm{BCE} = -\log L = -y \log \hat{y} - (1-y) \log(1-\hat{y}).
  3. y=1y = 1, y^1\hat{y} \to 1: BCE =1log(1)0()=0= -1\cdot\log(1) - 0\cdot(-\infty) = 0. y=1y = 1, y^0\hat{y} \to 0: BCE =1log(0)+= -1\cdot\log(0) \to +\infty. ✓
  4. K=2K = 2: p1=y^p_1 = \hat{y}, p0=1y^p_0 = 1-\hat{y}. CE=jyjlogpj=ylogy^(1y)log(1y^)=BCE\mathrm{CE} = -\sum_j y_j \log p_j = -y\log\hat{y} - (1-y)\log(1-\hat{y}) = \mathrm{BCE}. ✓

M2 — Softmax + CE gradient for K = 3. Let z=[2.0,1.0,0.0]z = [2.0, 1.0, 0.0] and the correct class be k=0k^* = 0.

  1. Compute softmax(z)=(p0,p1,p2)\mathrm{softmax}(z) = (p_0, p_1, p_2). (Hint: subtract max for numerical stability, then normalise.)
  2. Compute the CE loss L=logp0L = -\log p_0.
  3. Use the formula L/z=py\partial L / \partial z = p - y to write down the gradient vector.
  4. Verify: the gradient at position 0 is p01p_0 - 1, and at positions 1, 2 it is pjp_j.
Show answer — M2
  1. exp([2,1,0])=[7.3891,2.7183,1.0000]\exp([2, 1, 0]) = [7.3891, 2.7183, 1.0000]; sum =11.1073= 11.1073; p=[0.6652,0.2447,0.0900]p = [0.6652, 0.2447, 0.0900].
  2. CE=log(0.6652)=0.4076\mathrm{CE} = -\log(0.6652) = 0.4076.
  3. y=[1,0,0]y = [1, 0, 0]; L/z=py=[0.3348,0.2447,0.0900]\partial L / \partial z = p - y = [-0.3348, 0.2447, 0.0900].
  4. L/z0=0.66521=0.3348\partial L / \partial z_0 = 0.6652 - 1 = -0.3348 ✓; L/z1=0.2447\partial L / \partial z_1 = 0.2447 ✓; L/z2=0.0900\partial L / \partial z_2 = 0.0900 ✓.

M3 — Label smoothing: compute soft targets. Given K=4K = 4 classes, correct class k=2k^* = 2, smoothing factor ε=0.1\varepsilon = 0.1:

  1. Write down the hard one-hot target yy.
  2. Compute the label-smoothed target yLSy^{\mathrm{LS}} using yjLS=(1ε)yj+ε/Ky^{\mathrm{LS}}_j = (1-\varepsilon)\cdot y_j + \varepsilon/K.
  3. Verify that yLSy^{\mathrm{LS}} sums to 1.
  4. What is the optimal logit gap log((K1)(1ε)/ε)\log\big((K-1)(1-\varepsilon)/\varepsilon\big) for these values? (Give a numerical answer.)
Show answer — M3
  1. y=[0,0,1,0]y = [0, 0, 1, 0].
  2. yLS=[0.025,0.025,0.925,0.025]y^{\mathrm{LS}} = [0.025, 0.025, 0.925, 0.025] (y2=(10.1)1+0.1/4=0.9+0.025=0.925y_2 = (1-0.1)\cdot 1 + 0.1/4 = 0.9 + 0.025 = 0.925; others =0+0.025=0.025= 0 + 0.025 = 0.025).
  3. Sum =1.0000= 1.0000
  4. log(3×0.9/0.1)=log(27)3.2958\log(3 \times 0.9 / 0.1) = \log(27) \approx 3.2958.

Programming exercises (NumPy)

P1 — Implement cross-entropy from logits. Fill in the stub to compute CE loss directly from logits (numerically stable):

import numpy as np

def cross_entropy_from_logits(z: np.ndarray, k_star: int) -> float:
    """Cross-entropy loss for one example."""
    # STUB: compute log-softmax of z (numerically stable), then return −log_p[k_star]
    log_p = ___
    return float(-log_p[k_star])

z = np.array([2.0, 1.0, 0.0])
print(cross_entropy_from_logits(z, 0))  # should be ≈ 0.4076
Show solution — P1
log_p = z - np.log(np.sum(np.exp(z - z.max()))) - z.max()
# or more compactly:
log_p = z - (z.max() + np.log(np.sum(np.exp(z - z.max()))))

The log-sum-exp trick: subtract max(z)\max(z) before exponentiating to avoid overflow. logsoftmax(z)j=zjmax(z)logkezkmax(z)\log \mathrm{softmax}(z)_j = z_j - \max(z) - \log \sum_k e^{z_k - \max(z)}. This is how PyTorch's F.cross_entropy works internally.

P2 — Verify the gradient formula ∂L/∂z = ŷ − y numerically. Fill in the stub to verify the analytical gradient against a numerical approximation:

import numpy as np

def ce_loss(z: np.ndarray, k_star: int) -> float:
    z_s = z - z.max()
    log_p = z_s - np.log(np.sum(np.exp(z_s)))
    return float(-log_p[k_star])

z = np.array([2.0, 1.0, 0.0])
k_star = 0
h = 1e-5

# Numerical gradient (finite differences)
num_grad = np.array([
    (ce_loss(z + h * np.eye(len(z))[i], k_star) -
     ce_loss(z - h * np.eye(len(z))[i], k_star)) / (2 * h)
    for i in range(len(z))
])

# STUB: compute the analytical gradient ∂L/∂z = ŷ − y
analytic_grad = ___

print('Numerical:', num_grad.round(6))
print('Analytic: ', analytic_grad.round(6))
print('Match:', np.allclose(num_grad, analytic_grad, atol=1e-5))
Show solution — P2
z_s = z - z.max()
y_hat = np.exp(z_s) / np.sum(np.exp(z_s))  # softmax probabilities
y = np.zeros(len(z))
y[k_star] = 1.0
analytic_grad = y_hat - y

Numerical and analytical gradients match to within 1e-5 — confirming the derivation in the third step. The analytical gradient is vastly cheaper to compute and is exactly what backpropagation uses.

P3 — Label smoothing: compare losses and gradients. Fill in the stub to implement label smoothing and compare its CE loss with the hard-target CE:

import numpy as np

def smooth_labels(y: np.ndarray, eps: float) -> np.ndarray:
    """Apply label smoothing to a one-hot vector."""
    K = len(y)
    # STUB: return (1-eps)*y + eps/K
    return ___

K, k_star, eps = 4, 0, 0.1
y_hard = np.zeros(K); y_hard[k_star] = 1.0
y_soft = smooth_labels(y_hard, eps)

# Suppose the model is very confident: logits favour class k_star
z = np.array([5.0, 1.0, 1.0, 1.0])
z_s = z - z.max()
log_p = z_s - np.log(np.sum(np.exp(z_s)))
p = np.exp(log_p)

loss_hard = float(-log_p[k_star])
loss_soft = float(-y_soft @ log_p)  # − Σⱼ yⱼ^LS · log pⱼ

print(f'Hard CE: {loss_hard:.4f}')
print(f'Smoothed CE: {loss_soft:.4f}')
print(f'Softmax probs: {p.round(4)}')
Show solution — P3
return (1 - eps) * y + eps / K

Expected output: Hard CE: 0.0535 (very low — model is very confident and correct); Smoothed CE: 0.3535 (higher — label smoothing penalises overconfidence); Softmax probs: [0.9479, 0.0174, 0.0174, 0.0174].

Label smoothing always adds a uniform entropy term (ε/K)j(logpj)(\varepsilon/K)\sum_j(-\log p_j), preventing the model from assigning near-zero probability to any class.

Part 3 — Self-Test

Write your answer before revealing.

Q1 — Explain in one sentence what maximum likelihood estimation means in the context of a neural network classifier.

Q2 — What probability distribution over the prediction error (noise) makes MSE the correct MLE loss function? State the assumption precisely.

Q3 — Explain why the combined softmax + cross-entropy gradient ∂L/∂z = ŷ − y is considered particularly elegant compared to using MSE + softmax.

Q4 — What failure mode does label smoothing address, and how does it fix it?

Q5 — Numerical: given logit vector z = [2, 1, 0] and correct class k* = 0, compute softmax(z) and the CE loss −log p₀, both to 4 decimal places.


Cross-references: Loss Functions and the Learning Objective (base module) · Backpropagation Intuition — where the gradient flows backward from the CE loss — arrives with the dl-architectures track (coming soon).

Ask the mentor about this module

Ask a question about this content. The mentor explains and grounds its answer in what you are studying; asking is recorded as a learning signal, not a grade.

Ctrl/Cmd + Enter to send

Try it yourself

A scratch console for this page's ideas — ungraded, nothing you run here is recorded.

Scratch console

A scratch console with the scientific stack (pandas, numpy, scikit-learn). Runs on the server — no network, resource-limited and measured.

Output appears here.