Skip to main content
Recurrent Neural Networks

Recurrent Neural Networks

The Problem with Sequences

CNNs transformed computer vision. But they have a fundamental limitation: they assume fixed-size inputs. What about:
  • Text: “The cat sat on the mat” (6 words)
  • Time series: Stock prices over months (varying length)
  • Audio: Speech of different durations
  • Video: Frames over time
These are sequences - data where order matters and length varies.
The Core Insight: Sequences have temporal dependencies. The word “sat” depends on knowing “cat” came before it. Today’s stock price depends on yesterday’s. We need networks that can remember.The fundamental difference between a CNN and an RNN: a CNN asks “what is here?”, while an RNN asks “what just happened, and what does that mean for what comes next?” CNNs are spatial; RNNs are temporal.
Think of a feedforward network as someone with amnesia reading a book word by word. Each word is processed in isolation — when they reach “mat,” they have no memory of “cat” or “sat.” An RNN is like a normal reader: they carry a running mental summary of everything they have read so far. Each new word updates that summary. The summary is lossy — you cannot perfectly reconstruct every previous word from it — but it captures the gist that matters for understanding the next word. This running summary is the hidden state.

From Feedforward to Recurrent

The Feedforward Limitation

The Recurrent Solution

An RNN maintains a hidden state that carries information across time steps: ht=f(ht1,xt)h_t = f(h_{t-1}, x_t) Think of the hidden state as a person’s “running mental summary” while reading a book. After each sentence (xtx_t), the reader updates their understanding (hth_t) based on what they just read and what they already knew (ht1h_{t-1}). They cannot go back and re-read (that would be an attention mechanism), so their current understanding must compress everything important from the entire story so far into a fixed-size mental state.
RNN Unrolled Through Time

The RNN Equations

Mathematical Formulation

At each time step tt: ht=tanh(Wxhxt+Whhht1+bh)h_t = \tanh(W_{xh} x_t + W_{hh} h_{t-1} + b_h) yt=Whyht+byy_t = W_{hy} h_t + b_y Where:
  • xtRdx_t \in \mathbb{R}^{d} is the input at time tt
  • htRhh_t \in \mathbb{R}^{h} is the hidden state
  • ytRoy_t \in \mathbb{R}^{o} is the output
  • WxhRh×dW_{xh} \in \mathbb{R}^{h \times d} transforms input to hidden
  • WhhRh×hW_{hh} \in \mathbb{R}^{h \times h} transforms previous hidden to current
  • WhyRo×hW_{hy} \in \mathbb{R}^{o \times h} transforms hidden to output

RNN Architectures

Many-to-One (Sequence Classification)

Use the final hidden state to classify the entire sequence:
Many-to-One RNN

One-to-Many (Sequence Generation)

Generate a sequence from a single input:

Many-to-Many (Sequence-to-Sequence)

Transform one sequence into another:
RNN Architectures

Backpropagation Through Time (BPTT)

The Challenge of Temporal Gradients

RNNs are trained using BPTT - unrolling the network through time and applying backpropagation:

The Vanishing/Exploding Gradient Problem

Why Gradients Vanish

The gradient through time involves products of the recurrent weight matrix: hTh1=t=2Ththt1=t=2TWhhTdiag(tanh(ht1))\frac{\partial h_T}{\partial h_1} = \prod_{t=2}^{T} \frac{\partial h_t}{\partial h_{t-1}} = \prod_{t=2}^{T} W_{hh}^T \cdot \text{diag}(\tanh'(h_{t-1})) Mathematical intuition: This product is the crux of the problem. The tanh derivative peaks at 0.25 (since tanh(z)=1tanh2(z)\tanh'(z) = 1 - \tanh^2(z) and its maximum is 1 at z=0z=0, but typical values during training are 0.1-0.25). Multiply that by WhhW_{hh} at each step. If the largest singular value of Whhdiag(tanh)<1W_{hh} \cdot \text{diag}(\tanh') < 1 (which is almost always the case), the product shrinks exponentially: 0.255010300.25^{50} \approx 10^{-30}. That is effectively zero — the network cannot learn from information 50 steps ago because the gradient signal has been annihilated. Conversely, if the singular values exceed 1, the product explodes exponentially. This is why vanilla RNNs are caught in a double bind: they need large weights to maintain gradients, but large weights cause explosions. The only stable regime is the knife-edge where singular values equal exactly 1 — which is unrealistic to maintain during training.
Vanishing Gradients

The Impact on Learning

Solutions to Vanishing Gradients

Practical rule of thumb: If your sequence length is under 20 tokens, a vanilla RNN might work. For 20-200 tokens, use LSTM or GRU. For 200+ tokens, you almost certainly need attention or a transformer. The cutoffs are approximate, but the pattern holds: longer sequences need more sophisticated memory mechanisms.

Bidirectional RNNs

Why Look Both Ways?

In many tasks, future context is as important as past context:
  • “The bank of the river” vs “I went to the bank”
  • In translation, the end of a sentence can clarify the beginning
Bidirectional RNN

Deep RNNs

Stacking RNN Layers


Practical Example: Character-Level Language Model


RNN Applications


Exercises

Implement a complete RNN without using nn.RNN:
Verify it gives similar results to nn.RNN.
Implement the adding problem to test long-range dependencies:
  • Input: Two sequences - numbers and a mask
  • Output: Sum of numbers where mask is 1
  • Example: Numbers [0.3, 0.1, 0.8, 0.2], Mask [1, 0, 0, 1] → Output: 0.5
Test with sequence lengths 10, 50, 100, 200.
Build a model to classify names by nationality:
  • Input: Character sequence (name)
  • Output: Nationality (English, French, German, etc.)
Use the names dataset from PyTorch tutorials.
Build a simple date format converter:
  • Input: “January 15, 2023”
  • Output: “2023-01-15”
Generate synthetic training data and train encoder-decoder.
For a trained sentiment analysis model:
  1. Process sentences with known sentiment
  2. Extract hidden states at each position
  3. Visualize with t-SNE or PCA
  4. Color by sentiment and position
What patterns do you observe?

Training Pitfalls and Debugging Hints

Gradient clipping is not optional for RNNs. Unlike feedforward networks where gradient explosion is rare, RNNs will explode without clipping on any non-trivial sequence length. Always use torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0) as a default. If training is unstable, lower to 1.0. If loss plateaus, you might be clipping too aggressively — try raising to 10.0.Hidden state detachment between batches: When training on long documents split into chunks, you must detach() the hidden state between chunks. Without this, PyTorch tries to backpropagate through the entire document, consuming unbounded memory. Use hidden = hidden.detach() or hidden = tuple(h.detach() for h in hidden) for LSTMs.Pack padded sequences for variable lengths: If your batch contains sequences of different lengths, padding them and ignoring padding in the loss is not enough — the RNN still processes the padding tokens, which corrupts the hidden state. Use nn.utils.rnn.pack_padded_sequence and pad_packed_sequence to make the RNN skip padding entirely. This is one of the most common performance bugs in RNN training.Loss is NaN after a few epochs: Almost always caused by exploding gradients. Check: (1) Are you clipping gradients? (2) Is your learning rate too high? (3) Are there any sequences with extreme values in the input? Start with lr=0.001 for Adam and lr=0.1 for SGD with RNNs.The hidden state initialization trap: Initializing hidden states to zero is standard, but for bidirectional RNNs the backward direction’s “initial” state is the state at the end of the sequence. If your sequences are padded, the backward RNN starts from a padding position. Use packed sequences to avoid this.

Key Takeaways

Vanilla RNNs are rarely used in practice! The vanishing gradient problem makes them unable to learn long-range dependencies. In the next chapter, we’ll learn about LSTMs and GRUs - architectures specifically designed to solve this problem.

What’s Next

Module 9: LSTMs & GRUs

Solve the vanishing gradient problem with gated architectures — learn how LSTM and GRU cells maintain long-term memory through gates.

Interview Deep-Dive

Strong Answer:
  • In an RNN, the hidden state update is ht=tanh(Whhht1+Wxhxt+b)h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b). During BPTT, the gradient of loss with respect to early hidden states requires multiplying the Jacobian ht/ht1\partial h_t / \partial h_{t-1} across all time steps: t=1Tht/ht1\prod_{t=1}^{T} \partial h_t / \partial h_{t-1}.
  • Each Jacobian includes WhhTdiag(tanh(zt))W_{hh}^T \cdot \text{diag}(\tanh'(z_t)). The tanh derivative is at most 1 and typically 0.1-0.5. The product of TT such terms approaches zero exponentially. After 50 time steps, gradients can be 101010^{-10} or smaller.
  • The consequence is selective amnesia: the RNN can learn short-range patterns (2-5 steps) but cannot learn dependencies from 20+ steps ago. This is fundamentally different from the vanishing gradient in feedforward networks because the SAME weight matrix WhhW_{hh} is applied at every step, making the problem a function of the spectral radius of WhhW_{hh}.
  • If the largest singular value of WhhW_{hh} is less than 1, gradients vanish. If greater than 1, they explode. There is no stable middle ground for vanilla RNNs, which is why gated architectures (LSTM, GRU) were necessary.
Follow-up: Gradient clipping handles exploding gradients but not vanishing gradients. Why the asymmetry?Gradient clipping rescales the gradient norm when it exceeds a threshold, preventing catastrophically large updates. But for vanishing gradients, the gradients are not wrong in direction — they are just too small. You cannot amplify them because the gradient direction itself becomes unreliable when the signal-to-noise ratio is near zero. The solution must be architectural (LSTM gates, skip connections) rather than optimization-level (clipping). Clipping handles the exploding case; gating handles the vanishing case. They are complementary.
Strong Answer:
  • BPTT unrolls the RNN through time and applies standard backpropagation to the resulting feedforward graph. An RNN processing length TT is equivalent to a TT-layer feedforward network with shared weights. Gradients are computed backward through all TT steps.
  • Full BPTT has two problems: memory grows linearly with TT (all activations cached), and gradients vanish or explode through hundreds of multiplications.
  • Truncated BPTT limits backpropagation to a window of kk steps. Every kk steps, the hidden state is detached from the computation graph. Gradients only flow backward through the most recent kk steps.
  • Trade-off: the model can only LEARN dependencies up to kk steps long. A truncation of 35 means a 50-step dependency is invisible to the optimizer. However, the model can still USE long-range information already encoded in the hidden state — it just cannot learn to encode it better. The practical sweet spot is k=35256k = 35-256 for language modeling, balancing dependency learning against memory and stability.
Follow-up: How do transformers compare to truncated BPTT in terms of effective context?Transformers avoid gradient flow problems entirely — self-attention creates direct connections between any two positions, so the gradient path length is always 1 (through the attention weights). The limitation is computational: O(n2)O(n^2) attention scales poorly for very long contexts. But within the context window, every position has equal gradient access to every other position, unlike RNNs where gradient strength decays with distance. This is the fundamental reason transformers capture long-range dependencies more effectively.
Strong Answer:
  • Real-time streaming inference: RNNs process one token at a time with constant memory (O(1)O(1) per step), making them ideal for streaming audio transcription, real-time sensor processing, or any setting where you receive data one sample at a time and need immediate outputs. Transformers require buffering the entire context window before processing.
  • Extremely long sequences with limited compute: for sequences of 100,000+ steps (e.g., long-duration biosignals, multi-day time series), transformers’ O(n2)O(n^2) attention becomes prohibitive. RNNs process these in O(n)O(n) time with fixed memory, though they sacrifice long-range dependency quality.
  • Edge deployment with tight memory constraints: an LSTM cell has fixed-size state regardless of sequence length, making memory consumption predictable and small. A transformer’s KV-cache grows linearly with sequence length.
  • State Space Models (SSMs) like Mamba represent a modern compromise: they have RNN-like O(n)O(n) inference with transformer-like training parallelism, and they match transformer quality on many benchmarks. SSMs are increasingly the right answer for the “when would you use recurrence” question — they inherit the efficiency of RNNs without the gradient flow problems.
Follow-up: Why is Mamba considered a breakthrough for sequential modeling?Mamba (Gu and Dao, 2023) achieves linear-time inference like an RNN, parallel training like a transformer, and competitive quality on language modeling benchmarks. The key innovation is selective state spaces: the state transition matrices are input-dependent (selective), allowing the model to dynamically decide what to remember and forget — analogous to LSTM gating but formulated as a continuous-time system. This bridges the recurrence vs. attention divide by providing the efficiency of recurrence with the expressiveness of attention-like selection.