Prerequisites
Before diving into RNNs, you should be comfortable with:
- Neural Network Fundamentals: Forward/backward pass, activation functions, backpropagation (see Tutorial 21)
- Sequence Data: Time series, text as token sequences, variable-length inputs
- PyTorch Basics: Tensors, nn.Module, embedding layers, padding/packing sequences
- Linear Algebra: Matrix multiplication, recurrent weight matrices
Deep Learning
RNNs and LSTMs — Neural Networks That Remember
Explore recurrent neural networks designed to process sequential data with memory of past inputs.
- Sequential processing — handle time series and text data
- LSTM gates — solve the vanishing gradient problem
- GRU simplification — efficient recurrent architectures
Memory is the diary we all carry about with us.
Learning Objectives
By the end of this tutorial, you will be able to:
- Explain how vanilla RNNs process sequential data and their limitations
- Derive the LSTM gate equations and understand why they solve vanishing gradients
- Compare GRU and LSTM architectures and choose between them
- Implement bidirectional RNNs for full-context processing
- Build encoder-decoder (seq2seq) architectures for translation tasks
- Implement LSTM models in PyTorch for classification and generation
- Identify when to use RNNs vs Transformers for sequential tasks
RNN, LSTM and GRU — Complete Guide
Recurrent networks process sequential data by maintaining a hidden state that carries information across time steps. Unlike Transformers, they process one token at a time with memory per step but sequential operations for a sequence of length .
Vanilla RNN
At each time step , the RNN computes:
LSTM (Long Short-Term Memory)
LSTM (Hochreiter and Schmidhuber, 1997) introduces a cell state as an information highway, with three gates controlling information flow:
GRU (Gated Recurrent Unit)
GRU (Cho et al., 2014) simplifies LSTM by merging the cell and hidden state and using only two gates:
Bidirectional RNN
Processes the sequence in both directions and concatenates hidden states:
Use case: NER, sentiment analysis — where full context is available. Cannot be used for autoregressive generation.
PyTorch Implementation
Real-World Applications
1. Speech Recognition LSTMs process Mel-frequency spectrograms to transcribe speech to text. Apple's Siri and Google Assistant use LSTM-based acoustic models with Word Error Rates under 5%.
2. Time Series Forecasting LSTMs predict stock prices, weather, and energy demand by learning temporal patterns. They handle irregular time intervals and multivariate inputs naturally.
3. Music Generation LSTMs generate music by learning note sequences. Google's Magenta project generates original compositions by predicting the next note given the sequence so far.
4. Video Captioning Encoder-decoder LSTMs describe video content by processing frame features over time and generating natural language descriptions. Microsoft's video captioning system achieves CIDEr scores >100.
5. Anomaly Detection in IoT LSTMs learn normal patterns in sensor data streams (temperature, vibration, pressure) and flag anomalies in real-time for predictive maintenance in manufacturing.
6. Handwriting Recognition Bidirectional LSTMs process sequences of pen coordinates to recognize handwriting in real-time. Google's handwriting input supports 100+ languages with >90% accuracy.
Common Mistakes & How to Avoid Them
Key Formulas Reference
| Formula | Expression | Use Case |
|---|---|---|
| RNN Step | h_t = tanh(W_hh h_{t-1} + W_xh x_t + b) | Sequence processing |
| LSTM Forget | f_t = sigma(W_f [h_{t-1}, x_t] + b_f) | Control old memory |
| LSTM Cell Update | c_t = f_t c_{t-1} + i_t c_tilde | Linear gradient highway |
| GRU Update | h_t = (1-z) h_{t-1} + z h_tilde | Simpler gating |
| Bidirectional | h_t = [h_fwd || h_bwd] | Full context |
| Seq2Seq Bottleneck | context = h_T (encoder final state) | Translation, summarization |
Interview Questions
Practice Exercise
import torch
import torch.nn as nn
# Task 1: Implement an LSTM-based sentiment classifier
# - Embedding layer: vocab_size -> 128
# - LSTM: 128 -> 256, 2 layers, bidirectional
# - Attention mechanism over LSTM outputs
# - Classifier: 512 -> 128 -> 2 (positive/negative)
class SentimentLSTM(nn.Module):
def __init__(self, vocab_size, embed_dim=128, hidden_dim=256, num_classes=2):
super().__init__()
# Your implementation here
pass
def forward(self, x, lengths):
# Your implementation here
# Remember to pack_padded_sequence!
pass
# Task 2: Train on IMDB dataset
# - Use pretrained GloVe embeddings (optional)
# - Adam optimizer, lr=1e-3
# - Gradient clipping at 5.0
# - Achieve >85% accuracy
# Task 3: Implement simple attention over LSTM outputs
# attention_weight = torch.softmax(torch.bmm(lstm_out, query), dim=1)
# context = torch.bmm(attention_weight.permute(0,2,1), lstm_out)
Success criteria: >85% accuracy on IMDB test set. Ablation: compare bidirectional vs unidirectional, attention vs last-hidden-state.
Key Takeaways
What to Learn Next
-> Transformers Learn the architecture replacing RNNs.
-> NLP Fundamentals Master natural language processing basics.
-> Time Series Analysis Apply RNNs to time-dependent data.
-> Attention Deep Dive Understand how attention solves the bottleneck.
-> Neural Networks Understand the foundation of deep learning.
-> Sequence-to-Sequence Build models for translation and summarization.
Advanced Topics
Attention Mechanism for RNNs
The attention mechanism was originally designed to solve the bottleneck problem in Seq2Seq models:
The decoder receives a context vector that is a weighted sum of all encoder hidden states, where the weights indicate which input tokens are most relevant at decoding step .
Truncated Backpropagation Through Time (TBPTT)
Full BPTT unrolls the entire sequence, which is memory-intensive for long sequences. TBPTT processes the sequence in fixed-length chunks:
- Forward pass for steps
- Backward pass for those steps
- Detach the hidden state from the computation graph
- Continue with the next chunk
Key parameter: chunk size — determines how far back gradients flow. Common values: 35-100 steps.
Weighted softmax in Attention
Standard softmax computes . For numerical stability and better gradients:
This shift doesn't change the result but prevents overflow in floating-point computation.
Stacking and Deep RNNs
Deep RNNs stack multiple recurrent layers:
Recommendations:
- 2-3 layers is optimal for most tasks
- Add dropout between layers (not within time steps)
- Use skip connections from layer to layer for deeper RNNs
- Layer norm helps stabilize deep recurrent training
Comparison Table
| Model | Gates | Parameters | Memory | Best For |
|---|---|---|---|---|
| Vanilla RNN | None | 1x | ~10 steps | Short sequences, simple tasks |
| GRU | 2 (reset, update) | 3x | ~100 steps | Medium datasets, faster training |
| LSTM | 3 (forget, input, output) | 4x | ~1000 steps | Long dependencies, large datasets |
| Transformer | Attention (no gates) | Variable | Full context | NLP, when data/compute is sufficient |
| Mamba (SSM) | Selective state space | ~3x | Linear scaling | Very long sequences (100K+) |
Further Reading
- Hochreiter, S. & Schmidhuber, J. (1997). "Long Short-Term Memory." — The original LSTM paper, a landmark in deep learning.
- Cho, K. et al. (2014). "Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation." — The GRU paper.
- Sutskever, I. et al. (2014). "Sequence to Sequence Learning with Neural Networks." — Foundational seq2seq paper.
- Bahdanau, D. et al. (2014). "Neural Machine Translation by Jointly Learning to Align and Translate." — Attention mechanism for seq2seq.
- Pascanu, R. et al. (2013). "On the difficulty of training Recurrent Neural Networks." — Gradient clipping and vanishing gradient analysis.
- Colah's Blog: Understanding LSTM Networks — The most famous visual explanation of LSTM internals.
Quick Reference Cheat Sheet
| PyTorch Module | Key Parameters | Output Shape |
|---|---|---|
| nn.RNN(input, hidden) | num_layers, bidirectional, nonlinearity | (B, T, HD), (DL, B, H) |
| nn.LSTM(input, hidden) | num_layers, bidirectional, dropout | (B, T, H*D), (h_n, c_n) |
| nn.GRU(input, hidden) | num_layers, bidirectional, dropout | (B, T, H*D), (h_n) |
| nn.Embedding(vocab, dim) | padding_idx | (B, T, E) |
| pack_padded_sequence | lengths, batch_first, enforce_sorted | PackedSequence object |