iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

RNN / LSTM

PyTorch RNNs: nn.RNN, nn.LSTM, nn.GRU. Sequence modeling basics, hidden state, batching, and the patterns that actually work.

PyTorch — RNNs

EXAMPLE
import torch
import torch.nn as nn

# ===== Tensor shapes =====
# input: (seq_len, batch, input_size) by default
# Or set batch_first=True for (batch, seq_len, input_size)

# ===== nn.LSTM =====
lstm = nn.LSTM(input_size=10, hidden_size=64, num_layers=2, batch_first=True, dropout=0.2)

batch, seq_len, input_size = 16, 100, 10
x = torch.randn(batch, seq_len, input_size)

# Hidden + cell initialised to zero by default
output, (h_n, c_n) = lstm(x)
# output: (batch, seq_len, hidden_size) — last layer hidden state at each timestep
# h_n: (num_layers, batch, hidden_size) — final hidden for each layer
# c_n: (num_layers, batch, hidden_size) — final cell

# ===== Classification on top =====
class Classifier(nn.Module):
    def __init__(self, vocab_size, embed_dim=64, hidden=128, num_classes=2):
        super().__init__()
        self.emb = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden, batch_first=True, num_layers=1, bidirectional=True)
        self.fc = nn.Linear(hidden * 2, num_classes)   # *2 for bidirectional

    def forward(self, ids):
        x = self.emb(ids)                              # (B, T, E)
        out, (h, c) = self.lstm(x)
        # Use the last timestep:
        last = out[:, -1, :]
        return self.fc(last)

# ===== Variable-length sequences (padding + packing) =====
from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence, pad_packed_sequence

# Pad batch of variable-length tensors:
batch = [torch.tensor([1,2,3]), torch.tensor([4,5,6,7,8]), torch.tensor([9])]
lengths = torch.tensor([len(s) for s in batch])
padded = pad_sequence(batch, batch_first=True, padding_value=0)

# Pack so the LSTM skips padding:
packed = pack_padded_sequence(padded, lengths, batch_first=True, enforce_sorted=False)
output_packed, (h_n, c_n) = lstm(packed)
# Unpack to a padded tensor:
output, lengths_back = pad_packed_sequence(output_packed, batch_first=True)

# ===== GRU (lighter than LSTM, often comparable) =====
gru = nn.GRU(input_size=10, hidden_size=64, batch_first=True)
out, h_n = gru(x)   # GRU has no cell state

# ===== Vanilla RNN (rarely used directly) =====
rnn = nn.RNN(input_size=10, hidden_size=64, batch_first=True, nonlinearity='tanh')

# ===== Training tips =====
# - Gradient clipping to avoid explosion:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# - Teacher forcing for sequence-to-sequence training (feed ground truth as next input)

# - Use the appropriate loss:
#   classification:  CrossEntropyLoss on logits
#   language model:  CrossEntropyLoss with reshape over (B*T, V)
#   regression:      MSELoss

# ===== When RNNs vs Transformers =====
# Transformers dominate for most NLP tasks in 2026.
# RNNs still useful for:
#   - Small models / edge devices
#   - Streaming inputs (one timestep at a time)
#   - Time series with strong recurrence assumptions

# ===== Patterns to internalise =====
# - batch_first=True everywhere for consistency
# - pack_padded_sequence for variable-length batches
# - Gradient clipping (max_norm 1.0 or 5.0)
# - Bidirectional for classification; unidirectional for generation

# ===== Pitfalls =====
# - Wrong shape order (seq_len vs batch) — set batch_first=True
# - Treating padding as real input -> packs help
# - Vanishing gradients in vanilla RNN over long sequences -> use LSTM/GRU
# - Not zeroing or detaching hidden state between batches when treating them as independent

Why it matters

PyTorch RNNs: nn.LSTM and nn.GRU are the workhorses. batch_first=True, pack_padded_sequence for variable lengths, gradient clipping, bidirectional for classification. Transformers replaced them for most NLP, but RNNs remain relevant on edge + streaming + simple time series.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import torch.nn as nn
lstm = nn.LSTM(input_size=64, hidden_size=128, num_layers=2, batch_first=True, dropout=0.2)
Try it Yourself »

Discussion

Loading…