Sekvens-til-én: Sentimentanalyse med en LSTM
De lærende opbygger en sentimentklassifikator fra start til slut: tokeniserer anmeldelser, embedder ord med nn.Embedding, kører dem gennem en LSTM og klassificerer den sidste hidden state.
Sekvens-til-én: Sentimentanalyse med en LSTM er en gratis Machine Learning Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Machine Learning Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Machine Learning Academy-kurset indeholder 4 lektioner i alt.
Sekvens-til-én-klassifikation forklaret
I sekvens-til-én-opgaver læser modellen en hel sekvens og producerer én enkelt outputetiket. Sentimentanalyse er det klassiske eksempel: Givet en hel filmanmeldelse ('I loved every minute of this film') skal modellen forudsige én enkelt etiket — positiv eller negativ.
Dette står i kontrast til sekvens-til-sekvens (oversættelse, hvor hvert inputtoken producerer et outputtoken) og én-til-sekvens (billedtekstning). I sekvens-til-én kasserer vi LSTM-outputtene fra de mellemliggende tidstrin og bruger kun den sidste skjulte tilstand h_T, som teoretisk koder betydningen af hele inputsekvensen.
Datasæt: IMDB-filmanmeldelser
IMDB-datasættet indeholder 50.000 filmanmeldelser (25.000 til træning og 25.000 til test), der er mærket som positive eller negative. Hver anmeldelse er en tekststreng med variabel længde. Før vi sender den til en LSTM, skal vi udføre tre forbehandlingstrin: tokenisering (opdele teksten i ord), opbygning af ordforråd (knytte ord til heltalsindekser) og udfyldning (gøre alle sekvenser lige lange).
Vi skal også håndtere anmeldelser, der er meget lange — LSTM-træning bliver langsommere kvadratisk med sekvenslængden, så det er almindelig praksis at afkorte til 256 eller 512 tokens uden væsentligt at forringe nøjagtigheden.
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))Tokenisering og udfyldning af sekvenser
Udfyldning er nødvendig, fordi PyTorch DataLoaders kræver tensorer med samme form i en batch. Vi udfylder kortere sekvenser med et særligt <pad>-token (normalt indeks 0) og afkorter længere sekvenser til en maksimal længde.
Den afgørende pointe er, at LSTM'en skal ignorere udfyldningstokens. PyTorch's hjælpefunktioner pack_padded_sequence og pad_packed_sequence gør det muligt effektivt at springe udfyldningen over under LSTM-beregningen, hvilket er vigtigt for korrektheden — ellers opdaterer modellen sin skjulte tilstand ud fra meningsløs udfyldning, så den endelige h_T forvrides.
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)Embeddinglag: Fra ord til tætte vektorer
Heltalsindekser for ord kan ikke sendes direkte ind i en LSTM — vi skal konvertere dem til kontinuerte vektorer. Et embeddinglag nn.Embedding(vocab_size, embedding_dim) er en opslagstabel, der knytter hvert ordindeks til en tæt vektor af kommatal.
Almindelige embeddingdimensioner er 100, 200 eller 300. Embeddings er parametre, der kan læres, og som opdateres under træningen sammen med LSTM-vægtene. Alternativt kan du initialisere dem fra fortrænede ordvektorer som GloVe eller Word2Vec. Det kan gøre træningen på små datasæt markant hurtigere ved at levere allerede indlærte semantiske relationer mellem ord.
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)Opbygning af SentimentLSTM-modellen
Den komplette modelarkitektur består af tre dele: et embeddinglag, en LSTM og et fuldt forbundet hoved. Embeddinglaget konverterer tokenindekser til vektorer, LSTM'en behandler sekvensen, og det lineære lag knytter den sidste skjulte tilstand til én enkelt logit.
Vi tilføjer dropout efter embeddinglaget og før det lineære lag for at regularisere modellen. For outputtet anvender vi en sigmoid for at få en sandsynlighed mellem 0 og 1. Træningen bruger Binary Cross-Entropy-tab, da dette er binær klassifikation.
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,) logitsTræningsopsætning: Tab og optimeringsalgoritme
Til binær klassifikation bruger vi nn.BCEWithLogitsLoss, som kombinerer sigmoid-aktivering og binært krydsentropitab i én numerisk stabil operation. Det foretrækkes frem for at anvende sigmoid manuelt efterfulgt af nn.BCELoss.
Vi bruger Adam-optimeringsalgoritmen med en læringsrate på 1e-3. Adam tilpasser læringsraten for hver parameter, hvilket gør den velegnet til sparsomme gradienter, der opstår fra embeddings (kun embeddings for ord, der optræder i batchen, modtager gradientopdateringer). Brug gradientklipning med torch.nn.utils.clip_grad_norm_ for at forhindre eksploderende gradienter, som er et almindeligt problem ved træning af RNN'er.
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()))Træningsløkken for sentiment-LSTM
Træningsløkken til sekvens-til-én-klassifikation følger PyTorch's standardmønster: fremadrettet gennemløb, beregning af tab, backpropagering, klipning af gradienter og opdatering af parametre. Vi beregner også nøjagtighed ved at sammenligne den forudsagte klasse (sigmoid-output > 0.5) med de korrekte etiketter.
En vigtig overvejelse er at flytte både modellen og batchens tensorer til den samme enhed (CPU eller GPU). Brug af model.to(device) og tensor.to(device) sikrer ensartede beregninger. På en GPU tager træning af en LSTM med to lag på IMDB typisk 2-5 minutter pr. epoke.
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)Evaluering og overvågning af fremskridt
Under evalueringen kalder vi model.eval() og indlejrer fremadrettede gennemløb i torch.no_grad() for at deaktivere dropout og gradientsporing. Det giver os et uvildigt billede af modellens generaliseringsevne.
Vi følger fire tal på tværs af epoker: træningstab, valideringstab, træningsnøjagtighed og valideringsnøjagtighed. Hvis træningsnøjagtigheden stiger, mens valideringsnøjagtigheden går i stå, overtilpasser modellen — øg dropout, eller gør modellen mindre. Hvis begge er lave, undertilpasser modellen — tilføj LSTM-lag, eller øg den skjulte størrelse.
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}')Forventet ydeevne og almindelige fejl
En velindstillet LSTM-sentimentklassifikator på IMDB opnår typisk en testnøjagtighed på 85-90 %. Almindelige fejl, der forringer ydeevnen, omfatter: ikke at indstille model.eval() under evalueringen (dropout forbliver aktiv), ikke at nulstille gradienterne før backpropagering og at træne i for få epoker, før embeddings konvergerer.
En anden faldgrube er at bruge det samme ordforrådsindeks til ukendte ord og udfyldning. Brug separate tokens for <unk> (indeks 0) og <pad> (indeks 1), og angiv padding_idx=1 i embeddinglaget, så pad-embeddings altid er nul, og deres gradienter undertrykkes under træningen.
# 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
)Forudsigelser på ny tekst
For at klassificere nye anmeldelser på inference-tidspunktet anvender vi det samme forbehandlingsforløb som under træningen: tokenisering, konvertering til indekser, afkortning og tilføjelse af en batchdimension. Modellen returnerer en logit; når sigmoid anvendes, konverteres den til en sandsynlighed.
En forudsigelsesscore over 0,5 angiver en positiv sentiment. Du kan justere denne tærskel: hvis du sænker den til 0,3, øges genkaldelsen for positive anmeldelser på bekostning af præcisionen. Anvend altid model.eval() og torch.no_grad() under inference for at deaktivere dropout og undgå unødvendig gradientberegning.
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 til sentimentanalyse
Selvom LSTM'er var state of the art inden for NLP indtil omkring 2018, dominerer transformerbaserede modeller som BERT nu de fleste NLP-benchmarks. BERT opnår omkring 93–95 % på IMDB sammenlignet med LSTM'ers omkring 87–90 %.
LSTM'er er dog stadig relevante af flere grunde: De er langt mere beregningseffektive til meget lange sekvenser (transformere skalerer kvadratisk med sekvenslængden), de fungerer godt på tidsseriedata, hvor den sekventielle natur er iboende, og de har et betydeligt mindre hukommelsesforbrug. En forståelse af LSTM'er giver også det konceptuelle fundament, der er nødvendigt for at forstå, hvordan opmærksomhedsmekanismer forbedrer dem.
Hurtigt tjek
Test din forståelse af sekvensklassifikation baseret på LSTM fra denne lektion.
Opsummering af lektionen
I denne lektion lærte du, at en sekvens-til-en-arkitektur læser en hel inputsekvens og knytter den til én enkelt etiket ved hjælp af LSTM'ens sidste skjulte tilstand, at et forbehandlingsforløb til tekst kræver tokenisering, opbygning af et ordforråd og padding, før data sendes ind i modellen, og at embedding-lag konverterer diskrete ordindekser til tætte, indlærbare vektorer, der indfanger semantisk betydning. Dernæst udforsker vi transfer learning, hvor forudtrænede modelvægte erstatter træning fra bunden.
Lær Python med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Sekvens-til-én: Sentimentanalyse med en LSTM” gratis?
Ja — hele teksten til “Sekvens-til-én: Sentimentanalyse med en LSTM” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Machine Learning Academy-kurset, skal du opgradere til CoddyKit PRO. Machine Learning Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Sekvens-til-én: Sentimentanalyse med en LSTM”?
De lærende opbygger en sentimentklassifikator fra start til slut: tokeniserer anmeldelser, embedder ord med nn.Embedding, kører dem gennem en LSTM og klassificerer den sidste hidden state. Du øver dig i Machine Learning Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Machine Learning Academy?
Der kræves ingen tidligere erfaring. Machine Learning Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.
Hvor lang tid tager lektionen “Sekvens-til-én: Sentimentanalyse med en LSTM”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Machine Learning Academy-lektion?
Ja. Alle Machine Learning Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Vanilla-RNN'er: Hidden state og sekvensudrulning
- Problemet med forsvindende gradienter over dybe tidstrin
- LSTM-cellen: Input-, forget- og output gates
- Sekvens-til-én: Sentimentanalyse med en LSTM