LSTM-modellen: een complete gids voor Long Short-Term Memory-netwerken
Begrijp de binnenkant van LSTM-netwerken, de basis voor moderne LLM’s. Verken gatingmechanismen, gradiënten en bouw een sentimentclassificator met PyTorch.
Standaard recurrente neurale netwerken hebben moeite met geheugen. Naarmate sequenties langer worden dan 20-30 timesteps, krimpen gradiënten richting nul of lopen ze op tijdens de backpropagation, en verliest het netwerk het vermogen om verre delen van de input met elkaar te verbinden. Hochreiter en Schmidhuber pakten dit probleem aan in hun paper uit 1997 door Long Short-Term Memory-netwerken te introduceren.
In deze gids loop ik door de interne werking van LSTM’s voordat we naar een praktische implementatie in Python gaan. De laatste secties vergelijken LSTM’s met Transformers, zodat je de juiste architectuur voor jouw use case kunt kiezen.
Ik raad onze cursus Introduction to Deep Learning in PyTorch sterk aan om hands-on ervaring op te doen met het trainen van neurale netwerken met PyTorch, de basis voor LSTM-modellen.
Wat is een LSTM-model?
LSTM staat voor Long Short-Term Memory, een type recurrent neuraal netwerk (RNN) dat is ontworpen voor sequenties waarbij context uit veel eerdere input nog steeds van belang is.
Het kernidee achter LSTM’s is een gatingmechanisme dat de informatiestroom door het netwerk reguleert. In plaats van alle signalen gelijkelijk door te laten, leren gates wanneer ze context moeten behouden en wanneer ze die kunnen loslaten.
Dit selectieve geheugen stelt LSTM’s in staat afhankelijkheden over honderden timesteps te volgen, wat nuttig bleek voor taalmodellering, spraakherkenning en tijdreeksvoorspellingen. Om te begrijpen waarom deze architectuur bestaat, helpt het om te zien wat er misgaat bij standaard RNN’s.
Het verdwijnende-gradiëntprobleem in RNN’s
Recurrente netwerken geven informatie door van de ene timestep naar de volgende via een verborgen toestand. Elke stap vermenigvuldigt deze toestand met geleerde gewichten, werkt hem bij en geeft hem door. Voor training moeten foutsignalen achterwaarts door al die stappen worden gestuurd, en elke backward-pass verzwakt het signaal een beetje. Na genoeg stappen blijft er niets over.
Het is als een kopie van een kopie maken. Een paar keer gaat het prima. Doe je het vijftig keer, dan is de tekst nauwelijks leesbaar. RNN’s lopen hier ergens rond 20-30 timesteps tegenaan, wat verklaart waarom ze moeite hebben met langere sequenties.
Hoe LSTM’s langetermijnafhankelijkheden oplossen
LSTM’s voegen een apart geheugenspoor toe, de cell state, die parallel loopt aan de standaard hidden state. Het verschil is structureel: informatie in de cell state stroomt via optelling in plaats van herhaalde vermenigvuldiging, waardoor deze niet op dezelfde manier degradeert.
Om te bepalen wat er in en uit dit geheugen gaat, gebruiken LSTM’s gates. Elke gate is een klein neuraal netwerk dat waarden tussen 0 en 1 leert uit te sturen en zo als filter werkt. Een waarde dicht bij 0 blokkeert informatie; een waarde dicht bij 1 laat die door. Het netwerk leert tijdens training welke patronen het waard zijn om over lange sequenties te onthouden en welke kunnen worden weggegooid.
Dit gatingmechanisme onderscheidt LSTM’s van ‘vanilla’ RNN’s. In plaats van alle informatie door hetzelfde pad te dwingen, kunnen LSTM’s selectief context behouden over honderden timesteps. De volgende sectie laat precies zien hoe deze gates werken en hoe ze samen de cell state bijwerken.
Use-cases voor LSTM-modellen
LSTM’s kom je tegen waar sequentiële data ertoe doet. Enkele voorbeelden buiten de gebruikelijke NLP- en forecasting-toepassingen:
Banken voeren transactiesequenties door LSTM’s om fraude te vangen die regelgebaseerde systemen missen
Ziekenhuizen monitoren ECG-streams met bidirectionele LSTM’s en detecteren sneller hartritmestoornissen dan drempelalarmen
Fabrieken volgen trillingen van apparatuur in de tijd om onderhoud te plannen vóórdat er iets stukgaat
LSTM-architectuur: gates en geheugencellen
Deze sectie behandelt de interne werking van LSTM’s in detail. Wil je meteen naar de implementatie, ga dan door naar de volgende sectie. De architectuurdetails hier zijn niet vereist om LSTM’s te gebruiken, maar helpen bij debuggen of tunen.
Om te begrijpen hoe LSTM’s werken, volgen we een concreet voorbeeld: de sequentie "The clouds are dark..." verwerken om het volgende woord te voorspellen. Elk onderdeel is begrijpelijker als je ziet wat het met echte data doet.
Cell state en hidden state in LSTM’s
Een LSTM onderhoudt twee afzonderlijke vectoren die door het netwerk stromen: de cell state en de hidden state.
De cell state fungeert als langetermijnopslag. Hij draagt informatie over timesteps met gecontroleerde aanpassingen bij elke stap. Bij het verwerken van onze sequentie kan de cell state na "clouds" bijvoorbeeld "weergerelateerd zelfstandig naamwoord gezien" opslaan en die context behouden door "are", totdat "dark" het patroon versterkt.
De hidden state is de output van de cel bij elke timestep. Deze voedt de voorspellingen en de berekeningen van de volgende timestep. Na het verwerken van "dark" codeert de hidden state iets als "bijvoeglijk naamwoord dat weersomstandigheden beschrijft" dat een predictielaag kan gebruiken.
Waarom twee afzonderlijke sporen? Eén toestand zou tegelijk langetermijncontext moeten vasthouden én bruikbare outputs produceren. Die doelen botsen: stabiel geheugen vereist minimale verandering, terwijl bruikbare outputs transformatie vereisen. Door ze te scheiden kan elk spoor optimaliseren voor zijn doel.
Bij elke timestep neemt de cel drie inputs en produceert twee outputs:
Inputs:
Vorige cell state (C_{t-1}): wat het netwerk tot nu toe onthoudt
Vorige hidden state (h_{t-1}): de output van de vorige timestep
Huidige input (x_t): het nieuwe token, zoals "clouds" of "dark"
Outputs:
Bijgewerkte cell state (C_t): geheugen na verwerking van dit token
Nieuwe hidden state (h_t): de output van deze timestep
Hoe LSTM-gates rekenen
Vier componenten bepalen wat er in de cel gebeurt. Drie daarvan zijn gates die waarden tussen 0 en 1 uitsturen en als filters werken. De vierde is de candidate, die nieuwe inhoud voorstelt.
Wanneer "are" arriveert na "clouds", heeft elke component een taak:
Forget gate (f_t): Moeten we de "clouds"-context behouden? Weerszelfstandige naamwoorden gevolgd door koppelwerkwoorden zijn meestal relevant, dus de forget gate geeft waarden dicht bij 1 om het grootste deel van de cell state te behouden.
Input gate (i_t): Hoeveel moet "are" bijdragen aan het geheugen? Koppelwerkwoorden dragen minder voorspellend signaal, dus de input gate geeft lagere waarden.
Candidate (C̃_t): Welke nieuwe informatie kan "are" toevoegen? De candidate doet een voorstel, maar de input gate schaalt het omlaag.
Output gate (o_t): Wat moet de hidden state laten zien? De output gate filtert de cell state om h_t te produceren.
Elke component berekent zijn output op dezelfde manier:
Concateneer h_{t-1} en x_t tot één vector
Vermenigvuldig met een geleerde gewichtsmatrix
Voeg een bias-term toe
Pas een activatiefunctie toe
Gates gebruiken sigmoid als activatie, die waarden in het 0-1-bereik perst. De candidate gebruikt tanh en geeft waarden tussen -1 en 1, zodat hij zowel verhogingen als verlagingen voor de cell state kan voorstellen.
De vergelijkingen
Forget gate (sigmoid → 0 tot 1):
Input gate (sigmoid → 0 tot 1):
Output gate (sigmoid → 0 tot 1):
Candidate (tanh → -1 tot +1):
Elke component heeft een eigen gewichtsmatrix (W_f, W_i, W_c, W_o), zodat ze op verschillende patronen in de input leren reageren.
Hoe filtering werkt
Gate-outputs vermenigvuldigen zich positie-voor-positie met vectoren (elementgewijze vermenigvuldiging, geschreven als ⊙). Elke dimensie wordt onafhankelijk gefilterd.
Als een gate 0,9 uitstuur voor positie 1, behoudt die dimensie 90% van zijn waarde. Als hij 0,0 uitstuur voor positie 4, wordt die dimensie gewist. Bij het verwerken van "dark" na "clouds are" kan de forget gate hoge waarden uitsturen voor dimensies die zelfstandig naamwoord-/weercontext opslaan en lagere waarden voor dimensies met irrelevante eerdere context.
Informatiedoorstroming tussen forget-, add- en output-gates
De componenten combineren in drie stappen om de cell state bij te werken en de hidden state te produceren.
Stap 1: Forget
De forget gate filtert de vorige cell state:
Bij het verwerken van "dark" behoudt de forget gate de weercontext van "clouds" en wist hij dimensies die het lidwoord "The" volgden.
Stap 2: Add
De input gate schaalt het voorstel van de candidate en voegt het vervolgens toe aan de gefilterde cell state:
De candidate kan iets voorstellen als "bijvoeglijk naamwoord bij weer-zelfstandig naamwoord", en de input gate bepaalt hoe sterk dat in het geheugen wordt geschreven. Deze additieve update (niet multiplicatief) helpt gradiënten tijdens training doorstromen.
Stap 3: Output
De cell state gaat door tanh, waarna de output gate filtert wat de hidden state wordt:
De tanh houdt waarden begrensd. De output gate selecteert welke aspecten van het verzamelde geheugen zichtbaar moeten zijn voor voorspellingen op deze timestep.
De complete sequentie:
Hier is een vereenvoudigd spoor met een 4-dimensionale cell state:
Input gate gaat open voor dimensies 2 en 4; slaat zelfstandig-naamwoordpatroon op
3
"are"
[0.1, 0.6, 0.2, 0.3]
Forget gate ≈ 0.9 behoudt context; koppelwerkwoord voegt weinig toe
4
"dark"
[0.1, 0.8, 0.5, 0.6]
Het bijvoeglijk naamwoord versterkt het signaal in dimensies 2, 3, 4
Tegen timestep 4 hebben dimensies 2-4 een patroon opgebouwd. De hidden state h_4 gaat naar een predictielaag (niet onderdeel van de LSTM), die woordenschatkansen produceert. Dat opgebouwde patroon helpt het model woorden als "rain" of "sky" te verkiezen.
Het netwerk begrijpt geen weer. Het leert statistische correlaties: bepaalde inputsequenties gaan vaak vooraf aan bepaalde outputs.
Gradiëntdoorstroming tijdens training
De additieve structuur van de cell state biedt een directer gradiëntpad dan standaard RNN’s. Als je niet bekend bent met hoe gradiënten door neurale netwerken propagëren, behandelt de backpropagation-tutorial de basics.
In standaard RNN’s stromen gradiënten bij elke stap door gewichtsmatrices. Herhaalde vermenigvuldiging stapelt op: na 100 stappen met gewichten die gemiddeld 0,9 zijn, krimpt de gradiënt tot 0.9^100 ≈ 0.00003.
LSTM’s bieden een alternatief pad langs de cell state. De afgeleide van C_t ten opzichte van C_{t-1}:
Dit is de waarde van de forget gate, geen gewichtsmatrix. Als forget gates in de buurt van 1 blijven, gaan gradiënten met minder verval door. Als gates gemiddeld 0,95 zijn, dan na 100 stappen: 0.95^100 ≈ 0.006. Nog steeds verminderd, maar bruikbaar.
Aspect
RNN
LSTM
Gradiëntpad
Alleen gewichtsmatrices
Extra pad langs de cell state
100-staps gradiënt
~10^-5 (verdwenen)
~10^-2 tot 10^-1 (bruikbaar)
Praktisch geheugen
~10-20 timesteps
~50-200 timesteps
Praktische tip: biases van de forget gate worden vaak geïnitialiseerd op 1,0 of hoger, waardoor initiële outputs richting "alles onthouden" schuiven. Dit helpt het netwerk vroeg in de training langetermijnpatronen te leren.
LSTM’s mitigeren het verdwijnende-gradiëntprobleem in plaats van het te elimineren. De verbetering komt door de additieve update van de cell state, die een gradiëntsnelweg biedt naast de standaardpaden.
Hoe LSTM-modellen in Python te implementeren
De vorige sectie behandelde wat er in een LSTM-cel gebeurt: gates die informatie filteren, cell state die context bewaart en hidden state die outputs produceert. Nu zien we hoe PyTorch’s nn.LSTM dat allemaal in één module verpakt en gebruikt om filmreviews als positief of negatief te classificeren.
Tip om de code te draaien: trainen voor 50 epochs op een CPU duurt lang. Gebruik Google Colab of Kaggle Notebooks met GPU-versnelling voor snellere resultaten. Het complete script is beschikbaar als een GitHub Gist.
Een LSTM-classificator bouwen met PyTorch
Tekstclassificatie met LSTM’s volgt een patroon: zet woorden om in vectoren, laat de LSTM ze sequentieel verwerken en gebruik daarna de laatste output voor de voorspelling.
Woorden embedden in vectoren
Neurale netwerken werken met continue getallen, maar woorden zijn discrete symbolen. We kunnen "terrible" niet direct in matrixvermenigvuldiging voeren. De oplossing is een embedding-laag: een lookup-tabel waarin elk woord naar een leerbare vector wordt gemapt.
self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=padding_idx)Mogelijk gemaakt door
Als je embedding_dim=32 kiest, wordt elk woord een 32-dimensionale vector. Vroeg in de training zijn deze vectoren willekeurig. Terwijl het model leert, komen woorden met vergelijkbaar sentiment dichter bij elkaar te liggen in deze vectorruimte. "Terrible", "awful" en "boring" kunnen bij elkaar eindigen, terwijl "excellent" en "amazing" elders clusteren.
Het argument padding_idx vertelt de laag om padding-tokens op nulvectoren te houden, zodat ze het model niet beïnvloeden.
De LSTM-architectuur definiëren
self.lstm = nn.LSTM(
input_size=embedding_dim,
hidden_size=hidden_size,
num_layers=num_layers,
batch_first=True,
dropout=dropout if num_layers >1else0,
bidirectional=bidirectional,)Mogelijk gemaakt door
Deze ene regel creëert de volledige gate-machinerie uit de vorige sectie. De hidden_size bepaalt hoeveel informatie de cell state en hidden state kunnen dragen. Grotere waarden laten het model meer patronen volgen, maar vragen meer rekenwerk.
Met batch_first=True hebben inputs de vorm (batch, sequence_length, embedding_dim). De LSTM verwerkt elke positie van links naar rechts en werkt bij elke stap zijn interne toestanden bij.
De sequentie verwerken
Na het verwerken van "This movie was absolutely terrible" heeft de LSTM elk woord gezien. De laatste hidden state heeft vastgelegd wat het model uit die sequentie heeft geleerd. Voor sentiment kan dit iets coderen als "sterk negatief bijvoeglijk naamwoord aan het eind, dat eerdere neutrale inhoud neutraliseert".
defforward(self, x):
embedded = self.embedding(x)
lstm_out,(h_n, c_n)= self.lstm(embedded)
h_final = h_n[-1]return self.fc(h_final)Mogelijk gemaakt door
De LSTM geeft zowel outputs per timestep (lstm_out) als eindtoestanden (h_n, c_n). Voor classificatie hebben we alleen h_n[-1] nodig, de hidden state na het laatste token. Een lineaire laag zet dit vervolgens om naar klassescores.
Een review door het model volgen
Wanneer "This movie was terrible" de classifier binnenkomt:
Elk woord krijgt zijn embedding: vier vectoren met vorm (32,)
De LSTM verwerkt ze sequentieel. "This" initialiseert wat cell state. "Movie" werkt die iets bij. "Was" is neutraal en verandert weinig. "Terrible" triggert de forget gate om irrelevante context te wissen en de input gate om een sterk negatief signaal te schrijven.
De laatste hidden state vat het opgebouwde patroon samen.
De lineaire laag zet dit om naar twee scores: één voor positief, één voor negatief
Het model begrijpt geen taal. Het leert statistische patronen: bepaalde woordsequenties correleren met bepaalde labels.
De volledige modelklasse
Hieronder zie je de volledige klasse LSTMClassifier die we zojuist stap voor stap hebben opgebouwd:
De bidirectionele optie draait twee LSTM’s: één van links naar rechts en één van rechts naar links. Hun laatste hidden states worden geconcateneerd, zodat het model context uit beide richtingen heeft. Dit helpt wanneer betekenis afhangt van wat erna komt, zoals "not bad", waar "not" pas zinvol is gegeven "bad".
Tekst voorbereiden voor het model
LSTM’s hebben integersequenties nodig, geen strings. Preprocessing zet onbewerkte tekst om via drie stappen:
Stel een vocabulaire samen die woorden aan indexen koppelt
Zet elke review om naar een sequentie van indexen
Pad sequenties tot een uniforme lengte
defbuild_vocab(texts, max_vocab_size=10000):
counter = Counter()for text in texts:
counter.update(text.lower().split())
vocab ={"<pad>":0,"<unk>":1}for word, _ in counter.most_common(max_vocab_size -2):
vocab[word]=len(vocab)return vocabMogelijk gemaakt door
We reserveren index 0 voor padding en index 1 voor onbekende woorden. De vocabulaire beperken filtert zeldzame woorden waarvoor te weinig voorbeelden zijn om zinvolle embeddings te leren.
deftokenize_and_pad(texts, vocab, max_length=200):
sequences =[]for text in texts:
tokens = text.lower().split()
ids =[vocab.get(t, vocab["<unk>"])for t in tokens[:max_length]]
ids = ids +[vocab["<pad>"]]*(max_length -len(ids))
sequences.append(ids)return torch.tensor(sequences, dtype=torch.long)Mogelijk gemaakt door
Afkappen op max_length beperkt hoe ver terug de LSTM moet onthouden. Langere sequenties behouden meer context maar verhogen de reken- en geheugenkosten. Voor filmreviews vangen 100-200 tokens meestal de belangrijkste sentimentsignalen.
Padding zorgt ervoor dat elke sequentie dezelfde lengte heeft, zodat PyTorch ze kan batchen in één tensor. De padding-tokens worden na embedding nulvectoren en dragen weinig bij aan de eindtoestand.
De vocabulaire uitsluitend op trainingsdata bouwen voorkomt informatielekken. Als een zeldzaam woord alleen in testreviews voorkomt, moet het model het als onbekend behandelen in plaats van de embedding ervan al tijdens training geleerd te hebben.
LSTM-training en hyperparameters
De trainingslus werkt de gewichten van het model bij op basis van hoe ver de voorspellingen van de juiste waarden afliggen. Voor LSTM’s is één detail extra belangrijk: gradient clipping.
deftrain_model(model, train_loader, test_loader, n_epochs=5, lr=0.001):
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)for epoch inrange(n_epochs):
model.train()
total_loss, correct, total =0,0,0for batch_texts, batch_labels in train_loader:
optimizer.zero_grad()
outputs = model(batch_texts)
loss = criterion(outputs, batch_labels)
loss.backward()
torch.nn.utils.clip_grad_norm_(
model.parameters(),
max_norm=5.0)
optimizer.step()
total_loss += loss.item()
_, predicted = outputs.max(1)
correct +=(predicted == batch_labels).sum().item()
total += batch_labels.size(0)Mogelijk gemaakt door
Waarom gradient clipping belangrijk is voor LSTM’s
We legden eerder uit hoe LSTM’s verdwijnende gradiënten mitigeren via additieve updates van de cell state. Maar exploderende gradiënten blijven een risico. Wanneer gradiënten te groot worden, schieten gewichtsupdates door en raakt de training onstabiel.
clip_grad_norm_ begrenst de totale gradiëntgrootte. Als gradiënten max_norm=5.0 zouden overschrijden, worden ze proportioneel omlaag geschaald. Dit houdt updates stabiel zonder het gradiëntsignaal volledig te verwijderen.
model.eval() schakelt dropout uit tijdens evaluatie. torch.no_grad() slaat gradiëntberekening over omdat we geen gewichten bijwerken; dit bespaart geheugen en versnelt inferentie.
hidden_size=32: Klein genoeg voor snelle iteratie. Productiemodellen gebruiken vaak 128-512, maar naarmate de grootte toeneemt, nemen de opbrengsten af en vertraagt training.
embedding_dim=32: Gelijk aan hidden size is gebruikelijk maar niet vereist. Grotere embeddings vangen fijnere woordonderscheidingen tegen de prijs van meer parameters.
max_length=100: De meeste sentimentsignalen verschijnen vroeg in reviews. Langere sequenties helpen bij nuance, maar verviervoudigen het geheugengebruik als je max_length verdubbelt.
batch_size=64: Grotere batches geven stabielere gradiëntschattingen. Kleinere batches kunnen helpen lokale minima te verlaten. 32-128 is typisch.
lr=0.001: De standaard van Adam werkt voor de meeste LSTM-taken. Als de loss oscilleert, probeer 0,0001. Als het leren stokt, probeer 0,01.
n_epochs=50: Genoeg voor het model om te convergeren. Let op het moment waarop de testnauwkeurigheid stabiliseert of daalt terwijl de trainnauwkeurigheid blijft stijgen; dat duidt op overfitting.
Wat het model leert
Het draaien van deze configuratie levert output op zoals:
De trainingsnauwkeurigheid bereikt ongeveer 82% terwijl de testnauwkeurigheid rond de 80% uitkomt. Het model leert welke woordpatronen sentiment voorspellen. Sterke bijvoeglijke naamwoorden aan het eind wegen zwaar. Negaties zoals "not" draaien polariteit om. Bepaalde zinnen ("waste of time", "highly recommend") worden sterke signalen. Niets hiervan is geprogrammeerd; het komt voort uit correlaties in de trainingsdata.
Met minimale tuning en een klein model is 80% nauwkeurigheid redelijk. De kleine kloof tussen training en test laat zien dat het model goed generaliseert zonder ernstige overfitting. Door dropout toe te voegen, pretrained embeddings zoals GloVe te gebruiken of op meer data te trainen, kun je de nauwkeurigheid richting de hoge 80% duwen.
De prestaties van LSTM-modellen verbeteren
De basis-LSTM-architectuur uit de vorige secties werkt, maar verschillende technieken kunnen de nauwkeurigheid verhogen zonder over te stappen op een andere modelfamilie. Deze aanpakken zijn gevalideerd in duizenden productietoepassingen en onderzoekspapers.
Bidirectionele LSTM’s
Standaard LSTM’s verwerken sequenties van links naar rechts en bouwen alleen context op uit het verleden. Bidirectionele LSTM’s (BiLSTM’s) draaien twee parallelle passes: één vooruit, één achteruit. De uiteindelijke representatie op elke timestep combineert wat ervoor en erna kwam.
self.lstm = nn.LSTM(
input_size=embedding_dim,
hidden_size=hidden_size,
bidirectional=True,# Run both directions
batch_first=True,)# Output size doubles: hidden_size * 2Mogelijk gemaakt door
Voor sentimentanalyse is dit belangrijk wanneer een negatie na het woord verschijnt dat het beïnvloedt: "the movie was not bad" heeft forward-context nodig om te weten dat "bad" er is, en backward-context om te weten dat "not" eraan voorafgaat.
BiLSTM’s presteren consistent beter dan unidirectionele modellen bij named entity recognition (NER), sentimentclassificatie en spraakherkenning, met typische winst van 5-15%.
De trade-off: je hebt de volledige sequentie nodig op inferentietijd. Streamingtoepassingen die tokens één voor één ontvangen, kunnen geen bidirectionele verwerking gebruiken.
Attention toevoegen
Attention laat het model wegen welke timesteps het belangrijkst zijn voor een bepaalde voorspelling. In plaats van een hele sequentie in één laatste hidden state te persen, berekent attention een gewogen combinatie van alle hidden states.
In een review als "The acting was wooden, the plot made no sense, but the cinematography was stunning" leert attention zich te richten op zinsdelen met sentiment in plaats van alle woorden gelijk te behandelen. Het model kent hogere gewichten toe aan "wooden", "no sense" en "stunning" en verlaagt de weging van "the" en "was".
Attention voegt minimale rekentijd toe en levert vaak 2-5% nauwkeurigheidswinst op classificatietaken. Voor sequence-to-sequence-problemen zoals vertaling is attention tussen encoder en decoder vrijwel noodzakelijk voor goede prestaties.
LSTM-lagen stapelen
Eén LSTM-laag vangt patronen op één abstractieniveau. Door meerdere lagen te stapelen leert het netwerk hiërarchische features: de eerste laag detecteert lokale zinsdeelpatronen, hogere lagen combineren die tot begrip op documentniveau.
self.lstm = nn.LSTM(
input_size=embedding_dim,
hidden_size=hidden_size,
num_layers=3,# Stack three layers
dropout=0.3,# Dropout between layers (not within)
batch_first=True,)Mogelijk gemaakt door
Richtlijnen voor diepte:
1 laag: Eenvoudige taken, kleine datasets, snelle iteratie
2 lagen: De meeste productieapplicaties
3-4 lagen: Complexe temporele afhankelijkheden, grote datasets
Meer dan 4: Afnemende meeropbrengst, trainingsinstabiliteit
Elke extra laag verdubbelt grofweg de trainingstijd. Begin ondiep en voeg diepte alleen toe als validatiemetrics op een plateau komen.
Regularisatietechnieken
LSTM’s overfitten gemakkelijk, vooral op kleine datasets. Drie regularisatietechnieken werken goed samen:
Dropout
Gradient clipping
Early stopping
Dropout
Dropout zet willekeurig neuronen op nul tijdens training, waardoor het netwerk gedwongen wordt het leren over meerdere paden te verdelen. Pas dropout toe tussen LSTM-lagen, niet binnen de recurrente verbindingen (die de temporele leerprocessen kunnen verstoren).
# PyTorch applies dropout between stacked layers automatically
self.lstm = nn.LSTM(..., num_layers=2, dropout=0.3)# For single-layer LSTMs, add dropout after
self.dropout = nn.Dropout(0.3)Mogelijk gemaakt door
Dropoutrates van 0,2-0,5 werken voor de meeste taken. Hogere waarden helpen bij ernstige overfitting maar vertragen de convergentie.
Gradient clipping
Gradient clipping begrenst de grootte van gradiënten tijdens backpropagation. Hoewel LSTM’s verdwijnende gradiënten mitigeren, komen exploderende gradiënten nog steeds voor, vooral bij lange sequenties of diepe netwerken.
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)Mogelijk gemaakt door
Waarden tussen 1,0 en 5,0 zijn gebruikelijk. Als de training loss piekt of NaN-waarden oplevert, verlaag dan de drempel.
Early stopping
Early stopping bewaakt de validatie-loss en stopt de training wanneer die niet meer verbetert. Zo voorkom je dat het model trainingsdata uit het hoofd leert ten koste van generalisatie.
Voor een volledige walkthrough van trainingslussen, evaluatiemetrics en het debuggen van veelvoorkomende LSTM-problemen, zie de NLP met PyTorch-gids.
Pretrained embeddings
Willekeurige initialisatie van embeddings werkt, maar pretrained embeddings zoals GloVe of Word2Vec geven het model een voorsprong. Deze embeddings coderen semantische relaties die zijn geleerd uit miljarden woorden: "king" en "queen" eindigen dicht bij elkaar, net als "terrible" en "awful".
import numpy as np
defload_glove_embeddings(glove_path, vocab, embedding_dim):
embeddings = np.random.randn(len(vocab), embedding_dim)*0.01withopen(glove_path,'r')as f:for line in f:
parts = line.split()
word = parts[0]if word in vocab:
embeddings[vocab[word]]= np.array(parts[1:], dtype=np.float32)return torch.tensor(embeddings, dtype=torch.float32)# Load and freeze or fine-tune
pretrained = load_glove_embeddings('glove.6B.100d.txt', vocab,100)
model.embedding.weight.data.copy_(pretrained)
model.embedding.weight.requires_grad =True# Fine-tune during trainingMogelijk gemaakt door
Pretrained embeddings helpen vooral wanneer trainingsdata beperkt is. Met miljoenen voorbeelden halen willekeurig geïnitialiseerde embeddings het uiteindelijk in. Voor de meeste praktische toepassingen met 10k-100k samples kun je 3-8% nauwkeurigheidswinst verwachten van pretrained initialisatie.
Technieken combineren
Deze aanpakken stapelen. Een tekstclassificator op productieniveau kan gebruiken:
BiLSTM voor bidirectionele context
2 gestapelde lagen voor hiërarchische features
Attention-pooling in plaats van de laatste hidden state
GloVe-embeddings die tijdens training worden fijn-afgesteld
Dropout van 0,3 tussen lagen
Gradient clipping op 5,0
Early stopping met een patience van 5 epochs
Elke toevoeging verhoogt de complexiteit en trainingstijd. Begin met een baseline LSTM met één laag, meet prestaties en voeg vervolgens technieken één voor één toe om te zien wat winst oplevert voor jouw specifieke taak.
LSTM vs Transformers: de juiste architectuur kiezen
LSTM’s voerden de boventoon in sequentiemodellering van eind jaren 90 tot midden jaren 2010. Het paper uit 2017, "Attention Is All You Need", veranderde dat. Transformers drijven nu de meeste large language models aan, maar LSTM’s zijn niet verdwenen. Ze vullen verschillende niches.
Beperkingen van LSTM’s
Eerdere secties behandelden hoe LSTM’s sequenties één token per keer verwerken en waarom dat gatingmechanisme werkt. Op schaal wordt die sequentiële afhankelijkheid een bottleneck. Je GPU kan duizenden cores hebben, maar de meeste zijn ongebruikt terwijl de hidden-stateketen klaar is. Een input van 500 tokens betekent 500 bewerkingen op rij.
Transformers hebben deze beperking niet. Self-attention berekent relaties tussen alle posities tegelijk, zodat dezelfde sequentie van 500 tokens parallel wordt verwerkt. Op moderne hardware met grote batchgroottes vertaalt dit zich naar 10-100x snellere training.
De limiet voor sequentielengte is ook van belang. We zagen hoe LSTM’s gradiënten beter behouden dan ‘vanilla’ RNN’s, maar "beter" betekent niet "perfect". De prestaties dalen nog steeds ergens rond 200-500 tokens, afhankelijk van de taak.
Transformers berekenen aandacht tussen elk paar posities direct, dus token 1 kan even gemakkelijk aandacht geven aan token 1000. De trade-off is geheugen: attentiematrices groeien kwadratisch met de sequentielengte.
Gate-activaties maken interpretatie ook lastig. Waarden lopen van 0 tot 1 zonder duidelijke grenzen. Attentiongewichten in Transformers zijn ook geen perfecte verklaringen, maar geven een duidelijker signaal over welke inputtokens voor elke output van belang waren.
Wanneer LSTM of Transformers gebruiken
Transformers zijn logisch wanneer:
Je trainingsdata meer dan 100K voorbeelden omvat en je GPU-toegang hebt
De taak afhankelijkheden omvat over honderden tokens
Trainingssnelheid belangrijk is voor iteratiecycli
Pretrained modellen zoals GPT kunnen worden fijn-afgesteld voor jouw use case
LSTM’s passen nog steeds wanneer:
Het doelsysteem strikte geheugeneisen heeft (edge, embedded, mobiel)
Data schaars is (minder dan 10K voorbeelden) en Transformer-overfitting een zorg is
Voorspellingen moeten gebeuren op gedeeltelijke sequenties voordat de volledige input binnen is
Latentie-eisen lichte modellen vereisen
Tijdreeksvoorspelling zit ergens in het midden. Recente benchmarks laten zien dat beide architecturen vergelijkbaar presteren op univariate data met consistente patronen. Transformers lopen voorop wanneer meerdere features op complexe manieren interacteren, maar LSTM’s houden stand wanneer data schaars is of rekenkracht krap.
Hybride ontwerpen mengen beide benaderingen. Sommige architecturen stapelen LSTM-lagen met attentionmechanismen. xLSTM, gepubliceerd in 2024, herbekijkt het oorspronkelijke ontwerp met exponentiële gating en matrixgeheugen en laat concurrerende resultaten zien met Transformers op kleinere schales.
Aspect
LSTM
Transformer
Verwerking
Sequentieel (token voor token)
Parallel (alle posities tegelijk)
Trainingssnelheid
Langzamer (beperkte parallelisatie)
Sneller op GPU’s
Langetermijnafhankelijkheden
Degradeert voorbij 200-500 tokens
Handelt duizenden tokens af
Geheugenschaal
O(n) met sequentielengte
O(n²) voor attentiematrices
Prestaties met weinig data
Goed (minder parameters)
Vatbaar voor overfitting
Edge-deployment
Geschikt (lager geheugengebruik)
Uitdagend (grote modellen)
Streaming/realtime
Native ondersteuning
Vereist volledige sequentie
Pretrained-ecosysteem
Beperkt
Uitgebreid (BERT, GPT, enz.)
De keuze komt meestal neer op randvoorwaarden. Onbeperkte compute en data? Transformers winnen op de meeste NLP-taken. Een microcontroller met 10K trainingsvoorbeelden? LSTM’s blijven praktisch.
Conclusie
LSTM’s losten een echt probleem op toen ze in 1997 verschenen. Het gatingmechanisme van Hochreiter en Schmidhuber stelde netwerken eindelijk in staat te leren van sequenties langer dan enkele tientallen tokens. Die architectuur dreef bijna twee decennia lang machinevertaling, spraakherkenning en tijdreeksvoorspellingen aan.
Transformers pakken nu de meeste grootschalige NLP-taken op, maar LSTM’s zijn niet verouderd. Ze zijn nog steeds logisch voor edge-deployment, streamingtoepassingen en situaties met beperkte trainingsdata.
Als je aan een nieuw sequentiemodelleringsproject begint, probeer dan eerst een eenvoudige LSTM. Meet de prestaties, begrijp de faalmodi en beslis dan of het probleem echt een Transformer vereist. De concepten uit deze gids (gating, cell state, gradiëntdoorstroming) helpen direct bij het begrijpen van complexere architecturen.
Voor hands-on oefening raad ik onze cursus Recurrent Neural Networks aan, die aanvullende LSTM-toepassingen met begeleide oefeningen doorloopt.
LSTM FAQ's
Wat is het verschil tussen LSTM en een gewone RNN?⌄
Gewone RNN’s hebben moeite met lange sequenties omdat gradiënten tijdens backpropagation krimpen, waardoor het vermogen om van verre context te leren verloren gaat. LSTM’s voegen een cell state toe met gatingmechanismen die de informatiestroom controleren, waardoor het netwerk relevante context over honderden timesteps kan behouden in plaats van slechts 20-30.
Wanneer moet ik LSTM gebruiken in plaats van Transformers?⌄
LSTM’s werken beter wanneer je beperkte trainingsdata hebt (minder dan 10K voorbeelden), op geheugengebonden apparaten zoals mobiel of embedded wilt draaien, realtime voorspellingen nodig hebt op streamingdata vóórdat de volledige sequentie binnen is, of strikte latentie-eisen hebt die grotere modellen uitsluiten.
Wat betekent hidden_size in PyTorch LSTM?⌄
De parameter hidden_size bepaalt de dimensionaliteit van zowel de hidden state- als de cell state-vectoren. Grotere waarden laten het model meer patronen volgen, maar verhogen reken- en geheugenkosten. Waarden tussen 32-512 zijn gangbaar, met 128-256 typisch voor productie-tekstapplicaties.
Hoe voorkom ik overfitting bij mijn LSTM?⌄
Gebruik dropout tussen LSTM-lagen (rates van 0,2-0,5), pas gradient clipping toe om exploderende gradiënten te voorkomen, implementeer early stopping om te stoppen wanneer de validatie-loss niet meer verbetert, en overweeg pretrained embeddings zoals GloVe als je dataset klein is.
Wat is een bidirectionele LSTM en wanneer moet ik die gebruiken?⌄
Een bidirectionele LSTM draait twee passes over de sequentie: één vooruit en één achteruit. Dit geeft elke positie context uit beide richtingen, wat helpt wanneer betekenis afhankelijk is van toekomstige tokens (zoals "not bad", waarbij "not" "bad" moet zien). Gebruik het voor classificatie- en taggingtaken waarbij je de volledige sequentie op inferentietijd hebt, maar vermijd het voor streamingtoepassingen.
Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn.
Standaard recurrente neurale netwerken hebben moeite met geheugen. Naarmate sequenties langer worden dan 20-30 timesteps, krimpen gradiënten richting nul of lopen ze op tijdens de backpropagation, en verliest het netwerk het vermogen om verre delen van de input met elkaar te verbinden. Hochreiter en Schmidhuber pakten dit probleem aan in hun paper uit 1997 door Long Short-Term Memory-netwerken te introduceren.
In deze gids loop ik door de interne werking van LSTM’s voordat we naar een praktische implementatie in Python gaan. De laatste secties vergelijken LSTM’s met Transformers, zodat je de juiste architectuur voor jouw use case kunt kiezen.
Ik raad onze cursus Introduction to Deep Learning in PyTorch sterk aan om hands-on ervaring op te doen met het trainen van neurale netwerken met PyTorch, de basis voor LSTM-modellen.
Wat is een LSTM-model?
LSTM staat voor Long Short-Term Memory, een type recurrent neuraal netwerk (RNN) dat is ontworpen voor sequenties waarbij context uit veel eerdere input nog steeds van belang is.
Het kernidee achter LSTM’s is een gatingmechanisme dat de informatiestroom door het netwerk reguleert. In plaats van alle signalen gelijkelijk door te laten, leren gates wanneer ze context moeten behouden en wanneer ze die kunnen loslaten.
Dit selectieve geheugen stelt LSTM’s in staat afhankelijkheden over honderden timesteps te volgen, wat nuttig bleek voor taalmodellering, spraakherkenning en tijdreeksvoorspellingen. Om te begrijpen waarom deze architectuur bestaat, helpt het om te zien wat er misgaat bij standaard RNN’s.
Het verdwijnende-gradiëntprobleem in RNN’s
Recurrente netwerken geven informatie door van de ene timestep naar de volgende via een verborgen toestand. Elke stap vermenigvuldigt deze toestand met geleerde gewichten, werkt hem bij en geeft hem door. Voor training moeten foutsignalen achterwaarts door al die stappen worden gestuurd, en elke backward-pass verzwakt het signaal een beetje. Na genoeg stappen blijft er niets over.
Het is als een kopie van een kopie maken. Een paar keer gaat het prima. Doe je het vijftig keer, dan is de tekst nauwelijks leesbaar. RNN’s lopen hier ergens rond 20-30 timesteps tegenaan, wat verklaart waarom ze moeite hebben met langere sequenties.
Hoe LSTM’s langetermijnafhankelijkheden oplossen
LSTM’s voegen een apart geheugenspoor toe, de cell state, die parallel loopt aan de standaard hidden state. Het verschil is structureel: informatie in de cell state stroomt via optelling in plaats van herhaalde vermenigvuldiging, waardoor deze niet op dezelfde manier degradeert.
Om te bepalen wat er in en uit dit geheugen gaat, gebruiken LSTM’s gates. Elke gate is een klein neuraal netwerk dat waarden tussen 0 en 1 leert uit te sturen en zo als filter werkt. Een waarde dicht bij 0 blokkeert informatie; een waarde dicht bij 1 laat die door. Het netwerk leert tijdens training welke patronen het waard zijn om over lange sequenties te onthouden en welke kunnen worden weggegooid.
Dit gatingmechanisme onderscheidt LSTM’s van ‘vanilla’ RNN’s. In plaats van alle informatie door hetzelfde pad te dwingen, kunnen LSTM’s selectief context behouden over honderden timesteps. De volgende sectie laat precies zien hoe deze gates werken en hoe ze samen de cell state bijwerken.
Use-cases voor LSTM-modellen
LSTM’s kom je tegen waar sequentiële data ertoe doet. Enkele voorbeelden buiten de gebruikelijke NLP- en forecasting-toepassingen:
LSTM-architectuur: gates en geheugencellen
Deze sectie behandelt de interne werking van LSTM’s in detail. Wil je meteen naar de implementatie, ga dan door naar de volgende sectie. De architectuurdetails hier zijn niet vereist om LSTM’s te gebruiken, maar helpen bij debuggen of tunen.
Om te begrijpen hoe LSTM’s werken, volgen we een concreet voorbeeld: de sequentie "The clouds are dark..." verwerken om het volgende woord te voorspellen. Elk onderdeel is begrijpelijker als je ziet wat het met echte data doet.
Cell state en hidden state in LSTM’s
Een LSTM onderhoudt twee afzonderlijke vectoren die door het netwerk stromen: de cell state en de hidden state.
De cell state fungeert als langetermijnopslag. Hij draagt informatie over timesteps met gecontroleerde aanpassingen bij elke stap. Bij het verwerken van onze sequentie kan de cell state na "clouds" bijvoorbeeld "weergerelateerd zelfstandig naamwoord gezien" opslaan en die context behouden door "are", totdat "dark" het patroon versterkt.
De hidden state is de output van de cel bij elke timestep. Deze voedt de voorspellingen en de berekeningen van de volgende timestep. Na het verwerken van "dark" codeert de hidden state iets als "bijvoeglijk naamwoord dat weersomstandigheden beschrijft" dat een predictielaag kan gebruiken.
Waarom twee afzonderlijke sporen? Eén toestand zou tegelijk langetermijncontext moeten vasthouden én bruikbare outputs produceren. Die doelen botsen: stabiel geheugen vereist minimale verandering, terwijl bruikbare outputs transformatie vereisen. Door ze te scheiden kan elk spoor optimaliseren voor zijn doel.
Bij elke timestep neemt de cel drie inputs en produceert twee outputs:
Inputs:
Vorige cell state (
C_{t-1}): wat het netwerk tot nu toe onthoudtVorige hidden state (
h_{t-1}): de output van de vorige timestepHuidige input (
x_t): het nieuwe token, zoals "clouds" of "dark"Outputs:
Bijgewerkte cell state (
C_t): geheugen na verwerking van dit tokenNieuwe hidden state (
h_t): de output van deze timestepHoe LSTM-gates rekenen
Vier componenten bepalen wat er in de cel gebeurt. Drie daarvan zijn gates die waarden tussen 0 en 1 uitsturen en als filters werken. De vierde is de candidate, die nieuwe inhoud voorstelt.
Wanneer "are" arriveert na "clouds", heeft elke component een taak:
Forget gate (
f_t): Moeten we de "clouds"-context behouden? Weerszelfstandige naamwoorden gevolgd door koppelwerkwoorden zijn meestal relevant, dus de forget gate geeft waarden dicht bij 1 om het grootste deel van de cell state te behouden.Input gate (
i_t): Hoeveel moet "are" bijdragen aan het geheugen? Koppelwerkwoorden dragen minder voorspellend signaal, dus de input gate geeft lagere waarden.Candidate (
C̃_t): Welke nieuwe informatie kan "are" toevoegen? De candidate doet een voorstel, maar de input gate schaalt het omlaag.Output gate (
o_t): Wat moet de hidden state laten zien? De output gate filtert de cell state omh_tte produceren.Elke component berekent zijn output op dezelfde manier:
Concateneer
h_{t-1}enx_ttot één vectorVermenigvuldig met een geleerde gewichtsmatrix
Voeg een bias-term toe
Pas een activatiefunctie toe
Gates gebruiken sigmoid als activatie, die waarden in het 0-1-bereik perst. De candidate gebruikt tanh en geeft waarden tussen -1 en 1, zodat hij zowel verhogingen als verlagingen voor de cell state kan voorstellen.
De vergelijkingen
Forget gate (sigmoid → 0 tot 1):
Input gate (sigmoid → 0 tot 1):
Output gate (sigmoid → 0 tot 1):
Candidate (tanh → -1 tot +1):
Elke component heeft een eigen gewichtsmatrix (
W_f,W_i,W_c,W_o), zodat ze op verschillende patronen in de input leren reageren.Hoe filtering werkt
Gate-outputs vermenigvuldigen zich positie-voor-positie met vectoren (elementgewijze vermenigvuldiging, geschreven als
⊙). Elke dimensie wordt onafhankelijk gefilterd.Als een gate 0,9 uitstuur voor positie 1, behoudt die dimensie 90% van zijn waarde. Als hij 0,0 uitstuur voor positie 4, wordt die dimensie gewist. Bij het verwerken van "dark" na "clouds are" kan de forget gate hoge waarden uitsturen voor dimensies die zelfstandig naamwoord-/weercontext opslaan en lagere waarden voor dimensies met irrelevante eerdere context.
Informatiedoorstroming tussen forget-, add- en output-gates
De componenten combineren in drie stappen om de cell state bij te werken en de hidden state te produceren.
Stap 1: Forget
De forget gate filtert de vorige cell state:
Bij het verwerken van "dark" behoudt de forget gate de weercontext van "clouds" en wist hij dimensies die het lidwoord "The" volgden.
Stap 2: Add
De input gate schaalt het voorstel van de candidate en voegt het vervolgens toe aan de gefilterde cell state:
De candidate kan iets voorstellen als "bijvoeglijk naamwoord bij weer-zelfstandig naamwoord", en de input gate bepaalt hoe sterk dat in het geheugen wordt geschreven. Deze additieve update (niet multiplicatief) helpt gradiënten tijdens training doorstromen.
Stap 3: Output
De cell state gaat door tanh, waarna de output gate filtert wat de hidden state wordt:
De tanh houdt waarden begrensd. De output gate selecteert welke aspecten van het verzamelde geheugen zichtbaar moeten zijn voor voorspellingen op deze timestep.
De complete sequentie:
Hier is een vereenvoudigd spoor met een 4-dimensionale cell state:
Stap
Input
Cell state (vereenvoudigd)
Wat gebeurt er
1
"The"
[0.1, 0.0, 0.2, 0.0]
Minimale opslag; lidwoorden dragen weinig voorspellend signaal
2
"clouds"
[0.1, 0.7, 0.2, 0.3]
Input gate gaat open voor dimensies 2 en 4; slaat zelfstandig-naamwoordpatroon op
3
"are"
[0.1, 0.6, 0.2, 0.3]
Forget gate ≈ 0.9 behoudt context; koppelwerkwoord voegt weinig toe
4
"dark"
[0.1, 0.8, 0.5, 0.6]
Het bijvoeglijk naamwoord versterkt het signaal in dimensies 2, 3, 4
Tegen timestep 4 hebben dimensies 2-4 een patroon opgebouwd. De hidden state
h_4gaat naar een predictielaag (niet onderdeel van de LSTM), die woordenschatkansen produceert. Dat opgebouwde patroon helpt het model woorden als "rain" of "sky" te verkiezen.Het netwerk begrijpt geen weer. Het leert statistische correlaties: bepaalde inputsequenties gaan vaak vooraf aan bepaalde outputs.
Gradiëntdoorstroming tijdens training
De additieve structuur van de cell state biedt een directer gradiëntpad dan standaard RNN’s. Als je niet bekend bent met hoe gradiënten door neurale netwerken propagëren, behandelt de backpropagation-tutorial de basics.
In standaard RNN’s stromen gradiënten bij elke stap door gewichtsmatrices. Herhaalde vermenigvuldiging stapelt op: na 100 stappen met gewichten die gemiddeld 0,9 zijn, krimpt de gradiënt tot
0.9^100 ≈ 0.00003.LSTM’s bieden een alternatief pad langs de cell state. De afgeleide van
C_tten opzichte vanC_{t-1}:Dit is de waarde van de forget gate, geen gewichtsmatrix. Als forget gates in de buurt van 1 blijven, gaan gradiënten met minder verval door. Als gates gemiddeld 0,95 zijn, dan na 100 stappen:
0.95^100 ≈ 0.006. Nog steeds verminderd, maar bruikbaar.Aspect
RNN
LSTM
Gradiëntpad
Alleen gewichtsmatrices
Extra pad langs de cell state
100-staps gradiënt
~10^-5 (verdwenen)
~10^-2 tot 10^-1 (bruikbaar)
Praktisch geheugen
~10-20 timesteps
~50-200 timesteps
Praktische tip: biases van de forget gate worden vaak geïnitialiseerd op 1,0 of hoger, waardoor initiële outputs richting "alles onthouden" schuiven. Dit helpt het netwerk vroeg in de training langetermijnpatronen te leren.
LSTM’s mitigeren het verdwijnende-gradiëntprobleem in plaats van het te elimineren. De verbetering komt door de additieve update van de cell state, die een gradiëntsnelweg biedt naast de standaardpaden.
Hoe LSTM-modellen in Python te implementeren
De vorige sectie behandelde wat er in een LSTM-cel gebeurt: gates die informatie filteren, cell state die context bewaart en hidden state die outputs produceert. Nu zien we hoe PyTorch’s
nn.LSTMdat allemaal in één module verpakt en gebruikt om filmreviews als positief of negatief te classificeren.Tip om de code te draaien: trainen voor 50 epochs op een CPU duurt lang. Gebruik Google Colab of Kaggle Notebooks met GPU-versnelling voor snellere resultaten. Het complete script is beschikbaar als een GitHub Gist.
Een LSTM-classificator bouwen met PyTorch
Tekstclassificatie met LSTM’s volgt een patroon: zet woorden om in vectoren, laat de LSTM ze sequentieel verwerken en gebruik daarna de laatste output voor de voorspelling.
Woorden embedden in vectoren
Neurale netwerken werken met continue getallen, maar woorden zijn discrete symbolen. We kunnen "terrible" niet direct in matrixvermenigvuldiging voeren. De oplossing is een embedding-laag: een lookup-tabel waarin elk woord naar een leerbare vector wordt gemapt.
Als je
embedding_dim=32kiest, wordt elk woord een 32-dimensionale vector. Vroeg in de training zijn deze vectoren willekeurig. Terwijl het model leert, komen woorden met vergelijkbaar sentiment dichter bij elkaar te liggen in deze vectorruimte. "Terrible", "awful" en "boring" kunnen bij elkaar eindigen, terwijl "excellent" en "amazing" elders clusteren.Het argument
padding_idxvertelt de laag om padding-tokens op nulvectoren te houden, zodat ze het model niet beïnvloeden.De LSTM-architectuur definiëren
Deze ene regel creëert de volledige gate-machinerie uit de vorige sectie. De
hidden_sizebepaalt hoeveel informatie de cell state en hidden state kunnen dragen. Grotere waarden laten het model meer patronen volgen, maar vragen meer rekenwerk.Met
batch_first=Truehebben inputs de vorm(batch, sequence_length, embedding_dim). De LSTM verwerkt elke positie van links naar rechts en werkt bij elke stap zijn interne toestanden bij.De sequentie verwerken
Na het verwerken van "This movie was absolutely terrible" heeft de LSTM elk woord gezien. De laatste hidden state heeft vastgelegd wat het model uit die sequentie heeft geleerd. Voor sentiment kan dit iets coderen als "sterk negatief bijvoeglijk naamwoord aan het eind, dat eerdere neutrale inhoud neutraliseert".
De LSTM geeft zowel outputs per timestep (
lstm_out) als eindtoestanden (h_n,c_n). Voor classificatie hebben we alleenh_n[-1]nodig, de hidden state na het laatste token. Een lineaire laag zet dit vervolgens om naar klassescores.Een review door het model volgen
Wanneer "This movie was terrible" de classifier binnenkomt:
Elk woord krijgt zijn embedding: vier vectoren met vorm
(32,)De LSTM verwerkt ze sequentieel. "This" initialiseert wat cell state. "Movie" werkt die iets bij. "Was" is neutraal en verandert weinig. "Terrible" triggert de forget gate om irrelevante context te wissen en de input gate om een sterk negatief signaal te schrijven.
De laatste hidden state vat het opgebouwde patroon samen.
De lineaire laag zet dit om naar twee scores: één voor positief, één voor negatief
Het model begrijpt geen taal. Het leert statistische patronen: bepaalde woordsequenties correleren met bepaalde labels.
De volledige modelklasse
Hieronder zie je de volledige klasse
LSTMClassifierdie we zojuist stap voor stap hebben opgebouwd:De bidirectionele optie draait twee LSTM’s: één van links naar rechts en één van rechts naar links. Hun laatste hidden states worden geconcateneerd, zodat het model context uit beide richtingen heeft. Dit helpt wanneer betekenis afhangt van wat erna komt, zoals "not bad", waar "not" pas zinvol is gegeven "bad".
Tekst voorbereiden voor het model
LSTM’s hebben integersequenties nodig, geen strings. Preprocessing zet onbewerkte tekst om via drie stappen:
We reserveren index 0 voor padding en index 1 voor onbekende woorden. De vocabulaire beperken filtert zeldzame woorden waarvoor te weinig voorbeelden zijn om zinvolle embeddings te leren.
Afkappen op
max_lengthbeperkt hoe ver terug de LSTM moet onthouden. Langere sequenties behouden meer context maar verhogen de reken- en geheugenkosten. Voor filmreviews vangen 100-200 tokens meestal de belangrijkste sentimentsignalen.Padding zorgt ervoor dat elke sequentie dezelfde lengte heeft, zodat PyTorch ze kan batchen in één tensor. De padding-tokens worden na embedding nulvectoren en dragen weinig bij aan de eindtoestand.
De vocabulaire uitsluitend op trainingsdata bouwen voorkomt informatielekken. Als een zeldzaam woord alleen in testreviews voorkomt, moet het model het als onbekend behandelen in plaats van de embedding ervan al tijdens training geleerd te hebben.
LSTM-training en hyperparameters
De trainingslus werkt de gewichten van het model bij op basis van hoe ver de voorspellingen van de juiste waarden afliggen. Voor LSTM’s is één detail extra belangrijk: gradient clipping.
Waarom gradient clipping belangrijk is voor LSTM’s
We legden eerder uit hoe LSTM’s verdwijnende gradiënten mitigeren via additieve updates van de cell state. Maar exploderende gradiënten blijven een risico. Wanneer gradiënten te groot worden, schieten gewichtsupdates door en raakt de training onstabiel.
clip_grad_norm_begrenst de totale gradiëntgrootte. Als gradiëntenmax_norm=5.0zouden overschrijden, worden ze proportioneel omlaag geschaald. Dit houdt updates stabiel zonder het gradiëntsignaal volledig te verwijderen.model.eval()schakelt dropout uit tijdens evaluatie.torch.no_grad()slaat gradiëntberekening over omdat we geen gewichten bijwerken; dit bespaart geheugen en versnelt inferentie.Hyperparameters kiezen
Deze waarden weerspiegelen trade-offs:
hidden_size=32: Klein genoeg voor snelle iteratie. Productiemodellen gebruiken vaak 128-512, maar naarmate de grootte toeneemt, nemen de opbrengsten af en vertraagt training.embedding_dim=32: Gelijk aan hidden size is gebruikelijk maar niet vereist. Grotere embeddings vangen fijnere woordonderscheidingen tegen de prijs van meer parameters.max_length=100: De meeste sentimentsignalen verschijnen vroeg in reviews. Langere sequenties helpen bij nuance, maar verviervoudigen het geheugengebruik als jemax_lengthverdubbelt.batch_size=64: Grotere batches geven stabielere gradiëntschattingen. Kleinere batches kunnen helpen lokale minima te verlaten. 32-128 is typisch.lr=0.001: De standaard van Adam werkt voor de meeste LSTM-taken. Als de loss oscilleert, probeer 0,0001. Als het leren stokt, probeer 0,01.n_epochs=50: Genoeg voor het model om te convergeren. Let op het moment waarop de testnauwkeurigheid stabiliseert of daalt terwijl de trainnauwkeurigheid blijft stijgen; dat duidt op overfitting.Wat het model leert
Het draaien van deze configuratie levert output op zoals:
De trainingsnauwkeurigheid bereikt ongeveer 82% terwijl de testnauwkeurigheid rond de 80% uitkomt. Het model leert welke woordpatronen sentiment voorspellen. Sterke bijvoeglijke naamwoorden aan het eind wegen zwaar. Negaties zoals "not" draaien polariteit om. Bepaalde zinnen ("waste of time", "highly recommend") worden sterke signalen. Niets hiervan is geprogrammeerd; het komt voort uit correlaties in de trainingsdata.
Met minimale tuning en een klein model is 80% nauwkeurigheid redelijk. De kleine kloof tussen training en test laat zien dat het model goed generaliseert zonder ernstige overfitting. Door dropout toe te voegen, pretrained embeddings zoals GloVe te gebruiken of op meer data te trainen, kun je de nauwkeurigheid richting de hoge 80% duwen.
De prestaties van LSTM-modellen verbeteren
De basis-LSTM-architectuur uit de vorige secties werkt, maar verschillende technieken kunnen de nauwkeurigheid verhogen zonder over te stappen op een andere modelfamilie. Deze aanpakken zijn gevalideerd in duizenden productietoepassingen en onderzoekspapers.
Bidirectionele LSTM’s
Standaard LSTM’s verwerken sequenties van links naar rechts en bouwen alleen context op uit het verleden. Bidirectionele LSTM’s (BiLSTM’s) draaien twee parallelle passes: één vooruit, één achteruit. De uiteindelijke representatie op elke timestep combineert wat ervoor en erna kwam.
Voor sentimentanalyse is dit belangrijk wanneer een negatie na het woord verschijnt dat het beïnvloedt: "the movie was not bad" heeft forward-context nodig om te weten dat "bad" er is, en backward-context om te weten dat "not" eraan voorafgaat.
BiLSTM’s presteren consistent beter dan unidirectionele modellen bij named entity recognition (NER), sentimentclassificatie en spraakherkenning, met typische winst van 5-15%.
De trade-off: je hebt de volledige sequentie nodig op inferentietijd. Streamingtoepassingen die tokens één voor één ontvangen, kunnen geen bidirectionele verwerking gebruiken.
Attention toevoegen
Attention laat het model wegen welke timesteps het belangrijkst zijn voor een bepaalde voorspelling. In plaats van een hele sequentie in één laatste hidden state te persen, berekent attention een gewogen combinatie van alle hidden states.
In een review als "The acting was wooden, the plot made no sense, but the cinematography was stunning" leert attention zich te richten op zinsdelen met sentiment in plaats van alle woorden gelijk te behandelen. Het model kent hogere gewichten toe aan "wooden", "no sense" en "stunning" en verlaagt de weging van "the" en "was".
Attention voegt minimale rekentijd toe en levert vaak 2-5% nauwkeurigheidswinst op classificatietaken. Voor sequence-to-sequence-problemen zoals vertaling is attention tussen encoder en decoder vrijwel noodzakelijk voor goede prestaties.
LSTM-lagen stapelen
Eén LSTM-laag vangt patronen op één abstractieniveau. Door meerdere lagen te stapelen leert het netwerk hiërarchische features: de eerste laag detecteert lokale zinsdeelpatronen, hogere lagen combineren die tot begrip op documentniveau.
Richtlijnen voor diepte:
Elke extra laag verdubbelt grofweg de trainingstijd. Begin ondiep en voeg diepte alleen toe als validatiemetrics op een plateau komen.
Regularisatietechnieken
LSTM’s overfitten gemakkelijk, vooral op kleine datasets. Drie regularisatietechnieken werken goed samen:
Dropout
Dropout zet willekeurig neuronen op nul tijdens training, waardoor het netwerk gedwongen wordt het leren over meerdere paden te verdelen. Pas dropout toe tussen LSTM-lagen, niet binnen de recurrente verbindingen (die de temporele leerprocessen kunnen verstoren).
Dropoutrates van 0,2-0,5 werken voor de meeste taken. Hogere waarden helpen bij ernstige overfitting maar vertragen de convergentie.
Gradient clipping
Gradient clipping begrenst de grootte van gradiënten tijdens backpropagation. Hoewel LSTM’s verdwijnende gradiënten mitigeren, komen exploderende gradiënten nog steeds voor, vooral bij lange sequenties of diepe netwerken.
Waarden tussen 1,0 en 5,0 zijn gebruikelijk. Als de training loss piekt of
NaN-waarden oplevert, verlaag dan de drempel.Early stopping
Early stopping bewaakt de validatie-loss en stopt de training wanneer die niet meer verbetert. Zo voorkom je dat het model trainingsdata uit het hoofd leert ten koste van generalisatie.
Voor een volledige walkthrough van trainingslussen, evaluatiemetrics en het debuggen van veelvoorkomende LSTM-problemen, zie de NLP met PyTorch-gids.
Pretrained embeddings
Willekeurige initialisatie van embeddings werkt, maar pretrained embeddings zoals GloVe of Word2Vec geven het model een voorsprong. Deze embeddings coderen semantische relaties die zijn geleerd uit miljarden woorden: "king" en "queen" eindigen dicht bij elkaar, net als "terrible" en "awful".
Pretrained embeddings helpen vooral wanneer trainingsdata beperkt is. Met miljoenen voorbeelden halen willekeurig geïnitialiseerde embeddings het uiteindelijk in. Voor de meeste praktische toepassingen met 10k-100k samples kun je 3-8% nauwkeurigheidswinst verwachten van pretrained initialisatie.
Technieken combineren
Deze aanpakken stapelen. Een tekstclassificator op productieniveau kan gebruiken:
Elke toevoeging verhoogt de complexiteit en trainingstijd. Begin met een baseline LSTM met één laag, meet prestaties en voeg vervolgens technieken één voor één toe om te zien wat winst oplevert voor jouw specifieke taak.
LSTM vs Transformers: de juiste architectuur kiezen
LSTM’s voerden de boventoon in sequentiemodellering van eind jaren 90 tot midden jaren 2010. Het paper uit 2017, "Attention Is All You Need", veranderde dat. Transformers drijven nu de meeste large language models aan, maar LSTM’s zijn niet verdwenen. Ze vullen verschillende niches.
Beperkingen van LSTM’s
Eerdere secties behandelden hoe LSTM’s sequenties één token per keer verwerken en waarom dat gatingmechanisme werkt. Op schaal wordt die sequentiële afhankelijkheid een bottleneck. Je GPU kan duizenden cores hebben, maar de meeste zijn ongebruikt terwijl de hidden-stateketen klaar is. Een input van 500 tokens betekent 500 bewerkingen op rij.
Transformers hebben deze beperking niet. Self-attention berekent relaties tussen alle posities tegelijk, zodat dezelfde sequentie van 500 tokens parallel wordt verwerkt. Op moderne hardware met grote batchgroottes vertaalt dit zich naar 10-100x snellere training.
De limiet voor sequentielengte is ook van belang. We zagen hoe LSTM’s gradiënten beter behouden dan ‘vanilla’ RNN’s, maar "beter" betekent niet "perfect". De prestaties dalen nog steeds ergens rond 200-500 tokens, afhankelijk van de taak.
Transformers berekenen aandacht tussen elk paar posities direct, dus token 1 kan even gemakkelijk aandacht geven aan token 1000. De trade-off is geheugen: attentiematrices groeien kwadratisch met de sequentielengte.
Gate-activaties maken interpretatie ook lastig. Waarden lopen van 0 tot 1 zonder duidelijke grenzen. Attentiongewichten in Transformers zijn ook geen perfecte verklaringen, maar geven een duidelijker signaal over welke inputtokens voor elke output van belang waren.
Wanneer LSTM of Transformers gebruiken
Transformers zijn logisch wanneer:
LSTM’s passen nog steeds wanneer:
Tijdreeksvoorspelling zit ergens in het midden. Recente benchmarks laten zien dat beide architecturen vergelijkbaar presteren op univariate data met consistente patronen. Transformers lopen voorop wanneer meerdere features op complexe manieren interacteren, maar LSTM’s houden stand wanneer data schaars is of rekenkracht krap.
Hybride ontwerpen mengen beide benaderingen. Sommige architecturen stapelen LSTM-lagen met attentionmechanismen. xLSTM, gepubliceerd in 2024, herbekijkt het oorspronkelijke ontwerp met exponentiële gating en matrixgeheugen en laat concurrerende resultaten zien met Transformers op kleinere schales.
Aspect
LSTM
Transformer
Verwerking
Sequentieel (token voor token)
Parallel (alle posities tegelijk)
Trainingssnelheid
Langzamer (beperkte parallelisatie)
Sneller op GPU’s
Langetermijnafhankelijkheden
Degradeert voorbij 200-500 tokens
Handelt duizenden tokens af
Geheugenschaal
O(n) met sequentielengte
O(n²) voor attentiematrices
Prestaties met weinig data
Goed (minder parameters)
Vatbaar voor overfitting
Edge-deployment
Geschikt (lager geheugengebruik)
Uitdagend (grote modellen)
Streaming/realtime
Native ondersteuning
Vereist volledige sequentie
Pretrained-ecosysteem
Beperkt
Uitgebreid (BERT, GPT, enz.)
De keuze komt meestal neer op randvoorwaarden. Onbeperkte compute en data? Transformers winnen op de meeste NLP-taken. Een microcontroller met 10K trainingsvoorbeelden? LSTM’s blijven praktisch.
Conclusie
LSTM’s losten een echt probleem op toen ze in 1997 verschenen. Het gatingmechanisme van Hochreiter en Schmidhuber stelde netwerken eindelijk in staat te leren van sequenties langer dan enkele tientallen tokens. Die architectuur dreef bijna twee decennia lang machinevertaling, spraakherkenning en tijdreeksvoorspellingen aan.
Transformers pakken nu de meeste grootschalige NLP-taken op, maar LSTM’s zijn niet verouderd. Ze zijn nog steeds logisch voor edge-deployment, streamingtoepassingen en situaties met beperkte trainingsdata.
Als je aan een nieuw sequentiemodelleringsproject begint, probeer dan eerst een eenvoudige LSTM. Meet de prestaties, begrijp de faalmodi en beslis dan of het probleem echt een Transformer vereist. De concepten uit deze gids (gating, cell state, gradiëntdoorstroming) helpen direct bij het begrijpen van complexere architecturen.
Voor hands-on oefening raad ik onze cursus Recurrent Neural Networks aan, die aanvullende LSTM-toepassingen met begeleide oefeningen doorloopt.