3.5.2Sequence Models

Backpropagation through time

2,833 words13 min readdifficulty · medium6 backlinks

The Problem: Gradients in Recurrent Networks

A standard RNN has the form: ht=tanh(Whhht1+Wxhxt+bh)h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b_h) yt=Whyht+byy_t = W_{hy} h_t + b_y

We want to minimize a loss over a sequence: L=t=1TLtL = \sum_{t=1}^{T} L_t

where LtL_t is the loss at time step tt (e.g., cross-entropy for classification).

Challenge: To update WhhW_{hh}, we need LWhh\frac{\partial L}{\partial W_{hh}}. But WhhW_{hh} is used at every time step, so changes to it affect all future hidden states. The gradient must account for all paths through time.


Derivation from First Principles

Step 1: Unroll the Computation Graph

Write the forward pass explicitly for TT time steps:

h1=tanh(Whhh0+Wxhx1+bh)h2=tanh(Whhh1+Wxhx2+bh)hT=tanh(WhhhT1+WxhxT+bh)\begin{align} h_1 &= \tanh(W_{hh} h_0 + W_{xh} x_1 + b_h) \\ h_2 &= \tanh(W_{hh} h_1 + W_{xh} x_2 + b_h) \\ &\vdots \\ h_T &= \tanh(W_{hh} h_{T-1} + W_{xh} x_T + b_h) \end{align}

Each hth_t depends on ht1h_{t-1}, which depends on ht2h_{t-2}, and so on. This creates a chain of dependencies.

Step 2: Apply the Chain Rule Across Time

To find LWhh\frac{\partial L}{\partial W_{hh}}, sum contributions from all time steps: LWhh=t=1TLtWhh\frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^{T} \frac{\partial L_t}{\partial W_{hh}}

Why this step? WhhW_{hh} appears in the computation of h1,h2,,hTh_1, h_2, \dots, h_T, so the total gradient is the sum of partial effects at each time.

Now, LtL_t depends on yty_t, which depends on hth_t. But hth_t depends on ht1,ht2,,h1h_{t-1}, h_{t-2}, \dots, h_1, all of which involve WhhW_{hh}:

LtWhh=LththtWhh\frac{\partial L_t}{\partial W_{hh}} = \frac{\partial L_t}{\partial h_t} \cdot \frac{\partial h_t}{\partial W_{hh}}

Here's the key: htWhh\frac{\partial h_t}{\partial W_{hh}} must account for the direct effect (at time tt) and indirect effects (through earlier hidden states):

htWhh=htht1ht1Whh+htWhhdirect\frac{\partial h_t}{\partial W_{hh}} = \frac{\partial h_t}{\partial h_{t-1}} \cdot \frac{\partial h_{t-1}}{\partial W_{hh}} + \frac{\partial h_t}{\partial W_{hh}}\bigg|_{\text{direct}}

Why this step? By the multivariate chain rule, the gradient flows through ht1h_{t-1} (indirect path) and also directly affects hth_t (direct path).

Step 3: Recursive Gradient Flow

Define δt=Lht\delta_t = \frac{\partial L}{\partial h_t} (the gradient w.r.t. the post-activation hidden state). Then: δt=Ltht+Lht+1ht+1ht=Ltht+δt+1ht+1ht\delta_t = \frac{\partial L_t}{\partial h_t} + \frac{\partial L}{\partial h_{t+1}} \cdot \frac{\partial h_{t+1}}{\partial h_t} = \frac{\partial L_t}{\partial h_t} + \delta_{t+1} \cdot \frac{\partial h_{t+1}}{\partial h_t}

Why this step? Gradient at hth_t has two sources: the immediate loss LtL_t and the future loss propagated back from ht+1h_{t+1}.

For the RNN update rule, let at+1=Whhht+Wxhxt+1+bha_{t+1} = W_{hh} h_t + W_{xh} x_{t+1} + b_h be the pre-activation, so ht+1=tanh(at+1)h_{t+1} = \tanh(a_{t+1}): ht+1ht=diag(1tanh2(at+1))Whh=diag(1ht+12)Whh\frac{\partial h_{t+1}}{\partial h_t} = \text{diag}(1 - \tanh^2(a_{t+1})) \cdot W_{hh} = \text{diag}(1 - h_{t+1}^2) \cdot W_{hh}

Why this step? Derivative of tanh\tanh is 1tanh21 - \tanh^2, and ht+1h_{t+1} is computed as tanh(linear combination involving ht)\tanh(\text{linear combination involving } h_t). The Jacobian is diagonal (elementwise nonlinearity) times WhhW_{hh}.

Thus: δt=Ltht+WhhTdiag(1ht+12)δt+1\delta_t = \frac{\partial L_t}{\partial h_t} + W_{hh}^T \cdot \text{diag}(1 - h_{t+1}^2) \cdot \delta_{t+1}

Step 4: Gradient w.r.t. the Weights (don't forget the tanh\tanh factor!)

The weight WhhW_{hh} enters the pre-activation at=Whhht1+a_t = W_{hh} h_{t-1} + \dots, and ht=tanh(at)h_t = \tanh(a_t). So the local gradient w.r.t. WhhW_{hh} at step tt must include the tanh\tanh derivative: LtWhh=(δt(1ht2))ht1T\frac{\partial L_t}{\partial W_{hh}} = \left(\delta_t \odot (1 - h_t^2)\right) h_{t-1}^T

Why this step? δt=L/ht\delta_t = \partial L/\partial h_t is post-activation. To reach WhhW_{hh} we pass through the tanh\tanh: ht/at=diag(1ht2)\partial h_t / \partial a_t = \text{diag}(1 - h_t^2), and at/Whh=ht1T\partial a_t / \partial W_{hh} = h_{t-1}^T. Skipping the (1ht2)(1-h_t^2) term is a common bug.

Defining the pre-activation gradient δ^t=δt(1ht2)\hat{\delta}_t = \delta_t \odot (1 - h_t^2), the accumulated gradient is:

The BPTT Algorithm:

  1. Forward pass: Compute h1,,hTh_1, \dots, h_T and losses L1,,LTL_1, \dots, L_T.
  2. Backward pass: Initialize δT\delta_T, then for t=T1t = T-1 down to 11, compute δt\delta_t recursively.
  3. Accumulate gradients: LWhh=t=1T(δt(1ht2))ht1T=t=1Tδ^tht1T\frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^{T} \big(\delta_t \odot (1 - h_t^2)\big)\, h_{t-1}^T = \sum_{t=1}^{T} \hat{\delta}_t\, h_{t-1}^T


Visualizing the Unrolled Network

Figure — Backpropagation through time

The diagram shows:

  • Forward pass (left to right): hidden states hth_t computed sequentially.
  • Backward pass (right to left): gradients δt\delta_t flow backward through time.
  • Shared weights WhhW_{hh}: gradient contributions from all time steps are summed.


Worked Example 1: Simple 2-Step RNN

Setup: 1-dimensional RNN, T=2T=2, Whh=0.5W_{hh} = 0.5, Wxh=1.0W_{xh} = 1.0, tanh\tanh activation, h0=0h_0 = 0.

Input: x1=1.0,x2=0.5x_1 = 1.0, x_2 = 0.5. Target outputs: y^1=0.5,y^2=0.3\hat{y}_1 = 0.5, \hat{y}_2 = 0.3. Loss: Lt=12(yty^t)2L_t = \frac{1}{2}(y_t - \hat{y}_t)^2 with yt=hty_t = h_t (identity output for simplicity).

Forward pass: h1=tanh(0.50+1.01.0)=tanh(1.0)0.7616h_1 = \tanh(0.5 \cdot 0 + 1.0 \cdot 1.0) = \tanh(1.0) \approx 0.7616 y1=h1=0.7616,L1=12(0.76160.5)2=0.0342y_1 = h_1 = 0.7616, \quad L_1 = \frac{1}{2}(0.7616 - 0.5)^2 = 0.0342

h2=tanh(0.50.7616+1.00.5)=tanh(0.8808)0.7068h_2 = \tanh(0.5 \cdot 0.7616 + 1.0 \cdot 0.5) = \tanh(0.8808) \approx 0.7068 y2=h2=0.7068,L2=12(0.70680.3)2=0.0827y_2 = h_2 = 0.7068, \quad L_2 = \frac{1}{2}(0.7068 - 0.3)^2 = 0.0827

Why these steps? We apply the RNN equations directly. Each hidden state depends on the previous one and the current input.

Backward pass (compute post-activation δt\delta_t):

At t=2t=2: δ2=L2h2=h20.3=0.4068\delta_2 = \frac{\partial L_2}{\partial h_2} = h_2 - 0.3 = 0.4068

Why this step? Derivative of squared error w.r.t. h2=y2h_2 = y_2.

At t=1t=1: L1h1=h10.5=0.2616\frac{\partial L_1}{\partial h_1} = h_1 - 0.5 = 0.2616

h2h1=(1h22)Whh=(10.70682)0.5=0.50060.5=0.2503\frac{\partial h_2}{\partial h_1} = (1 - h_2^2) \cdot W_{hh} = (1 - 0.7068^2) \cdot 0.5 = 0.5006 \cdot 0.5 = 0.2503

Why this step? Derivative of tanh\tanh at the pre-activation is 1h221 - h_2^2, and the linear part contributes WhhW_{hh}.

δ1=0.2616+0.40680.2503=0.3634\delta_1 = 0.2616 + 0.4068 \cdot 0.2503 = 0.3634

Gradient — include the tanh\tanh factor (1ht2)(1 - h_t^2)!

Pre-activation gradients: δ^2=δ2(1h22)=0.4068(10.70682)=0.40680.5006=0.2036\hat{\delta}_2 = \delta_2 \cdot (1 - h_2^2) = 0.4068 \cdot (1 - 0.7068^2) = 0.4068 \cdot 0.5006 = 0.2036 δ^1=δ1(1h12)=0.3634(10.76162)=0.36340.4200=0.1526\hat{\delta}_1 = \delta_1 \cdot (1 - h_1^2) = 0.3634 \cdot (1 - 0.7616^2) = 0.3634 \cdot 0.4200 = 0.1526

LWhh=δ^1h0+δ^2h1=0.15260+0.20360.76160.1551\frac{\partial L}{\partial W_{hh}} = \hat{\delta}_1 \cdot h_0 + \hat{\delta}_2 \cdot h_1 = 0.1526 \cdot 0 + 0.2036 \cdot 0.7616 \approx 0.1551

Why this step? Each time step contributes δ^tht1\hat{\delta}_t \cdot h_{t-1} (pre-activation gradient times the incoming hidden state). The t=2t=2 term is 0.4068(10.70682)0.76160.1550.4068 \cdot (1 - 0.7068^2) \cdot 0.7616 \approx 0.155not 0.3100.310. Forgetting the (1ht2)(1 - h_t^2) factor doubles the answer here; that is the classic error.


Worked Example 2: Gradient Vanishing in Long Sequences

Setup: T=10T = 10, Whh=0.9W_{hh} = 0.9, xt=1.0x_t = 1.0 for all tt, h0=0h_0 = 0.

Forward pass (computed carefully): h1=tanh(0.90+1.0)=tanh(1.0)0.7616h_1 = \tanh(0.9 \cdot 0 + 1.0) = \tanh(1.0) \approx 0.7616 h2=tanh(0.90.7616+1.0)=tanh(1.6854)0.9331h_2 = \tanh(0.9 \cdot 0.7616 + 1.0) = \tanh(1.6854) \approx 0.9331 h3=tanh(0.90.9331+1.0)=tanh(1.8398)0.9506h_3 = \tanh(0.9 \cdot 0.9331 + 1.0) = \tanh(1.8398) \approx 0.9506 h4=tanh(0.90.9506+1.0)=tanh(1.8555)0.9522h_4 = \tanh(0.9 \cdot 0.9506 + 1.0) = \tanh(1.8555) \approx 0.9522 h5tanh(0.90.9522+1.0)=tanh(1.8570)0.9524,h_5 \approx \tanh(0.9 \cdot 0.9522 + 1.0) = \tanh(1.8570) \approx 0.9524, \dots

Why these steps? Hidden states saturate toward a fixed point near 0.9520.952 because tanh\tanh squashes large values.

Backward pass: Suppose δ10=1.0\delta_{10} = 1.0. The recurrence factor is: ht+1ht=(1ht+12)Whh\frac{\partial h_{t+1}}{\partial h_t} = (1 - h_{t+1}^2)\cdot W_{hh}

Using h0.952h \approx 0.952 in the saturated region, 1h210.906=0.0941 - h^2 \approx 1 - 0.906 = 0.094, so each factor is 0.0940.90.0846\approx 0.094 \cdot 0.9 \approx 0.0846.

At t=9t=9: δ9=1.00.08460.0846\delta_9 = 1.0 \cdot 0.0846 \approx 0.0846 At t=8t=8: δ8=0.08460.08460.00716\delta_8 = 0.0846 \cdot 0.0846 \approx 0.00716

Why this step? Each backward step multiplies by (1ht+12)Whh0.085<1(1 - h_{t+1}^2)\cdot W_{hh} \approx 0.085 < 1. Since hth_t saturates near 0.9520.952, (1ht2)(1 - h_t^2) is tiny, causing exponential decay.

After 9 steps: δ10.084692.4×1010\delta_1 \approx 0.0846^{9} \approx 2.4\times 10^{-10}. Vanishing gradient: information from time 10 barely reaches time 1.

Why this matters: Long-range dependencies cannot be learned. This motivates LSTMs and GRUs.





Truncated BPTT: Practical Compromise

For long sequences, storing all hidden states and computing exact gradients is memory-intensive. Truncated BPTT splits the sequence into chunks of length kk:

  1. Forward pass for kk steps.
  2. Backward pass for those kk steps only.
  3. Detach the gradient and continue forward for the next kk steps.

Trade-off: Gradients cannot propagate beyond kk steps, limiting the model's ability to learn long-range dependencies. But it reduces memory from O(T)O(T) to O(k)O(k).

Formula: LWhht=t0t0+kδ^tht1T\frac{\partial L}{\partial W_{hh}} \approx \sum_{t=t_0}^{t_0 + k} \hat{\delta}_t\, h_{t-1}^T

where t0t_0 is the start of the current chunk.



Recall Explain to a 12-Year-Old

Imagine you're learning to play a song on the piano. The song has 100 notes, and you want to get better.

Normal learning (feedforward): You play one note, your teacher corrects you, you move to the next note. Each note is independent. Recurrent learning (RNN): Each note you play depends on the previous notes. If you mess up note 50, it affects notes 51, 52, ..., 100. To learn, you need to figure out how each note contributed to the final mistake.

Backpropagation through time: After playing all 100 notes, you "rewind" the song and ask: "How did note 1 affect the final sound?" You trace backward: note 100 depended on 99, which depended on 98, .., back to note 1. You adjust how you played note 1 based on the entire chain of effects.

The problem: the further back you go, the fuzzier your memory of what happened (vanishing gradients). That's why RNNs struggle with long songs (sequences). LSTMs are like taking better notes so you remember earlier parts clearly.



Connections


Summary

Backpropagation through time

Concept Map

reuses

unrolled into

time as

used every step

sum over t

chain rule

requires

sum of

split into

split into

recursion

flows backward

RNN recurrence h_t

Shared W_hh

Deep feedforward net

Spatial depth layers

All temporal paths

Total loss L

Per-step loss L_t

dL/dW_hh

Per-step contributions

Direct effect at t

Indirect via h_t-1

Backprop signal delta_t

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, RNN mein ek hi weight matrix WhhW_{hh} har time step pe baar baar use hota hai — yahi core cheez hai. Ab problem yeh hai ki jab hum gradient calculate karte hain training ke liye, tab yeh shared weight future ke saare hidden states ko affect karta hai. Toh idea yeh hai ki hum RNN ko "unroll" karte hain — matlab time ke har step ko ek alag layer ki tarah imagine karte hain, jaise ek bahut deep feedforward network ho jismein saari layers same weights share karti hain. Isiliye iska naam hai "Backpropagation Through Time" — kyunki gradient time steps ke through peeche flow karta hai. Simple words mein: temporal recurrence ko spatial depth ki tarah treat kar rahe hain.

Ab why-it-matters wala part. Chain rule apply karte waqt, kisi bhi time tt pe gradient ke do sources hote hain — ek immediate loss LtL_t jo abhi ke output se aata hai, aur doosra future se aane wala gradient jo ht+1h_{t+1} ke through back-propagate hota hai. Isko hum recursively likhte hain as δt\delta_t, aur har step pe WhhTW_{hh}^T aur tanh\tanh ka derivative (1h2)(1 - h^2) multiply hota hai. Yeh jo repeated multiplication hai, wahi asli kahani hai — kyunki agar yeh factors baar-baar chhote hote gaye toh gradient vanish ho jaata hai (long-term memory nahi bachti), aur agar bade ho gaye toh explode ho jaata hai. Isi vanishing/exploding gradient problem ki wajah se aage chalke LSTM aur GRU jaise architectures invent huye.

Toh bhai, BPTT samajhna isliye zaroori hai kyunki yeh foundation hai — jab tak tumhe yeh clear nahi hoga ki gradient time ke through kaise flow karta hai aur kyun weak ya explode hota hai, tab tak tum yeh appreciate nahi kar paoge ki modern sequence models (LSTM, attention, transformers) yeh saari problems kaise solve karte hain. Ek chhoti si tip: notation mein confuse mat hona — WhhW_{hh} hamesha hidden-to-hidden hai, aur yahi wala matrix time ke across shared hota hai, isko baaki WxhW_{xh} aur WhyW_{hy} se alag rakhna.

Go deeper — visual, from zero

Test yourself — Sequence Models

Connections