Neural Mastery
← Back to Practice

Softmax Gradient (Vector-Jacobian Product)

Difficulty: Hard · Concept: Activation Functions — Auxiliary Functions

Sigmoid Derivative was elementwise: each output only depends on its own input, so σ(zi)\sigma'(z_i) is a single number per element. Softmax is fundamentally different — every output si=softmax(z)is_i = \text{softmax}(z)_i depends on all of zz (through the shared normalizing sum), so its derivative isn't a list of numbers, it's a full n×nn \times n Jacobian matrix: sizj=si(δijsj)\frac{\partial s_i}{\partial z_j} = s_i(\delta_{ij} - s_j), where δij\delta_{ij} is 11 if i=ji=j else 00.

Explicitly building that n×nn \times n matrix during backprop would be wasteful — real deep learning frameworks never materialize it. Instead they compute the vector-Jacobian product (VJP) directly: given the upstream gradient Ls\frac{\partial L}{\partial s} (how loss changes with each softmax output), get Lz\frac{\partial L}{\partial z} (how loss changes with each softmax input) in one O(n)O(n) pass, using an identity that falls out of the Jacobian formula above:

Lzj=sj(LsjiLsisi)\frac{\partial L}{\partial z_j} = s_j\left(\frac{\partial L}{\partial s_j} - \sum_i \frac{\partial L}{\partial s_i} s_i\right)

Your task: implement softmax_gradient(s, grad_output), where s is a softmax output (already computed, sums to 1) and grad_output is L/s\partial L/\partial s. Return L/z\partial L/\partial z, the same length as s.

Implement it yourself
assert softmax_gradient([0.5, 0.5], [1, 0]) == [0.25, -0.25] assert softmax_gradient([1 / 3, 1 / 3, 1 / 3], [1, 1, 1]) == [0.0, 0.0, 0.0] assert all(abs(a - b) < 1e-9 for a, b in zip(softmax_gradient([0.7, 0.2, 0.1], [1, 0, 0]), [0.7 * (1 - 0.7), 0.2 * (0 - 0.7), 0.1 * (0 - 0.7)])) assert all(abs(a - b) < 1e-9 for a, b in zip(softmax_gradient([0.9, 0.1], [0, 1]), [-0.09, 0.09]))

Next: Sample Mean and Variance From Scratch

Last updated Sep 5, 2026Edit this pageReport an issue