3.5.2 · D2Sequence Models

Visual walkthrough — Backpropagation through time

2,422 words11 min readBack to topic

Prerequisites we lean on: 3.5.01-Recurrent-Neural-Networks-RNNs (what an RNN is) and 2.2.03-Backpropagation-Algorithm (the chain rule as gradient flow). The payoff of this picture — why the backward arrows shrink or blow up — is 3.5.03-Vanishing-and-Exploding-Gradients, repaired later by 3.5.04-Long-Short-Term-Memory-LSTM and 3.5.05-Gated-Recurrent-Unit-GRU.


Step 1 — One cell, drawn as a machine

WHAT. An RNN is a single little machine that you feed one input at a time. At step it takes the input and its own memory from last step , mixes them, squashes the mix, and produces a new memory . Then a second, simpler stage reads that memory out into a prediction .

WHY a picture first. Before any calculus, you must see the two arrows going in ( and ), the memory produced, and the read-out arrow that turns into the prediction . Every gradient later is just these same arrows, walked backward.

PICTURE.

Figure — Backpropagation through time

Term by term, the memory update and the read-out are:

  • — the network's prediction at step (what it says), obtained by reading the memory out through .

Step 2 — Repeat the machine: time becomes depth

WHAT. To process a sequence of length , we don't build machines. We use the same machine times, feeding its own memory back as next-step memory, and reading out a prediction at each step. If we "unroll" this loop and lay the copies side by side, it looks exactly like a deep feedforward network — one layer per time step.

WHY. Backprop is a recipe for feedforward graphs. By unrolling, we turn the loop into a graph backprop already understands. The one twist: every copy shares the same (and the same , , , ).

PICTURE.

Figure — Backpropagation through time

Read the yellow arrows as memory flowing forward. Each blue box is one call of the same cell from Step 1, and each drops a prediction downward. We compare every prediction to the target (the correct answer we wanted), which we write . The total loss adds up a small loss at each step:

  • — the prediction the network made (from Step 1's read-out).
  • — the target: the correct value we wanted at step . The hat means "the true label", not "estimate" here — read as prediction minus truth, i.e. the error.
  • — the mistake made at step (half the squared error).
  • The sum — total mistake over the whole sequence.

Step 3 — Read-out first: how reaches the loss

WHAT. Before blame can flow through time, it has to enter at each step from the loss. The loss sees the prediction ; the prediction sees the memory through . So the "local" blame on is a two-link chain: loss → prediction → memory.

WHY. The recursion in the next step uses a term . It is not magic — it is exactly this read-out chain, and we compute it now so nothing is assumed later.

PICTURE.

Figure — Backpropagation through time

  • — differentiate : the prediction's error.
  • — since , a nudge in moves by .

While the read-out weights are in view, their own gradients drop out of the very same chain:

  • multiplied , so its gradient carries an ; was just added, so its gradient is the bare error.

Step 4 — Send back one error signal: define

WHAT. Backprop asks, at each memory : "if you nudged up a little, how much would the total loss change?" We name that number

WHY a name. is the "blame" landing on memory . Instead of recomputing it from scratch each time, we will show it can be built from the next step's blame — that recursion is the heart of BPTT.

PICTURE.

Figure — Backpropagation through time

The pink arrows are flowing right to left — opposite the yellow forward arrows.

  • — total blame on memory at time .
  • — "tiny change in". reads: tiny change in per tiny change in .

Blame on has exactly two sources (look at the two pink arrows meeting at in the figure):

  • First term: the mistake causes right now through its own prediction — exactly the read-out chain from Step 3, .
  • Second term: also fed , so it is partly to blame for every future mistake. That future blame is , passed back through the link .

WHAT. We need the number that tells us how a nudge in ripples into . Recall where is the pre-activation (the raw sum before squashing).

WHY the chain of two pieces. A nudge in first changes the raw sum (through ), then passes that change on with its own slope . Two gates in a row, so we multiply.

PICTURE.

Figure — Backpropagation through time

  • — how steep the S-curve is at this point. Look at the figure: near the flat ends of this is almost (a nudge barely moves the output); near the steep middle it is close to .
  • — the linear stretch inside.

Putting Steps 4 and 5 together, the backward recursion (in matrix form, = put the vector on a diagonal, = transpose) is:


Step 6 — Cross the squash to reach the weight:

WHAT. is blame on the post-activation memory . But (and and ) live before the squash, inside . To send blame to them we must first push back through the , gaining one more slope factor.

WHY it's easy to forget. The picture makes it obvious: there is a box sitting between and . You cannot walk to without passing that box, and passing it costs a .

PICTURE.

Figure — Backpropagation through time

Define the pre-activation gradient:

  • — elementwise multiply (multiply matching entries; here, in 1-D, ordinary multiply).
  • — the same slope, now for step 's own memory.

Step 7 — Collect blame onto the shared weights (the sums!)

WHAT. Now we harvest. At each step, the pre-activation was built as , so a nudge in moves in proportion to , a nudge in in proportion to , and a nudge in moves it directly. The blame each earns from step is times its multiplier. Because all these parameters are shared, add these over all steps.

WHY the sum. Back in Step 2 we saw the parameters appear in every box. Each box's opinion is one term; the total gradient is their sum.

PICTURE.

Figure — Backpropagation through time

The input weight and the memory bias follow the same logic — same , different multiplier ( for , and for ):

Together with the read-out gradients from Step 3 ( and ), every parameter of the RNN now has its gradient. The recipe is complete.


Step 8 — The edge cases you must not trip on

WHAT. Three corners that break naive code.

PICTURE.

Figure — Backpropagation through time
  1. The last step . There is no future, so does not exist. The recursion starts clean: only. (Yellow box, right edge.)
  2. The first step . Its incoming memory is , the initial state. In the weight sum the term is . If (a common default), that whole term vanishes — not a bug, just zero times anything. (Note 's term does not vanish: its multiplier is , not .)
  3. Saturated . If a memory sits near , then , so both and the backward hop shrink toward . Blame stops flowing — the network "forgets". (Pink arrow fading to nothing.)
Recall Quick self-checks

Where does the sum over come from? ::: is shared across all time steps, so each step contributes one gradient term and we add them. What is the local term , written out? ::: The read-out chain — loss to prediction (error ) times prediction to memory (). Why must you multiply by before touching ? ::: Because sits before the ; you cross the squash to reach it, and crossing costs its slope . What starts the backward recursion, and why? ::: , because step has no future step feeding blame back.


The one-picture summary

Figure — Backpropagation through time

Yellow = forward memory flow, and each cell reads out a prediction compared to target . Pink = backward blame flow. Blame enters at each step through the read-out (), rides back through time multiplying by per hop, crosses each squash (gaining ) and drops a term into the shared bucket for every parameter: for , for , for . Empty the buckets = your gradients.

Recall Feynman retelling (say it out loud)

Picture one little machine that eats an input and its own last memory, mixes them, squashes with a tanh, and hands out a fresh memory — then a second little step reads that memory out into a prediction . To handle a whole sentence, we run the same machine over and over and lay the runs in a row — now it's just a deep network where every layer is the same machine, and each layer makes a prediction we compare to the truth .

To train it, we walk backward. At each memory we ask, "how much are you to blame for the total mistake?" That blame, , comes from two places: the mistake you made right here — which arrives through the read-out, error times the read-out weight — plus all the future mistakes you caused by feeding the next memory, passed back through a link that is the tanh's steepness times the memory-mixing weight.

To finally fix each shared weight, at every step I push the blame through the tanh (that's the tiny factor people forget), multiply by whatever that weight multiplied on the way in (the old memory for , the input for , nothing for the bias), and toss the result in that weight's shared bucket. Because the weights were reused everywhere, I add every step's contribution. Those totals — plus the read-out weight and its bias — are the gradients. And notice: every backward step multiplies by that same times , so chain a lot of them and the signal either withers away or blows up, which is the famous headache the LSTM and GRU were invented to cure.

Next: watch that repeated multiplication run wild in 3.5.03-Vanishing-and-Exploding-Gradients, or read this whole page in Hinglish.