Part XI — Sequence Models: RNNs, LSTMs, Attention & The Transformer Block · Chapter 2

Recurrent Neural Networks (RNN)

Hook

Last chapter's network failed because it summed its tokens into one vector before doing anything else — by the time a weight ever touched the data, the order was already gone. The fix isn't a bigger network. It's a different shape: process one token at a time, and carry something forward.

Intuition
A: [X, Y]start0.00X0.76Y-0.23
h0 = 0 → h1 = 0.762 → h2 = -0.234

A recurrent network keeps a single running number, the hidden state, starting at h0=0h_0 = 0. At every step it folds in the next token and its own previous state: h1h_1 depends on token 1, h2h_2 depends on token 2 and on h1h_1 — which already encoded token 1. Nothing is ever summed away. Slide WhW_h and watch how much the earlier token's fingerprint survives into h2h_2.

Formalize
ht=tanh(Wxxt+Whht1+b)h_t = \tanh(W_x \cdot x_t + W_h \, h_{t-1} + b)
  • hth_t — the hidden state at timestep tt: the running summary the network carries forward.
  • xtx_t — the input token at timestep tt.
  • WxW_x — the weight matrix applied to the current input token.
  • WhW_h — the weight matrix applied to the previous hidden state (the recurrent weight).
  • ht1h_{t-1} — the hidden state carried over from the previous timestep.
  • bb — the bias term.
  • tanh\tanh — the squashing activation that keeps the hidden state bounded between 1-1 and 11.
  1. Weights shared across time

    The same weights WxW_x, WhW_h, bb are reused at every timestep — exactly the "share one small set of weights across many positions" idea from convolution, just applied across time instead of space.

  2. Recurrence carries the whole history

    The recurrence is what makes h2h_2 depend on the entire sequence seen so far, not just the token that just arrived.

Play
A: [X, Y]start0.00X0.76Y-0.23B: [Y, X]start0.00Y-0.76X0.23
final hA = -0.234, final hB = 0.234 — separation = 0.468

These are the exact two sequences from Chapter 5 — "X then Y" and "Y then X" — the ones a sum-pooled network could never tell apart, because they pooled to the identical vector (1,1)(1,1) no matter the order. Watch h1h_1 here: it's already different for the two sequences, before the second token even arrives, simply because the RNN saw a different first token. Sum-pooling could never do that.

Worked example

With Wx=(1,1)W_x = (1,-1), b=0b=0, Wh=1W_h=1:

  1. "X then Y"

    h1=tanh(1)0.762h_1 = \tanh(1) \approx 0.762, then h2=tanh(1+1×0.762)=tanh(0.238)0.234h_2 = \tanh(-1 + 1 \times 0.762) = \tanh(-0.238) \approx -0.234.

  2. "Y then X"

    h1=tanh(1)0.762h_1 = \tanh(-1) \approx -0.762, then h2=tanh(1+1×(0.762))=tanh(0.238)0.234h_2 = \tanh(1 + 1 \times (-0.762)) = \tanh(0.238) \approx 0.234.

  3. Compare

    Same weights, same two tokens — but processed in the opposite order, the final hidden states land on opposite signs. That sign is something a simple readout layer can actually use.

Checkpoint

Adjust the recurrent weight until the two sequences’ final hidden states differ by more than 1.9.

A: [X, Y]start0.00X0.76Y-0.23B: [Y, X]start0.00Y-0.76X0.23
separation = 0.468
Drag the slider to try it
Summary
ht=tanh(Wxxt+Whht1+b)h_t = \tanh(W_x \cdot x_t + W_h \, h_{t-1} + b)

Carrying a hidden state forward, instead of pooling everything at once, is what let this network do something Chapter 5's architecture couldn't do at any weight setting. But notice how far you had to push WhW_h to get a confident separation — and recall there was one particular weight where the two sequences collapsed back to the same state. Pushing a recurrent weight that hard, repeated over a long sequence, is exactly what causes the next chapter's problem: vanishing gradients.