Float32 spans roughly 10^{-38} to 10^{38}: \expoverflows to \infty once its argument passes \approx +88, and past \approx -88 it gradually underflows through the subnormals, hitting exactly 0 near -104.
Feed the from-scratch softmax the logits \mathbf{o}=(1000, 0, 0): \exp(1000)=\infty, the ratio is \infty/\infty=NaN, and a NaN propagates through the backward pass. The example in the softmax-from-scratch section; the fused loss below never forms that ratio.
Fix, step 1: shift by the max
Numerical stability
Softmax is unchanged if we subtract the same constant from every logit (the \exp\bar{o} factors cancel). Choose \bar{o}=\max_k o_k:
Now every exponent o_j - \bar{o} \le 0, so each \exp lands in (0, 1]: no overflow. The denominator sits in [1, q].
Fix, step 2: never form the softmax
Numerical stability
Underflow remains a problem if we then take \log of a near-zero probability. But we only ever want \log \hat y_j for the loss, so fold the \log in and the division disappears:
The gap peaks at the tiex = 0, where it equals \log 2 \approx 0.69, the bound \log q you proved in the softmax-regression section (exercise 6), here at q = 2. The gap decreases away from the tie.
03
In code
one fused call, four frameworks
Pass logits directly to the loss
The fused loss
SparseCategoricalCrossentropy(from_logits=True) is the switch that says “these are scores, not probabilities”, so Keras does the stable log-sum-exp instead of assuming a softmax already ran:
The name differs by library; the interface does not. The built-in fused loss takes logits, not probabilities: passing softmax outputs would softmax twice.
Defined once on Classifier (note the #@save): the whole book inherits the stable loss.
04
Train
same linear model with framework components
Train
Results
Same Fashion-MNIST, same 10 epochs, same Trainer:
data = d2l.FashionMNIST(batch_size=256)model = SoftmaxRegression(num_outputs=10, lr=0.1)trainer = d2l.Trainer(max_epochs=10)trainer.fit(model, data)
In the displayed run, validation accuracy is ~83–84%, close to the from-scratch model in the softmax-from-scratch section. The implementation uses a stable fused loss rather than explicit softmax followed by clipping.
Recap
Wrap-up
From scratch taught what softmax and cross-entropy are; concise uses the stable framework components typical of applications.
The forward pass outputs logits; the built-in loss owns the softmax.
That built-in is the log-sum-exp rewrite \ell = \bar{o} + \log\sum_k e^{o_k-\bar{o}} - o_y, not a naive softmax → log → NLL.
lse is a smooth max: within \log q of \max_k o_k, gap largest (\log 2 for q{=}2) exactly at the tie.
The fused expression avoids the float32 \pm 88 (and -104) thresholds by evaluating the stable log-sum-exp form directly.