AI-For-Beginners/translations/nl/lessons/5-NLP/16-RNN/RNNPyTorch.ipynb

479 lines
24 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Recurrent neurale netwerken\n",
"\n",
"In de vorige module hebben we gebruik gemaakt van rijke semantische representaties van tekst en een eenvoudige lineaire classifier bovenop de embeddings. Wat deze architectuur doet, is de geaggregeerde betekenis van woorden in een zin vastleggen, maar het houdt geen rekening met de **volgorde** van woorden, omdat de aggregatiebewerking bovenop de embeddings deze informatie uit de oorspronkelijke tekst verwijdert. Omdat deze modellen de volgorde van woorden niet kunnen modelleren, kunnen ze geen complexere of dubbelzinnige taken oplossen, zoals tekstgeneratie of vraag-antwoord-systemen.\n",
"\n",
"Om de betekenis van een tekstvolgorde vast te leggen, moeten we een andere neurale netwerkarchitectuur gebruiken, die een **recurrent neurale netwerk**, of RNN, wordt genoemd. In een RNN laten we onze zin één symbool tegelijk door het netwerk gaan, en het netwerk produceert een bepaalde **toestand**, die we vervolgens samen met het volgende symbool opnieuw aan het netwerk doorgeven.\n",
"\n",
"Gegeven de invoervolgorde van tokens $X_0,\\dots,X_n$, creëert een RNN een reeks neurale netwerkblokken en traint deze reeks end-to-end met behulp van backpropagation. Elk netwerkblok neemt een paar $(X_i,S_i)$ als invoer en produceert $S_{i+1}$ als resultaat. De eindtoestand $S_n$ of uitvoer $X_n$ gaat naar een lineaire classifier om het resultaat te produceren. Alle netwerkblokken delen dezelfde gewichten en worden end-to-end getraind met één backpropagation-pass.\n",
"\n",
"Omdat toestandsvectoren $S_0,\\dots,S_n$ door het netwerk worden doorgegeven, kan het de sequentiële afhankelijkheden tussen woorden leren. Bijvoorbeeld, wanneer het woord *niet* ergens in de volgorde voorkomt, kan het leren om bepaalde elementen binnen de toestandsvector te ontkennen, wat resulteert in negatie.\n",
"\n",
"> Aangezien de gewichten van alle RNN-blokken in de afbeelding worden gedeeld, kan dezelfde afbeelding worden weergegeven als één blok (rechts) met een recurrente feedbacklus, die de uitvoertoestand van het netwerk teruggeeft aan de invoer.\n",
"\n",
"Laten we eens kijken hoe recurrente neurale netwerken ons kunnen helpen om onze nieuwsdataset te classificeren.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"from torchnlp import *\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_size = len(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Eenvoudige RNN-classificator\n",
"\n",
"Bij een eenvoudige RNN is elke recurrente eenheid een simpel lineair netwerk dat een gecombineerd invoervector en toestandsvector ontvangt en een nieuwe toestandsvector produceert. PyTorch vertegenwoordigt deze eenheid met de `RNNCell`-klasse, en een netwerk van dergelijke cellen - als de `RNN`-laag.\n",
"\n",
"Om een RNN-classificator te definiëren, passen we eerst een embedding-laag toe om de dimensionaliteit van de invoerwoordenschat te verlagen, en plaatsen we vervolgens een RNN-laag erboven:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class RNNClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,h = self.rnn(x)\n",
" return self.fc(x.mean(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Opmerking:** We gebruiken hier een niet-getrainde embeddinglaag voor eenvoud, maar voor nog betere resultaten kunnen we een voorgetrainde embeddinglaag gebruiken met Word2Vec- of GloVe-embeddings, zoals beschreven in de vorige eenheid. Voor een beter begrip kun je deze code aanpassen om te werken met voorgetrainde embeddings.\n",
"\n",
"In ons geval zullen we een data loader met padding gebruiken, zodat elke batch een aantal gepadde sequenties van dezelfde lengte bevat. De RNN-laag neemt de sequentie van embedding-tensoren en produceert twee outputs:\n",
"* $x$ is een sequentie van RNN-celuitgangen bij elke stap\n",
"* $h$ is een eindtoestand voor het laatste element van de sequentie\n",
"\n",
"Daarna passen we een volledig verbonden lineaire classifier toe om het aantal klassen te bepalen.\n",
"\n",
"> **Opmerking:** RNN's zijn vrij moeilijk te trainen, omdat wanneer de RNN-cellen worden uitgerold over de lengte van de sequentie, het resulterende aantal lagen dat betrokken is bij backpropagation vrij groot is. Daarom moeten we een kleine leersnelheid selecteren en het netwerk trainen op een grotere dataset om goede resultaten te behalen. Dit kan behoorlijk lang duren, dus het gebruik van een GPU heeft de voorkeur.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.3090625\n",
"6400: acc=0.38921875\n",
"9600: acc=0.4590625\n",
"12800: acc=0.511953125\n",
"16000: acc=0.5506875\n",
"19200: acc=0.57921875\n",
"22400: acc=0.6070089285714285\n",
"25600: acc=0.6304296875\n",
"28800: acc=0.6484027777777778\n",
"32000: acc=0.66509375\n",
"35200: acc=0.6790056818181818\n",
"38400: acc=0.6929166666666666\n",
"41600: acc=0.7035817307692308\n",
"44800: acc=0.7137276785714286\n",
"48000: acc=0.72225\n",
"51200: acc=0.73001953125\n",
"54400: acc=0.7372794117647059\n",
"57600: acc=0.7436631944444444\n",
"60800: acc=0.7503947368421052\n",
"64000: acc=0.75634375\n",
"67200: acc=0.7615773809523809\n",
"70400: acc=0.7662642045454545\n",
"73600: acc=0.7708423913043478\n",
"76800: acc=0.7751822916666666\n",
"80000: acc=0.7790625\n",
"83200: acc=0.7825\n",
"86400: acc=0.7858564814814815\n",
"89600: acc=0.7890513392857142\n",
"92800: acc=0.7920474137931034\n",
"96000: acc=0.7952708333333334\n",
"99200: acc=0.7982258064516129\n",
"102400: acc=0.80099609375\n",
"105600: acc=0.8037594696969697\n",
"108800: acc=0.8060569852941176\n"
]
}
],
"source": [
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Long Short Term Memory (LSTM)\n",
"\n",
"Een van de belangrijkste problemen van klassieke RNNs is het zogenaamde **probleem van verdwijnende gradiënten**. Omdat RNNs end-to-end worden getraind in één backpropagatiepass, is het moeilijk om fouten door te geven aan de eerste lagen van het netwerk, waardoor het netwerk geen relaties kan leren tussen verre tokens. Een van de manieren om dit probleem te vermijden is door **expliciet toestandsbeheer** te introduceren met behulp van zogenaamde **poorten**. Er zijn twee bekendste architecturen van dit type: **Long Short Term Memory** (LSTM) en **Gated Relay Unit** (GRU).\n",
"\n",
"![Afbeelding die een voorbeeld van een long short term memory cel toont](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"Het LSTM-netwerk is georganiseerd op een manier die lijkt op RNN, maar er zijn twee toestanden die van laag naar laag worden doorgegeven: de actuele toestand $c$, en de verborgen vector $h$. Bij elke eenheid wordt de verborgen vector $h_i$ gecombineerd met de invoer $x_i$, en zij bepalen wat er met de toestand $c gebeurt via **poorten**. Elke poort is een neuraal netwerk met sigmoidactivatie (uitvoer in het bereik $[0,1]$), wat kan worden gezien als een bitmasker wanneer vermenigvuldigd met de toestandsvector. Er zijn de volgende poorten (van links naar rechts op de afbeelding hierboven):\n",
"* **vergeetpoort** neemt de verborgen vector en bepaalt welke componenten van de vector $c$ we moeten vergeten en welke we moeten doorgeven.\n",
"* **invoerpoort** neemt wat informatie van de invoer en verborgen vector, en voegt deze toe aan de toestand.\n",
"* **uitvoerpoort** transformeert de toestand via een lineaire laag met $\\tanh$-activatie, en selecteert vervolgens enkele van zijn componenten met behulp van de verborgen vector $h_i$ om een nieuwe toestand $c_{i+1}$ te produceren.\n",
"\n",
"Componenten van de toestand $c$ kunnen worden gezien als vlaggen die aan- en uitgezet kunnen worden. Bijvoorbeeld, wanneer we een naam *Alice* tegenkomen in de reeks, willen we misschien aannemen dat het verwijst naar een vrouwelijk personage, en de vlag in de toestand verhogen dat we een vrouwelijk zelfstandig naamwoord in de zin hebben. Wanneer we vervolgens de zinsnede *en Tom* tegenkomen, zullen we de vlag verhogen dat we een meervoudig zelfstandig naamwoord hebben. Door de toestand te manipuleren kunnen we dus veronderstellen dat we grammaticale eigenschappen van zinsdelen bijhouden.\n",
"\n",
"> **Note**: Een geweldige bron om de interne werking van LSTM te begrijpen is dit uitstekende artikel [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) van Christopher Olah.\n",
"\n",
"Hoewel de interne structuur van een LSTM-cel complex kan lijken, verbergt PyTorch deze implementatie in de `LSTMCell`-klasse, en biedt het een `LSTM`-object om de hele LSTM-laag te vertegenwoordigen. Daarom zal de implementatie van een LSTM-classificator erg lijken op de eenvoudige RNN die we hierboven hebben gezien:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"class LSTMClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,(h,c) = self.rnn(x)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.259375\n",
"6400: acc=0.25859375\n",
"9600: acc=0.26177083333333334\n",
"12800: acc=0.2784375\n",
"16000: acc=0.313\n",
"19200: acc=0.3528645833333333\n",
"22400: acc=0.3965625\n",
"25600: acc=0.4385546875\n",
"28800: acc=0.4752777777777778\n",
"32000: acc=0.505375\n",
"35200: acc=0.5326704545454546\n",
"38400: acc=0.5557552083333334\n",
"41600: acc=0.5760817307692307\n",
"44800: acc=0.5954910714285714\n",
"48000: acc=0.6118333333333333\n",
"51200: acc=0.62681640625\n",
"54400: acc=0.6404779411764706\n",
"57600: acc=0.6520138888888889\n",
"60800: acc=0.662828947368421\n",
"64000: acc=0.673546875\n",
"67200: acc=0.6831547619047619\n",
"70400: acc=0.6917897727272727\n",
"73600: acc=0.6997146739130434\n",
"76800: acc=0.707109375\n",
"80000: acc=0.714075\n",
"83200: acc=0.7209134615384616\n",
"86400: acc=0.727037037037037\n",
"89600: acc=0.7326674107142858\n",
"92800: acc=0.7379633620689655\n",
"96000: acc=0.7433645833333333\n",
"99200: acc=0.7479032258064516\n",
"102400: acc=0.752119140625\n",
"105600: acc=0.7562405303030303\n",
"108800: acc=0.76015625\n",
"112000: acc=0.7641339285714286\n",
"115200: acc=0.7677777777777778\n",
"118400: acc=0.7711233108108108\n"
]
},
{
"data": {
"text/plain": [
"(0.03487814127604167, 0.7728)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Verpakte sequenties\n",
"\n",
"In ons voorbeeld moesten we alle sequenties in de minibatch aanvullen met nullen. Hoewel dit leidt tot enig geheugenverlies, is het bij RNN's nog kritischer dat extra RNN-cellen worden gecreëerd voor de aangevulde invoeritems, die deelnemen aan de training maar geen belangrijke invoerinformatie bevatten. Het zou veel beter zijn om de RNN alleen te trainen op de daadwerkelijke sequentielengte.\n",
"\n",
"Om dat te doen, wordt een speciaal formaat voor het opslaan van aangevulde sequenties geïntroduceerd in PyTorch. Stel dat we een aangevulde minibatch hebben die er als volgt uitziet:\n",
"```\n",
"[[1,2,3,4,5],\n",
" [6,7,8,0,0],\n",
" [9,0,0,0,0]]\n",
"```\n",
"Hier stelt 0 de aangevulde waarden voor, en de daadwerkelijke lengtevector van de invoersequenties is `[5,3,1]`.\n",
"\n",
"Om een RNN effectief te trainen met aangevulde sequenties, willen we beginnen met het trainen van de eerste groep RNN-cellen met een grote minibatch (`[1,6,9]`), maar vervolgens stoppen met het verwerken van de derde sequentie en doorgaan met trainen met kleinere minibatches (`[2,7]`, `[3,8]`), enzovoort. Een verpakte sequentie wordt dus weergegeven als één vector - in ons geval `[1,6,9,2,7,3,8,4,5]`, en een lengtevector (`[5,3,1]`), waarmee we eenvoudig de oorspronkelijke aangevulde minibatch kunnen reconstrueren.\n",
"\n",
"Om een verpakte sequentie te produceren, kunnen we de functie `torch.nn.utils.rnn.pack_padded_sequence` gebruiken. Alle recurrente lagen, waaronder RNN, LSTM en GRU, ondersteunen verpakte sequenties als invoer en produceren verpakte uitvoer, die kan worden gedecodeerd met behulp van `torch.nn.utils.rnn.pad_packed_sequence`.\n",
"\n",
"Om een verpakte sequentie te kunnen produceren, moeten we de lengtevector doorgeven aan het netwerk, en daarom hebben we een andere functie nodig om minibatches voor te bereiden:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def pad_length(b):\n",
" # build vectorized sequence\n",
" v = [encode(x[1]) for x in b]\n",
" # compute max length of a sequence in this minibatch and length sequence itself\n",
" len_seq = list(map(len,v))\n",
" l = max(len_seq)\n",
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
" torch.tensor(len_seq)\n",
" )\n",
"\n",
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Het daadwerkelijke netwerk zou erg lijken op `LSTMClassifier` hierboven, maar de `forward`-pass ontvangt zowel de opgevulde minibatch als de vector van sequentielengtes. Na het berekenen van de embedding, berekenen we de verpakte sequentie, geven deze door aan de LSTM-laag, en pakken vervolgens het resultaat weer uit.\n",
"\n",
"> **Opmerking**: We gebruiken het uitgepakte resultaat `x` eigenlijk niet, omdat we de output van de verborgen lagen gebruiken in de volgende berekeningen. Daarom kunnen we het uitpakken volledig verwijderen uit deze code. De reden dat we het hier plaatsen, is zodat je deze code gemakkelijk kunt aanpassen, mocht je de netwerkoutput nodig hebben in verdere berekeningen.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"class LSTMPackClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x, lengths):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
" pad_x,(h,c) = self.rnn(pad_x)\n",
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.285625\n",
"6400: acc=0.33359375\n",
"9600: acc=0.3876041666666667\n",
"12800: acc=0.44078125\n",
"16000: acc=0.4825\n",
"19200: acc=0.5235416666666667\n",
"22400: acc=0.5559821428571429\n",
"25600: acc=0.58609375\n",
"28800: acc=0.6116666666666667\n",
"32000: acc=0.63340625\n",
"35200: acc=0.6525284090909091\n",
"38400: acc=0.668515625\n",
"41600: acc=0.6822596153846154\n",
"44800: acc=0.6948214285714286\n",
"48000: acc=0.7052708333333333\n",
"51200: acc=0.71521484375\n",
"54400: acc=0.7239889705882353\n",
"57600: acc=0.7315277777777778\n",
"60800: acc=0.7388486842105263\n",
"64000: acc=0.74571875\n",
"67200: acc=0.7518303571428572\n",
"70400: acc=0.7576988636363636\n",
"73600: acc=0.7628940217391305\n",
"76800: acc=0.7681510416666667\n",
"80000: acc=0.7728125\n",
"83200: acc=0.7772235576923077\n",
"86400: acc=0.7815393518518519\n",
"89600: acc=0.7857700892857142\n",
"92800: acc=0.7895043103448276\n",
"96000: acc=0.7930520833333333\n",
"99200: acc=0.7959072580645161\n",
"102400: acc=0.798994140625\n",
"105600: acc=0.802064393939394\n",
"108800: acc=0.8051378676470589\n",
"112000: acc=0.8077857142857143\n",
"115200: acc=0.8104600694444445\n",
"118400: acc=0.8128293918918919\n"
]
},
{
"data": {
"text/plain": [
"(0.029785829671223958, 0.8138166666666666)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Opmerking:** Je hebt misschien gemerkt dat we de parameter `use_pack_sequence` doorgeven aan de trainingsfunctie. Momenteel vereist de functie `pack_padded_sequence` dat de lengte-sequentie-tensor zich op het CPU-apparaat bevindt, en daarom moet de trainingsfunctie voorkomen dat de lengte-sequentiegegevens naar de GPU worden verplaatst tijdens het trainen. Je kunt de implementatie van de functie `train_emb` bekijken in het bestand [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bidirectionele en meerlaagse RNN's\n",
"\n",
"In onze voorbeelden werkten alle recurrente netwerken in één richting, van het begin van een reeks naar het einde. Dat lijkt logisch, omdat het lijkt op hoe we lezen en luisteren naar spraak. Maar omdat we in veel praktische gevallen willekeurige toegang hebben tot de invoerreeks, kan het zinvol zijn om de recurrente berekening in beide richtingen uit te voeren. Dergelijke netwerken worden **bidirectionele** RNN's genoemd, en je kunt ze maken door de parameter `bidirectional=True` door te geven aan de constructor van RNN/LSTM/GRU.\n",
"\n",
"Bij het werken met een bidirectioneel netwerk hebben we twee verborgen toestandsvectoren nodig, één voor elke richting. PyTorch codeert deze vectoren als één vector met een dubbele grootte, wat erg handig is, omdat je de resulterende verborgen toestand meestal doorgeeft aan een volledig verbonden lineaire laag. Je hoeft dan alleen rekening te houden met deze toename in grootte bij het maken van de laag.\n",
"\n",
"Een recurrent netwerk, of het nu eendirectioneel of bidirectioneel is, legt bepaalde patronen binnen een reeks vast en kan deze opslaan in de toestandsvector of doorgeven aan de uitvoer. Net als bij convolutionele netwerken kunnen we een andere recurrente laag bovenop de eerste bouwen om patronen van een hoger niveau vast te leggen, opgebouwd uit de laag-niveau patronen die door de eerste laag zijn geëxtraheerd. Dit brengt ons bij het concept van een **meerlaagse RNN**, die bestaat uit twee of meer recurrente netwerken, waarbij de uitvoer van de vorige laag wordt doorgegeven aan de volgende laag als invoer.\n",
"\n",
"![Afbeelding van een meerlaagse long-short-term-memory-RNN](../../../../../translated_images/nl/multi-layer-lstm.dd975e29bb2a59fe.webp)\n",
"\n",
"*Afbeelding afkomstig uit [deze geweldige post](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) van Fernando López*\n",
"\n",
"PyTorch maakt het bouwen van dergelijke netwerken eenvoudig, omdat je alleen de parameter `num_layers` hoeft door te geven aan de constructor van RNN/LSTM/GRU om automatisch meerdere lagen van herhaling te creëren. Dit betekent ook dat de grootte van de verborgen/toestandsvector evenredig toeneemt, en je moet hiermee rekening houden bij het verwerken van de uitvoer van de recurrente lagen.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN's voor andere taken\n",
"\n",
"In deze unit hebben we gezien dat RNN's kunnen worden gebruikt voor sequentieclassificatie, maar ze kunnen in feite nog veel meer taken aan, zoals tekstgeneratie, machinale vertaling en meer. We zullen deze taken in de volgende unit behandelen.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Disclaimer**: \nDit document is vertaald met behulp van de AI-vertalingsservice [Co-op Translator](https://github.com/Azure/co-op-translator). Hoewel we streven naar nauwkeurigheid, dient u zich ervan bewust te zijn dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in de oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor cruciale informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor eventuele misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
"translation_date": "2025-08-28T21:50:04+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "nl"
}
},
"nbformat": 4,
"nbformat_minor": 2
}