Deep Learning

LSTM, GRU and bidirectional RNN

This lecture fixes the memory problem of plain RNNs with gated cells (LSTM, GRU). It then reads sequences in both directions (Bi-LSTM, Bi-RNN). After this page you can compute gate outputs, GRU updates, merge shapes and embedding lookups by hand.

Why long sequences break plain RNNsSearch on YouTube

A plain RNN keeps one hidden state. It rewrites this state at each step with the new input. Old information gets morphed at each step. After many steps the first inputs leave almost no trace.

The slides show this with a short gap. The sky is blank. The answer is blue. A short context is enough. A plain RNN predicts this well.

The slides then show a long gap. Harit lives in India for 13 years. He loves movies and works as an artist. He is very fluent in blank. The answer is Hindi. The clue (India) sits far from the blank. A plain RNN struggles here. It has morphed the early clue away.

The slides show a subject switch. Mark is a good singer and lived in LA for three years. Jacob is also a good singer. His debut was a great success. Congrats blank. The answer is Jacob. The model must forget Mark and keep Jacob. A plain RNN has no clear forget path.

LSTM is a notebook with an eraser and a gate. The cell keeps old notes. The gates erase stale notes and add fresh notes. The reader sees only a clean copy.

Cell state and hidden stateSearch on YouTube

An RNN has one vector: the hidden state hth_t. It stores memory and it also drives the output. These two roles clash. Storage needs stability. Output needs change.

LSTM splits the roles into two vectors. The cell state CtC_t stores information (internal memory). The hidden state hth_t produces the output and prediction. Gates control the flow between them.

  • Cell state CtC_t: a slow conveyor. It carries facts across many steps. Changes to it are small and gated.
  • Hidden state hth_t: a filtered view of CtC_t. It feeds the classifier and the next step.

LSTM gatesSearch on YouTube

LSTM uses three gates. Each gate is a sigmoid in [0,1][0, 1]. A value 00 blocks flow. A value 11 passes all flow.

The three gates act in order. The forget gate drops stale memory. The input gate adds fresh memory. The output gate filters what leaves.

F-I-O: Forget past, Input new, Output filtered

Forget drops part of Ct−1C_{t-1} · Input adds the candidate to CtC_t · Output filters CtC_t into hth_t.

Shapes and batch-first layoutSearch on YouTube

The slides use BB for batch size (number of words in the batch). They use DD for feature size (size of each word vector). They use HH for hidden size (number of hidden units).

In batch-first layout each input batch has shape (B,T,D)(B, T, D). Here TT is sequence length. Each hidden and cell state has shape (B,H)(B, H). A D×HD \times H matrix maps input to hidden. A H×HH \times H matrix maps hidden to hidden. A B×HB \times H result holds one row per sequence.

Practical frameworks favour batch-first layout. Without batching each vector is a column. With batching each vector is a row block.

Forget gateSearch on YouTube

The forget gate decides which part of Ct−1C_{t-1} to keep. It reads xtx_t and ht−1h_{t-1}. It outputs ftf_t in [0,1][0, 1].

ft=σ(Wfxt+Ufht−1+bf)f_t = \sigma(W_f x_t + U_f h_{t-1} + b_f)

WfW_f is D×HD \times H (input to gate). UfU_f is H×HH \times H (hidden to gate). bfb_f is the bias of size HH. σ\sigma is sigmoid. ftf_t has shape (B,H)(B, H).

A value near 11 keeps the old cell entry. A value near 00 erases it. For the Mark-Jacob story the gate learns to erase Mark when Jacob arrives.

Input gate and candidateSearch on YouTube

The input gate has two parts. The gate iti_t decides where to write. The candidate C~t\tilde{C}_t holds the new values. The value it=0i_t = 0 stores nothing. The value it=1i_t = 1 stores the full candidate.

it=σ(Wixt+Uiht−1+bi)i_t = \sigma(W_i x_t + U_i h_{t-1} + b_i)

WiW_i is D×HD \times H and UiU_i is H×HH \times H. The output iti_t has shape (B,H)(B, H).

C~t=tanh⁡(Wgxt+Ught−1+bg)\tilde{C}_t = \tanh(W_g x_t + U_g h_{t-1} + b_g)

WgW_g is D×HD \times H and UgU_g is H×HH \times H. The candidate lies in [−1,1][-1, 1].

Ct=ft⊙Ct−1+it⊙C~tC_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t

⊙\odot is element-wise product. CtC_t has shape (B,H)(B, H).

The cell update is a sum. The old term keeps gated memory. The new term adds gated input. This sum path carries gradients far. It avoids the fast decay of plain RNNs.

Output gate and hidden stateSearch on YouTube

The output gate decides which part of CtC_t to show. A closed gate (ot=0o_t = 0) blocks the cell. An open gate (ot=1o_t = 1) passes all of it.

ot=σ(Woxt+Uoht−1+bo)o_t = \sigma(W_o x_t + U_o h_{t-1} + b_o)

WoW_o is D×HD \times H and UoU_o is H×HH \times H. The output oto_t has shape (B,H)(B, H).

ht=ot⊙tanh⁡(Ct)h_t = o_t \odot \tanh(C_t)

hth_t has shape (B,H)(B, H). The tanh⁡\tanh bounds the cell to [−1,1][-1, 1] before filtering.

The slides stress two computation styles. Separate computation learns one matrix per gate. Unified computation stacks the four matrices and learns once. Both learn the same count of parameters. Unified form runs faster.

Worked example. One cell unit. Prior cell Ct−1=0.4C_{t-1} = 0.4. Pre-activation sums are zf=0.4z_f = 0.4, zi=−0.4z_i = -0.4, zg=0.5z_g = 0.5, zo=0.8z_o = 0.8.

Gates. Apply sigmoid and tanh⁡\tanh.

ft=σ(0.4)=0.5987it=σ(−0.4)=0.4013C~t=tanh⁡(0.5)=0.4621ot=σ(0.8)=0.6900\begin{aligned} f_t &= \sigma(0.4) = 0.5987\\ i_t &= \sigma(-0.4) = 0.4013\\ \tilde{C}_t &= \tanh(0.5) = 0.4621\\ o_t &= \sigma(0.8) = 0.6900 \end{aligned}

Cell and hidden state. Combine old memory and new input.

Ct=0.5987×0.4+0.4013×0.4621=0.2395+0.1854=0.4249ht=0.6900×tanh⁡(0.4249)=0.6900×0.4011=0.2767\begin{aligned} C_t &= 0.5987 \times 0.4 + 0.4013 \times 0.4621 = 0.2395 + 0.1854 = \mathbf{0.4249}\\ h_t &= 0.6900 \times \tanh(0.4249) = 0.6900 \times 0.4011 = \mathbf{0.2767} \end{aligned}

The cell keeps most of the past (0.24) and adds some present (0.19). The output gate then passes 69 percent of it.

Exam shortcut: sigmoid 00 gives 0.50.5 and large positive gives near 11. tanh⁡\tanh 00 gives 00. Test ft=1f_t = 1, it=0i_t = 0 first. Then Ct=Ct−1C_t = C_{t-1} (pure memory).

Move the gate sliders and watch the cell state update. The readout repeats the lecture numbers at each step.

F 0.60I 0.40O 0.69C(t-1) = 0.4cand = 0.4621C = 0.4249h = 0.2767

Valve shade shows gate value. A shut valve blocks flow.

C = 0.5987 x 0.4 + 0.4013 x 0.4621 = 0.2395 + 0.1854 = 0.4249; h = 0.6900 x tanh(0.4249) = 0.2767

GRUSearch on YouTube

LSTM trains slowly. It learns four gate matrices per step. GRU is a simpler LSTM with two gates and one state. It merges cell and hidden state into one hidden state hth_t. It is faster and uses fewer parameters.

GRU gates:

  • Reset gate rtr_t: controls how much of ht−1h_{t-1} enters the candidate.
  • Update gate ztz_t: decides how much of the candidate replaces ht−1h_{t-1}.
rt=σ(Wrxt+Urht−1+br)r_t = \sigma(W_r x_t + U_r h_{t-1} + b_r)

rtr_t has shape (B,H)(B, H). A value near 00 ignores the past when forming the candidate.

zt=σ(Wzxt+Uzht−1+bz)z_t = \sigma(W_z x_t + U_z h_{t-1} + b_z)

ztz_t has shape (B,H)(B, H). A value near 11 takes the new candidate. A value near 00 keeps the old state.

h~t=tanh⁡(Wgxt+Ug(rt⊙ht−1)+bg)\tilde{h}_t = \tanh(W_g x_t + U_g (r_t \odot h_{t-1}) + b_g)

The reset gate sits inside the candidate. It scales ht−1h_{t-1} before the matrix acts.

ht=(1−zt)⊙ht−1+zt⊙h~th_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t

hth_t has shape (B,H)(B, H). The two weights sum to 11 per unit.

The last line can copy the past directly. When ztz_t is near 00, hth_t stays near ht−1h_{t-1}. This direct copy path cuts the vanishing effect.

Worked example. One GRU unit. Prior state ht−1=0.6h_{t-1} = 0.6. Pre-activation sums are zr=−0.2z_r = -0.2, zz=0.4z_z = 0.4, zh=0.2z_h = 0.2.

Gates and candidate.

rt=σ(−0.2)=0.4502zt=σ(0.4)=0.5987h~t=tanh⁡(0.2)=0.1974\begin{aligned} r_t &= \sigma(-0.2) = 0.4502\\ z_t &= \sigma(0.4) = 0.5987\\ \tilde{h}_t &= \tanh(0.2) = 0.1974 \end{aligned}

Final state. Mix past and candidate with weights that sum to 11.

ht=(1−0.5987)×0.6+0.5987×0.1974=0.4013×0.6+0.1182=0.2408+0.1182=0.3590\begin{aligned} h_t &= (1 - 0.5987) \times 0.6 + 0.5987 \times 0.1974\\ &= 0.4013 \times 0.6 + 0.1182 = 0.2408 + 0.1182 = \mathbf{0.3590} \end{aligned}

The update gate keeps 40 percent of the past and takes 60 percent of the candidate.

LSTMGRU
Two states (CtC_t and hth_t)One state (hth_t)
Three gates (forget, input, output)Two gates (reset, update)
Four D×HD \times H plus four H×HH \times H matricesThree of each
Slower training, strong on long gapsFaster training, close accuracy on short gaps

Bidirectional LSTMSearch on YouTube

A plain LSTM reads past and present only. State at time tt sees x1…xtx_1 \dots x_t. Some tasks need the future too. A missing word may depend on words after it.

The slides show fill-in (interpolation). I am blank. Add one clue: I am blank hungry. Add more: I am blank hungry, I can eat a whole pig. Each future word narrows the blank. One direction alone cannot use the later words.

The slides show NER with apple. Sentence A: Dhaval loves apple, it keeps him healthy. Here apple is fruit. Sentence B: Dhaval loves apple, he bought their latest phone. Here apple is a brand. The word after apple (it versus he, healthy versus phone) fixes the label. A forward-only reader tags too early.

Bi-LSTM fixes this. It runs two LSTM layers. One reads forward from step 11 to TT. One reads backward from step TT to 11. Each layer keeps its own hidden and cell states. At each step it combines the two hidden states.

Forward passSearch on YouTube

The forward LSTM reads x1x_1 first and xTx_T last. At step tt it uses xtx_t, forward ht−1h_{t-1} and forward Ct−1C_{t-1}. It stores past context.

Backward passSearch on YouTube

The backward LSTM reads xTx_T first and x1x_1 last. At step tt it uses xtx_t, backward ht+1h_{t+1} and backward Ct+1C_{t+1}. It stores future context.

Combine stepSearch on YouTube

After both passes each step has two vectors. The model merges them into one output.

yt=[h→t;h←t]y_t = [\overrightarrow{h}_t ; \overleftarrow{h}_t]

h→t\overrightarrow{h}_t is the forward hidden state of size HH. h←t\overleftarrow{h}_t is the backward hidden state of size HH. [⋅;⋅][\cdot ; \cdot] is concat. yty_t has size 2H2H.

yt=Wy[h→t;h←t]+byy_t = W_y [\overrightarrow{h}_t ; \overleftarrow{h}_t] + b_y

WyW_y maps 2H2H to the label size. Other merges (sum, average, max) keep size HH.

Benefits and drawbacksSearch on YouTube

Benefits:

  • It sees past and future context at each step.
  • It captures long-range ties in both directions.
  • It scores higher on speech, translation and sentiment tasks.
  • It accepts extra layers such as attention on top.

Drawbacks:

  • It costs about twice the compute of one LSTM.
  • It needs large data to avoid overfitting.
  • It is hard to interpret (a black box).

WorkflowSearch on YouTube

  1. Take the input sequence (words or characters as vectors).
  2. Map tokens to dense embeddings (semantic vectors).
  3. Run the Bi-LSTM layer over past and future context.
  4. Combine forward and backward states at each step (concat or transform).

Worked example. Batch B=32B = 32, length T=10T = 10, hidden size H=64H = 64 per direction. Each direction output has shape (32,10,64)(32, 10, 64).

Concat merge. Stack the last axis.

y=[h→;h←]shape(y)=(32,10,64+64)=(32,10,128)\begin{aligned} y &= [\overrightarrow{h} ; \overleftarrow{h}]\\ \text{shape}(y) &= (32, 10, 64 + 64) = \mathbf{(32, 10, 128)} \end{aligned}

Sum, average and max merges. Keep the same shape.

ysum=h→+h←shape (32,10,64)yavg=(h→+h←)/2shape (32,10,64)ymax=max⁡(h→,h←)shape (32,10,64)\begin{aligned} y_{\text{sum}} &= \overrightarrow{h} + \overleftarrow{h} && \text{shape } \mathbf{(32, 10, 64)}\\ y_{\text{avg}} &= (\overrightarrow{h} + \overleftarrow{h})/2 && \text{shape } \mathbf{(32, 10, 64)}\\ y_{\text{max}} &= \max(\overrightarrow{h}, \overleftarrow{h}) && \text{shape } \mathbf{(32, 10, 64)} \end{aligned}

Concat doubles the classifier input. The other three keep it at 6464.

Bidirectional RNNSearch on YouTube

A Bi-RNN applies the same two-direction idea with plain RNN cells. It passes the input through two RNNs. One reads forward and one reads in reverse. It then merges the two outputs into one final output.

Bi-RNN suits NLP and speech tasks. Plain RNN suits time-series prediction. Time series has no future at test time. Text tagging can use both sides.

Dual BPTTSearch on YouTube

Training uses two backprop passes. The forward RNN reads the sequence in order. Its error updates the forward weights. The backward RNN reads the sequence in reverse. Its error updates the backward weights. The loop repeats until the model converges. Both sides then shape each prediction.

Merge modesSearch on YouTube

The slides list four merges. The choice depends on the task. Concat is the default. It works well in many cases.

yt=[h→t;h←t]y_t = [\overrightarrow{h}_t ; \overleftarrow{h}_t]

Concat stacks the two vectors. An H+HH + H output has size 2H2H.

yt=h→t+h←ty_t = \overrightarrow{h}_t + \overleftarrow{h}_t

Sum adds the vectors element-wise. The shape stays HH.

yt=(h→t+h←t)/2y_t = (\overrightarrow{h}_t + \overleftarrow{h}_t)/2

Average means the element-wise mean. The shape stays HH.

yt=max⁡(h→t,h←t)y_t = \max(\overrightarrow{h}_t, \overleftarrow{h}_t)

Maximum takes the larger entry per unit. The shape stays HH.

C-S-A-M: Concat, Sum, Average, Max

Concat doubles size (2H2H) · Sum adds · Average halves the sum · Max picks the larger entry.

Do not use Bi-RNN for live forecasting. Future inputs do not exist at test time. Use a forward-only RNN or LSTM there.

Text to vectorsSearch on YouTube

A network needs numbers, not letters. The pipeline turns text into vectors. It has four stages: text, tokenize, token IDs and embedding lookup. Each word then enters the net as an NN-dim vector at time tt.

  1. Split text into tokens (words or sub-words).
  2. Map each token to an integer ID from the vocabulary.
  3. Look up each ID in the embedding matrix (one row per token).
  4. Feed the NN-dim rows in order (pad short sentences to equal length).

One-hot form is 1×V1 \times V (one 11 in a long sparse row). The embedding matrix is V×DV \times D (one dense row per word). Lookup skips the sparse product. It copies rows by index. This cuts sparse compute.

Vector options:

  • One-hot: a sparse vector per token.
  • Bag of words: counts per word (order lost).
  • TF-IDF: counts weighted by rarity across docs.
  • Word embeddings: dense vectors (Word2Vec, GloVe, FastText).
  • Contextual embeddings: vectors that shift with context (BERT, RoBERTa).

Static versus contextual embeddingsSearch on YouTube

Static vectors give one vector per word. The slides use bank. Sentence 1: The bank approved my loan. Sentence 2: I sat on the bank of the river. The static vector is about [0.21,−0.43,0.17,…,0.62][0.21, -0.43, 0.17, \dots, 0.62] in both. It cannot shift sense.

Contextual models read the full context (e.g. with a Transformer). The same word then gets two vectors. Loan-bank gives about [0.18,−0.72,0.31,…,0.44][0.18, -0.72, 0.31, \dots, 0.44]. River-bank gives about [0.61,0.12,−0.27,…,0.83][0.61, 0.12, -0.27, \dots, 0.83]. The vectors differ. The model can tag finance versus nature.

Worked example. Use the three shown entries of each bank vector. Static vectors are equal, so their gap is zero. Contextual vectors differ.

vloan=[0.18,−0.72,0.31]vriver=[0.61,0.12,−0.27]d=vriver−vloan=[0.43,0.84,−0.58]∥d∥=0.432+0.842+0.582=1.2269=1.11\begin{aligned} v_{\text{loan}} &= [0.18, -0.72, 0.31]\\ v_{\text{river}} &= [0.61, 0.12, -0.27]\\ d &= v_{\text{river}} - v_{\text{loan}} = [0.43, 0.84, -0.58]\\ \lVert d \rVert &= \sqrt{0.43^2 + 0.84^2 + 0.58^2} = \sqrt{1.2269} = \mathbf{1.11} \end{aligned}

The static gap is 0\mathbf{0}. The contextual gap is 1.11\mathbf{1.11}. The shift separates the two senses.

Embedding matrix, OOV and fine-tuneSearch on YouTube

Two ways to get the matrix:

  • Train it with the task (random start, learn rows with the model).
  • Use pretrained rows (Word2Vec, GloVe, FastText).

Out-of-vocabulary (OOV) words miss the table. Two fixes exist. Map them to one unknown vector. Or build them from sub-word parts (FastText style).

Fine-tune means keep training the rows on task data. Do not train a fresh Word2Vec only on new words. Its space can sit in a new coordinate frame. Then old and new rows clash. Train jointly or learn a map between spaces.

Exam phrasing: static embeddings map one word to one vector; contextual embeddings map one word plus context to one vector. Name Word2Vec/GloVe for static and BERT for contextual.

Quick self-testSearch on YouTube

1. Why does a plain RNN fail on the Harit-Hindi sentence?

It rewrites one state at each step. The early clue (India) fades over the long gap. LSTM keeps it on the cell path.

2. State the roles of cell state and hidden state in one line each.

Cell state CtC_t stores facts across steps. Hidden state hth_t is the filtered output shown to the next layer.

3. Pre-activations are 0.4 (forget), -0.4 (input), 0.5 (candidate), 0.8 (output), with prior cell 0.4. Find the new cell and hidden state.

ft=0.5987f_t = 0.5987, it=0.4013i_t = 0.4013, candidate 0.46210.4621, ot=0.69o_t = 0.69. Ct=0.4249C_t = 0.4249 and ht=0.2767h_t = 0.2767.

4. Differentiate reset gate and update gate of a GRU.

Reset rtr_t scales the past inside the candidate. Update ztz_t mixes the candidate with the past. Small ztz_t keeps ht−1h_{t-1}.

5. GRU has prior state 0.6, reset pre-activation -0.2, update pre-activation 0.4 and candidate 0.1974. Find the new state.

rt=0.4502r_t = 0.4502 and zt=0.5987z_t = 0.5987. ht=0.4013×0.6+0.5987×0.1974=0.3590h_t = 0.4013 \times 0.6 + 0.5987 \times 0.1974 = 0.3590.

6. Why does the apple NER case need a backward pass?

The label of apple depends on later words (it/healthy versus he/phone). A forward-only pass tags before it sees them.

7. Each Bi-LSTM direction gives shape (32, 10, 64). Give concat, sum, average and max shapes.

Concat gives (32,10,128)(32, 10, 128). Sum, average and max each give (32,10,64)(32, 10, 64).

8. List the four Bi-RNN merge modes and state which one changes the size.

Concat, sum, average and max. Only concat changes size (HH to 2H2H). The rest keep HH.

9. Static bank vectors match in both sentences but contextual ones differ by 1.11 on the shown prefix. What does this show?

Static form stores one sense per word (gap 0). Contextual form stores sense plus context (gap 1.11). Hence river-bank and loan-bank split.

10. How do you handle OOV words, and what is wrong with a separate Word2Vec only for new words?

Map OOV words to an unknown vector or build them from sub-words. A separate Word2Vec can land in a new coordinate frame. Then its rows clash with the old space.

  • Plain RNNs morph old inputs away; long gaps (Harit-Hindi) and subject switches (Mark-Jacob) need gated memory.
  • LSTM splits memory (CtC_t) from output (hth_t); gates are sigmoids in [0,1][0, 1], candidate uses tanh⁡\tanh.
  • Forget gate: ft=σ(Wfxt+Ufht−1+bf)f_t = \sigma(W_f x_t + U_f h_{t-1} + b_f); input gate plus candidate build Ct=ft⊙Ct−1+it⊙C~tC_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t; output gate gives ht=ot⊙tanh⁡(Ct)h_t = o_t \odot \tanh(C_t).
  • Shapes: input (B,T,D)(B, T, D), states (B,H)(B, H), maps D×HD \times H and H×HH \times H; frameworks use batch-first layout.
  • GRU merges states: reset rtr_t, update ztz_t, candidate h~t\tilde{h}_t, mix ht=(1−zt)⊙ht−1+zt⊙h~th_t = (1-z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t.
  • Bi-LSTM runs forward and backward LSTMs and merges per step; it resolves fill-in blanks and apple NER via future words.
  • Bi-RNN runs two RNNs with dual BPTT; merges are concat (2H2H), sum, average and max (each HH).
  • Text pipeline: tokenize, map to IDs, look up V×DV \times D rows, feed NN-dim vectors; static bank vectors match while contextual loan/river vectors differ.
  • OOV fix: unknown vector or sub-word parts; fine-tune jointly so spaces stay aligned.