Coding Now – Best AI & Full Stack Courses in Delhi NCR | 100% Placement
Limited Offer: Get 50% OFF on AI & Full Stack Courses
📞 Call Now: +91 9667708830
Back to Deep Learning Notes
Topic #663

Practice: RNN & LSTM

Implementation exercises for recurrent networks — implementing an RNN cell manually, then building a real LSTM-based sequence classifier in PyTorch.

🟢 Problem 1: Implement a single RNN cell's forward step manually

Task: Implement one time step of a vanilla RNN's hidden state update: \(h_t = \tanh(W_{xh}x_t + W_{hh}h_{t-1} + b)\).

def rnn_cell_forward(x_t, h_prev, W_xh, W_hh, b):
    return np.tanh(x_t @ W_xh + h_prev @ W_hh + b)

input_size, hidden_size = 4, 3
W_xh = np.random.randn(input_size, hidden_size) * 0.1
W_hh = np.random.randn(hidden_size, hidden_size) * 0.1
b = np.zeros(hidden_size)

x_t = np.random.randn(1, input_size)
h_prev = np.zeros((1, hidden_size))
h_t = rnn_cell_forward(x_t, h_prev, W_xh, W_hh, b)
print(f"New hidden state: {h_t}")

🟡 Problem 2: Unroll the RNN cell across a full sequence

Task: Given a sequence of 5 time steps, apply your RNN cell repeatedly, carrying the hidden state forward, and collect all hidden states.

def rnn_forward_sequence(X_seq, W_xh, W_hh, b, hidden_size):
    batch_size = X_seq.shape[0]
    h_t = np.zeros((batch_size, hidden_size))
    all_hidden_states = []

    for t in range(X_seq.shape[1]):   # iterate over time steps
        x_t = X_seq[:, t, :]
        h_t = rnn_cell_forward(x_t, h_t, W_xh, W_hh, b)
        all_hidden_states.append(h_t)

    return np.stack(all_hidden_states, axis=1)   # (batch, seq_len, hidden_size)

X_seq = np.random.randn(2, 5, input_size)   # batch=2, seq_len=5
hidden_states = rnn_forward_sequence(X_seq, W_xh, W_hh, b, hidden_size)
print(hidden_states.shape)   # (2, 5, 3)

Hint if stuck: The key idea is that h_t from one iteration becomes h_prev for the next — this is exactly what "recurrent" means, and is the only thing carrying information across time steps.

🟡 Problem 3: Build an LSTM-based sentiment classifier in PyTorch

Task: Build a model that embeds input tokens, processes them with an LSTM, and classifies the final hidden state as positive/negative sentiment.

class LSTMClassifier(nn.Module):
    def __init__(self, vocab_size, embed_dim=64, hidden_dim=128, num_classes=2):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
        self.fc = nn.Linear(hidden_dim, num_classes)

    def forward(self, x):
        embedded = self.embedding(x)                  # (batch, seq_len, embed_dim)
        _, (h_n, c_n) = self.lstm(embedded)              # h_n: (1, batch, hidden_dim)
        final_hidden = h_n.squeeze(0)                     # (batch, hidden_dim)
        return self.fc(final_hidden)

model = LSTMClassifier(vocab_size=5000)
sample_input = torch.randint(0, 5000, (4, 20))   # batch=4, seq_len=20
logits = model(sample_input)
print(logits.shape)   # (4, 2)

Hint if stuck: nn.LSTM returns two things: the output at every time step, and a tuple of the final hidden and cell states — for classification using only the final summary of the sequence, you typically want h_n, the final hidden state.

🔴 Problem 4: Compare a vanilla RNN vs LSTM on a long-dependency task

Task: Create a synthetic task where the correct output depends on information from early in a long sequence (e.g. classify based on the first token, with 50 irrelevant tokens after it). Train both a vanilla RNN and an LSTM on this task and compare their accuracy.

# Synthetic task: label = 1 if first token > 0, else 0 -- but the sequence is 50 tokens long
def generate_long_dependency_data(num_samples, seq_len=50):
    X = np.random.randn(num_samples, seq_len, 1)
    y = (X[:, 0, 0] > 0).astype(int)   # label depends ONLY on the very first time step
    return torch.tensor(X, dtype=torch.float32), torch.tensor(y, dtype=torch.long)

# Train both nn.RNN and nn.LSTM based classifiers on this data with identical
# hyperparameters, and compare final test accuracy -- the LSTM should notably
# outperform the vanilla RNN, directly demonstrating why LSTM's gating helps
# preserve information over long sequences that a vanilla RNN tends to lose

Hint if stuck: If both models perform similarly, try increasing seq_len further — the vanilla RNN's disadvantage becomes more pronounced as the dependency distance grows, which is exactly the point this exercise is designed to demonstrate.

Related DL Notes

Want to go beyond the notes?

Join Coding Hubs School of AI's Deep Learning course — live mentorship, real projects, and 100% placement support.

Enroll Now — Free Demo Available
💬 Talk to Advisor
1
WhatsApp

Latest from Our Blog

Insights on AI, Data Science, Full Stack & Career

View All Articles →