← All posts

Foundations

Post 9 of 15

LSTM and GRU: gates that decide what to keep

Posts in this series · 15

The last post ended with a network that could not remember. A one-unit recurrent neural network (RNN) was shown a single number, +1+1 or −1-1, and then a string of zeros, and asked at the end what the first number was. With a delay of 10 steps it learned. With a delay of 20 it never did, however long it trained.

This post fixes that, with an idea from 1997 that carried speech recognition and speech synthesis for most of the 2010s: the long short-term memory (LSTM) cell, and its lighter cousin the gated recurrent unit (GRU). We will run both by hand on tiny numbers, see exactly why the gradient survives, and then train an LSTM on the same task that defeated the plain RNN.

The whole model at a glance

Here is a whole LSTM at work on the same task as the RNN post’s opening figure: read “hell” one character at a time and predict the next character, “ello”. The cell is tiny: 2 numbers on its long-term belt and 2 in its hidden state, with weights trained offline and rounded to one decimal. The gates, the belt and the hidden state are each explained below; step through once now to see where each one sits and what it does to the numbers.

An LSTM unrolled over four characters: gates, cell belt, hidden state and next-character probabilities
Figure 1. An LSTM from input to output. At each character the gates decide what to forget, what to write and what to show; the belt carries the memory forward; the hidden state is read out as next-character probabilities. Same task and same layout as the RNN figure, so the two can be compared step by step.

The problem, in one line

Recall the plain scalar RNN from post 8:

ht=tanh⁡(wx xt+wh ht−1+b)h_t = \tanh\left(w_x \, x_t + w_h \, h_{t-1} + b\right)

Going back one step, the gradient is multiplied by wh×(1−ht2)w_h \times (1 - h_t^2). Going back kk steps multiplies kk such factors together. With wh=0.5w_h = 0.5, the echo task from post 8 kept only 0.0014 of its first input after ten steps, and over 20 steps the backward product was about 0.520=0.000000950.5^{20} = 0.00000095, roughly one in a million. With the tanh⁡\tanh slopes included it is even smaller, 0.00000079.

The everyday picture from post 8 was a whispering game: each child repeats the message at half volume, and the twentieth child hears nothing. The cause is that the memory has only one road, and that road is “multiply by whw_h, then squash” at every step. Nothing can travel far along it.

The fix is to build a second road where the message is not forced through a multiplication by a fixed weight at each step, and to let the network decide, step by step, how much to keep. The tool for “decide how much” is called a gate.

The gate: a dimmer switch

In one sentence: a gate is a number between 0 and 1, computed by a sigmoid, that multiplies another number to let all of it, none of it, or some of it through.

The sigmoid from post 1 squashes any number into the range from 0 to 1:

σ(z)=11+e−z\sigma(z) = \frac{1}{1 + e^{-z}}

A few values you will see again and again in this post:

zz−4−2−101234
σ(z)\sigma(z)0.01800.11920.26890.50.73110.88080.95260.9820

A gate is used by multiplying: output=σ(z)×v\text{output} = \sigma(z) \times v, where vv is the value being gated. The everyday picture is a dimmer switch on a lamp. The lamp’s full brightness is vv; the dimmer decides what fraction reaches the room.

Worked: the value is v=0.8v = 0.8.

Why a sigmoid and not a hard on/off switch? Because a hard switch has slope zero everywhere, and gradient descent from post 2 needs a slope to learn which way to turn the dial. The sigmoid is a soft switch that can be learned. And the score zz is computed from the current input and memory with its own weights, so the network learns when to open each gate.

When the value is a vector, a gate is a vector of the same length, and the multiplication is elementwise: number 1 times number 1, number 2 times number 2, and so on. This is written ⊙\odot. For example (0.9,0.1)⊙(2,5)=(1.8,0.5)(0.9, 0.1) \odot (2, 5) = (1.8, 0.5): each memory slot gets its own dimmer.

A sigmoid gate value multiplying a signal, like a dimmer on a lamp
Figure 2. A gate is a dimmer. Defaults z = 2, v = 0.8: gate 0.8808, output 0.7046. At z = 0 the output is 0.4; at z = −2 it is 0.0954. The gate never quite reaches 0 or 1.

The LSTM cell

Hochreiter and Schmidhuber (1997) introduced the LSTM. The version used today, and in this post, also has a forget gate, which Gers, Schmidhuber and Cummins (2000) added; the 1997 cell could not erase its own memory.

Two memories: the cell state and the hidden state

An LSTM carries two numbers (or vectors) from step to step:

The everyday picture for the cell state is a conveyor belt running along a factory line. Items ride along it untouched unless a worker reaches in. At each station (time step) one worker may take some items off (forget), another may put new items on (write), and a third looks at the belt and reports a summary (output). Crucially, the belt itself does not squash or rescale what it carries: what is not removed simply rides on.

The six equations, in plain words first

At each step the cell does four small calculations from the same two inputs, xtx_t and ht−1h_{t-1}, and then two updates. In plain words:

  1. Forget gate ftf_t: how much of the old belt to keep.
  2. Input gate iti_t: how much of the new candidate to write.
  3. Candidate gtg_t: what new content could be written.
  4. Output gate oto_t: how much of the belt to show.
  5. Cell update: keep part of the old belt, add part of the candidate.
  6. Hidden update: show a squashed, gated view of the belt.

For one unit (everything is a single number), they are:

ft=σ(wf xt+uf ht−1+bf)it=σ(wi xt+ui ht−1+bi)gt=tanh⁡(wg xt+ug ht−1+bg)ot=σ(wo xt+uo ht−1+bo)ct=ft ct−1+it gtht=ot tanh⁡(ct)\begin{aligned} f_t &= \sigma\left(w_f \, x_t + u_f \, h_{t-1} + b_f\right) \\ i_t &= \sigma\left(w_i \, x_t + u_i \, h_{t-1} + b_i\right) \\ g_t &= \tanh\left(w_g \, x_t + u_g \, h_{t-1} + b_g\right) \\ o_t &= \sigma\left(w_o \, x_t + u_o \, h_{t-1} + b_o\right) \\ c_t &= f_t \, c_{t-1} + i_t \, g_t \\ h_t &= o_t \, \tanh(c_t) \end{aligned}

Each of the four gate lines is exactly one neuron from post 1, with two inputs. An LSTM unit is four neurons that share their inputs, plus the two update lines.

The heart is line five: ct=ft ct−1+it gtc_t = f_t \, c_{t-1} + i_t \, g_t. The old belt is multiplied by a dimmer and then added to the new write. There is no weight matrix and no tanh⁡\tanh on that path. Hold on to that; it is the whole reason the gradient survives.

In the vector version every symbol becomes a vector, each w∗w_\ast and u∗u_\ast becomes a matrix, and the products in lines five and six are elementwise (⊙\odot):

ct=ft⊙ct−1+it⊙gt,ht=ot⊙tanh⁡(ct)\mathbf{c}_t = \mathbf{f}_t \odot \mathbf{c}_{t-1} + \mathbf{i}_t \odot \mathbf{g}_t, \qquad \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t)

Running it by hand

Take the echo task from post 8: input x=(1, 0, 0, 0)x = (1,\ 0,\ 0,\ 0), and the cell must still remember the 11 at the end. Weights chosen so the arithmetic is easy, and so that each gate has an obvious job:

To keep the arithmetic short, every recurrent weight u∗u_\ast here is zero, so the gates only look at the input. In a trained LSTM they also look at ht−1h_{t-1}, and Figure 3 has sliders for them.

Step 1 (x1=1x_1 = 1, belt empty: c0=0c_0 = 0).

f1=σ(3)=0.9526,i1=σ(4×1−2)=σ(2)=0.8808,g1=tanh⁡(2)=0.9640,o1=0.5f_1 = \sigma(3) = 0.9526, \quad i_1 = \sigma(4 \times 1 - 2) = \sigma(2) = 0.8808, \quad g_1 = \tanh(2) = 0.9640, \quad o_1 = 0.5 c1=0.9526×0+0.8808×0.9640=0+0.8491=0.8491c_1 = 0.9526 \times 0 + 0.8808 \times 0.9640 = 0 + 0.8491 = 0.8491 h1=0.5×tanh⁡(0.8491)=0.5×0.6906=0.3453h_1 = 0.5 \times \tanh(0.8491) = 0.5 \times 0.6906 = 0.3453

The input gate was open and the candidate was strong, so 0.8491 went onto the belt.

Step 2 (x2=0x_2 = 0).

f2=0.9526,i2=σ(−2)=0.1192,g2=tanh⁡(0)=0,o2=0.5f_2 = 0.9526, \quad i_2 = \sigma(-2) = 0.1192, \quad g_2 = \tanh(0) = 0, \quad o_2 = 0.5 c2=0.9526×0.8491+0.1192×0=0.8088,h2=0.5×tanh⁡(0.8088)=0.5×0.6690=0.3345c_2 = 0.9526 \times 0.8491 + 0.1192 \times 0 = 0.8088, \qquad h_2 = 0.5 \times \tanh(0.8088) = 0.5 \times 0.6690 = 0.3345

Step 3 (x3=0x_3 = 0). The same gates: c3=0.9526×0.8088=0.7705c_3 = 0.9526 \times 0.8088 = 0.7705, h3=0.5×0.6472=0.3236h_3 = 0.5 \times 0.6472 = 0.3236.

Step 4 (x4=0x_4 = 0). c4=0.9526×0.7705=0.7339c_4 = 0.9526 \times 0.7705 = 0.7339, h4=0.5×0.6255=0.3127h_4 = 0.5 \times 0.6255 = 0.3127.

stepxtx_tftf_titi_tgtg_toto_tctc_thth_t
110.95260.88080.96400.50.84910.3453
200.95260.119200.50.80880.3345
300.95260.119200.50.77050.3236
400.95260.119200.50.73390.3127

Compare post 8’s plain RNN on the same input (wh=0.5w_h = 0.5): its memory at step 4 was 0.0896, and it was halving every step. The LSTM’s belt is at 0.7339 and losing under 5 percent per step. After 20 steps the belt still holds 0.8491×0.952619=0.8491 \times 0.9526^{19} = 0.3373; the plain RNN’s memory after ten was already 0.0014.

What would change? Lower bfb_f to 0 and ft=0.5f_t = 0.5: the belt now halves each step, just like the plain RNN. Raise bfb_f to 6 and ft=0.9975f_t = 0.9975: the belt barely moves. Set wi=0w_i = 0 and bi=−4b_i = -4 so the input gate is always nearly shut (0.01800.0180): the 1 at step 1 is hardly written at all, c1=0.0180×0.9640=0.0174c_1 = 0.0180 \times 0.9640 = 0.0174. Set the output gate’s bias to −4-4: the belt keeps its memory, but hth_t shows only 2 percent of it. The memory is there, just not on display.

An LSTM cell with its cell-state belt, four gates and output, computed one operation at a time
Figure 3. One LSTM cell, step by step. Defaults: input 1, 0, 0, 0; b_f = 3, w_i = 4, b_i = −2, w_g = 2, all other weights 0. The belt reads 0.8491, 0.8088, 0.7705, 0.7339 and the output 0.3453, 0.3345, 0.3236, 0.3127. Drag b_f to 0 and watch the belt halve each step instead. The figure keeps every recurrent weight at 0, as in the text.

Why the belt keeps the gradient alive

Now the backward pass, the part that broke the plain RNN.

In one sentence: a small nudge to the old belt ct−1c_{t-1} reaches the new belt ctc_t multiplied only by the forget gate.

Look again at the cell update, ct=ft ct−1+it gtc_t = f_t \, c_{t-1} + i_t \, g_t. If you nudge ct−1c_{t-1} by a tiny amount, the first term moves by ftf_t times that amount, and the second term does not involve ct−1c_{t-1} at all. So, along the belt,

∂ct∂ct−1=ft\frac{\partial c_t}{\partial c_{t-1}} = f_t

Compare the plain RNN’s link, wh×(1−ht2)w_h \times (1 - h_t^2): a fixed weight times a tanh⁡\tanh slope that shrinks whenever the memory is strong. The belt’s link has no fixed weight and no squashing slope. It is whatever the forget gate says, and the network learns to set it near 1 exactly when it wants to remember.

By the chain rule from post 1, going back kk steps along the belt multiplies the forget gates:

∂ct∂ct−k=ft×ft−1×⋯×ft−k+1\frac{\partial c_t}{\partial c_{t-k}} = f_t \times f_{t-1} \times \dots \times f_{t-k+1}

Worked over 20 steps, each factor written as the same number for simplicity:

backward factor per stepafter 10 stepsafter 20 stepsafter 30 steps
plain RNN, wh=0.5w_h = 0.5 (slopes = 1)0.000980.000000950.00000000093
LSTM belt, f=σ(1)=0.7311f = \sigma(1) = 0.73110.04360.00190.000083
LSTM belt, f=σ(2)=0.8808f = \sigma(2) = 0.88080.28100.07900.0222
LSTM belt, f=σ(3)=0.9526f = \sigma(3) = 0.95260.61520.37840.2328

With the forget gate at 0.9526, 38 percent of the gradient survives 20 steps; the plain RNN keeps less than a millionth. That is a factor of about 400,000.

The everyday picture: on the conveyor belt, an item placed at the start of the line reaches the end unless a worker removes it. Sending a complaint back up the line (“the item at the end was wrong”) travels the same belt in reverse, and it is only weakened at stations where workers were removing things.

Hochreiter and Schmidhuber’s 1997 cell had no forget gate at all; its self-connection was fixed at exactly 1, which they called the constant error carousel: the backward factor along the cell was exactly 1, forever. The forget gate, added in 2000, gives that up deliberately, so the cell can also clear memory it no longer needs, for example at the start of a new sentence.

Two honest caveats.

Gradient size against steps back for a plain RNN and an LSTM belt
Figure 4. Two roads for the gradient. Defaults: RNN w_h = 0.5 with tanh slopes on, LSTM b_f = 3 (f = 0.9526). At 20 steps back the RNN line reads 0.00000079 and the LSTM line 0.3784. Set b_f = 0 and the gold line falls on top of 0.5 to the power k.

The forget-gate bias trick

If the forget gate decides everything, what should it be before training? Weights usually start as small random numbers, and biases at 0. A forget bias of 0 gives f=σ(0)=0.5f = \sigma(0) = 0.5: the belt halves at every step, exactly the plain RNN’s failure. The network starts out forgetting, and, as post 8 showed, if the gradient has already vanished at the start, there is nothing to learn from.

The fix is one line: start the forget bias at a positive number, usually bf=1b_f = 1, so ff starts around 0.730.73 and the belt begins by remembering. Gers, Schmidhuber and Cummins (2000) already started their forget gates with positive biases, and Jozefowicz, Zaremba and Sutskever (2015), after an automated search over more than ten thousand recurrent architectures, singled it out: adding a bias of 1 to the forget gate closed most of the gap between the LSTM and the best gated designs they found. Many libraries now offer it as an option.

What does it buy? From the table above: after 20 steps, σ(0)20=0.00000095\sigma(0)^{20} = 0.00000095, σ(1)20=0.0019\sigma(1)^{20} = 0.0019, σ(2)20=0.0790\sigma(2)^{20} = 0.0790. Each step up in the bias multiplies the surviving gradient by roughly 40 at 20 steps. You will see this in the trainer: forget bias 0 fails at a delay of 20, bias 1 succeeds at 20 but fails at 40, bias 2 succeeds at all four delays.

A side note on peepholes. Gers and Schmidhuber (2000, “Recurrent nets that time and count”) also let the gates look directly at the cell state, adding a term like pf ct−1p_f \, c_{t-1} inside each gate’s score. Greff et al. (2017) compared eight LSTM variants on speech, handwriting and music and found peepholes made no significant difference on those tasks; the forget gate and the output activation were the parts that mattered most. Most libraries leave peepholes out.

The GRU: two gates instead of three

Cho et al. (2014) proposed a simpler gated cell as part of their encoder-decoder for machine translation; it is now called the gated recurrent unit (GRU). It merges the belt and the hidden state into one memory, hth_t, and uses two gates.

The equations

In plain words: the reset gate decides how much of the old memory to use when drafting a new candidate; the update gate decides how much of that candidate replaces the old memory.

zt=σ(wz xt+uz ht−1+bz)rt=σ(wr xt+ur ht−1+br)h~t=tanh⁡(wh xt+uh (rt ht−1)+bh)ht=(1−zt) ht−1+zt h~t\begin{aligned} z_t &= \sigma\left(w_z \, x_t + u_z \, h_{t-1} + b_z\right) \\ r_t &= \sigma\left(w_r \, x_t + u_r \, h_{t-1} + b_r\right) \\ \tilde{h}_t &= \tanh\left(w_h \, x_t + u_h \, (r_t \, h_{t-1}) + b_h\right) \\ h_t &= (1 - z_t) \, h_{t-1} + z_t \, \tilde{h}_t \end{aligned}

The last line is an interpolation: a weighted average of old and new, with weights 1−zt1 - z_t and ztz_t that always add up to 1. The everyday picture is a mixing tap: one handle sets the blend between the hot pipe (old memory) and the cold pipe (new candidate). You cannot get more water by opening both; the GRU’s “keep” and “write” are tied together, where the LSTM has separate forget and input dials.

A naming warning. Which side ztz_t multiplies differs between sources. Cho et al. (2014) wrote ht=zt ht−1+(1−zt) h~th_t = z_t \, h_{t-1} + (1 - z_t) \, \tilde{h}_t, so their zz is the fraction kept. Chung et al. (2014) wrote it the other way, as above, so zz is the fraction written. PyTorch’s nn.GRU documentation follows Cho’s form, ht=(1−zt)⊙nt+zt⊙ht−1h_t = (1 - z_t) \odot n_t + z_t \odot h_{t-1}, and also applies the reset gate after the recurrent multiplication rather than before. Both conventions describe the same family of cells; a learned network simply flips the sign of the gate’s weights. This post uses Chung’s form because “z = how much new” reads more naturally next to the LSTM’s input gate.

Running it by hand

Same input x=(1, 0, 0, 0)x = (1,\ 0,\ 0,\ 0), h0=0h_0 = 0. Weights:

Step 1. z1=0.8808z_1 = 0.8808, r1=0.5r_1 = 0.5, r1h0=0r_1 h_0 = 0.

h~1=tanh⁡(2×1+1×0)=tanh⁡(2)=0.9640\tilde{h}_1 = \tanh(2 \times 1 + 1 \times 0) = \tanh(2) = 0.9640 h1=(1−0.8808)×0+0.8808×0.9640=0+0.8491=0.8491h_1 = (1 - 0.8808) \times 0 + 0.8808 \times 0.9640 = 0 + 0.8491 = 0.8491

Step 2. z2=0.1192z_2 = 0.1192, r2=0.5r_2 = 0.5, r2h1=0.5×0.8491=0.4246r_2 h_1 = 0.5 \times 0.8491 = 0.4246.

h~2=tanh⁡(0+1×0.4246)=0.4008\tilde{h}_2 = \tanh(0 + 1 \times 0.4246) = 0.4008 h2=0.8808×0.8491+0.1192×0.4008=0.7479+0.0478=0.7957h_2 = 0.8808 \times 0.8491 + 0.1192 \times 0.4008 = 0.7479 + 0.0478 = 0.7957

Step 3. r3h2=0.3978r_3 h_2 = 0.3978, h~3=0.3781\tilde{h}_3 = 0.3781, h3=0.8808×0.7957+0.1192×0.3781=0.7008+0.0451=0.7459h_3 = 0.8808 \times 0.7957 + 0.1192 \times 0.3781 = 0.7008 + 0.0451 = 0.7459.

Step 4. r4h3=0.3729r_4 h_3 = 0.3729, h~4=0.3566\tilde{h}_4 = 0.3566, h4=0.6570+0.0425=0.6995h_4 = 0.6570 + 0.0425 = 0.6995.

stepztz_trtht−1r_t h_{t-1}h~t\tilde{h}_tkept (1−zt)ht−1(1-z_t) h_{t-1}written zth~tz_t \tilde{h}_thth_t
10.880800.964000.84910.8491
20.11920.42460.40080.74790.04780.7957
30.11920.39780.37810.70080.04510.7459
40.11920.37290.35660.65700.04250.6995

The GRU holds 0.6995 at step 4, against the plain RNN’s 0.0896. The memory leaks slowly because, with z=0.1192z = 0.1192, 88 percent of the old value is kept, and the small write pulls it only partly towards the weaker candidate.

The backward story is the same as the belt: along the direct path, ∂ht∂ht−1\frac{\partial h_t}{\partial h_{t-1}} includes the term 1−zt1 - z_t, and with 1−zt=0.88081 - z_t = 0.8808 for 20 steps that path keeps 0.880820=0.07900.8808^{20} = 0.0790. To keep longer, the network learns to push ztz_t closer to 0 on quiet steps. (The other terms, through the gates and the candidate, add to this; they do not replace it.)

What would change? Set the reset gate’s bias to −4-4 (r=0.018r = 0.018): the candidate almost ignores the past (h~2=tanh⁡(0.018×0.8491)=0.0153\tilde{h}_2 = \tanh(0.018 \times 0.8491) = 0.0153), so step 2 is nearly just the kept 88 percent: h2=0.7479+0.1192×0.0153=0.7497h_2 = 0.7479 + 0.1192 \times 0.0153 = 0.7497. Set bz=0b_z = 0: z=0.5z = 0.5 on quiet steps, and the memory is half replaced each time.

A limit of these hand-picked weights: the update gate opens because the input is positive. With x1=−1x_1 = -1 it would score σ(−6)=0.0025\sigma(-6) = 0.0025 and barely write. The same is true of the LSTM example’s input gate. Trained networks find weights that work for both signs; the trainer below learns them.

A GRU blending its old memory with a new candidate, one operation at a time
Naming
Figure 5. One GRU cell, step by step. Defaults: input 1, 0, 0, 0; w_z = 4, b_z = −2, reset weights 0, w_h = 2, u_h = 1. The memory reads 0.8491, 0.7957, 0.7459, 0.6995. A toggle switches to Cho’s naming (z = fraction kept); the numbers do not change, only the label.

An LSTM learns what the RNN could not

Now the payoff. The task is exactly post 8’s: input (s, 0, 0, …, 0)(s,\ 0,\ 0,\ \dots,\ 0) of length TT (the delay), with s=+1s = +1 or −1-1, and the target at the last step is ss. The output is y=v hTy = v \, h_T, and the loss is half the squared error, averaged over the two examples. Every pass computes the full gradient by backpropagation through time and takes one gradient-descent step. There is no randomness.

“Learned” means the loss first falls below 0.01. Each run is capped at 3,000 passes.

delay TT5102040
plain RNN825nevernever
LSTM, bf=0b_f = 097745nevernever
LSTM, bf=1b_f = 14264339never
LSTM, bf=2b_f = 230273173

And the very first gradient for the weight that writes ss into memory (wxw_x for the RNN, the candidate’s wgw_g for the LSTM):

delay TT5102040
plain RNN−0.0226−0.00072−0.00000070−0.00000000000067
LSTM, bf=2b_f = 2−0.0557−0.0306−0.0088−0.00070

Four things to notice.

  1. At delay 40 the LSTM’s first gradient is a billion times larger than the RNN’s. The RNN’s signal passes through 39 factors of about 0.5; the LSTM’s passes through 39 forget gates of σ(2)=0.8808\sigma(2) = 0.8808, and 0.880839=0.00710.8808^{39} = 0.0071.
  2. The forget bias decides everything. With bf=0b_f = 0 the LSTM is no better than the plain RNN at long delays, because σ(0)=0.5\sigma(0) = 0.5 is post 8’s wh=0.5w_h = 0.5 in disguise. That is the bias trick, measured.
  3. At short delays the RNN is faster. It has 3 weights and a bigger learning rate; the LSTM has 13. Gates are not free; they pay off when memory has to be long.
  4. In every successful LSTM run, training pushed bfb_f up to about 2.35, so f≈0.91f \approx 0.91 on quiet steps: the network learned to keep its belt.

Post 8 also rescued the RNN by starting whw_h at 1.0. That works for one bit, but it fixes the memory’s decay rate for every input. The LSTM can keep at one moment and clear at the next, because its forget gate is computed fresh from each input.

A plain RNN and an LSTM training side by side to remember their first input
Training
Delay
LSTM forget bias
Figure 6. Remember the first input, again. Defaults: delay 20, forget bias 2. The RNN stays at loss 0.5; the LSTM passes below 0.01 at pass 31. Choose delay 40: LSTM at pass 73. Set forget bias 0: the LSTM fails at 20 too.

How many weights?

A recurrent layer’s size is set by two numbers: nn, the input size (for example 80 mel numbers per frame), and mm, the hidden size (for example 256 units).

In one sentence: each gate (or candidate) is one ordinary layer reading both the input and the old hidden state, so it costs nm+m2+mn m + m^2 + m weights, and an LSTM has four of them while a GRU has three.

RNN=nm+m2+m,LSTM=4 (nm+m2+m),GRU=3 (nm+m2+m)\text{RNN} = n m + m^2 + m, \qquad \text{LSTM} = 4\,(n m + m^2 + m), \qquad \text{GRU} = 3\,(n m + m^2 + m)

Worked with n=80n = 80, m=256m = 256:

nm+m2+m=20,480+65,536+256=86,272n m + m^2 + m = 20{,}480 + 65{,}536 + 256 = 86{,}272

So the plain RNN has 86,272 weights, the GRU 3×86,272=3 \times 86{,}272 = 258,816, and the LSTM 4×86,272=4 \times 86{,}272 = 345,088. A GRU is 25 percent smaller than an LSTM of the same width. For our one-unit toys (n=m=1n = m = 1): 3, 9 and 12, which, plus the output weight, is the LSTM trainer’s 13.

Library counts can differ slightly. PyTorch, for example, keeps two bias vectors per gate (one on the input side, one on the recurrent side), adding mm per gate: 346,112 for that LSTM and 259,584 for the GRU.

You can check all three in PyTorch:

import torch.nn as nn

count = lambda m: sum(p.numel() for p in m.parameters())
print("RNN ", count(nn.RNN(80, 256)))
print("GRU ", count(nn.GRU(80, 256)))
print("LSTM", count(nn.LSTM(80, 256)))
# the four gates are stacked: 4 × 256 rows
print(nn.LSTM(80, 256).weight_ih_l0.shape)

It prints:

RNN  86528
GRU  259584
LSTM 346112
torch.Size([1024, 80])

The plain RNN reads 86,528, the formula’s 86,272 plus the second bias of 256. The last line shows how PyTorch stores the four gates: one input matrix with 4×256=1,0244 \times 256 = 1{,}024 rows, the gates stacked on top of each other.

Which is better? Chung et al. (2014) compared them on music and raw speech-signal modelling and found both gated units clearly beat the plain tanh⁡\tanh RNN, with no consistent winner between them. Jozefowicz et al. (2015) and Greff et al. (2017) reached similar conclusions on larger studies: with a sensible forget bias, the LSTM is hard to beat, and the GRU is a good, cheaper alternative.

Weight counts of a plain RNN, a GRU and an LSTM layer
Biases
Figure 7. Counting weights. Defaults n = 80, m = 256: RNN 86,272, GRU 258,816, LSTM 345,088. With the PyTorch toggle: 346,112 and 259,584. Set n = m = 1: 3, 9, 12.

Bidirectional and stacked

Two ways to build bigger recurrent models from these cells, briefly.

LSTMs in speech, and what replaced them

Then attention, and the Transformer built on it, replaced them almost everywhere, for two reasons that follow directly from this post.

The gate idea did not disappear, though. The Transformer’s residual connections (post 11) are an always-open version of the belt’s “add, don’t overwrite”, and gating reappears in modern state-space models. The next step is attention; you can already see it at work in the speech model of the Pocket TTS post.

What to remember

Try it yourself

Sources