RNNs, LSTMs, GRUs
- Elman (1990) introduced simple RNNs for sequence processing; LSTMs by Hochreiter & Schmidhuber (1997) solved the vanishing gradient problem in long sequences
- Dominated NLP sequence-to-sequence tasks until transformers (2017)
- Still used in time-series forecasting, audio modeling, and embedded systems where Transformers are too heavy
RNNs process sequences one step at a time, maintaining a hidden state that carries information from past steps. LSTMs add gating mechanisms (input, forget, output gates) that control what information flows through the cell state, enabling long-range dependencies.
- The attention mechanism in Transformers was designed to overcome the fixed-size bottleneck of the RNN hidden state
- Bidirectional RNNs and stacked LSTMs connect to the encoder-decoder architecture for seq2seq tasks
- State space models (Mamba) are modern alternatives to RNNs for long-sequence modeling
- Define a vanilla RNN and derive backpropagation through time (BPTT) by unfolding across the sequence and applying standard backprop.
- Explain the vanishing and exploding gradient problem as a product of T Jacobians (Bengio 1994; Pascanu 2013) and apply gradient clipping.
- Derive the LSTM gates from the design objective of preserving long-range gradients via a gated additive cell-state recurrence (Hochreiter & Schmidhuber 1997).
- Compare the LSTM, GRU (Cho 2014), and bidirectional/stacked variants; pick the right one for a given size and data regime.
- Explain why Bahdanau attention (2014) and ultimately the Transformer displaced RNNs for sequence modelling.
TL;DR
A recurrent neural network shares one transition across time: . BPTT [2] unfolds the recurrence and runs standard backprop, but the chain rule produces a product of Jacobians whose spectral norm controls whether gradients vanish or explode across long sequences [3][4]. LSTMs [5] fix vanishing gradients with a gated additive cell state , so the gradient through is multiplied by the (often near-1) forget gate rather than a saturating Jacobian. GRUs [7] simplify to two gates and one state. Bidirectional and stacked variants are standard. Bahdanau attention [9] over the encoder hidden states removed the seq2seq [8] bottleneck, and the Transformer [11] displaced RNNs entirely for most sequence tasks because it is fully parallel across time and captures longer effective context.
Intuition
A feed-forward network sees one example at a time. A recurrent network sees a sequence, and at each step it carries forward a hidden state that summarizes everything it has read so far. The simplest version is
and that single recurrence, applied at every time step, is what makes the network “recurrent.” The same parameters are reused across time, exactly the same way a CNN reuses its kernel across spatial positions. Sharing across time is the inductive bias for sequences.
The catch: training requires backpropagating through the entire chain , and the chain rule multiplies Jacobians of the recurrence. If those Jacobians shrink, gradients vanish past a few dozen steps; if they grow, gradients explode. LSTMs and GRUs are architectural fixes for the vanishing case; gradient clipping is the production fix for the exploding case.
Consider modelling a 100-token sentence with an MLP that takes the whole sentence at once. You would need parameters per layer and the model would have to re-learn what “the” looks like at every position separately. An RNN learns one that handles “the” wherever it appears, with no positional duplication. The price is that the model is sequential: depends on , so you cannot parallelize across time. That sequential dependency is the single biggest reason Transformers eventually displaced RNNs for large-scale language modelling [11].
Math
Vanilla RNN and backpropagation through time (BPTT)
Forward pass
For an input sequence and an initial hidden state , the vanilla RNN computes
for . Define a per-step loss and a total loss .
Unfolding
Treat the recurrent network as a feed-forward network of depth where layer shares parameters with every other layer. This is “unfolding” the RNN through time. Then BPTT is just standard backpropagation [1] on the unfolded graph, with the constraint that the gradient w.r.t. each shared weight is the sum over time:
The product of Jacobians
The dangerous term is , a product of Jacobians. For a vanilla RNN with tanh,
where . Bound the spectral norm: , so . The product of such factors satisfies
Vanishing vs exploding
- If the largest singular value of is less than 1, the product contracts geometrically to zero. The gradient signal from a loss at time never reaches inputs far in the past; the RNN cannot learn dependencies past about time steps. This is the vanishing gradient problem.
- If the largest singular value is greater than 1, the product grows geometrically. Gradients explode, weights take wild updates, training diverges. This is the exploding gradient problem.
In practice both happen on the same network, on different sequences. The standard production fix for explosion is gradient clipping [4]: scale the full gradient vector if its norm exceeds a threshold,
The standard fix for vanishing is to redesign the recurrence so it has a path with near-identity Jacobian; that is what LSTMs and GRUs do.
LSTM: deriving the gates from the gradient-flow objective
Design objective
We want a recurrence such that is close to the identity along most paths, even after many time steps. The simplest way to get this is to make the recurrence additive:
Then exactly, and gradients propagate through the cell state without contraction or explosion. But pure addition has no mechanism to forget old information or to control how much new information enters.
Adding gates
Replace the constants in the additive recurrence with gates in that the network learns:
where is the forget gate (how much of the old cell state to keep), is the input gate (how much new information to write), and is the candidate update (the actual new information). Each is a small MLP of the previous hidden state and current input:
Finally the output gate controls what the cell exposes as the hidden state:
This is the LSTM [5], with the forget gate added by Gers, Schmidhuber, and Cummins [6] three years later (the original LSTM had no forget gate; without it the cell state grew unboundedly).
Gradient flow through the cell state
Differentiate the cell-state recurrence:
The dominant term is . If the network learns to keep on relevant dimensions, the cell-state gradient is multiplied by something close to the identity at each step, and gradients can flow through hundreds of time steps. Compare to vanilla RNN where the per-step factor is , whose spectral norm is bounded by but can shrink arbitrarily through the saturating .
The hidden-state branch () does saturate, but the cell state itself does not: that decoupling is the design insight. Long-range dependencies live in ; the squashed is what is exposed to the output.
Initial bias trick
In practice, initializing the forget-gate bias to a positive value (commonly or ) makes at initialization, so the cell state retains most of its previous value before training begins. Without this, the LSTM starts in a regime where and gradients still attenuate by over steps, defeating the design [12].
GRU: a smaller LSTM with two gates
The Gated Recurrent Unit (GRU) [7] simplifies the LSTM in two ways:
- Merge the cell state and hidden state. There is just , no separate .
- Merge the input and forget gates into a single update gate. The forget probability is exactly minus the input probability.
The equations:
The reset gate controls how much of the previous hidden state contributes to the candidate (zero means “ignore the past, treat this token fresh”). The update gate controls the convex combination between the previous state and the candidate.
Parameter count: a GRU has roughly gate matrices (vs an LSTM’s ), so it is about 25% smaller than an LSTM with the same hidden size. Empirically GRU and LSTM perform within noise of each other on most tasks; GRU is preferred when parameters or compute are tight (smaller embeddings, edge devices, smaller datasets), LSTM is the safer default at large scale.
Bidirectional and stacked variants
- Bidirectional: run a forward LSTM/GRU on and a backward one on in parallel, then concatenate the hidden states . Standard for sequence labelling (NER, POS tagging) where the entire sentence is available at inference. Not usable for streaming or autoregressive decoding.
- Stacked: feed the hidden states of one RNN as inputs to another RNN above it. Stacking deepens the temporal feature hierarchy; 2-3 layers is typical, more rarely helps and adds optimization issues. Modern speech recognition pipelines (Graves et al. [10]) stacked deep bidirectional LSTMs with batch norm or layer norm between layers.
Bahdanau attention: removing the seq2seq bottleneck
The original seq2seq architecture [8] compresses a source sequence into a single fixed-size encoder hidden state , then conditions the decoder on that state. For long sentences the bottleneck is fatal: a 100-word English sentence has to fit into one -dim vector before the decoder reads any of it.
Bahdanau et al. [9] replaced the bottleneck with soft attention over the entire encoder hidden state sequence . At decoder step with decoder state , compute alignment scores
normalize via softmax over ,
and form a context vector . Then condition the decoder step on in addition to and the previous output.
This is exactly the mechanism that survived (in scaled-dot-product form) into the Transformer [11]. The structural improvement is that the decoder can attend over the entire encoder output, so the gradient path from any decoder step to any encoder step is just one attention edge, not recurrent steps. The encoder bottleneck is gone, and effective context length jumps from “as much as can fit in ” to “the entire input sequence.”
Implementations
Three idiomatic versions of the same LSTM cell: PyTorch’s one-liner nn.LSTM, a manual cell that exposes every gate, and a JAX/Flax sketch.
import torch
import torch.nn as nn
# One-liner: a stacked, bidirectional LSTM.
lstm = nn.LSTM(
input_size=128,
hidden_size=256,
num_layers=2,
bidirectional=True,
batch_first=True,
)
# x: (batch, time, features). h0, c0 default to zeros.
x = torch.randn(32, 50, 128)
y, (hN, cN) = lstm(x)
# y: (32, 50, 512) hN, cN: (4, 32, 256) -- 4 = 2 layers * 2 directions
print(y.shape, hN.shape, cN.shape)Run a tiny manual LSTM in NumPy on a parity-counting toy task (output 1 if the running count of 1s in the input sequence is odd):
(no output yet)
Comparison
| Architecture | Parameters per cell | Captures long deps | Parallel across time | Training stability | Dominant era |
|---|---|---|---|---|---|
| Vanilla RNN | ~ d (d_in + d_h) | no (vanishing past ~10 steps) | no (sequential) | unstable | 1990s, mostly historical |
| LSTM | ~ 4 d (d_in + d_h) | yes (cell-state path) | no (sequential) | good with clipping | 2014-2017 NLP and speech |
| GRU | ~ 3 d (d_in + d_h) | yes (similar mechanism) | no (sequential) | good with clipping | 2014-2017, smaller-budget niche |
| Transformer | ~ 4 d^2 per layer (attn + FFN) | yes (any-to-any attention) | yes | good with warmup + LR schedule | 2017-present |
| Bidirectional LSTM | 2x LSTM cost | yes | no | good | 2014-2018 sequence labelling |
| Stacked LSTM (2-3 layers) | L x LSTM cost | yes | no | requires layer/batch norm at depth | 2014-2018 speech (DeepSpeech) |
Real-world uses
| Domain | Use | Why an RNN/LSTM/GRU works (or worked) |
|---|---|---|
| Machine translation (pre-2017) | seq2seq with attention (Bahdanau) | Variable-length input + variable-length output; attention removed the encoder bottleneck. Now Transformer dominant. |
| Speech recognition | Deep bidirectional LSTM with CTC loss (DeepSpeech, Listen Attend and Spell) | Streaming-friendly forward LSTM; bidirectional version when latency permits. Conformers (CNN + Transformer) now SOTA. |
| Time series forecasting | Demand, sensor, finance | Sequence with strong autocorrelation; LSTM is still a competitive baseline at small/medium scale. |
| Gesture & activity recognition | Sensor / video classification | Variable-length input from accelerometer or per-frame CNN features. |
| Online learning agents | Reinforcement learning policies with memory | LSTM hidden state acts as the agent's memory of partially observable history (DeepMind R2D2, IMPALA). |
Pitfalls
Without clipping, RNN training will eventually take a step of catastrophic magnitude on a sequence whose particular Jacobian product blows up. Standard recipe: clip the global gradient norm to 1 or 5 (torch.nn.utils.clip_grad_norm_(params, 1.0)). Adding clipping costs almost nothing and saves training runs [4].
BPTT stores activations for every time step in order to compute gradients. A 1024-token sequence costs 1024x the activation memory of a single forward pass. Two production fixes: truncated BPTT (backprop only the last steps and detach the rest), and gradient checkpointing (recompute activations in segments at backward time, trading compute for memory). The same trade-off lives in Transformer training, but Transformers are at least parallel across time.
Standard seq2seq training feeds the ground-truth previous token to the decoder (“teacher forcing”). At inference there is no ground truth, so the decoder is fed its own previous prediction. The distribution mismatch causes “exposure bias”: small errors compound. Mitigations include scheduled sampling (Bengio 2015) and minimum-risk training; in modern Transformer-based language models the issue is reduced because beam search and pretraining at scale dominate the sample-compounding effect.
If you initialize the LSTM forget-gate bias to zero, the gate sits at and the cell state attenuates by over steps. The standard fix is to initialize to (or ), so at start and gradients flow until the network learns selective forgetting. PyTorch’s nn.LSTM does not do this for you; check the framework defaults.
Pop quiz
- 1. Why does a vanilla RNN suffer from vanishing gradients?
- 2. Why does the LSTM cell-state recurrence c_t = f_t * c_{t-1} + i_t * c_tilde help with vanishing gradients?
- 3. When would you prefer a GRU over an LSTM?
- 4. What problem did Bahdanau attention solve in seq2seq?
- 5. What does gradient clipping do, and why is it standard for RNN training?
Interview Q&A
Derive the LSTM cell-state equation from the design objective of preserving long-range gradients.research
Objective. Build a recurrence such that is close to the identity along most paths, even after many time steps.
Step 1: additive update. The simplest such recurrence is , which gives exactly. But pure addition has no mechanism to forget or to control how much new information enters.
Step 2: gate the previous state. Replace the constant multiplier on with a learned gate :
When on the dimensions that matter, the cell-state gradient is and gradients flow. When on a dimension, that dimension is forgotten.
Step 3: gate the new information. should not blow up the cell state, so multiply it by an input gate and scale the actual update through a :
Step 4: gate the output. The cell state is the long-term memory; the network should be able to expose only part of it at each step. Add an output gate :
Step 5: each gate is a small sigmoid layer of .
Result. The LSTM [5], with the forget gate added by Gers, Schmidhuber, Cummins [6]. Initialize to so at start and the cell-state path is approximately identity from training step zero.
Why does the cell state, and not the hidden state, carry long-range information in an LSTM?onsite
The hidden state is squashed by at every time step (). The is bounded and has gradient at most 1, so any path through saturates exactly like a vanilla RNN’s recurrence: gradients shrink geometrically through it.
The cell state is additive and gated: , with no applied to along the recurrence. The gradient through at one step is plus small terms, and the network can learn to keep near 1 on the dimensions that should remember, so gradients flow through the cell state with negligible attenuation.
The architectural decoupling is the point: is the unbounded long-term memory and propagates gradients; is the squashed short-term view exposed at each step. Vanilla RNNs collapse the two and pay for it with vanishing gradients.
When would you prefer a GRU over an LSTM?phone
Three reasons that all reduce to “smaller and cheaper”:
- Parameter budget. A GRU has gate matrices vs an LSTM’s , so it is about 25% smaller per cell at the same hidden size. On edge devices, mobile, or any place where memory or quantization matter, GRU is the cheaper choice.
- Smaller datasets. Empirically GRU and LSTM are within noise on most tasks [7], but the smaller GRU can mildly resist overfitting on small datasets.
- Faster wall-clock. Fewer matrix multiplies per step. On GPU the difference is usually less than 25% (memory bandwidth dominates) but on CPU it can be 1.3-1.5x.
LSTM is the safer default at large scale and on stateful tasks (where the explicit cell state has been observed to help). For most modern work, both are largely displaced by Transformers; GRU/LSTM linger in streaming contexts and small-budget edge models.
Why did attention 'fix' the seq2seq encoder bottleneck?onsite
The original seq2seq [8] compressed the entire source sentence into one fixed-size vector . Two structural problems:
- Capacity bottleneck. A 100-word sentence has to fit into one -dim vector. Empirically translation BLEU degraded sharply on sentences past about 30-40 words.
- Gradient bottleneck. The gradient from any decoder step to any encoder step has to pass through the chain , paying the vanishing-gradient tax.
Bahdanau attention [9] replaces the bottleneck with a learned weighted average over the entire encoder hidden state sequence: at each decoder step , compute alignment scores , normalize via softmax to get , and form context . The decoder reads from anywhere in the encoder at any time.
Two consequences:
- No more capacity bottleneck. The decoder sees the full encoder sequence at each step.
- One-step gradient path. From decoder step to encoder step , the gradient flows through one attention edge. No T-deep chain to traverse.
This is exactly the mechanism the Transformer [11] generalized (multi-head, scaled dot-product, applied to self-attention in addition to cross-attention). Once you accept the attention principle, the only remaining role for the recurrence is sequential bookkeeping, which is itself the parallelism bottleneck Transformers eliminated.
A weekend project to internalize the recurrence:
- Implement the manual LSTM cell from the CodeCompare above in PyTorch (no
nn.LSTM). Train on parity counting over sequences of length 50 and reach 99% test accuracy. - Replace the LSTM cell with a vanilla RNN cell. Watch it fail at length 50 (it will plateau around 50% accuracy). Quantify how the failure threshold scales with sequence length.
- Add gradient clipping and confirm it stabilizes the vanilla RNN on shorter sequences.
- Stretch: swap the LSTM cell for a GRU cell, confirm accuracy is similar with ~25% fewer parameters, and measure the wall-clock speedup.
- Stretch further: add Bahdanau attention on top of an encoder-decoder LSTM and reproduce the 30-40 word BLEU improvement on a small WMT translation subset.
References
- Rumelhart, D. E., Hinton, G. E., and Williams, R. J. (1986). Learning representations by back-propagating errors. Nature 323, 533-536. doi:10.1038/323533a0
- Werbos, P. J. (1990). Backpropagation through time: what it does and how to do it. Proceedings of the IEEE 78(10), 1550-1560. doi:10.1109/5.58337
- Bengio, Y., Simard, P., and Frasconi, P. (1994). Learning long-term dependencies with gradient descent is difficult. IEEE Transactions on Neural Networks 5(2), 157-166. doi:10.1109/72.279181
- Pascanu, R., Mikolov, T., and Bengio, Y. (2013). On the difficulty of training recurrent neural networks. ICML 2013. arXiv:1211.5063
- Hochreiter, S. and Schmidhuber, J. (1997). Long Short-Term Memory. Neural Computation 9(8), 1735-1780. doi:10.1162/neco.1997.9.8.1735
- Gers, F. A., Schmidhuber, J., and Cummins, F. (2000). Learning to forget: Continual prediction with LSTM. Neural Computation 12(10), 2451-2471. doi:10.1162/089976600300015015
- Cho, K., van Merrienboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., and Bengio, Y. (2014). Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation. EMNLP 2014. arXiv:1406.1078
- Sutskever, I., Vinyals, O., and Le, Q. V. (2014). Sequence to Sequence Learning with Neural Networks. NeurIPS 2014. arXiv:1409.3215
- Bahdanau, D., Cho, K., and Bengio, Y. (2014). Neural Machine Translation by Jointly Learning to Align and Translate. ICLR 2015 (arXiv preprint 2014). arXiv:1409.0473
- Graves, A., Mohamed, A.-R., and Hinton, G. (2013). Speech Recognition with Deep Recurrent Neural Networks. ICASSP 2013. arXiv:1303.5778
- Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, L., and Polosukhin, I. (2017). Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762
- Goodfellow, I., Bengio, Y., and Courville, A. (2016). Deep Learning. MIT Press. https://www.deeplearningbook.org
Interactive Demo
Tip: Watch the colored hidden state change as each word is processed - this is the RNN memory