Chapter 3 · Section 3.3 · 90 min

Recurrent language models

The MLP language model of §3.2 reads a fixed window of n−1n-1 tokens with separate weights for each slot. Language does not respect windows. In "the keys that I left on the table by the door last night are gone", the verb agrees with keys, twelve words back, with a singular noun (night) right next to it. A 5-gram model cannot see it, and widening the window means more weights, separate for every position.

Here is a different design. Read the sentence one token at a time, like a person does, and keep a running summary in a vector. At each step, combine the summary so far with the new token to get the next summary, using the same function every time. The summary is a memory of unbounded history, and the parameters do not grow with sentence length. This is a recurrent neural network, the idea Elman (1990) made popular as the simple recurrent network, and the architecture that ruled language modeling from Mikolov et al. (2010) until the Transformer.

§0.8 sketched the recurrence, the product of Jacobians, and the gate in a few paragraphs. This section does it properly. It builds an RNN language model, derives backpropagation through time and checks it numerically, and then does the analysis every interviewer expects, namely why gradients through a recurrence vanish or explode, what the eigenvalues and singular values of the recurrent matrix have to do with it, and how the LSTM and GRU gates fix it. The linear algebra it leans on (eigendecomposition, spectral radius, singular values, and operator norms) is in §0.10.

A recurrent network reads one token at a time

At step tt the network looks up the embedding xt=Ewt∈Rd\mathbf x_t = \mathbf E_{w_t} \in \R^{d} of the current token, and updates its hidden state ht∈Rdh\mathbf h_t \in \R^{d_h},

ht=tanh⁡ ⁣(Whh ht−1+Wxh xt+bh),h0=0,\mathbf h_t = \tanh\!\left(\mathbf W_{hh}\,\mathbf h_{t-1} + \mathbf W_{xh}\,\mathbf x_t + \mathbf b_h\right), \qquad \mathbf h_0 = \mathbf 0,

with Whh∈Rdh×dh\mathbf W_{hh} \in \R^{d_h \times d_h}, Wxh∈Rdh×d\mathbf W_{xh} \in \R^{d_h \times d}, and bh∈Rdh\mathbf b_h \in \R^{d_h}. To predict the next token it reads the state out through a softmax,

q(wt+1∣w≤t)=softmax⁡ ⁣(Why ht+by),Why∈RV×dh.q(w_{t+1} \mid w_{\le t}) = \operatorname{softmax}\!\left(\mathbf W_{hy}\,\mathbf h_t + \mathbf b_y\right), \qquad \mathbf W_{hy} \in \R^{V \times d_h}.

In plain words, the new memory is a squashed mix of the old memory and the new word, and the prediction is read off the memory. The loss is the same next-token cross-entropy as before, summed or averaged over all positions, so a sequence of T+1T+1 tokens gives TT predictions and one loss.

Unrolled in time, the network is a very deep feed-forward network with one layer per token, and every layer shares the same weights.

 target:      w2          w3          w4
               ↑           ↑           ↑
            softmax     softmax     softmax
               ↑           ↑           ↑
  h0  ──W_hh──▶ h1 ──W_hh──▶ h2 ──W_hh──▶ h3
                ↑           ↑           ↑
              W_xh        W_xh        W_xh
                ↑           ↑           ↑
 input:        w1          w2          w3

A scalar example shows what "memory" means. Take ht=tanh⁡(0.5 ht−1+xt)h_t = \tanh(0.5\, h_{t-1} + x_t) and feed in x=(1,0,0,0)x = (1, 0, 0, 0). Then h1=tanh⁡(1)=0.762h_1 = \tanh(1) = 0.762, h2=tanh⁡(0.381)=0.363h_2 = \tanh(0.381) = 0.363, h3=tanh⁡(0.182)=0.180h_3 = \tanh(0.182) = 0.180, and h4=0.090h_4 = 0.090. The single input echoes forward, halving at every step because the recurrent weight is 0.5. With a recurrent weight above 1 the echo would grow until the tanh caps it. Keep this picture in mind, because the whole vanishing-gradient story is this echo, run backward.

Python · cell 1

As in §3.2, the code stores each matrix transposed (Wxh has shape (d, dh)), so that row vectors and minibatches multiply on the left. The equations and the code compute the same thing.

Warm-up

Forward pass of an RNN language model

Implement rnn_forward(P, ids, h0). P holds E (V, d), Wxh (d, dh), Whh (dh, dh), bh (dh,), Why (dh, V), by (V,). The sequence ids has length T+1T+1. Feed tokens ids[0] to ids[T-1] as inputs, predict ids[1] to ids[T], and return (loss, H) where loss is the mean cross-entropy over the TT predictions and H stacks h1,…,hT\mathbf h_1, \dots, \mathbf h_T into shape (T, dh).

Hidden tests, hints, and AI help, from $1.99 a month.

Backpropagation through time

To train the RNN we need the gradient of the loss with respect to the shared weights. Unroll the network over the sequence, and it is an ordinary computation graph, so ordinary backpropagation applies. Because the same Whh\mathbf W_{hh} appears at every step, its gradient is the sum of its gradients at every step. This is backpropagation through time (BPTT), named and explained by Werbos (1990).

The only new ingredient is that the hidden state ht\mathbf h_t influences the loss through two routes, through its own prediction at step tt and through the next state ht+1\mathbf h_{t+1}. So the error arriving at ht\mathbf h_t is a sum. Let L=∑tℓt\mathcal L = \sum_t \ell_t, write pt\mathbf p_t for the predicted distribution at step tt and yty_t for its target (the token wt+1w_{t+1}), write hˉt=∂L/∂ht\bar{\mathbf h}_t = \partial\mathcal L/\partial\mathbf h_t for the error arriving at the state (the adjoint notation of §0.3), and let δt\boldsymbol\delta_t be the error at the pre-activation inside the tanh. Run the recursion backward from t=Tt = T with δT+1=0\boldsymbol\delta_{T+1} = \mathbf 0,

hˉt=∂L∂ht=Why⊤(pt−eyt)+Whh⊤δt+1,δt=hˉt⊙(1−ht⊙ht).\bar{\mathbf h}_t = \frac{\partial \mathcal L}{\partial \mathbf h_t} = \mathbf W_{hy}^\top(\mathbf p_t - \mathbf e_{y_t}) + \mathbf W_{hh}^\top \boldsymbol\delta_{t+1}, \qquad \boldsymbol\delta_t = \bar{\mathbf h}_t \odot (1 - \mathbf h_t \odot \mathbf h_t).

Then the weight gradients collect one outer product per step,

∂L∂Whh=∑t=1Tδt ht−1⊤,∂L∂Wxh=∑t=1Tδt xt⊤,∂L∂Why=∑t=1T(pt−eyt) ht⊤,\frac{\partial \mathcal L}{\partial \mathbf W_{hh}} = \sum_{t=1}^{T} \boldsymbol\delta_t\, \mathbf h_{t-1}^\top, \qquad \frac{\partial \mathcal L}{\partial \mathbf W_{xh}} = \sum_{t=1}^{T} \boldsymbol\delta_t\, \mathbf x_t^\top, \qquad \frac{\partial \mathcal L}{\partial \mathbf W_{hy}} = \sum_{t=1}^{T} (\mathbf p_t - \mathbf e_{y_t})\, \mathbf h_t^\top,

with ∂L/∂bh=∑tδt\partial\mathcal L/\partial\mathbf b_h = \sum_t \boldsymbol\delta_t, ∂L/∂by=∑t(pt−eyt)\partial\mathcal L/\partial\mathbf b_y = \sum_t (\mathbf p_t - \mathbf e_{y_t}), and Wxh⊤δt\mathbf W_{xh}^\top\boldsymbol\delta_t scatter-added into the embedding row of wtw_t.

In plain words, walk backward through time carrying an error vector. At each step add the error from that step's own prediction, push the total back through the tanh, record outer products for the weights, and hand the error to the previous step through Whh⊤\mathbf W_{hh}^\top.

∂ Where the recursion comes from

Write ut=Whhht−1+Wxhxt+bh\mathbf u_t = \mathbf W_{hh}\mathbf h_{t-1} + \mathbf W_{xh}\mathbf x_t + \mathbf b_h for the pre-activation, so ht=tanh⁡(ut)\mathbf h_t = \tanh(\mathbf u_t). The state ht\mathbf h_t affects L\mathcal L only through ℓt\ell_t (via the logits ot=Whyht+by\mathbf o_t = \mathbf W_{hy}\mathbf h_t + \mathbf b_y) and through ut+1\mathbf u_{t+1}. By the multivariable chain rule, contributions along different routes add,

∂L∂ht=(∂ot∂ht) ⁣⊤∂ℓt∂ot+(∂ut+1∂ht) ⁣⊤∂L∂ut+1=Why⊤(pt−eyt)+Whh⊤δt+1,\frac{\partial \mathcal L}{\partial \mathbf h_t} = \left(\frac{\partial \mathbf o_t}{\partial \mathbf h_t}\right)^{\!\top}\frac{\partial \ell_t}{\partial \mathbf o_t} + \left(\frac{\partial \mathbf u_{t+1}}{\partial \mathbf h_t}\right)^{\!\top}\frac{\partial \mathcal L}{\partial \mathbf u_{t+1}} = \mathbf W_{hy}^\top(\mathbf p_t - \mathbf e_{y_t}) + \mathbf W_{hh}^\top\boldsymbol\delta_{t+1},

using the softmax-cross-entropy gradient ∂ℓt/∂ot=pt−eyt\partial\ell_t/\partial\mathbf o_t = \mathbf p_t - \mathbf e_{y_t} and defining δt+1=∂L/∂ut+1\boldsymbol\delta_{t+1} = \partial\mathcal L/\partial\mathbf u_{t+1}. The tanh acts elementwise with derivative 1−tanh⁡21 - \tanh^2, so δt=hˉt⊙(1−ht2)\boldsymbol\delta_t = \bar{\mathbf h}_t \odot (1 - \mathbf h_t^2).

For the weights, Whh\mathbf W_{hh} enters every ut\mathbf u_t, and ∂ut,i/∂Whh,ij=ht−1,j\partial u_{t,i}/\partial W_{hh,ij} = h_{t-1,j}. Summing over the steps where it is used gives ∑tδtht−1⊤\sum_t \boldsymbol\delta_t\mathbf h_{t-1}^\top. The same reasoning gives the other gradients. At the last step there is no future, which is why δT+1=0\boldsymbol\delta_{T+1} = \mathbf 0, and the gradient for the initial state is Whh⊤δ1\mathbf W_{hh}^\top\boldsymbol\delta_1.

BPTT has a memory cost. The backward pass needs every ht\mathbf h_t, so a sequence of length TT stores TT hidden states per layer and per example. For a long stream of text, like a whole book, that is impossible. Truncated BPTT splits the stream into chunks of KK steps. The hidden state is carried forward from chunk to chunk, so the forward pass still sees the whole history, but gradients are stopped at chunk boundaries (in PyTorch, h = h.detach()). The cost is that the gradient cannot teach the model any dependency longer than KK steps, even though information can still flow forward further than that.

Interview

Backpropagation through time, checked numerically

Implement rnn_bptt(P, ids, h0) for the model of exercise 3.3a. Return (loss, grads), where loss is the mean cross-entropy and grads is a dict with the gradients of that mean loss for E, Wxh, Whh, bh, Why, by, and also h0. Every gradient is compared with finite differences.

Hidden tests, hints, and AI help, from $1.99 a month.

Why gradients vanish or explode

This part of the story trips most people up, so let's go slowly. Look at how the error travels in the BPTT recursion. Ignoring the new error injected at each step, it is multiplied at every step by the transpose of

Jt=∂ht∂ht−1=diag⁡ ⁣(1−ht⊙ht)Whh.\mathbf J_t = \frac{\partial \mathbf h_t}{\partial \mathbf h_{t-1}} = \operatorname{diag}\!\left(1 - \mathbf h_t \odot \mathbf h_t\right)\mathbf W_{hh} .

So the influence of the state kk steps back on the current state is a product of kk Jacobians,

∂hT∂hT−k=JT JT−1⋯JT−k+1.\frac{\partial \mathbf h_T}{\partial \mathbf h_{T-k}} = \mathbf J_T\, \mathbf J_{T-1} \cdots \mathbf J_{T-k+1} .

In plain words, a signal from kk steps ago reaches the present only after being multiplied by Whh\mathbf W_{hh} and squashed by the tanh derivative kk times. A product of many matrices tends to shrink to nothing or blow up, and only a knife-edge in between keeps it stable.

Run the scalar echo from the start of the section backward to see it by hand. With ht=tanh⁡(0.5 ht−1+xt)h_t = \tanh(0.5\,h_{t-1} + x_t) the one-by-one Jacobian is Jt=0.5 (1−ht2)J_t = 0.5\,(1 - h_t^2). The states were h2=0.363h_2 = 0.363, h3=0.180h_3 = 0.180, h4=0.090h_4 = 0.090, so J2=0.5×0.868=0.434J_2 = 0.5 \times 0.868 = 0.434, J3=0.484J_3 = 0.484, and J4=0.496J_4 = 0.496. The sensitivity of h4h_4 to h1h_1 is their product, 0.434×0.484×0.496=0.1040.434 \times 0.484 \times 0.496 = 0.104, a little below the 0.53=0.1250.5^3 = 0.125 that the recurrent weight alone would give, because each tanh derivative shaves off a bit more. Going one step further back, to the input x1x_1, multiplies by 1−h12=0.421 - h_1^2 = 0.42 and leaves 0.0440.044. Three steps cost a factor of twenty. Thirty steps would cost about 10−910^{-9}.

The linear case is all about eigenvalues

Drop the nonlinearity for a moment, so Jt=Whh\mathbf J_t = \mathbf W_{hh} and the product is Whhk\mathbf W_{hh}^k. Suppose Whh\mathbf W_{hh} is diagonalizable (the eigendecomposition of §0.10), Whh=QΛQ−1\mathbf W_{hh} = \mathbf Q \boldsymbol\Lambda \mathbf Q^{-1}, with eigenvalues λ1,…,λdh\lambda_1, \dots, \lambda_{d_h} on the diagonal of Λ\boldsymbol\Lambda. Then

Whhk=Q Λk Q−1,Λk=diag⁡(λ1k,…,λdhk).\mathbf W_{hh}^k = \mathbf Q\,\boldsymbol\Lambda^k\,\mathbf Q^{-1}, \qquad \boldsymbol\Lambda^k = \operatorname{diag}(\lambda_1^k, \dots, \lambda_{d_h}^k).

Express any gradient vector in the eigenvector basis. Its component along eigenvector ii is multiplied by λik\lambda_i^k after kk steps. Let the spectral radius be ρ(Whh)=max⁡i∣λi∣\rho(\mathbf W_{hh}) = \max_i |\lambda_i|. If ρ<1\rho < 1, every component decays geometrically and the gradient from far in the past vanishes. If ρ>1\rho > 1, the components along eigenvectors with ∣λi∣>1|\lambda_i| > 1 grow geometrically and the gradient explodes. Pascanu, Mikolov, and Bengio (2013) state exactly this for the linear case. ρ<1\rho < 1 is sufficient for the long-term components to vanish, and ρ>1\rho > 1 is necessary for them to explode.

The numbers are brutal. With ρ=0.9\rho = 0.9, a gradient 50 steps back is scaled by 0.950≈0.0050.9^{50} \approx 0.005, and 100 steps back by 0.9100≈2.7×10−50.9^{100} \approx 2.7 \times 10^{-5}. With ρ=1.1\rho = 1.1, 50 steps back it is scaled by 1.150≈1171.1^{50} \approx 117, and 100 steps back by about 1.4×1041.4 \times 10^4. Only ρ\rho very close to 1 gives anything usable, and training does not stay there by itself.

The tanh only makes vanishing worse

Now put the nonlinearity back. The tanh derivative 1−tanh⁡2(u)1 - \tanh^2(u) lies in (0,1](0, 1], equal to 1 only at u=0u = 0 and tiny when the unit saturates. Write γ\gamma for an upper bound on the derivative of the nonlinearity (γ=1\gamma = 1 for tanh and γ=1/4\gamma = 1/4 for the logistic sigmoid) and σmax⁡\sigma_{\max} for the largest singular value of Whh\mathbf W_{hh}. The operator norm ∥A∥\Vert\mathbf A\Vert (the largest factor by which A\mathbf A can stretch a vector, which equals its largest singular value, see §0.10) is submultiplicative, ∥AB∥≤∥A∥ ∥B∥\Vert\mathbf A\mathbf B\Vert \le \Vert\mathbf A\Vert\,\Vert\mathbf B\Vert, and a diagonal matrix stretches by at most its largest entry in absolute value, here at most γ\gamma. So

∥∂hT∂hT−k∥  ≤  ∏s=T−k+1T∥diag⁡(1−hs2)∥ ∥Whh∥  ≤  (γ σmax⁡)k.\left\Vert \frac{\partial \mathbf h_T}{\partial \mathbf h_{T-k}} \right\Vert \;\le\; \prod_{s=T-k+1}^{T} \left\Vert \operatorname{diag}(1 - \mathbf h_s^2)\right\Vert\,\left\Vert \mathbf W_{hh}\right\Vert \;\le\; \left(\gamma\, \sigma_{\max}\right)^{k}.

In plain words, each step can stretch the gradient by at most γσmax⁡\gamma\sigma_{\max}, so after kk steps by at most that number to the power kk. It follows that σmax⁡<1/γ\sigma_{\max} < 1/\gamma is sufficient for the gradient to vanish exponentially, and σmax⁡>1/γ\sigma_{\max} > 1/\gamma is necessary (but not sufficient) for it to explode. This is the theorem of Pascanu et al. (2013). It explains the observation of Bengio, Simard, and Frasconi (1994) and of Hochreiter's 1991 thesis that gradient descent struggles to learn long-term dependencies in recurrent networks.

What happens to the gradient as it flows back through time?
A 24-unit RNN reads 60 random inputs. The curves show the size (root-mean-square singular value) of the Jacobian from the last hidden state back to the state k steps earlier, on a log scale. Slide the spectral radius of the recurrent matrix across 1.
Loading interactive figure…

Try three settings. With an orthogonal W\mathbf W, inputs off (input scale 0), and ρ=0.9\rho = 0.9 or 1.11.1, the linear curve lies exactly on ρk\rho^k, the straight line on a log scale. Switch to a Gaussian W\mathbf W with the same ρ\rho and the curve bulges above the line before falling, because σmax⁡\sigma_{\max} is larger. Then raise the input scale. The tanh units saturate, the mean tanh derivative drops, and the tanh curve falls far below the linear one even when ρ>1\rho > 1. In a saturating tanh network, vanishing is the common case and explosion needs large weights.

Python · cell 2

The linear column matches ρ50\rho^{50} exactly, because an orthogonal matrix scaled by ρ\rho has every singular value equal to ρ\rho. The tanh column is far smaller in all three rows, because driven by inputs the units spend much of their time away from zero, where the derivative is less than one.

What does vanishing do to learning? The total gradient for Whh\mathbf W_{hh} is a sum over pairs of steps, a loss at step tt reaching back to a state at step t−kt - k. The terms with small kk are of ordinary size, and the terms with large kk are exponentially small. So the gradient is dominated by short-range dependencies, and the model learns local structure (spelling, short phrases) long before it learns anything that spans a clause, if it ever does. Exploding gradients do the opposite damage. A single huge step throws the parameters far away, and the loss spikes or becomes NaN.

Gradient clipping tames explosions

Explosions have a cheap fix that you already know from §0.6, clipping by global norm. If the norm of the whole gradient, taken over all parameters together, exceeds a threshold cc, rescale it to have norm cc, g←g⋅min⁡(1,c/∥g∥)\mathbf g \leftarrow \mathbf g \cdot \min(1, c/\Vert\mathbf g\Vert). The direction is kept and only the length of the step is capped. What is worth adding here is why recurrent networks need it so badly, because the reason is a picture.

Pascanu et al. (2013), who proposed norm clipping for RNNs, explained explosions as cliffs. A recurrent map can have several stable states (attractors), and a small change in the weights can move the trajectory from one basin to another. Near that boundary the final state, and so the loss, changes abruptly, so the loss surface has a nearly vertical wall with flat plateaus on either side. On the wall the gradient is huge. It is also only valid over a tiny distance, because it describes the wall's slope, not the plateau beyond it. A full gradient step there extrapolates the slope far past where it holds and throws the parameters somewhere arbitrary.

The smallest example is one unit with a self-weight of 2, which with no bias has two attractors near ±0.96\pm 0.96. Start it at h0=−0.9h_0 = -0.9, run it for 20 steps with a bias bb, and ask for h20=0.8h_{20} = 0.8. For small bb the state stays in the negative basin, for large bb it escapes to the positive one, and in between is the cliff.

Python · cell 3

Off the cliff the gradient is around a hundredth. On it, it is tens of thousands of times larger, and one unclipped step moves bb by about 30 when the whole interesting region is one unit wide. Clipping turns that step into an ordinary one in the same direction. In a real network with millions of weights the same thing happens along a few directions at a time, which is why training curves of RNNs (and of large Transformers) show sudden loss spikes, and why the norm before clipping is the number to log.

Two practical notes. The threshold is a hyperparameter, set relative to the typical gradient norm. Pascanu et al. suggested a value around the average norm over many updates, Sutskever, Vinyals, and Le (2014) clipped their translation model at 5, and large Transformer runs today clip at 1.0 (§8.8). And the norm must be global, over all parameters together, because clipping each tensor separately changes the direction of the update.

Clipping does nothing for vanishing gradients. You cannot rescale a gradient that carries no information about the distant past. Initialization helps a little. An orthogonal Whh\mathbf W_{hh} starts with every singular value at exactly 1 (Saxe, McClelland, and Ganguli 2014), and Le, Jaitly, and Hinton (2015) trained ReLU RNNs from an identity Whh\mathbf W_{hh} on tasks with long gaps. But training moves the weights off these points and the tanh still saturates, so initialization only delays the problem. A lasting fix needs a change of architecture.

The LSTM gives the gradient an additive path

The long short-term memory of Hochreiter and Schmidhuber (1997) attacks the product of Jacobians at its root. Instead of rewriting its memory at every step through a matrix and a squashing function, an LSTM keeps a separate cell state ct\mathbf c_t that is updated mostly by addition, and it uses learned gates (vectors of numbers between 0 and 1) to decide what to write, what to keep, and what to show.

At each step the LSTM computes four vectors from the current input and the previous hidden state, each with its own weights,

it=σ(Wixt+Uiht−1+bi)input gate, how much to writeft=σ(Wfxt+Ufht−1+bf)forget gate, how much to keepgt=tanh⁡(Wgxt+Ught−1+bg)candidate, what to writeot=σ(Woxt+Uoht−1+bo)output gate, how much to show\begin{aligned} \mathbf i_t &= \sigma(\mathbf W_i\mathbf x_t + \mathbf U_i\mathbf h_{t-1} + \mathbf b_i) && \text{input gate, how much to write}\\ \mathbf f_t &= \sigma(\mathbf W_f\mathbf x_t + \mathbf U_f\mathbf h_{t-1} + \mathbf b_f) && \text{forget gate, how much to keep}\\ \mathbf g_t &= \tanh(\mathbf W_g\mathbf x_t + \mathbf U_g\mathbf h_{t-1} + \mathbf b_g) && \text{candidate, what to write}\\ \mathbf o_t &= \sigma(\mathbf W_o\mathbf x_t + \mathbf U_o\mathbf h_{t-1} + \mathbf b_o) && \text{output gate, how much to show} \end{aligned}

and then updates the cell and the hidden state,

ct=ft⊙ct−1+it⊙gt,ht=ot⊙tanh⁡(ct).\mathbf c_t = \mathbf f_t \odot \mathbf c_{t-1} + \mathbf i_t \odot \mathbf g_t, \qquad \mathbf h_t = \mathbf o_t \odot \tanh(\mathbf c_t).

Here each W∗∈Rdh×d\mathbf W_\ast \in \R^{d_h \times d}, each U∗∈Rdh×dh\mathbf U_\ast \in \R^{d_h \times d_h}, and σ\sigma is the logistic sigmoid. In plain words, the cell is a notepad. The forget gate decides how much of the old note to keep, the input gate decides how much of the new candidate to add, and the output gate decides how much of the note to reveal as the hidden state that the rest of the network sees.

Now look at the gradient along the cell. Holding the gates fixed, ∂ct/∂ct−1=diag⁡(ft)\partial\mathbf c_t / \partial\mathbf c_{t-1} = \operatorname{diag}(\mathbf f_t), so

∂cT∂cT−k=diag⁡ ⁣(fT⊙fT−1⊙⋯⊙fT−k+1)+(paths through the gates and h).\frac{\partial \mathbf c_T}{\partial \mathbf c_{T-k}} = \operatorname{diag}\!\left(\mathbf f_T \odot \mathbf f_{T-1} \odot \cdots \odot \mathbf f_{T-k+1}\right) + \text{(paths through the gates and } \mathbf h\text{)} .

There is no weight matrix in this product and no tanh derivative. The gradient along the cell is scaled only by the forget gates, which the network itself controls. When it needs to remember something, it can hold ft≈1\mathbf f_t \approx 1 and the gradient passes through many steps nearly undiminished. When it needs to forget, it closes the gate. In the original 1997 LSTM there was no forget gate at all, so the cell path was exactly the identity, which Hochreiter and Schmidhuber called the constant error carousel. The forget gate was added by Gers, Schmidhuber, and Cummins (2000) so that the network could also reset its memory.

The widget below is a single LSTM cell with its weights set by hand to count bracket depth. Its input gate opens on brackets, the candidate is +1+1 for "(" and −1-1 for ")", the forget gate is a constant set by its bias, and the output gate opens only on "?".

How do the gates of an LSTM keep a count?
A single LSTM cell, wired by hand to track how deeply nested the brackets are. Each column is one input character. Darker cells mean larger values (blue positive, pink negative). Hover a column to read the numbers.
Loading interactive figure…

Play with three things. Load the "long gap" preset, three open brackets followed by forty distractors. With the default forget bias of 7 (f≈0.999f \approx 0.999) the cell still holds a count of about 2.7 at the "?" (each bracket writes i⋅g≈0.95i \cdot g \approx 0.95, and 41 steps of f=0.999f = 0.999 cost a few percent), and the product of forget gates (the gradient along the cell path) stays above 0.95. Lower the forget bias to 0, so every ft=0.5f_t = 0.5, and the count has leaked away after a few steps and the gradient product is around 10−1310^{-13}. Finally compare the cell with the plain tanh RNN row. The RNN state is squashed to (−1,1)(-1, 1) at every step, so it cannot represent depth 4 differently from depth 2.

In practice the four gates' weights are stacked into single matrices so that one matrix multiply computes all of them. PyTorch's nn.LSTM stores weight_ih with shape (4 dh, d), weight_hh with shape (4 dh, dh), and two bias vectors, with the gates in the order input, forget, candidate, output. An LSTM layer therefore has 4(dhd+dh2+dh)4(d_h d + d_h^2 + d_h) parameters (PyTorch's two biases make it 4(dhd+dh2+2dh)4(d_h d + d_h^2 + 2d_h)), four times a plain RNN layer.

Interview

An LSTM cell, forward pass

Implement lstm_cell(x, h, c, W, U, b) for a batch. Shapes are x (B, d), h and c (B, dh), W (4 dh, d), U (4 dh, dh), and b (4 dh,). Compute the pre-activations as x @ W.T + h @ U.T + b, split them into four blocks of dh in the PyTorch order input, forget, candidate, output, and return (h_new, c_new).

Hidden tests, hints, and AI help, from $1.99 a month.

Interviewers often follow "write the LSTM forward pass" with "now backpropagate through one cell". It is the same outer-product bookkeeping as BPTT, with one trap. The upstream gradient reaches the new cell ct\mathbf c_t by two routes, directly from the next step's cell and through ht=ot⊙tanh⁡(ct)\mathbf h_t = \mathbf o_t \odot \tanh(\mathbf c_t).

∂ One LSTM cell, backward

Suppose the rest of the network hands back hˉt=∂L/∂ht\bar{\mathbf h}_t = \partial\mathcal L/\partial\mathbf h_t and cˉt next=∂L/∂ct\bar{\mathbf c}_t^{\,\text{next}} = \partial\mathcal L/\partial\mathbf c_t along the direct cell route from step t+1t+1. A product of two vectors written side by side is elementwise (⊙\odot).

The cell, from both routes. ht=ot⊙tanh⁡(ct)\mathbf h_t = \mathbf o_t \odot \tanh(\mathbf c_t) gives ∂ht,k/∂ct,k=ot,k (1−tanh⁡2ct,k)\partial h_{t,k}/\partial c_{t,k} = o_{t,k}\,(1 - \tanh^2 c_{t,k}), so the total error at the cell is

cˉt=cˉt next+hˉt⊙ot⊙(1−tanh⁡2(ct)).\bar{\mathbf c}_t = \bar{\mathbf c}_t^{\,\text{next}} + \bar{\mathbf h}_t \odot \mathbf o_t \odot \big(1 - \tanh^2(\mathbf c_t)\big).

The four branches. From ht=ot⊙tanh⁡(ct)\mathbf h_t = \mathbf o_t \odot \tanh(\mathbf c_t), oˉt=hˉt⊙tanh⁡(ct)\bar{\mathbf o}_t = \bar{\mathbf h}_t \odot \tanh(\mathbf c_t). From ct=ft⊙ct−1+it⊙gt\mathbf c_t = \mathbf f_t \odot \mathbf c_{t-1} + \mathbf i_t \odot \mathbf g_t, fˉt=cˉt⊙ct−1\bar{\mathbf f}_t = \bar{\mathbf c}_t \odot \mathbf c_{t-1}, iˉt=cˉt⊙gt\bar{\mathbf i}_t = \bar{\mathbf c}_t \odot \mathbf g_t, gˉt=cˉt⊙it\bar{\mathbf g}_t = \bar{\mathbf c}_t \odot \mathbf i_t, and the error handed to the previous cell is cˉt−1=cˉt⊙ft\bar{\mathbf c}_{t-1} = \bar{\mathbf c}_t \odot \mathbf f_t. That last term is the additive cell path from above, diag⁡(ft)\operatorname{diag}(\mathbf f_t), now as an explicit gradient.

Through the activations. The sigmoid has σ′=σ(1−σ)\sigma' = \sigma(1 - \sigma) and the tanh has tanh⁡′=1−tanh⁡2\tanh' = 1 - \tanh^2, so the errors at the four pre-activations are iˉt⊙it(1−it)\bar{\mathbf i}_t \odot \mathbf i_t(1 - \mathbf i_t), fˉt⊙ft(1−ft)\bar{\mathbf f}_t \odot \mathbf f_t(1 - \mathbf f_t), gˉt⊙(1−gt2)\bar{\mathbf g}_t \odot (1 - \mathbf g_t^2), and oˉt⊙ot(1−ot)\bar{\mathbf o}_t \odot \mathbf o_t(1 - \mathbf o_t). Stack them in the order of the weight blocks into one vector zˉt∈R4dh\bar{\mathbf z}_t \in \R^{4d_h}.

The weights and inputs. The pre-activation is zt=Wxt+Uht−1+b\mathbf z_t = \mathbf W\mathbf x_t + \mathbf U\mathbf h_{t-1} + \mathbf b with the stacked W∈R4dh×d\mathbf W \in \R^{4d_h \times d} and U∈R4dh×dh\mathbf U \in \R^{4d_h \times d_h}, so as for any linear layer, ∂L/∂W=zˉtxt⊤\partial\mathcal L/\partial\mathbf W = \bar{\mathbf z}_t\mathbf x_t^\top, ∂L/∂U=zˉtht−1⊤\partial\mathcal L/\partial\mathbf U = \bar{\mathbf z}_t\mathbf h_{t-1}^\top, ∂L/∂b=zˉt\partial\mathcal L/\partial\mathbf b = \bar{\mathbf z}_t, xˉt=W⊤zˉt\bar{\mathbf x}_t = \mathbf W^\top\bar{\mathbf z}_t, and hˉt−1=U⊤zˉt\bar{\mathbf h}_{t-1} = \mathbf U^\top\bar{\mathbf z}_t (plus whatever ht−1\mathbf h_{t-1} receives from its own prediction). For a batch, sum the outer products over the batch.

Interview

Backpropagate through one LSTM cell

Implement lstm_cell_backward(x, h, c, W, U, b, dh_new, dc_new) for the cell of exercise 3.3d (same shapes and gate order). dh_new and dc_new, both (B, dh), are the gradients of the loss with respect to the cell's outputs h_new and c_new. Return a dict with the gradients for "x", "h", "c" (the previous state and cell), and "W", "U", "b", summed over the batch. Every gradient is checked against finite differences.

Hidden tests, hints, and AI help, from $1.99 a month.

The GRU merges the gates

Cho et al. (2014) proposed a simpler gated unit in the same paper that introduced the RNN encoder-decoder (§3.5). They called it a new type of hidden unit, and Chung et al. (2014) named it the gated recurrent unit, or GRU. It has no separate cell and only two gates. With zt\mathbf z_t the update gate and rt\mathbf r_t the reset gate,

zt=σ(Wzxt+Uzht−1+bz),rt=σ(Wrxt+Urht−1+br),h~t=tanh⁡ ⁣(Wxt+U(rt⊙ht−1)+b),ht=zt⊙ht−1+(1−zt)⊙h~t.\begin{aligned} \mathbf z_t &= \sigma(\mathbf W_z\mathbf x_t + \mathbf U_z\mathbf h_{t-1} + \mathbf b_z), \qquad \mathbf r_t = \sigma(\mathbf W_r\mathbf x_t + \mathbf U_r\mathbf h_{t-1} + \mathbf b_r),\\ \tilde{\mathbf h}_t &= \tanh\!\big(\mathbf W\mathbf x_t + \mathbf U(\mathbf r_t \odot \mathbf h_{t-1}) + \mathbf b\big),\\ \mathbf h_t &= \mathbf z_t \odot \mathbf h_{t-1} + (1 - \mathbf z_t) \odot \tilde{\mathbf h}_t . \end{aligned}

In plain words, the update gate interpolates between keeping the old state and replacing it with a candidate, and the reset gate decides how much of the old state the candidate may look at. When zt≈1\mathbf z_t \approx 1 the state is copied forward and ∂ht/∂ht−1\partial\mathbf h_t/\partial\mathbf h_{t-1} contains the term diag⁡(zt)\operatorname{diag}(\mathbf z_t), an additive shortcut just like the LSTM cell. The GRU has three blocks of weights instead of four, so about 25% fewer parameters than an LSTM of the same width. Empirically the two are close. Jozefowicz et al. (2015) found the GRU slightly ahead of an LSTM with the default forget bias and roughly tied once the bias was set to 1, and Greff et al. (2017), in an extensive study of LSTM variants, found the forget gate and the output activation to be the most critical components.

Core

A GRU cell, Cho et al. convention

Implement gru_cell(x, h, W, U, b) with the equations above. W has shape (3 dh, d), U has shape (3 dh, dh), and b has shape (3 dh,), with blocks in the order reset, update, candidate. Note that the reset gate multiplies ht−1\mathbf h_{t-1} before the candidate's matrix U\mathbf U, and the update gate zt\mathbf z_t multiplies the old state.

Hidden tests, hints, and AI help, from $1.99 a month.

Gates in action, measured

The cleanest way to see what gating buys is to measure the gradient directly. Below, a loss depends only on the last hidden state of a randomly initialized network, and we measure how large its gradient is with respect to the very first input as the sequence gets longer. This is the signal that would teach the network a dependency spanning the whole sequence.

🔥 In PyTorchfor a machine with PyTorch

The plain RNN's gradient falls by many orders of magnitude between 5 and 50 steps, the vanishing gradient in its rawest form. An LSTM with a zero forget bias does barely better, since f≈0.5f \approx 0.5 is a leaky memory. A bias of 1 (f≈0.73f \approx 0.73) decays much more slowly but still vanishes by 100 steps, which is fine as a starting point because training can raise the bias where memory pays. With the forget bias at 3 (f≈0.95f \approx 0.95) the gradient stays of order one over 100 steps. That last row is more than the cell path alone explains, since 0.95100≈0.0060.95^{100} \approx 0.006. The rest arrives along the other paths of the derivation above (through h\mathbf h and the gates), which at a random initialization can amplify as well as shrink, so read the table as a measurement rather than a formula. The architecture did not solve the problem by itself. It gave the network a knob, the forget gate, that can solve it.

Training an RNN language model the real way

Here is the complete training loop for a character-level LSTM language model with truncated BPTT, the recipe behind Karpathy's famous char-rnn experiments (Karpathy 2015) and Graves's (2013) handwriting and text generation. The stream is cut into chunks of K=64K = 64 characters. The hidden state is carried across chunks but detached, and the gradient's global norm is clipped.

🔥 In PyTorchfor a machine with PyTorch

Within 200 steps the loss falls from about log⁡V≈3.1\log V \approx 3.1 to below 0.2, and the samples are mostly well-formed sentences of the toy language with the occasional misspelling. Run the same loop for longer on Shakespeare, Wikipedia, or the Linux source code, as Karpathy did, and the samples become convincing pastiches with balanced brackets and plausible indentation. Karpathy, Johnson, and Fei-Fei (2015) then looked inside such models and found individual cells with readable jobs, one tracking whether the text is inside a quotation, one the position along a line, one the nesting depth of code. The bracket-counting cell of the widget above is the hand-built version of what these networks learn on their own.

Three details in this loop are easy to get wrong in an interview. The state must be detached at every chunk boundary, or the graph grows forever and memory explodes. The streams must be laid out so that chunk k+1k+1 of row bb continues chunk kk of row bb, otherwise carrying the state is meaningless. And clipping happens after backward and before step.

Stacking layers and regularizing them

Real recurrent language models have more than one layer. A stacked RNN feeds each layer's hidden states to the next layer as its inputs at the same time step. Writing ht(l)\mathbf h_t^{(l)} for the state of layer ll at step tt, with ht(0)=xt\mathbf h_t^{(0)} = \mathbf x_t,

ht(l)=tanh⁡ ⁣(Whh(l) ht−1(l)+Wxh(l) ht(l−1)+bh(l)),l=1,…,L,\mathbf h_t^{(l)} = \tanh\!\left(\mathbf W_{hh}^{(l)}\,\mathbf h_{t-1}^{(l)} + \mathbf W_{xh}^{(l)}\,\mathbf h_t^{(l-1)} + \mathbf b_h^{(l)}\right), \qquad l = 1, \dots, L,

and the softmax reads the top layer ht(L)\mathbf h_t^{(L)} (the same stacking works with LSTM or GRU cells). In plain words, depth in this direction adds computation at every step without making the path through time any longer. Two to four layers was typical. Sutskever et al. used four in their translation model (§3.5), and ELMo uses two (§3.4). In PyTorch this is the num_layers argument.

Large LSTM language models overfit quickly, and the obvious regularizer goes wrong. Dropout with a fresh mask at every step on the recurrent connection ht−1→ht\mathbf h_{t-1} \to \mathbf h_t corrupts the memory over and over, so information cannot survive many steps. Zaremba, Sutskever, and Vinyals (2014) showed that dropout works well if it is applied only to the non-recurrent connections (on the embeddings, between layers, and before the softmax). PyTorch's nn.LSTM(..., num_layers=2, dropout=0.3) does the between-layer part, and you add the other two yourself. Gal and Ghahramani (2016) then showed that the recurrent connections can be dropped too, as long as the same mask is reused at every step of a sequence (variational dropout). AWD-LSTM combined that with DropConnect on the recurrent matrix itself, a mask on the entries of Whh\mathbf W_{hh} drawn once per batch, and with tying the softmax weights to the embedding table (§3.2). Those choices, not a new cell, produced its record perplexities.

unitmemory updatepath for the gradientparameters per layernotes
tanh RNNht=tanh⁡(Whhht−1+… )\mathbf h_t = \tanh(\mathbf W_{hh}\mathbf h_{t-1} + \dots)product of diag⁡(1−h2)Whh\operatorname{diag}(1-\mathbf h^2)\mathbf W_{hh}dh(d+dh+1)d_h(d + d_h + 1)vanishes or explodes, needs clipping
LSTMct=ft⊙ct−1+it⊙gt\mathbf c_t = \mathbf f_t\odot\mathbf c_{t-1} + \mathbf i_t\odot\mathbf g_tproduct of forget gates4dh(d+dh+1)4 d_h(d + d_h + 1)separate cell, forget bias near 1
GRUht=zt⊙ht−1+(1−zt)⊙h~t\mathbf h_t = \mathbf z_t\odot\mathbf h_{t-1} + (1-\mathbf z_t)\odot\tilde{\mathbf h}_tproduct of update gates3dh(d+dh+1)3 d_h(d + d_h + 1)fewer parameters, similar quality

Gating solved the vanishing gradient well enough that LSTMs dominated language modeling, speech recognition, and translation for most of the 2010s. Two problems remained, and they will drive the rest of this chapter. First, the whole history still has to fit into one fixed-size vector. Second, step tt cannot be computed until step t−1t-1 is done, so training cannot be parallelized across the sequence, which is why the Transformer of Chapter 5 won once hardware made parallelism the main currency. The recurrence itself has made a comeback in linear-time sequence models, where the same eigenvalue analysis returns as the stability condition of state-space models that keep a recurrent state but train in parallel.

Read more

Watch next

🎬 Recurrent Neural Networks (RNNs), Clearly Explained!!! · StatQuest with Josh StarmerShared weights and unrolling, with a gentle look at exploding and vanishing gradients.
🎬 Long Short-Term Memory (LSTM), Clearly Explained · StatQuest with Josh StarmerThe LSTM gates one stage at a time.
🎬 Vanishing and exploding gradients in RNNs (NLP817 9.5) · Herman KamperThe product-of-Jacobians argument written out on the board.
🎬 CS231n Winter 2016: Lecture 10: Recurrent Neural Networks, Image Captioning, LSTM · Andrej KarpathyThe classic lecture on character RNNs, gradient flow, and LSTMs.