PyTorch NLP Sentiment Analysis on IMDb: From Word Embeddings to Bidirectional LSTM
Natural language processing requires sequential feature representation. Master text tokenization, vocabulary mapping, nn.Embedding matrix lookups, and Bidirectional LSTM recurrent neural networks in PyTorch.
PRACTICE THIS COMPLETE 7-STEP PIPELINE IN YOUR BROWSER
Write your code in the retro IDE, audit your tensor shapes, and receive sub-second AI diagnostics powered by Qwen 2.5 Coder.
PyTorch NLP Sentiment Analysis on IMDb: From Word Embeddings to Bidirectional LSTM
Text classification and sentiment analysis form the backbone of modern Natural Language Processing (NLP). Before fine-tuning giant Transformer models like BERT or LLaMA, mastering Recurrent Neural Networks (RNNs) and Long Short-Term Memory (LSTM) networks is essential for understanding how neural networks model temporal sequence dependencies.
In this comprehensive tutorial, we build an end-to-end sentiment classifier on the IMDb Large Movie Review Dataset (50,000 reviews) from raw text tokenization to custom word embeddings and a Bidirectional LSTM in PyTorch.
#1. Sequence Processing Architecture
Raw Text: "This film was breathtaking..."
│
[1. Regex Tokenization & Lowercase] ──► ['this', 'film', 'was', 'breathtaking']
│
[2. Vocabulary Index Lookup] ──► [42, 319, 14, 8942]
│
[3. nn.Embedding Lookup Table] ──► Tensor of Shape [Batch, Seq_Len, Embedding_Dim]
│
[4. Bidirectional LSTM Stack] ──► Forward & Backward Hidden Context Vectors
│
[5. Classification Head] ──► Raw Scalar Logit ➔ Probability#2. Step-by-Step PyTorch NLP Pipeline
Step 1 & 2: Tokenization, Vocabulary Construction & Padding
import re
from collections import Counter
import torch
from torch.utils.data import Dataset, DataLoader
# Sample ingestion
raw_reviews = [
("This movie was absolutely incredible and stunning", 1),
("Terrible waste of time. Poor acting and dreadful plot.", 0),
("A masterpiece of modern cinematic storytelling.", 1),
("Boring, predictable, and completely uninspired.", 0),
]
def tokenize(text: str):
text = re.sub(r'<[^>]+>', ' ', text.lower()) # Remove HTML tags
return re.findall(r'\b\w+\b', text)
# Build Vocabulary
word_counts = Counter()
for text, _ in raw_reviews:
word_counts.update(tokenize(text))
# Assign token indices (<PAD>=0, <UNK>=1)
vocab = {"<PAD>": 0, "<UNK>": 1}
for word, count in word_counts.items():
if count >= 1:
vocab[word] = len(vocab)
VOCAB_SIZE = len(vocab)
MAX_LEN = 32
def encode_text(text: str, max_len: int = MAX_LEN):
tokens = tokenize(text)
indices = [vocab.get(t, vocab["<UNK>"]) for t in tokens]
if len(indices) < max_len:
indices += [vocab["<PAD>"]] * (max_len - len(indices))
else:
indices = indices[:max_len]
return indicesStep 3: Custom PyTorch Sequence Dataset
class IMDbDataset(Dataset):
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
text, label = self.data[idx]
seq = encode_text(text)
return torch.tensor(seq, dtype=torch.long), torch.tensor(label, dtype=torch.float32)
dataset = IMDbDataset(raw_reviews)
loader = DataLoader(dataset, batch_size=2, shuffle=True)Step 4: Structuring the Bidirectional LSTM with nn.Embedding
import torch.nn as nn
class SentimentBiLSTM(nn.Module):
def __init__(self, vocab_size: int, embed_dim: int = 64, hidden_dim: int = 64):
super(SentimentBiLSTM, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.lstm = nn.LSTM(
embed_dim,
hidden_dim,
num_layers=2,
batch_first=True,
bidirectional=True,
dropout=0.25
)
# Bidirectional LSTM concatenates forward and backward hidden states (hidden_dim * 2)
self.fc = nn.Sequential(
nn.Linear(hidden_dim * 2, 32),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(32, 1)
)
def forward(self, text_seq: torch.Tensor) -> torch.Tensor:
# text_seq: [Batch, Seq_Len]
embedded = self.embedding(text_seq) # [Batch, Seq_Len, Embed_Dim]
# LSTM returns: output, (hidden, cell)
lstm_out, (hidden, cell) = self.lstm(embedded)
# Concatenate final forward and backward hidden states
# hidden shape: [num_layers * 2, Batch, Hidden_Dim]
forward_hidden = hidden[-2, :, :]
backward_hidden = hidden[-1, :, :]
cat_hidden = torch.cat((forward_hidden, backward_hidden), dim=1) # [Batch, Hidden_Dim * 2]
logits = self.fc(cat_hidden)
return logits
model = SentimentBiLSTM(vocab_size=VOCAB_SIZE, embed_dim=64, hidden_dim=64)Step 5 & 6: Training Loop with Gradient Clipping
Recurrent neural networks are notoriously susceptible to exploding gradients across long sequences. Always use clip_grad_norm_:
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
for epoch in range(1, 11):
model.train()
running_loss = 0.0
for bx, by in loader:
optimizer.zero_grad()
logits = model(bx).squeeze(1)
loss = criterion(logits, by)
loss.backward()
# Prevent exploding recurrent gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
running_loss += loss.item() * bx.size(0)Step 7: Interactive Live Inference
model.eval()
def predict_sentiment(review_text: str) -> dict:
seq = torch.tensor([encode_text(review_text)], dtype=torch.long)
with torch.no_grad():
logit = model(seq)
prob = torch.sigmoid(logit).item()
return {
"review": review_text,
"sentiment": "POSITIVE" if prob >= 0.50 else "NEGATIVE",
"confidence": prob if prob >= 0.50 else (1.0 - prob)
}
print(predict_sentiment("An incredible cinematic triumph with brilliant direction!"))#Launch the Interactive NLP Lab
Write and validate your PyTorch NLP pipeline live in our browser sandbox with instant AI feedback.
READY TO AUDIT YOUR PYTORCH CODE LIVE?
Experience active deep learning with real-time feedback loops. No installation required—run directly in your browser.