Session S03
Cross-entropy · Numerical stability · Learning rates
Deep Learning — CS 4715/5715 · University of Idaho
\[\theta \;\leftarrow\; \theta - \eta\,\nabla_\theta L(\theta)\]
The scorer is S02's linear model — \(z = \mathbf{w}^\top\mathbf{x} + b\), a score computed from features. An output head turns the score into a prediction, and the head picks the loss:
| Head | Prediction | Task | Loss (today) |
|---|---|---|---|
| identity | \(\hat{y} = z\) | regression | MSE |
| sigmoid | \(p = \sigma(z)\) | binary classification | binary cross-entropy |
| softmax | \(\mathbf{p} = \operatorname{softmax}(\mathbf{z})\) one scorer per class: \(z_k = \mathbf{w}_k^\top\mathbf{x} + b_k\) | multiclass classification | cross-entropy |
nn.Linear\[L\big(\theta - \eta\,\nabla_\theta L\big) \;\approx\; L(\theta) - \eta\,\lVert\nabla_\theta L\rVert^2 \;\le\; L(\theta)\] (first-order Taylor, small \(\eta\))
\[\theta^\star \;=\; \arg\max_\theta \prod_i p_\theta(y_i \mid x_i) \;=\; \arg\min_\theta \Big[-\sum_i \log p_\theta(y_i \mid x_i)\Big]\]
\[\text{MSE} \;=\; \frac{1}{N}\sum_i (\hat{y}_i - y_i)^2\]
One forward pass, identity head, on the straight line the reading's code fits — \(y = 2x + 1\), so \(w = 2,\, b = 1\): \[x = 3 \;\xrightarrow{\;wx + b\;}\; \hat{y} = 7, \qquad \text{label } y = 5, \qquad L = (7-5)^2 = 4\]
The prediction is not a knob we can turn — the parameters are. Chain rule, then one step with \(\eta = 0.025\): \[\frac{\partial L}{\partial w} = \underbrace{2(\hat{y} - y)}_{=\,4}\cdot\, x = 12, \qquad \frac{\partial L}{\partial b} = 2(\hat{y} - y)\cdot 1 = 4\] \[w \leftarrow 2 - 0.025 \times 12 = 1.7, \qquad b \leftarrow 1 - 0.025 \times 4 = 0.9\] \[\text{forward again:}\quad \hat{y} = 1.7 \times 3 + 0.9 = 6, \qquad L: \; 4 \to (6-5)^2 = 1\]
Sigmoid head now: \(\mathbf{x} \xrightarrow{\;\mathbf{w}^\top\mathbf{x} + b\;} z \xrightarrow{\;\sigma\;} p \xrightarrow{\;\text{loss}\;} L\) — the table scans the scorer's output \(z\) for one example with true label \(y = 1\):
# One example with true label y = 1, prediction p = sigmoid(z).
# MSE loss: L = (p - y)^2 -> dL/dz = 2 * (p - y) * p * (1 - p)
# Binary cross-entropy: L = -log(p) -> dL/dz = p - y
z (logit) p=sigmoid(z) |dL/dz| MSE |dL/dz| CE
-8.0 0.0003 0.000670 0.9997
-4.0 0.0180 0.034690 0.9820
-2.0 0.1192 0.184956 0.8808
0.0 0.5000 0.250000 0.5000
2.0 0.8808 0.025031 0.1192
First row: the model is as wrong as it gets (\(p = 0.0003\) on the true class) — and MSE's gradient is ~1000× smaller than binary cross-entropy's. MSE punishes confident errors with a whisper.
Binary cross-entropy on a sigmoid output: \(p = \sigma(z)\), \(y \in \{0, 1\}\), \(L = -\big[y \log p + (1-y)\log(1-p)\big]\)
STEP 0 · sigmoid's own derivative \[\frac{dp}{dz} \;=\; \frac{d}{dz}\Big[(1 + e^{-z})^{-1}\Big] \;=\; \frac{e^{-z}}{(1 + e^{-z})^2} \;=\; p\,(1 - p)\]
STEP 1 · loss w.r.t. the probability, term by term \[\frac{\partial L}{\partial p} \;=\; -\Big[\, y\cdot\frac{1}{p} \;+\; (1-y)\cdot\frac{-1}{1-p} \,\Big] \;=\; -\frac{y}{p} + \frac{1 - y}{1 - p}\] (the second term picks up a \(-1\) from the inner derivative of \(1-p\) — the classic by-hand sign slip)
Two independent facts, nothing combined yet. Step 0 is the exact factor that killed MSE; step 1 is the log's contribution — each term differentiates to a reciprocal.
STEP 2 · chain rule — multiply the two pieces \[\frac{\partial L}{\partial z} \;=\; \Big(-\frac{y}{p} + \frac{1 - y}{1 - p}\Big)\; p\,(1 - p)\]
STEP 3 · distribute — each denominator meets its own factor \[\begin{aligned} &= -y\,(1 - p) \;+\; (1 - y)\,p \\ &= -y + y\,p + p - y\,p \\ &= \;\boxed{\;p - y\;} \end{aligned}\]
\[\mathbf{p} = \operatorname{softmax}(\mathbf{z}), \qquad p_k \;=\; \frac{e^{z_k}}{\sum_j e^{z_j}}\]
BETWEEN TWO DISTRIBUTIONS — target \(\mathbf{y}\), prediction \(\mathbf{p}\) \[H(\mathbf{y}, \mathbf{p}) = -\sum_k y_k \log p_k \;\;\xrightarrow{\;\mathbf{y} \,=\, \operatorname{onehot}(y)\;}\;\; -\log p_y\] (one-hot target: every term but the true class multiplies by 0 — the sum collapses)
The binary formula \(-\big[y \log p + (1-y)\log(1-p)\big]\) is the \(C = 2\) case — \(y\) and \(1-y\) are the two one-hot slots.
And the multiclass gradient, for the softmax head? Next page, line by line.
Softmax head now — multiclass, say ten animal classes. The goal: the same clean gradient the sigmoid case earned.
STEP 1 · expand the loss — the log eats the exp \[L = -\log \operatorname{softmax}(\mathbf{z})_y = -\log \frac{e^{z_y}}{\sum_j e^{z_j}} = -z_y + \log \textstyle\sum_j e^{z_j}\]
STEP 2 · derivative w.r.t. one score \(z_k\), each part separately \[\frac{\partial L}{\partial z_k} = \underbrace{-\,\mathbf{1}[k = y]}_{\text{from } -z_y} \;+\; \underbrace{\frac{e^{z_k}}{\sum_j e^{z_j}}}_{\text{from } \log \sum_j e^{z_j}} \;=\; \operatorname{softmax}(\mathbf{z})_k - \mathbf{1}[k = y]\]
Softmax head: three scorers, one per class — \(z_k = \mathbf{w}_k^\top\mathbf{x} + b_k\) — produced this input's scores:
\[\mathbf{z} = (2,\, 1,\, 0), \quad \text{true class: index } 0 \;\Rightarrow\; \operatorname{onehot}(0) = (1,\, 0,\, 0)\] \[\mathbf{p} = \operatorname{softmax}(\mathbf{z}) \approx (0.665,\; 0.245,\; 0.090)\] \[L = -\log p_0 = -\log 0.665 \approx 0.408\] \[\nabla_{\mathbf{z}} L = \mathbf{p} - \operatorname{onehot}(0) \approx (0.665,\; 0.245,\; 0.090) - (1,\, 0,\, 0)\] \[= (-0.335,\; 0.245,\; 0.090)\]
def softmax_naive(z):
e = np.exp(z)
return e / e.sum()
def softmax_stable(z):
e = np.exp(z - z.max()) # shift so the largest logit is 0
return e / e.sum()
z = np.array([15.0, 1000.0, -8.0]) # logits like this appear in real runs
naive softmax: [ 0. nan 0.]
stable softmax: [0. 1. 0.]
naive cross-entropy for class 0: inf
stable cross-entropy for class 0: 985.0000
exp overflows float64 just past \(z = 709\); naive: \(e^{1000} = \infty\), then \(\infty/\infty = \texttt{nan}\) — and one nan poisons everything downstream. Subtracting the max is legal because only differences matter.
[0. 1. 0.] — where did class 0's probability go?STEP 1 · start from the log-probability (the cancellation page's expression) \[\log \operatorname{softmax}(\mathbf{z})_k \;=\; z_k - \log \textstyle\sum_j e^{z_j}\]
STEP 2 · shift every exponent by \(m = \max_j z_j\) — factor \(e^{m}\) out of the sum \[\sum_j e^{z_j} = \sum_j e^{m + (z_j - m)} = e^{m} \sum_j e^{z_j - m} \;\;\Rightarrow\;\; \log \sum_j e^{z_j} = m + \log \sum_j e^{z_j - m}\]
STEP 3 · substitute back \[\log \operatorname{softmax}(\mathbf{z})_k \;=\; z_k - m - \log \textstyle\sum_j e^{z_j - m}\]
STEP 4 · shift the logits \((15,\, 1000,\, -8)\) by \(m = 1000\) \[(15,\, 1000,\, -8) \;\longrightarrow\; (-985,\, 0,\, -1008)\]
STEP 5 · the loss for class 0, computed entirely in logs \[L = -\log p_0 = 985 + \log\big(e^{-985} + 1 + e^{-1008}\big) \;=\; \textbf{985.0000}\] (the sum inside is 1 plus dust — its log is essentially 0)
nn.CrossEntropyLoss takes raw logits; softmax-then-log by hand reintroduces the underflow
# 2-D quadratic bowl, deliberately ill-conditioned:
# L(w) = 0.5 * (w[0]**2 + 25 * w[1]**2); grad = [w[0], 25 * w[1]]
for lr in [0.005, 0.06, 0.081]:
w = np.array([10.0, 1.0])
for step in range(101):
w = w - lr * grad(w)
lr = 0.005 loss at steps 0/25/50/75/100: 62.5, 38.93, 30.29, 23.57, 18.35
lr = 0.06 loss at steps 0/25/50/75/100: 62.5, 2.267, 0.1027, 0.004657, 0.0002111
lr = 0.081 loss at steps 0/25/50/75/100: 62.5, 43.7, 147.7, 507.6, 1745
The idea goes back to Cauchy (1847); everything practical hides in the learning rate \(\eta\) (lr in code).
lr = 0.005): loss creeps — training "works" but wastes your compute budgetlr = 0.06): loss collapses by orders of magnitudelr = 0.081): each step overshoots the valley floor, lands higher on the opposite wall — geometric divergenceSTEP 1 · the smallest honest loss: a 1-D quadratic with curvature \(a\) \[L(w) = \tfrac{a}{2} w^2, \qquad L'(w) = a\,w \qquad \text{\small (the \(\tfrac{a}{2}\) makes the derivative exactly \(a\,w\))}\] Designed, not derived — no model, no data: near a minimum every smooth loss is locally this shape (second-order Taylor), with \(a\) the local curvature.
STEP 2 · write the gradient-descent update, then substitute \[w \;\leftarrow\; w - \eta\, L'(w) \;=\; w - \eta\, a\, w\]
STEP 3 · factor \(w\) out \[w - \eta\, a\, w \;=\; (1 - \eta a)\, w\]
STEP 4 · iterate: the same constant, every step \[w_1 = (1 - \eta a)\, w_0, \qquad w_2 = (1 - \eta a)\, w_1 = (1 - \eta a)^2\, w_0\] \[w_t = (1 - \eta a)^t\, w_0 \qquad\text{geometric, ratio } r = 1 - \eta a\]
STEP 5 · the loss follows the square \[L(w_t) = \tfrac{a}{2}\, w_t^2 = \tfrac{a}{2}\,\big(r^t w_0\big)^2 = r^{2t}\,\tfrac{a}{2}\, w_0^2 = r^{2t}\, L(w_0)\]
\[|1 - \eta a| < 1 \;\iff\; 0 < \eta a < 2 \;\iff\; \boxed{\;\eta < \frac{2}{a}\;}\]
| Symptom | Likely cause | Fix |
|---|---|---|
Loss becomes nan | Naive softmax/log, or divergent lr | Fused log-softmax loss; drop lr 10× |
| Stuck at mediocre accuracy, flat loss curve | Double softmax (probs into a logits loss) | Raw logits into CrossEntropyLoss |
| Loss decreases very slowly, a straight shallow line | lr far too small | Raise 3–10× until fast or unstable, back off one step |
| MSE on 0/1 labels barely learns once saturated | \(p(1-p)\) kills gradients | Cross-entropy for classification, always |
| Loss oscillates but trends down | Minibatch noise + near-limit lr | Judge the smoothed trend; act only if it grows |