De la séquence vers un élément : analyse des sentiments avec un LSTM
Vous construirez un classifieur de sentiments de bout en bout : vous tokeniserez les avis, représenterez les mots avec nn.Embedding, les passerez dans un LSTM et classerez l’état caché final.
De la séquence vers un élément : analyse des sentiments avec un LSTM est une leçon Machine Learning Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Machine Learning Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Machine Learning Academy comprend 4 leçons au total.
Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.
Sequence-to-One Classification Explained
In sequence-to-one tasks, the model reads an entire sequence and produces a single output label. Sentiment analysis is the classic example: given a full movie review ('I loved every minute of this film'), predict a single label — positive or negative.
This contrasts with sequence-to-sequence (translation, where every input token produces an output token) and one-to-sequence (image captioning). In sequence-to-one, we discard intermediate LSTM outputs and use only the final hidden state h_T, which theoretically encodes the meaning of the entire input sequence.
Dataset: IMDB Movie Reviews
The IMDB dataset contains 50,000 movie reviews (25,000 train, 25,000 test) labelled as positive or negative. Each review is a variable-length string of text. Before feeding it to an LSTM, we need three preprocessing steps: tokenisation (split text into words), vocabulary building (map words to integer indices), and padding (make all sequences the same length).
We also need to handle reviews that are very long — LSTM training slows quadratically with sequence length, so truncating to 256 or 512 tokens is common practice without significantly hurting accuracy.
from torchtext.datasets import IMDB
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
tokenizer = get_tokenizer('basic_english')
def yield_tokens(data_iter):
for _, text in data_iter:
yield tokenizer(text)
train_iter = IMDB(split='train')
vocab = build_vocab_from_iterator(yield_tokens(train_iter),
specials=['<unk>', '<pad>'])
vocab.set_default_index(vocab['<unk>'])
print('Vocabulary size:', len(vocab))Tokenising and Padding Sequences
Padding is necessary because PyTorch DataLoaders require tensors of the same shape within a batch. We pad shorter sequences with a special <pad> token (usually index 0) and truncate longer ones to a maximum length.
The key insight is that the LSTM should ignore padding tokens. PyTorch's pack_padded_sequence and pad_packed_sequence utilities let you efficiently skip padding during the LSTM computation, which is important for correctness — otherwise the model will update its hidden state based on meaningless padding, distorting the final h_T.
import torch
from torch.nn.utils.rnn import pad_sequence
def text_pipeline(text):
return vocab(tokenizer(text))[:256] # Truncate to 256 tokens
def label_pipeline(label):
return 1 if label == 'pos' else 0
def collate_batch(batch):
labels, texts, lengths = [], [], []
for label, text in batch:
labels.append(label_pipeline(label))
processed = torch.tensor(text_pipeline(text), dtype=torch.long)
texts.append(processed)
lengths.append(len(processed))
texts = pad_sequence(texts, batch_first=True, padding_value=1) # 1 = <pad>
return torch.tensor(labels), texts, torch.tensor(lengths)Embedding Layer: Words to Dense Vectors
Integer word indices cannot be fed directly into an LSTM — we need to convert them to continuous vectors. An embedding layer nn.Embedding(vocab_size, embedding_dim) is a lookup table that maps each word index to a dense vector of floats.
Common embedding dimensions are 100, 200, or 300. The embeddings are learnable parameters that get updated during training alongside the LSTM weights. Alternatively, you can initialise them from pre-trained word vectors like GloVe or Word2Vec, which dramatically speeds up training on small datasets by providing pre-learned semantic relationships between words.
import torch.nn as nn
vocab_size = 25000
embedding_dim = 100
hidden_size = 128
output_dim = 1 # Binary classification: positive/negative
embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=1)
# Optional: load pre-trained GloVe vectors
# from torchtext.vocab import GloVe
# glove = GloVe(name='6B', dim=100)
# embedding.weight.data.copy_(glove.vectors)
print('Embedding weight shape:', embedding.weight.shape) # (25000, 100)Building the SentimentLSTM Model
The complete model architecture has three parts: an embedding layer, an LSTM, and a fully connected head. The embedding converts token indices to vectors, the LSTM processes the sequence, and the linear layer maps the final hidden state to a single logit.
We add dropout after the embedding and before the linear layer to regularise the model. For the output, we apply a sigmoid to get a probability between 0 and 1. Training uses Binary Cross-Entropy loss since this is binary classification.
import torch
import torch.nn as nn
class SentimentLSTM(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_size, num_layers, dropout):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=1)
self.lstm = nn.LSTM(embed_dim, hidden_size, num_layers,
batch_first=True, dropout=dropout)
self.dropout = nn.Dropout(dropout)
self.fc = nn.Linear(hidden_size, 1)
def forward(self, text):
embedded = self.dropout(self.embedding(text)) # (B, T, embed_dim)
output, (h_n, c_n) = self.lstm(embedded)
last_hidden = self.dropout(h_n[-1]) # Final layer hidden state
return self.fc(last_hidden).squeeze(1) # (B,) logitsTraining Setup: Loss and Optimizer
For binary classification we use nn.BCEWithLogitsLoss, which combines sigmoid activation and binary cross-entropy loss in a single numerically stable operation. This is preferred over applying sigmoid manually followed by nn.BCELoss.
We use the Adam optimiser with a learning rate of 1e-3. Adam adapts learning rates per parameter, making it well-suited for sparse gradients that arise from embeddings (only the embeddings for words that appear in the batch receive gradient updates). Use gradient clipping with torch.nn.utils.clip_grad_norm_ to prevent exploding gradients, a common issue in RNN training.
import torch
import torch.nn as nn
import torch.optim as optim
model = SentimentLSTM(
vocab_size=25000, embed_dim=100,
hidden_size=128, num_layers=2, dropout=0.3
)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.BCEWithLogitsLoss()
# Gradient clipping prevents exploding gradients in RNNs
MAX_GRAD_NORM = 1.0
print('Model parameters:', sum(p.numel() for p in model.parameters()))The Training Loop for Sentiment LSTM
The training loop for sequence-to-one classification follows the standard PyTorch pattern: forward pass, compute loss, backpropagate, clip gradients, update parameters. We also compute accuracy by comparing predicted class (sigmoid output > 0.5) to ground truth labels.
One important consideration is moving both the model and batch tensors to the same device (CPU or GPU). Using model.to(device) and tensor.to(device) ensures consistent computation. On a GPU, training a two-layer LSTM on IMDB typically takes 2-5 minutes per epoch.
def train_epoch(model, loader, optimizer, criterion, device):
model.train()
total_loss, correct = 0.0, 0
for labels, texts, lengths in loader:
labels, texts = labels.float().to(device), texts.to(device)
optimizer.zero_grad()
predictions = model(texts) # Forward pass
loss = criterion(predictions, labels) # Compute loss
loss.backward() # Backprop
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # Clip
optimizer.step() # Update weights
total_loss += loss.item()
preds = (torch.sigmoid(predictions) > 0.5).float()
correct += (preds == labels).sum().item()
return total_loss / len(loader), correct / len(loader.dataset)Evaluation and Monitoring Progress
During evaluation we call model.eval() and wrap forward passes in torch.no_grad() to disable dropout and gradient tracking. This gives us an unbiased view of generalisation performance.
We track four numbers across epochs: train loss, validation loss, train accuracy, and validation accuracy. If train accuracy climbs but validation accuracy stagnates, the model is overfitting — increase dropout or reduce model size. If both are low, the model is underfitting — add LSTM layers or increase hidden size.
def evaluate(model, loader, criterion, device):
model.eval()
total_loss, correct = 0.0, 0
with torch.no_grad():
for labels, texts, lengths in loader:
labels, texts = labels.float().to(device), texts.to(device)
predictions = model(texts)
loss = criterion(predictions, labels)
total_loss += loss.item()
preds = (torch.sigmoid(predictions) > 0.5).float()
correct += (preds == labels).sum().item()
return total_loss / len(loader), correct / len(loader.dataset)
# Training run
for epoch in range(5):
tr_loss, tr_acc = train_epoch(model, train_loader, optimizer, criterion, device)
vl_loss, vl_acc = evaluate(model, val_loader, criterion, device)
print(f'Epoch {epoch+1}: Train Acc={tr_acc:.3f}, Val Acc={vl_acc:.3f}')Expected Performance and Common Mistakes
A well-tuned LSTM sentiment classifier on IMDB typically achieves 85-90% test accuracy. Common mistakes that hurt performance include: not setting model.eval() during evaluation (dropout remains active), not zeroing gradients before backprop, and training for too few epochs before the embeddings converge.
Another pitfall is using the same vocabulary index for unknown words and padding. Separate <unk> (index 0) and <pad> (index 1) tokens and pass padding_idx=1 to the embedding layer so pad embeddings are always zero and their gradients are suppressed during training.
# Common mistakes checklist:
# 1. Missing model.eval() before evaluation
# 2. Missing optimizer.zero_grad() before backward
# 3. Applying sigmoid twice (using BCELoss instead of BCEWithLogitsLoss)
# 4. Not handling unknown words (<unk> token)
# 5. Padding and unknown sharing the same index
# Correct: separate special tokens
vocab = build_vocab_from_iterator(
yield_tokens(train_iter),
specials=['<unk>', '<pad>'], # 0 and 1
min_freq=2 # Discard rare words
)Making Predictions on New Text
To classify new reviews at inference time, we apply the same preprocessing pipeline used during training: tokenise, convert to indices, truncate, and add a batch dimension. The model returns a logit; applying sigmoid converts it to a probability.
A prediction score above 0.5 indicates positive sentiment. You can adjust this threshold: lowering it to 0.3 increases recall for positive reviews at the cost of precision. Always apply model.eval() and torch.no_grad() during inference to disable dropout and avoid unnecessary gradient computation.
def predict_sentiment(model, text, vocab, tokenizer, device, max_len=256):
model.eval()
tokens = vocab(tokenizer(text))[:max_len]
tensor = torch.tensor(tokens, dtype=torch.long).unsqueeze(0).to(device)
with torch.no_grad():
logit = model(tensor)
prob = torch.sigmoid(logit).item()
return 'positive' if prob > 0.5 else 'negative', prob
label, score = predict_sentiment(
model,
'This film was absolutely brilliant. Loved every scene!',
vocab, tokenizer, device
)
print(f'Sentiment: {label} ({score:.3f})')LSTM vs BERT for Sentiment Analysis
While LSTMs were state-of-the-art for NLP until around 2018, transformer-based models like BERT now dominate most NLP benchmarks. BERT achieves ~93-95% on IMDB, compared to LSTM's ~87-90%.
However, LSTMs remain relevant for several reasons: they are far more computationally efficient for very long sequences (transformers scale quadratically with sequence length), they work well on time-series data where the sequential nature is intrinsic, and they have a much smaller memory footprint. Understanding LSTMs also provides the conceptual foundation needed to understand how attention mechanisms improve upon them.
Quick Check
Test your understanding of LSTM-based sequence classification from this lesson.
Lesson Recap
In this lesson you learned: sequence-to-one architecture reads an entire input sequence and maps it to a single label using the final LSTM hidden state, text preprocessing pipeline requires tokenisation, vocabulary building, and padding before data enters the model, and embedding layers convert discrete word indices into dense learnable vectors that capture semantic meaning. Next up we explore transfer learning, where pre-trained model weights replace training from scratch.
Apprends Python avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « De la séquence vers un élément : analyse des sentiments avec un LSTM » est-elle gratuite ?
Oui — le texte complet de « De la séquence vers un élément : analyse des sentiments avec un LSTM » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Machine Learning Academy, passe à CoddyKit PRO. Le cours Machine Learning Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « De la séquence vers un élément : analyse des sentiments avec un LSTM » ?
Vous construirez un classifieur de sentiments de bout en bout : vous tokeniserez les avis, représenterez les mots avec nn.Embedding, les passerez dans un LSTM et classerez l’état caché final. Tu pratiques Machine Learning Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Machine Learning Academy ?
Aucune expérience préalable n'est requise. Machine Learning Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « De la séquence vers un élément : analyse des sentiments avec un LSTM » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Machine Learning Academy ?
Oui. Chaque leçon Machine Learning Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- RNN classiques : état caché et déroulement des séquences
- Le problème de disparition des gradients sur de longues séquences
- Cellule LSTM : portes d’entrée, d’oubli et de sortie
- De la séquence vers un élément : analyse des sentiments avec un LSTM