Experimental deep-learning mechanics

Gradient Autopsy

A controlled study of why deep neural networks fail to train. Across 132 instrumented training runs plus a training-free propagation grid, this project measures how depth, activation, initialization, normalization, learning rate and optimizer choice decide whether useful gradients survive the trip back to the input layer.

github.com/Gariyuuu/gradient-autopsy·PyTorch · Fashion-MNIST · MLPs up to depth 100
132training runs
4.7Mmetric rows
100max depth
60failed runs
7gradient extinctions

The central finding

Depth is the exponent, not the cause

Every experiment here is organized around one number: the per-layer propagation factor, the amount by which a single layer multiplies the backward signal.

A network of depth multiplies the gradient by roughly on its way to the input. That single exponent explains the whole corpus. A stack with is fine at depth 10 and fine at depth 100. A stack with is already dead at depth 5.

Depth does not cause gradient failure — it exponentiates it. Every intervention that worked in this corpus moves toward 1 or breaks the product that compounds it. Every intervention that failed left alone.

Gradient log-norm across layer and epoch for four networks: healthy, vanishing, collapsing and exploding
The whole study in one measurement. over (layer, epoch) for four networks. Flat colour is ; the vertical ramp in panels 2 and 3 is a per-layer multiplier below 1 compounding with depth; grey marks layers whose gradient reached exactly zero in float32.

for every activation and initialization studied

Computed by Gaussian quadrature, not a small-signal approximation. He initialization is exactly the choice that puts ReLU at .

10⁻²10⁻¹χ = 110100relu / he1.000relu / xavier0.500relu / N(0,1)1.3e+2relu / orthogonal1.000tanh / he0.840tanh / xavier0.464tanh / orthogonal0.840tanh / N(0,1)8.505sigmoid / xavier0.0448sigmoid / he0.0448sigmoid / N(0,1)1.061gelu / he0.979gelu / xavier0.456

Bars run from . Faded bars are within 12% of 1 — the survivable regime. Note that sigmoid cannot be fixed by initialization: caps for any input distribution.

Experiment A · 48 runs

Where each activation breaks

Width, optimizer, learning rate and seed held fixed; only depth and the nonlinearity move. Each activation gets the initialization the literature recommends for it.

0.00.20.40.60.825102550100chancedepth (hidden layers)best validation accuracy
sigmoid (Xavier)tanh (Xavier)relu (He)gelu (He)
activationsurvives tofails atpropagation factormechanism
sigmoid250.045derivative ceiling, not saturation
GELU10250.979 → 0.5He does not hold at 1
tanh25500.464contractive, forward variance collapses
ReLU25501.000, but dead units accumulate

Sigmoid fails early — and not for the textbook reason

The standard explanation is saturation: units pinned near 0 or 1 where the derivative vanishes. That is not what the instrumentation shows. In the depth-25 sigmoid configuration the measured saturation fraction is 4.9e-5 — essentially no saturated units — while the gradient has already collapsed by 14.8 decades.

The cause is intrinsic and unavoidable: , so no matter where the units sit. The contraction is multiplicative, and depth compounds it. Nothing about the input distribution can rescue it.

Saturation fraction by layer for sigmoid, tanh, ReLU and GELU across depths
Measured saturation by layer. The sigmoid panel is flat at zero at every depth — the failure in the panel is real, but saturation is not its cause.
-16-12-8-4025102550100vanishing thresholddepth (hidden layers)log₁₀ gradient propagation ratio
sigmoid (Xavier)tanh (Xavier)relu (He)gelu (He)

Gradient propagation ratio, . Points are missing where the input-side gradient underflowed to exactly zero and the ratio is undefined.

Experiment B · 20 runs

A factor of two, compounded 100 times

Xavier and He differ by exactly in weight scale. For ReLU that is instead of . At depth 10 the difference is invisible. At depth 25 it decides whether the network trains at all.

ReLU networkXavier (χ = 0.500)He (χ = 1.000)gap
depth 100.8320.836+0.4 pts
depth 250.3120.811+49.9 pts
0.00.20.40.60.825102550100chancedepth (hidden layers)best validation accuracy
ReLU / He (χ = 1)ReLU / Xavier (χ = 0.5)

The two curves are indistinguishable until depth 10, then separate. This is what versus looks like in practice.

GELU: He does not transfer

He initialization is derived for ReLU, which is positively homogeneous. GELU is not, so and the variance-preservation argument does not carry over. In this setup drifts from 0.979 at the first layer toward 0.5 deeper in the stack, and the forward variance collapses by layer 100. This is a statement about the configuration measured here, not about every possible GELU setup.

Experiment C · 12 runs

Normalization rescue

Four architectures that Experiments A and B showed to be broken, re-run with BatchNorm and LayerNorm inserted between every linear layer and its activation. On these four selected cases, BatchNorm rescued 3 of 4 and LayerNorm rescued 1.

broken architecturenoneBatchNormLayerNorm
relu d25 normal_1.00.1050.4850.588
relu d50 xavier_normal0.1000.1180.182
sigmoid d25 xavier_normal0.1160.8380.116
tanh d50 xavier_normal0.2150.7890.176

This is a result about these four failure cases, not a general ranking. BatchNorm rescues the sigmoid stack because it re-standardizes the pre-activations every layer, breaking the product outright. LayerNorm normalizes across features within a sample and does not restore the same statistics here.

Experiments D & E · 52 runs

Why the learning rate cannot fix it

A vanishing gradient is often treated as something a bigger step size can compensate for. It cannot. Sweeping across six decades moves the sigmoid stack's propagation ratio essentially not at all — the geometry of the backward pass does not depend on the step size.

-16-12-8-401e-51e-41e-31e-21e-1110learning ratelog₁₀ gradient propagation ratio
ReLU / He, depth 10 (healthy)sigmoid / Xavier, depth 25 (vanishing)

Six decades of learning rate; the vanishing stack's ratio stays pinned near -15.03. Raising only buys divergence: the ReLU network produces non-finite losses at .

Optimizers, each tuned on its own grid

Comparing optimizers at one shared learning rate only measures which optimizer likes that number, so each gets its own grid and is represented by its best result.

optimizerhealthy net (ReLU/He d10)broken net (sigmoid/Xavier d25)
sgd0.8380.116 chance
sgd_momentum0.8530.116 chance
adam0.8600.105 chance
adamw0.8510.105 chance

An observation about Adam. Once per-element gradients fall below Adam's floor — by default — the normalization stops being scale-invariant and the update collapses toward zero instead of rescaling. That is what was measured here with PyTorch's default ; other implementations or values may behave differently.

Training-free propagation grid

Theory against measurement

No training at all: standardized noise pushed through freshly initialized random networks up to depth 100, measured layer by layer, and compared with the Gaussian variance recursion.

Both expectations are evaluated by quadrature, so the prediction is exact under the Gaussian assumption rather than a small-signal linearization. It tracks the measurement across the full dynamic range the corpus spans — tens of decades of decay, and tens of decades of growth.

Predicted versus measured forward and backward variance over 100 layers for six activation and initialization pairs
Prediction (black) against measurement (coloured). Forward variance on top, backward gradient variance below, over 100 layers, for six activation/initialization pairs.

The residual is a finite-width effect

The recursion is a large-width limit; at finite width a single network's log-variance performs a random walk around it. If the framework is right, the residual should shrink as the network widens — and it does.

0.0010.010.111032641282565121024width nmean |log₁₀ residual| over 100 layers
tanh / Xaviergelu / Herelu / He

tanh/Xavier falls from 0.198 at to 0.006 at . ReLU/He is the marginal case, where the log-variance walk has no restoring drift, so its residual shrinks more slowly and less smoothly.

Diagnosis engine

Automatic autopsy

Every run is diagnosed by a deterministic, rule-based engine — no model, no LLM. Thresholds are declared constants, verdicts are auditable, and the whole corpus can be re-diagnosed from disk without retraining. Across 132 runs it returned 56 healthy, 16 degraded and 60 failed.

AUTOPSY RESULT
--------------
Run: A_depth_activation__matched_sigmoid_d25__5e0b80effe
Status: Failed

Architecture:
25 x 256 sigmoid, init=xavier_normal, norm=none, sgd lr=0.05

Gradient attenuation:
output -> input gradient ratio = 1.58e-15 (log10 = -14.80)
||grad W_first|| = 1.43e-15, ||grad W_last|| = 7.92e-01

Activation propagation:
Var(h_L)/Var(h_1) = 2.89e-01 (log10 = -0.54)

Parameter updates:
first 18 layers update/weight < 1e-07
Parameter updates have stalled in 18/26 linear layers (update/weight < 1e-07); the first 18 layers are frozen.

Learning outcome:
validation accuracy 0.0930 (chance 0.1000), final train loss 2.3173

Findings:
  [critical] vanishing_gradient: Gradient attenuates toward the input: output -> input gradient ratio = 1.58e-15.
  [critical] stalled_updates: Parameter updates have stalled in 18/26 linear layers (update/weight < 1e-07); the first 18 layers are frozen.

Three rules that came from the data

These exist because real experiment output broke simpler assumptions — the part of the work that only shows up once you run the corpus.

Boundaries

What this does not show

Reproduce

Verification in three minutes, or the full corpus in 4.3 hours

The fast path needs no training corpus: it runs the test suite, exercises the entire pipeline on four tiny networks, and re-checks every number quoted in the report against the generated tables.

git clone github.com/Gariyuuu/gradient-autopsy
cd gradient-autopsy && pip install -r requirements.txt && pip install -e .

pytest -q                            # 119 tests
python scripts/run_smoke.py          # end-to-end pipeline check, ~5 s
python scripts/verify_artifacts.py   # corpus + documented-number consistency

The full corpus, if you want it — about 4.3 hours of wall clock on the documented CPU, of which 4.2 h is recorded training time and the rest is the training-free theory grid and initialization audit:

python -m gradient_autopsy.experiments.run_all   # 132 runs
python -m gradient_autopsy.figures               # rebuild all figures
streamlit run app/streamlit_app.py               # interactive explorer

A systems note. Runtime is dominated by the dying networks, not the deep ones. Once gradients enter the float32 subnormal range, CPU arithmetic slows by one to two orders of magnitude — a depth-25 sigmoid run takes 329 s while a depth-50 one takes 13 s, because the deeper network underflows all the way to exactly zero, and zero is fast. Runtime is not monotonic in depth. This is a property of the tested CPU; other hardware handles subnormals differently.