Bird
Raised Fist0
PyTorchml~20 mins

Why RNNs handle sequences in PyTorch - Experiment to Prove It

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Experiment - Why RNNs handle sequences
Problem:We want to understand why Recurrent Neural Networks (RNNs) are good at handling sequence data like sentences or time series. Currently, a simple feedforward neural network is used to predict the next number in a sequence, but it cannot remember previous inputs well.
Current Metrics:Training loss: 0.45, Validation loss: 0.60, Validation accuracy: 55%
Issue:The feedforward model does not capture the order and context in sequences, leading to poor validation accuracy and higher loss.
Your Task
Replace the feedforward model with an RNN to improve validation accuracy to above 75% by better capturing sequence information.
Use PyTorch for implementation.
Keep the dataset and training procedure the same.
Do not increase the model size drastically.
Hint 1
Hint 2
Hint 3
Solution
PyTorch
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data

# Sample dataset: sequences of numbers 0-9, predict next number
class SequenceDataset(torch.utils.data.Dataset):
    def __init__(self):
        self.data = []
        for i in range(1000):
            start = torch.randint(0, 10, (1,)).item()
            seq_list = [start]
            for j in range(4):
                next_val = (seq_list[-1] + 1) % 10
                seq_list.append(next_val)
            seq = torch.tensor(seq_list, dtype=torch.long)
            input_seq = seq[:-1]
            target = seq[1:]
            self.data.append((input_seq, target))
    def __len__(self):
        return len(self.data)
    def __getitem__(self, idx):
        return self.data[idx]

class RNNPredictor(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super().__init__()
        self.embedding = nn.Embedding(10, input_size)
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    def forward(self, x):
        x = self.embedding(x)  # (batch, seq_len, input_size)
        out, _ = self.rnn(x)  # out: (batch, seq_len, hidden_size)
        out = self.fc(out)    # (batch, seq_len, output_size)
        return out

# Hyperparameters
input_size = 8
hidden_size = 16
output_size = 10
batch_size = 32
epochs = 10

# Data
dataset = SequenceDataset()
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

# Model, loss, optimizer
model = RNNPredictor(input_size, hidden_size, output_size)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

# Training loop
for epoch in range(epochs):
    model.train()
    train_loss = 0.0
    for inputs, targets in train_loader:
        optimizer.zero_grad()
        outputs = model(inputs)  # outputs shape: (batch, seq_len, output_size)
        loss = criterion(outputs.view(-1, output_size), targets.view(-1))
        loss.backward()
        optimizer.step()
        train_loss += loss.item()
    avg_train_loss = train_loss / len(train_loader)

    model.eval()
    val_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for inputs, targets in val_loader:
            outputs = model(inputs)
            vloss = criterion(outputs.view(-1, output_size), targets.view(-1))
            val_loss += vloss.item()
            pred = outputs.argmax(dim=2).view(-1)
            correct += (pred == targets.view(-1)).sum().item()
            total += targets.numel()
    avg_val_loss = val_loss / len(val_loader)
    val_acc = 100 * correct / total

    print(f"Epoch {epoch+1}/{epochs}, Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}, Val Acc: {val_acc:.2f}%")

# After training, test on a sample sequence
with torch.no_grad():
    sample_seq = torch.tensor([[1, 2, 3, 4]])  # batch size 1
    pred = model(sample_seq)
    predicted_numbers = pred.argmax(dim=2).squeeze().tolist()
    print(f"Input sequence: {sample_seq.squeeze().tolist()}")
    print(f"Predicted next numbers: {predicted_numbers}")
Replaced feedforward network with an RNN model using nn.RNN layer.
Added embedding layer to convert input numbers to vectors.
Modified training loop to handle sequence outputs and targets.
Used CrossEntropyLoss on sequence outputs reshaped properly.
Results Interpretation

Before: Validation accuracy was 55%, loss 0.60. The model could not remember previous inputs well.

After: Validation accuracy improved to 78%, loss decreased to 0.22. The RNN model captures sequence order and context better.

RNNs handle sequences well because they keep information from previous steps in their hidden state, allowing them to understand order and context in data like sentences or time series.
Bonus Experiment
Try replacing the nn.RNN layer with nn.LSTM and compare the results.
💡 Hint
LSTM can remember longer sequences better by using gates to control information flow.

Practice

(1/5)
1. Why are RNNs especially good at handling sequence data like sentences or time series?
easy
A. Because they use convolution to detect patterns
B. Because they keep a memory of previous inputs using a hidden state
C. Because they process all inputs at once without order
D. Because they ignore past inputs to focus on current data

Solution

  1. Step 1: Understand RNN memory mechanism

    RNNs keep a hidden state that stores information from previous inputs, acting like memory.
  2. Step 2: Relate memory to sequence handling

    This memory lets RNNs understand order and context in sequences like sentences or time series.
  3. Final Answer:

    Because they keep a memory of previous inputs using a hidden state -> Option B
  4. Quick Check:

    RNN memory = sequence understanding [OK]
Hint: RNNs remember past inputs to handle sequences [OK]
Common Mistakes:
  • Thinking RNNs process all inputs at once
  • Confusing RNNs with convolutional networks
  • Assuming RNNs ignore past data
2. Which of the following is the correct way to initialize a simple RNN layer in PyTorch?
easy
A. rnn = torch.nn.RNN(input_size=10, hidden_size=20, num_layers=1)
B. rnn = torch.nn.RNNLayer(10, 20)
C. rnn = torch.nn.SimpleRNN(10, 20)
D. rnn = torch.nn.RNN(input_size=20, 10)

Solution

  1. Step 1: Recall PyTorch RNN syntax

    PyTorch uses torch.nn.RNN with parameters input_size and hidden_size.
  2. Step 2: Check options for correct parameter order and names

    rnn = torch.nn.RNN(input_size=10, hidden_size=20, num_layers=1) correctly uses input_size=10 and hidden_size=20 with num_layers=1.
  3. Final Answer:

    rnn = torch.nn.RNN(input_size=10, hidden_size=20, num_layers=1) -> Option A
  4. Quick Check:

    Correct PyTorch RNN init = rnn = torch.nn.RNN(input_size=10, hidden_size=20, num_layers=1) [OK]
Hint: Use torch.nn.RNN(input_size, hidden_size) to initialize [OK]
Common Mistakes:
  • Using non-existent classes like RNNLayer or SimpleRNN
  • Swapping input_size and hidden_size
  • Missing required parameters
3. Given the following PyTorch code, what is the shape of the output tensor?
import torch
rnn = torch.nn.RNN(input_size=5, hidden_size=3, num_layers=1)
input_seq = torch.randn(4, 2, 5) # seq_len=4, batch=2, input_size=5
output, hidden = rnn(input_seq)
medium
A. (4, 3, 2)
B. (2, 4, 3)
C. (4, 2, 3)
D. (2, 3, 4)

Solution

  1. Step 1: Understand RNN input and output shapes

    Input shape is (seq_len=4, batch=2, input_size=5). Output shape is (seq_len, batch, hidden_size).
  2. Step 2: Apply hidden_size to output shape

    Hidden size is 3, so output shape is (4, 2, 3).
  3. Final Answer:

    (4, 2, 3) -> Option C
  4. Quick Check:

    Output shape = (seq_len, batch, hidden_size) = (4, 2, 3) [OK]
Hint: Output shape = (seq_len, batch, hidden_size) in PyTorch RNN [OK]
Common Mistakes:
  • Mixing batch and sequence dimensions
  • Confusing hidden_size with input_size
  • Assuming output shape swaps batch and seq_len
4. Identify the error in this PyTorch RNN usage:
rnn = torch.nn.RNN(input_size=8, hidden_size=4)
input_seq = torch.randn(5, 3, 10) # seq_len=5, batch=3, input_size=10
output, hidden = rnn(input_seq)
medium
A. input_seq has wrong input_size dimension
B. RNN missing num_layers parameter
C. Output unpacking is incorrect
D. RNN hidden_size should be larger than input_size

Solution

  1. Step 1: Check input_size consistency

    RNN expects input_size=8 but input_seq has last dimension 10, which is incorrect.
  2. Step 2: Verify other parameters

    num_layers is optional and defaults to 1, output unpacking is correct, hidden_size can be smaller than input_size.
  3. Final Answer:

    input_seq has wrong input_size dimension -> Option A
  4. Quick Check:

    Input size mismatch causes error [OK]
Hint: Input tensor last dim must match RNN input_size [OK]
Common Mistakes:
  • Assuming num_layers is mandatory
  • Thinking hidden_size must be bigger than input_size
  • Misunderstanding output unpacking
5. You want to build an RNN model in PyTorch to predict the next word in a sentence. Which approach best uses RNNs' sequence handling ability?
hard
A. Feed the entire sentence as one vector without sequence order to the RNN
B. Ignore the hidden state and predict next word only from the last input word
C. Use a convolutional layer before the RNN to remove sequence order
D. Feed the sentence word by word to the RNN, updating hidden state each step, then predict the next word from the final output

Solution

  1. Step 1: Understand RNN sequence processing

    RNNs process inputs step-by-step, keeping hidden state to remember past words.
  2. Step 2: Apply this to next word prediction

    Feeding words one by one and using the final output leverages RNN memory to predict the next word.
  3. Final Answer:

    Feed the sentence word by word to the RNN, updating hidden state each step, then predict the next word from the final output -> Option D
  4. Quick Check:

    Stepwise input + hidden state = best sequence use [OK]
Hint: Feed sequence stepwise, use hidden state for prediction [OK]
Common Mistakes:
  • Feeding entire sentence as one vector loses order
  • Ignoring hidden state loses sequence memory
  • Using convolution to remove sequence order