NeuronCanvas
Neural Networks
Backpropagation

Vanishing Gradients

What happens when backpropagation's chained derivatives shrink to nearly nothing in early layers, and why it matters.

Step 01 - The break

Five layers, and the bottom one never moves

Five layers. Every weight a perfectly ordinary 0.6. One backward pass.

gradient arriving at the output ......... 1.000000
through layer 5 ......................... 0.145437
through layer 4 ......................... 0.021150
through layer 3 ......................... 0.003073
through layer 2 ......................... 0.000444
through layer 1 ......................... 0.000061
and what each layer’s weight actually receives, at lr = 0.1
layer 5 update 0.014237254 = 2.372876% of the weight
layer 1 update 0.000010161 = 0.001693% of the weight
layer 5 moves 1401 times further than layer 1, from the same
backward pass

Nothing was misconfigured. The weights are not large, the learning rate is not wrong, and the network is only five layers deep. It will train, the loss will fall, and the bottom half will still be roughly the random numbers it started with.

Step 02 - Before this page
Step 03 - The stage

Drag the depth slider from 2 to 8 and watch the last bar fall through four gridlines. Then drag the weight scale up past 1.0 and watch the bars flip direction. Both of those are the same multiplication.

Preview frame from the "Vanishing Gradients" animation0:15

What this animation shows

A bar chart of gradient magnitude per layer starts flat and near zero, then grows into a shape where each layer closer to the input is dramatically shorter than the last, captioned "vanishing: the signal shrinks toward the early layers." The chart then swaps to a mirror-image version where early-layer bars tower over the later ones, captioned "exploding: the same compounding, running the other way." This shows both failure modes the page covers as two sides of the same chained-multiplication problem.

10001001010.10.010.0010.0001outputlayer 5layer 4layer 3layer 2layer 1

This axis is log-scaled and centered on 1.0 - each gridline above center is a ×10 growth, each gridline below is a ×10 drop, because the real values shrink or grow so fast that on an ordinary axis every bar past the second one would look like it's touching zero or flying off the top.

per-layer factor (sigmoid derivative × weight scale 1.0) = 0.1966
after 5 layers: 0.1966^5 = 2.938e-4 (vanishing)

At weight scale ≈1×, each layer's factor sits right at the tipping point between the two regimes - nudge the slider either direction to see which way it goes.

Step 04 - One question first

Five sigmoid layers. What fraction of the gradient reaches layer 1?

  • aAbout a tenth
  • bAbout a hundredth
  • cAbout a thousandth
  • dAbout a hundred-thousandth
Commit to a guess, then open this

About a hundred-thousandth - 0.0000610, which is 0.0061%. And the reason it is that small rather than the 0.00032 usually quoted is worth having straight away. The per-layer factor is not sigmoid’s slope on its own. Each layer multiplies by the slope and by the weight. Here the slope is about 0.24 and the weight is 0.6, so the factor is 0.145, not 0.24. A figure quoted as a power of the slope alone has told you half of a multiplication.

Step 05 - Plain explanation

The backpropagation walkthrough showed that the gradient reaching an early layer is a product of every local slope between that layer and the output. Multiplying several numbers that are each less than 1 makes the result smaller every single time you add another one to the chain.

Sigmoid’s slope has a hard ceiling of 0.25, reached only at z = 0, and it falls away fast on either side: 0.1966 at z = 1, 0.1050 at z = 2, 0.0031 at z = 5.77. That gives two figures worth holding onto. In the best imaginable case, where every layer sits exactly at the peak and every weight is exactly 1, five layers give 0.25⁵ = 0.00098, under 0.1% of the original gradient; that is the optimistic bound and it is already bad. In the case usually quoted as typical, a pre-activation of about 1 with weights near 1, the slope is 0.1966, close enough to 0.2 that the figure gets written 0.2⁵ = 0.00032, and that is exactly what the demo shows at its default settings. The trace in rung 2 comes in lower still, at 0.0000610, sixteen times below the optimistic bound and five times below the typical one, for no reason other than its weights being 0.6 rather than 1. Sigmoid cannot avoid any of this; the ceiling is built into its shape.

Chinese whispers with a volume knob

Five people in a line. Each one passes the message on at 20% of the volume they heard it. By the front there is nothing to act on - not a garbled message, an inaudible one. Now imagine each person passes it on three times louder instead: by the front it is 243 times the original, which is not a message either. The people are layers, the volume multiplier is the slope times the weight, and the person at the front is the first layer, which is the one deciding what raw features the whole network is built on.

Where it breaks downIn the game the message also gets distorted. Here it is exactly the right message, scaled to nothing, which is worse - a distorted message can still be acted on, a zero cannot.

Step 06 - The depth ladder
WordsOne multiplication per layer, done five timesWhy depth is a multiplication problem, and the two directions it fails in.Rung 01

The gradient reaching an early layer is a product of every local slope between that layer and the output. Multiplying several numbers that are each less than 1 makes the result smaller every single time you add another one to the chain.

Sigmoid’s slope has a hard ceiling of 0.25, reached only at z = 0, and it falls away fast on either side: 0.1966 at z = 1, 0.1050 at z = 2, 0.0031 at z = 5.77. The optimistic bound over five layers is 0.25⁵ = 0.00098; the usually-quoted typical case is 0.2⁵ = 0.00032, which is what the demo shows at its defaults.

ReLU’s slope is exactly 1 whenever it’s active, so a chain of active ReLU units multiplies 1×1×1×1×1=1, no depth-driven shrinkage at all. But that’s not the whole story: a ReLU neuron that’s always inactive has slope exactly 0, and once that happens it stops learning permanently, a “dead ReLU.” ReLU trades gradual vanishing for a different failure mode, not a free pass.

Vanishing has a mirror image: exploding. The chain rule doesn’t just multiply activation derivatives together - at each layer it also multiplies in that layer’s outgoing weight. If the weights are large, the per-layer factor can climb above 1, and the exact same compounding multiplication that made the gradient vanish now makes it explode.

NumbersA real backward pass through five real layersThe actual arithmetic, forward and backward, plus what each layer’s weight really receives.Rung 02

Before any powers of anything, here is a real gradient shrinking in a real network, computed the same way as the backpropagation walkthrough. The network is deliberately as simple as it can be while still being deep: five layers, one sigmoid neuron each, every weight set to 0.6 (positive, so blue), every bias 0, input 1.0.

Worked example - forward pass through five sigmoid layers, w = 0.6, x = 1.0
layer 1 z = 0.6 * 1.000000 = 0.600000 a = 0.645656 slope a(1-a) = 0.228784
layer 2 z = 0.6 * 0.645656 = 0.387394 a = 0.595655 slope = 0.240850
layer 3 z = 0.6 * 0.595655 = 0.357393 a = 0.588409 slope = 0.242184
layer 4 z = 0.6 * 0.588409 = 0.353046 a = 0.587356 slope = 0.242369
layer 5 z = 0.6 * 0.587356 = 0.352414 a = 0.587203 slope = 0.242396

Now backward. Start with a gradient of 1.0 arriving at the output, and pass it through each layer. The gradient travels from the last layer to the first, so this block and the lists below it run layer 5 first, the reverse of the forward table. Each pass multiplies by that layer’s slope, then by the weight:

Worked example - backward pass, gradient arriving at the output = 1.000000
through layer 5 * 0.242396 * 0.6 -> 0.145437
through layer 4 * 0.242369 * 0.6 -> 0.021150
through layer 3 * 0.242184 * 0.6 -> 0.003073
through layer 2 * 0.240850 * 0.6 -> 0.000444
through layer 1 * 0.228784 * 0.6 -> 0.000061
the per-layer factors are slope * weight, listed layer 5 first:
0.145437, 0.145421, 0.145310, 0.144510, 0.137271
and their product is exactly the number above, 0.0000610 = 0.0061%

Five layers, and 99.994% of the signal is gone. Nothing unusual was done to produce that; the weights are a perfectly ordinary 0.6 and the network is only five layers deep.

Look at what the per-layer factor is actually made of, because this is where most explanations quietly cut a corner. It is 0.145, not 0.24. Sigmoid’s slope contributes about 0.24, and the weight contributes 0.6, and it is their product that gets multiplied five times.

That also explains why the demo on this page reports a different number. It runs at a weight scale of 1.0 and a pre-activation of 1.0, so its per-layer factor is sigmoid’s slope on its own, sigmoid'(1.0) = 0.1966. This trace uses a weight of 0.6, so its factor carries that 0.6 as well and comes out at 0.145. Neither is wrong; they are two settings of the same knob, and the demo’s weight-scale slider is exactly the knob that moves between them.

“The training signal has all but disappeared” is worth converting into something you can picture. Here is what each layer’s weight actually receives from that single backward pass, at lr = 0.1:

Worked example - the update each layer’s weight receives, same run, lr = 0.1
layer 5 dL/dw = 0.14237254 update 0.014237254 = 2.372876% of 0.6
layer 4 dL/dw = 0.02074114 update 0.002074114 = 0.345686%
layer 3 dL/dw = 0.00305102 update 0.000305102 = 0.050850%
layer 2 dL/dw = 0.00047791 update 0.000047791 = 0.007965%
layer 1 dL/dw = 0.00010161 update 0.000010161 = 0.001693%
layer 5’s weight moves 1401 times further than layer 1’s,
from the same backward pass

That number, 1,401×, is the practical meaning of vanishing gradients. The network is not broken and it is not frozen; the last layer is learning at a normal speed while the first layer is effectively standing still. And the first layer is the one deciding what raw features everything else is built on. You get a network that trains, produces a falling loss, and has a bottom half that is still essentially the random numbers it was initialised with.

The fix has to change the per-layer factor, so it is worth seeing what each activation function contributes to it. Same five-layer network, same w = 0.6, same input, and the factor lists again run layer 5 first:

Worked example - identical network, three activations, factor = slope * weight
sigmoid factors 0.145437 0.145421 0.145310 0.144510 0.137271
gradient reaching layer 1 = 0.0000610
tanh factors 0.597375 0.592686 0.579517 0.541773 0.426947
gradient reaching layer 1 = 0.0474601 778x better
ReLU factors 0.600000 0.600000 0.600000 0.600000 0.600000
gradient reaching layer 1 = 0.0777600 1275x better
and ReLU with w = 1.0 instead of 0.6:
factors 1.000000 * 5, gradient reaching layer 1 = 1.0000000 no loss at all

tanh beats sigmoid because its maximum slope is 1.0 rather than 0.25, four times higher, and that factor of four compounds. ReLU’s slope is exactly 1 wherever it is active, so its per-layer factor is the weight and nothing else, and the last row is the case everyone quotes: with weights near 1, the gradient passes through untouched. This is the entire argument for the ReLU family, now with the numbers behind it, and the dead-ReLU caveat is the price.

And the mirror image, in numbers. A per-layer factor of just 3, which one bigger weight is all it takes to produce, compounding over 5 layers gives 3⁵ = 243. Over 10 layers it is 3¹⁰ = 59,049. The update to a weight becomes a wild, oversized jump instead of a careful nudge, and training can diverge outright - loss shooting up instead of down, or turning into NaN.

PictureEight bars on a log axisEvery claim in rung 2, as a slider you can move.Rung 03

The axis in Figure 02 is log-scaled and centred on 1.0, so every gridline is a factor of ten and growth and shrinkage are mirror images rather than two unrelated pictures. At the defaults - sigmoid, depth 5, weight scale 1.0 - the per-layer factor reads 0.1966 and the final value 2.9e-4, which is the 0.2⁵ figure in rung 1 rather than the trace’s 0.0000610, because this widget runs at weight scale 1.0 and the trace runs at 0.6. Both are correct and rung 2 says why.

Now change one thing at a time. Depth 5 to 8: the last bar drops three more gridlines. Sigmoid to tanh: every bar rises, because tanh’s slope at the same point is 0.41997 rather than 0.19661, more than double. Tanh to ReLU: the bars go flat, all at exactly 1.0, because ReLU’s slope is exactly 1 and the factor is the weight scale and nothing else. Tick ‘Make some neurons dead’: the bars go to zero and stay there, and the readout says “blocked by a dead neuron” - not small, blocked.

The weight scale slider is what turns this from two separate demos into one: at 1×, it reproduces the vanishing behavior from the top of this page exactly. Push it below 1× and the bars shrink faster - smaller weights, faster vanishing. Push it above 1× and the bars flip to growing instead - the chart is centered on 1.0 specifically so growth and shrinkage read as mirror images of the same underlying multiplication, not two unrelated phenomena.

EquationThe per-layer factor, and clippingOne expression, and the one line of arithmetic that caps an explosion.Rung 04

There is one expression on this page and everything else is a consequence of it. The gradient reaching layer k from the output is the product, over every layer in between, of that layer’s activation slope times that layer’s outgoing weight. Two ingredients per layer, multiplied, then multiplied again across layers.

grad_at_layer_k = product over layers j > k of ( slope_j * w_j )

The standard fix for the exploding side is blunt and effective: gradient clipping. Before applying an update, check the size (the norm) of the gradient vector; if it’s above some threshold, rescale the whole vector down to that threshold before taking the step. Direction is preserved, only the size is capped. It doesn’t fix whatever’s causing the explosion, but it stops one enormous update from wrecking days of otherwise good training, which in practice is usually enough.

Clipping is one line of arithmetic, so here it is performed. The “norm” of a gradient is the same length measurement from the gradient descent page: square every component, add, take the square root.

Worked example - clipping by global norm, threshold 5.0
gradient g = [ +12.0, -9.0, +4.0 ]
norm = sqrt(12^2 + 9^2 + 4^2) = sqrt(241) = 15.524175
15.524175 > 5.0, so clip
scale = 5.0 / 15.524175 = 0.322078
clipped g = [ 12.0 * 0.322078, -9.0 * 0.322078, 4.0 * 0.322078 ]
= [ +3.864940, -2.898705, +1.288313 ]
new norm = sqrt(14.938 + 8.402 + 1.660) = 5.000000
direction check, ratios to the first component:
before clipping [ 1.000000, -0.750000, +0.333333 ]
after clipping [ 1.000000, -0.750000, +0.333333 ] identical
and a gradient that is already small, [ +1.0, -2.0, +2.0 ], has norm
exactly 3.0, below the threshold, so it passes through untouched

Every component was multiplied by the same 0.322078, which is why the direction is preserved exactly. That is the difference between clipping by norm and clipping each component separately at some value: the latter changes the direction of the update, the former only its length. Norm clipping is the one used in practice, and clip_grad_norm_ is the function you will see in real training code.

General caseThe whole toolkitFive fixes, four of them already on this site, and why real networks use them together.Rung 05

Clipping stops explosions and does nothing for vanishing. Four other things do, and all of them appear elsewhere on this site, which is worth collecting in one place because a reader finishing this page is entitled to know that the problem has been solved rather than just described.

better activations ReLU and its family, per-layer factor 1 instead
of 0.25. The single biggest change.
scaled initialisation He and Xavier size the weights so the per-layer
factor starts near 1 rather than near 0.145.
normalization layers batch norm rescales each layer's inputs back to a
fixed spread every forward pass, so the compounding
cannot run away in either direction.
residual connections add a shortcut path that skips a block entirely.
The gradient arrives through the shortcut with a
factor of exactly 1, so it cannot vanish no matter
how many blocks are stacked.
gating LSTM and GRU cells keep a path through time whose
factor is near 1 by construction.

Two of those have their own pages here: scaled initialisation and normalization layers. Residual connections are covered in the convolutional networks module.

The reason to know all five rather than just the first is that they attack the same multiplication from different sides, and deep networks in practice use several at once. A modern residual network uses He initialisation, ReLU-family activations, normalization layers and skip connections together, and each one is aimed at the same product of per-layer factors that this page has been computing.

Step 07 - Why this and not that

Why this and not that

If sigmoid is this bad, why was it ever used?

Because for a two- or three-layer network it is fine - 0.1966² = 0.039, which is a real loss and a survivable one - and because it outputs a probability, which is genuinely what you want at the output layer. Sigmoid is still correct for binary output. It is its use in every hidden layer of a deep network that the numbers here rule out, and that use is precisely what stopped in about 2011.

Why not just use a much larger learning rate for the early layers?

People have tried, and it is fragile: the factor by which the gradient shrank is not known in advance, it differs per layer, and it changes as training moves the weights. You would be tuning a per-layer learning rate against a moving target. The fixes in rung 5 change the factor itself rather than compensating for it, which is why they won.

Is exploding just vanishing with a bigger number?

Same mechanism, opposite side of 1, and completely different symptoms. Vanishing is silent: the loss falls, nothing errors, and the early layers quietly do not learn. Exploding is loud: one enormous update, then NaN, then the run is over. The loud one is far easier to diagnose, which is why clipping - a blunt fix for the loud problem - is standard, and there is no equivalent one-liner for the quiet one.

Does clipping fix vanishing too?

No, and rung 5 opens by saying so. Clipping caps a gradient that is too large; it does nothing whatsoever to a gradient that is too small. A threshold of 5.0 leaves a gradient of 0.000061 exactly as it was - the block in rung 4 shows a small gradient passing through completely untouched, which is the design.

If residual connections solve it, why is anything else needed?

Because a residual connection gives the gradient a path with a factor of exactly 1; it does not remove the other path, which still has all the same multipliers. Deep networks in practice stack several fixes: He initialisation, ReLU-family activations, normalization layers and skip connections, all aimed at the same product. Each one moves the factor closer to 1 by a different route.

Name origins
Vanishing gradient
Named in Sepp Hochreiter’s 1991 diploma thesis, which analysed exactly this product and is the direct ancestor of the LSTM. The word is literal: the gradient does not become wrong, it becomes absent.
Exploding gradient
The mirror term, coined for the same product on the other side of 1. Both names describe what the number does, not what causes it, which is why they get used for symptoms with several different causes.
Clipping
From signal processing, where a signal that exceeds a range is clipped flat at the boundary. Gradient norm clipping is a gentler relative: it scales the whole vector down rather than flattening any component, which is why the direction check in rung 4 matters.
Norm
Latin norma, a carpenter’s square: a standard to measure against. Here it is the ordinary distance formula, the same one the gradients page used to combine two numbers into one.
Dead ReLU
A unit whose input is negative for every example in the dataset. Its slope is exactly 0, so its gradient is exactly 0, so its weights never change, so its input stays negative. “Dead” rather than “sleeping” because there is no mechanism that can wake it.
Residual connection
The block learns the residual, the difference between its input and the desired output, rather than the output itself. The shortcut carries the input forward unchanged, which is also what gives the gradient a path with factor 1.
Step 08 - Where people go wrong
10001001010.10.010.0010.0001outputlayer 5layer 4layer 3layer 2layer 1

This axis is log-scaled and centered on 1.0 - each gridline above center is a ×10 growth, each gridline below is a ×10 drop, because the real values shrink or grow so fast that on an ordinary axis every bar past the second one would look like it's touching zero or flying off the top.

per-layer factor (sigmoid derivative × weight scale 1.0) = 0.1966
after 5 layers: 0.1966^5 = 2.938e-4 (vanishing)

At weight scale ≈1×, each layer's factor sits right at the tipping point between the two regimes - nudge the slider either direction to see which way it goes.

Step 09 - Practice
  1. 01
    Find the shallowest sigmoid network at weight scale 1.0 that loses more than 99% of its gradient.
    Hint

    The per-layer factor is 0.1966. When does 0.1966^k fall below 0.01?

    Answer

    Depth 3, where 0.1966³ = 0.0076, so 99.24% is gone. Depth 2 leaves 3.87%. Three layers is not a deep network by any standard, and sigmoid has already destroyed 99% of the training signal. That is the fact the ReLU family was adopted for.

  2. 02
    Find every configuration in this widget whose per-layer factor is exactly 1.0000, and say what they have in common.
    Hint

    Read the factor line while changing the activation and the weight scale.

    Answer

    Exactly one: ReLU at weight scale 1.0. Sigmoid would need a weight scale of 5.09 and tanh 2.38; the slider stops at 3.0, so tanh at 2.4 is the closest either gets and it reads 1.0079. What ReLU-at-1.0 has that the others do not is a slope of exactly 1, so the factor is the weight scale alone with nothing multiplied into it. That is the whole argument for the ReLU family in one readout, and it is why He initialisation aims at exactly this configuration.

  3. 03
    Make the gradient explode with tanh, then explain why you cannot do it with sigmoid.
    Hint

    Compare each activation’s slope at the widget’s pre-activation of 1.0 against the slider’s maximum.

    Answer

    tanh explodes from weight scale 2.5 upward - factor 1.0499, and at 3.0 it is 1.2599, giving 6.350 over eight layers. Sigmoid cannot be made to explode at all here: sigmoid'(1.0) = 0.19661, so the weight scale would have to exceed 1/0.19661 = 5.09 and the slider stops at 3.0, where the factor is still only 0.5898. Sigmoid is not merely worse than tanh - it is stuck on the vanishing side of 1 across the entire range this widget offers, which is a fair summary of its position in deep networks.

  4. 04
    Reproduce the page’s own trace as closely as this widget allows, and account for the difference.
    Hint

    The trace in rung 2 gives 0.0000610 at w = 0.6. This gives 2.28e-5. Which quantity differs?

    Answer

    The widget computes the slope at a fixed pre-activation of 1.0, giving 0.19661 × 0.6 = 0.1180, and 0.1180⁵ = 2.28e-5. The trace computes each layer’s real pre-activation from the forward pass - 0.600000, 0.387394, 0.357393, 0.353046, 0.352414 - where sigmoid’s slope is nearer 0.24, giving per-layer factors around 0.1454 and a product of 6.10e-5. The trace’s factors are larger because its pre-activations are closer to zero, where sigmoid is steepest. Both are correct; the widget is a fixed-point approximation and the trace is the real forward pass, and knowing which is which is the point of this task.

  5. 05
    Perform the clipping arithmetic in rung 4 on a different gradient and confirm the direction is preserved.
    Hint

    Norm, then scale, then check the ratios between components.

    Answer

    Take g = [+6.0, −8.0, 0.0] with threshold 5.0. Norm is √(36+64) = 10.0, above the threshold, so scale by 5.0/10.0 = 0.5 and the clipped vector is [+3.0, −4.0, 0.0] with norm exactly 5.0. Ratios to the first component: [1.0, −1.3333, 0.0] before and [1.0, −1.3333, 0.0] after. Every component was multiplied by the same 0.5, which is why the direction survives. Clip each component separately at 5.0 instead and you would get [5.0, −5.0, 0.0], a ratio of [1.0, −1.0, 0.0] - a different direction, and a different update. That is why clip_grad_norm_ and not clip_grad_value_ is the standard.

Step 10 - Seen in the wild
  • LSTM, 1997Hochreiter and Schmidhuber designed the LSTM cell specifically to defeat this multiplication, after Hochreiter’s 1991 thesis analysed it. Its constant-error path is a route through time whose per-step factor is near 1 by construction - the same fix as a residual connection, six years earlier and in the other direction.
  • LLM trainingAlmost every large language model’s training loop calls clip_grad_norm_ on model.parameters() with a threshold of 1.0. That single line is the block in rung 4, run once per step, for months.
Step 11 - Memory anchor

Whisper a message through five people who each pass on a fifth of what they heard. The person at the front hears nothing, and they are the one deciding what the message was about.

Change the fraction to one and the message arrives intact. Every fix on this page is a way of changing the fraction.

Step 12 - The next break

This page has been about the gradient’s journey backward through a network’s hidden layers, and every fix in rung 5 is about the middle of the network. None of them says anything about the two ends. The first layer receives raw data whose scale you did not choose, and the last layer has to produce an answer in whatever shape the task demands - a number, a probability, one of a thousand categories - and the activation you put there is not a free choice. Pick the wrong one and a perfectly healthy backward pass arrives at an output layer that cannot express the answer at all, however well it was trained.

Output Activations