Backpropagation Through Time › A Worked Example

Michael BrenndoerferMay 10, 20255 min read
Language AI HandbookAll chapters
Part 11 of 67Recurrent Neural Networks
Chapter 75Backpropagation Through Time › A Worked Example
Reading section 6 of 15 · A Worked Example · 5 minSection 6 of 15 · 5 min

Book contents

Section 6 of 15 · 5 min read

Backpropagation Through Time › A Worked ExampleOne section · Sections in this chapter6 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Derivations and code can make BPTT seem abstract. Working through a tiny numerical example by hand is the fastest way to build intuition about what the equations compute, where the values come from, and why the backward pass has to visit timesteps in reverse order.

Let's trace through BPTT on a minimal example to make the mechanics concrete.

Consider an RNN with hidden dimension d=2d = 2, processing a sequence of three steps. The recurrent weight matrix is:

Whh=(0.50.10.20.5)\mathbf{W}_{hh} = \begin{pmatrix} 0.5 & 0.1 \\ 0.2 & 0.5 \end{pmatrix}

After the forward pass, the hidden states are:

h0=(00),h1=(0.30.5),h2=(0.40.6),h3=(0.50.7)\mathbf{h}_0 = \begin{pmatrix} 0 \\ 0 \end{pmatrix}, \quad \mathbf{h}_1 = \begin{pmatrix} 0.3 \\ 0.5 \end{pmatrix}, \quad \mathbf{h}_2 = \begin{pmatrix} 0.4 \\ 0.6 \end{pmatrix}, \quad \mathbf{h}_3 = \begin{pmatrix} 0.5 \\ 0.7 \end{pmatrix}

The error signals flowing back from the output layer (local gradients from each timestep's loss) are:

∂L1∂h1=(0.1−0.2),∂L2∂h2=(0.3−0.1),∂L3∂h3=(−0.20.4)\frac{\partial \mathcal{L}_1}{\partial \mathbf{h}_1} = \begin{pmatrix} 0.1 \\ -0.2 \end{pmatrix}, \quad \frac{\partial \mathcal{L}_2}{\partial \mathbf{h}_2} = \begin{pmatrix} 0.3 \\ -0.1 \end{pmatrix}, \quad \frac{\partial \mathcal{L}_3}{\partial \mathbf{h}_3} = \begin{pmatrix} -0.2 \\ 0.4 \end{pmatrix}

These local gradients come from the output layer: specifically, they are the derivatives of each timestep's loss Lt\mathcal{L}_t with respect to the hidden state ht\mathbf{h}_t at that step. In practice, this derivative is computed by backpropagating through the output weight matrix Why\mathbf{W}_{hy} and then through the softmax (if applicable). For this example, we take them as given to focus on the backward-through-time portion.

Step 1: Initialize δ3\boldsymbol{\delta}_3 at the last timestep

The backward pass starts from the final timestep. The initial error signal is simply the local gradient:

δ3=∂L3∂h3=(−0.20.4)\boldsymbol{\delta}_3 = \frac{\partial \mathcal{L}_3}{\partial \mathbf{h}_3} = \begin{pmatrix} -0.2 \\ 0.4 \end{pmatrix}

The tanh derivative (elementwise) at timestep 3 is:

g3=(1−0.521−0.72)=(1−0.251−0.49)=(0.750.51)\mathbf{g}_3 = \begin{pmatrix} 1 - 0.5^2 \\ 1 - 0.7^2 \end{pmatrix} = \begin{pmatrix} 1 - 0.25 \\ 1 - 0.49 \end{pmatrix} = \begin{pmatrix} 0.75 \\ 0.51 \end{pmatrix}

The modified error (tanh derivative applied before backpropagating) is:

δ3⊙g3=(−0.2×0.750.4×0.51)=(−0.1500.204)\boldsymbol{\delta}_3 \odot \mathbf{g}_3 = \begin{pmatrix} -0.2 \times 0.75 \\ 0.4 \times 0.51 \end{pmatrix} = \begin{pmatrix} -0.150 \\ 0.204 \end{pmatrix}

Step 2: Propagate backward to δ2\boldsymbol{\delta}_2

The recurrence carries the error signal one step further back:

δ2=∂L2∂h2+WhhT(δ3⊙g3)\boldsymbol{\delta}_2 = \frac{\partial \mathcal{L}_2}{\partial \mathbf{h}_2} + \mathbf{W}_{hh}^T (\boldsymbol{\delta}_3 \odot \mathbf{g}_3)

Computing the matrix-vector product:

WhhT(δ3⊙g3)=(0.50.20.10.5)(−0.1500.204)=(−0.075+0.041−0.015+0.102)=(−0.0340.087)\mathbf{W}_{hh}^T (\boldsymbol{\delta}_3 \odot \mathbf{g}_3) = \begin{pmatrix} 0.5 & 0.2 \\ 0.1 & 0.5 \end{pmatrix} \begin{pmatrix} -0.150 \\ 0.204 \end{pmatrix} = \begin{pmatrix} -0.075 + 0.041 \\ -0.015 + 0.102 \end{pmatrix} = \begin{pmatrix} -0.034 \\ 0.087 \end{pmatrix}

Note that WhhT\mathbf{W}_{hh}^T is Whh\mathbf{W}_{hh} with rows and columns swapped, because we are differentiating Whhh\mathbf{W}_{hh}\mathbf{h} with respect to h\mathbf{h}.

Adding the local gradient:

δ2=(0.3−0.1)+(−0.0340.087)=(0.266−0.013)\boldsymbol{\delta}_2 = \begin{pmatrix} 0.3 \\ -0.1 \end{pmatrix} + \begin{pmatrix} -0.034 \\ 0.087 \end{pmatrix} = \begin{pmatrix} 0.266 \\ -0.013 \end{pmatrix}

Step 3: Accumulate the gradient contribution to Whh\mathbf{W}_{hh}

At each timestep, the contribution to ∂L∂Whh\frac{\partial \mathcal{L}}{\partial \mathbf{W}_{hh}} is an outer product of the modified error signal and the previous hidden state. For timestep 3:

ΔWhh(3)=(δ3⊙g3)⋅h2T=(−0.1500.204)(0.40.6)=(−0.060−0.0900.0820.122)\Delta \mathbf{W}_{hh}^{(3)} = (\boldsymbol{\delta}_3 \odot \mathbf{g}_3) \cdot \mathbf{h}_2^T = \begin{pmatrix} -0.150 \\ 0.204 \end{pmatrix} \begin{pmatrix} 0.4 & 0.6 \end{pmatrix} = \begin{pmatrix} -0.060 & -0.090 \\ 0.082 & 0.122 \end{pmatrix}

where:

  • (δ3⊙g3)(\boldsymbol{\delta}_3 \odot \mathbf{g}_3): the 2×12 \times 1 modified error vector at time 3
  • h2T\mathbf{h}_2^T: the 1×21 \times 2 previous hidden state as a row vector
  • The outer product gives a 2×22 \times 2 gradient matrix matching the shape of Whh\mathbf{W}_{hh}

The total gradient for Whh\mathbf{W}_{hh} is the sum of these outer products across all three timesteps:

∂L∂Whh=ΔWhh(1)+ΔWhh(2)+ΔWhh(3)\frac{\partial \mathcal{L}}{\partial \mathbf{W}_{hh}} = \Delta \mathbf{W}_{hh}^{(1)} + \Delta \mathbf{W}_{hh}^{(2)} + \Delta \mathbf{W}_{hh}^{(3)}

This example illustrates several important properties of BPTT. First, the backward pass must run in strict reverse order: you cannot compute δ2\boldsymbol{\delta}_2 until you have computed δ3\boldsymbol{\delta}_3. Second, each timestep's gradient contribution is an outer product that has exactly the shape of the weight matrix, which is why the gradient accumulation is just a simple sum. Third, the magnitude of the gradient contribution from each timestep depends both on the local error signal and on how much that signal has been attenuated by the chain of Jacobians from later steps.

Understanding What the Gradients Tell Us

It is instructive to think about what the gradient ∂L∂Whh\frac{\partial \mathcal{L}}{\partial \mathbf{W}_{hh}} says. The entry at position (i,j)(i, j) in this matrix tells you: if you increase the weight connecting hidden dimension jj of the previous state to hidden dimension ii of the current state, how does the total loss change? A large positive value means increasing that weight increases the loss. A large negative value means increasing it decreases the loss. A near-zero value means that weight has minimal effect on the loss, perhaps because the gradient signal has been attenuated by vanishing gradients along its path.

When the gradient for Whh\mathbf{W}_{hh} is very small (close to the zero matrix), gradient descent will barely update the recurrent weights. This is the vanishing gradient problem: the model cannot effectively learn from gradient signal that traveled a long path through the unrolled network. When the gradient is very large, a single gradient step will make a drastic change to the weights, destabilizing training. The next chapter examines both pathologies in rigorous detail.