The vanishing gradient
28 min read
The previous lesson, on backpropagation through time (BPTT), ended with a correct gradient and a loose end. Correct, because the numerical probe confirmed it entry by entry; a loose end, because computing it repeats a single move that nobody stopped to examine. To carry the error from one position back to the one before, you multiply it by the recurrent matrix , and in a forty-token sequence that multiplication comes round almost forty times. This lesson measures what repeating the same multiplication that many times in a row does to the gradient, and finds the number in that puts a ceiling on the result.
This is no numerical curiosity: what's at stake is the one reason to prefer a recurrent neural network (RNN) over the network this block opened with. Take a review that starts I doubt this helps anyone and, thirty words later, ends with its rating. That opening I doubt flips the sign of everything after it, but the network can only learn the connection if the gradient computed at the last word survives the long trip to the first ones. If it fades along the way, the network never learns to pin the rating on those opening words, and the memory that defined it ends up, in practice, as a memory of the recent past only.
Before tackling the whole matrix, strip away coordinates until only one is left. With the state is a number, is a number , and carrying the error one step back means multiplying it by . Repeating that step times multiplies it by .
And has no middle ground. If it sinks: with , forty steps leave the error at , twelve orders of magnitude below where it started. If it shoots up: . Only keeps the error the same size, and that's a knife-edge a single point wide: stray a hair either way and you fall off one side or the other. With more than one coordinate, becomes a matrix, and its role splits between two numbers that the argument below keeps apart: one caps the gradient step by step, mask included; the other sets the long-run pace of the matrix multiplied by itself. The explorable shows the second, the spectral radius, for a recurrence with no mask (every slope of the at ). Slide it to either side of and you'll see both halves of the phenomenon: below, the curve dives; above, it goes through the roof.
The product that links two positions
Go back to the previous lesson's backward recurrence and follow it for more than one step. There, each step's error came from the next one's in two moves, transport and mask:
The with the slope of the amounts to multiplying by a diagonal matrix (, with those slopes on the diagonal and zeros everywhere else), so one step of the backward pass multiplies by two matrices: first , then that diagonal. Chain the recurrence from the last step down to some distant step and the pair repeats, once per position:
The product has pairs, one for each step between and (the distance the error travels). And every pair contains the same : this isn't a different layer per position, the way the blocks of the concatenated first layer were in the lesson on why the multilayer perceptron (MLP) fails on sequences, but a single matrix raised, in effect, to the power of the distance. That's the move the previous lesson kept repeating without looking at it.
Why it decays or explodes
A product of matrices is hard to look at directly; its size isn't. Take norms on both sides and use the fact that the norm of a product is never bigger than the product of the norms:
That is now measuring a matrix, not a vector. For any matrix it's defined as
the largest factor by which can stretch a vector's length, taking the worst case over every possible direction. That definition is what makes true for any (multiply both sides of the definition by ), and applying it twice in a row gives : stretches by at most , and then stretches that result by at most a further .
Both factors in each pair have a ceiling. For a diagonal matrix, that maximum is easy to find: stretches each coordinate by its own entry, so the worst case is lining up with the coordinate whose entry is largest in absolute value, and . Here each diagonal entry is a slope of the (one per coordinate of ): it never goes above , and as soon as that coordinate saturates it gets close to . Those are the red bands you already saw in the explorable in the lesson on activation functions. The largest of those slopes across every step from to is therefore also ; call it . The ceiling for comes from the same definition, without the convenience of a diagonal, and the one new fact you need here is that a matrix stretches a vector's length by at most its largest singular value , so multiplying by it times stretches it by at most . Put the two bounds together over the pairs:
There's the exponent, and with it the one condition that holds whatever happens along the way. The error reaching position is at most the error at the end multiplied by raised to the distance. If , the gradient vanishes, with no escape: the ceiling plummets with the distance and the gradient can't rise above it, whatever the masks turn out to be. Since , is enough to guarantee this on its own, and saturation only adds to the margin. The converse doesn't hold. If , the ceiling no longer holds the gradient down and it can explode, but nothing forces it to: that inequality is necessary for an explosion, not sufficient.
The bound is also usually too generous: a given vector rarely gets stretched by the maximum at every single step. What really does when multiplied by itself over the long run is measured by a different number, its spectral radius (the largest modulus among its eigenvalues), which never exceeds . In a recurrence with no mask, every slope at , the product is a power of , and there really does decide: below the gradient sinks, above it shoots up, and in between there's nothing but that knife-edge. It's the number the explorable let you move.
Once the masks get in the way, stops being a guarantee: the product alternates with diagonals and is no longer the power of a single matrix. What still holds then is , which is why that's the real condition. You won't need to compute any eigenvalues in this lesson; what you do need to know is that exists, that it sets the pace of the matrix on its own, and that it never exceeds .
See why the spectral radius stops deciding once the masks come in
In a recurrence with no mask, the product is , and in the long run tends to the spectral radius as grows. That's why, in that case, guarantees vanishing and makes the norm of the power grow without limit, even though over short distances the bound with runs ahead of the real pace.
With the , what repeats is no longer but each pair, and a mask can switch off precisely the coordinate that was holding the growth in check. Take . Its square is the zero matrix, so and its powers die out in two steps. But if the mask is at every step, each pair is , whose spectral radius is , and the product grows like . That doesn't contradict the real condition: here and , and a of never promised the gradient would sink.
Check it on a real matrix, without reducing it to a single coordinate: numpy.linalg has svd for
and eig for the eigenvalues that give .
W = np.array([[0.5, 4.0],
[0.0, 0.5]])
sigma_max = np.linalg.svd(W, compute_uv=False)[0] # largest singular value
rho = np.max(np.abs(np.linalg.eig(W)[0])) # spectral radius
print("sigma_max = %.4f" % sigma_max)
print("rho = %.4f" % rho)
print()
print(" d ||W^d|| ||W^d||**(1/d)")
for d in [1, 5, 10, 20, 40]:
Wd = np.linalg.matrix_power(W, d)
norm_d = np.linalg.svd(Wd, compute_uv=False)[0]
print("%2d %9.2e %14.4f" % (d, norm_d, norm_d ** (1 / d)))
The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.
For this , but : the singular-value bound overestimates the pace of the power by a factor of eight. The table bears this out. At the raw norm is (the two coincide by construction), and at it's still above (the power can grow for several steps even though guarantees it sinks in the long run). By the raw norm is tiny and has dropped below , closing in on : actual numbers confirming what the argument above could only assert. And the two numbers answer different questions: the power on its own sinks, but with , this guarantees nothing once the masks come in.
Seeing it and clipping it in NumPy
The first cell in this section boils everything down to the one-coordinate case and looks at the raw exponent; the second tackles the half of the problem that does have a cheap fix.
The scalar case isn't a toy: it's the formula above with , where , and are all the same number. Run it and compare the middle row () with the other two.
# back multiplies it by w**d. The tanh mask (slope <= 1) can only
# shrink it further, so w = 1 is already the most favourable case.
print(" w d=10 d=20 d=40")
for w in [0.5, 1.0, 1.5]:
print("%.1f %9.2e %9.2e %9.2e" % (w, w ** 10, w ** 20, w ** 40))
The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.
The first row sinks, the last shoots up and the middle one doesn't move, exactly as the start of the lesson said it would. With , the error arrives forty steps back at of its original size, and no gradient descent can learn from a signal that small. And that's the best case: as soon as the state saturates, the mask pushes it lower still.
Explosion, on the other hand, has a straightforward answer. If the problem is that the gradient's norm grows with nothing to rein it in, give it a ceiling: fix a threshold and, whenever exceeds it, rescale the gradient to bring it back to exactly that size. This is gradient clipping:
The with is the key: if the gradient is already small, the factor equals and leaves it alone; it only acts on a gradient that has gone over the limit, and it does so by multiplying it by a positive number, which changes its length but not its direction. Check it.
rng = np.random.default_rng(1)
kappa = 5.0 # the ceiling on the norm
def clip(g):
n = np.linalg.norm(g)
return g * min(1.0, kappa / n), n # factor <= 1: shrinks or leaves alone, never turns
for name, g in [("normal ", rng.normal(size=100) * 0.3),
("exploded", rng.normal(size=100) * 3.0)]:
g_clip, n = clip(g)
cosine = float(g @ g_clip) / (n * np.linalg.norm(g_clip))
print("%s ||g|| = %6.2f -> ||clipped|| = %.2f (cosine with g: %.4f)"
% (name, n, np.linalg.norm(g_clip), cosine))
The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.
The normal gradient passes through untouched; the exploded one, with a norm of almost , comes out clipped to exactly , and its cosine with the original is (same direction, different size). Clipping rescues training when the gradient explodes, and it costs almost nothing. What it doesn't do, and this deserves saying out loud, is anything at all about vanishing: a gradient that has already shrunk to has a tiny norm, the equals , and it stays exactly as it was.
Test your intuition
Four questions: what makes the gradient vanish or explode, what clipping fixes and what it doesn't, whether the mask can save the day, and what's lost when it can't.
What makes an RNN's gradient vanish or explode as it travels back through a long sequence?
Gradient clipping rescales when the gradient's norm exceeds a threshold . Which problem does it solve, and which does it leave untouched?
The mask that appears at every step of the backward pass is the slope of the . If the largest singular value of satisfies , the gradient vanishes. Can the mask rescue it?
What does an RNN lose when the gradient linking the end of the sequence to the start has vanished?
Clipping caps the explosion, but it leaves the other half of the problem where it was. A gradient that has been multiplied down towards zero doesn't recover by being multiplied by , and no choice of fixes both at once: pushing below so the gradient can't explode is exactly what guarantees that it vanishes. The problem isn't the weights, it's the path: any path that multiplies by the same matrix at every step ends up at one of the two extremes.
The way out is to change the path. What's needed is a route along which the state moves from one step to the next by adding, rather than passing through yet again: across a sum, the gradient flows without being multiplied once per position, so it neither sinks nor shoots up. That additive path is the cell state of the long short-term memory (LSTM), and building it one gate at a time, each gate fixing a specific failure of the vanilla RNN, is the next lesson, on the LSTM and its gates.
Further reading2 sources · 1 paper, 1 book
Where this lesson comes from, and where to go next. None of it is needed to carry on with the course.
- On the difficulty of training Recurrent Neural Networks
Both halves of this lesson come from here: the bound with the slope γ that guarantees vanishing (the spectral radius only decides in the linear case), and the clipping that caps the gradient's norm at a threshold. It doesn't get into gates.
- Gradient Flow in Recurrent Nets: the Difficulty of Learning Long-Term Dependencies
Its §2 proves what you see here on a plot: the error travelling back through the sequence decays or grows exponentially with the number of steps. It gathers the 1991 and 1994 proofs; its §4 only surveys remedies, the LSTM among them.