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

479 lines
24 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Rekurrenta neurala nätverk\n",
"\n",
"I den föregående modulen har vi använt rika semantiska representationer av text och en enkel linjär klassificerare ovanpå embeddingarna. Vad denna arkitektur gör är att fånga den aggregerade betydelsen av ord i en mening, men den tar inte hänsyn till **ordningen** av orden, eftersom aggregeringsoperationen ovanpå embeddingarna tar bort denna information från den ursprungliga texten. Eftersom dessa modeller inte kan modellera ordningen av ord, kan de inte lösa mer komplexa eller tvetydiga uppgifter som textgenerering eller frågesvar.\n",
"\n",
"För att fånga betydelsen av en textsekvens behöver vi använda en annan neural nätverksarkitektur, som kallas för ett **rekurrent neuralt nätverk**, eller RNN. I RNN skickar vi vår mening genom nätverket en symbol i taget, och nätverket producerar ett **tillstånd**, som vi sedan skickar tillbaka till nätverket tillsammans med nästa symbol.\n",
"\n",
"Givet den inmatade sekvensen av tokens $X_0,\\dots,X_n$, skapar RNN en sekvens av neurala nätverksblock och tränar denna sekvens från början till slut med hjälp av backpropagering. Varje nätverksblock tar ett par $(X_i,S_i)$ som indata och producerar $S_{i+1}$ som resultat. Slutligt tillstånd $S_n$ eller utdata $X_n$ skickas till en linjär klassificerare för att producera resultatet. Alla nätverksblock delar samma vikter och tränas från början till slut med en enda backpropageringspass.\n",
"\n",
"Eftersom tillståndsvektorerna $S_0,\\dots,S_n$ skickas genom nätverket, kan det lära sig de sekventiella beroendena mellan ord. Till exempel, när ordet *inte* dyker upp någonstans i sekvensen, kan det lära sig att negera vissa element inom tillståndsvektorn, vilket resulterar i negation.\n",
"\n",
"> Eftersom vikterna för alla RNN-block i bilden är delade, kan samma bild representeras som ett enda block (till höger) med en rekurrent återkopplingsslinga, som skickar nätverkets utgångstillstånd tillbaka till ingången.\n",
"\n",
"Låt oss se hur rekurrenta neurala nätverk kan hjälpa oss att klassificera vår nyhetsdatamängd.\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": [
"## Enkel RNN-klassificerare\n",
"\n",
"När det gäller enkel RNN är varje återkommande enhet ett enkelt linjärt nätverk som tar en sammansatt inmatningsvektor och tillståndsvektor och producerar en ny tillståndsvektor. PyTorch representerar denna enhet med klassen `RNNCell`, och nätverk av sådana celler - som `RNN`-lager.\n",
"\n",
"För att definiera en RNN-klassificerare kommer vi först att använda ett inbäddningslager för att minska dimensionen på inmatningsvokabulären, och sedan lägga till ett RNN-lager ovanpå:\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": [
"> **Note:** Här använder vi ett otränat inbäddningslager för enkelhetens skull, men för ännu bättre resultat kan vi använda ett förtränat inbäddningslager med Word2Vec- eller GloVe-inbäddningar, som beskrivs i föregående avsnitt. För att få en bättre förståelse kan du anpassa denna kod för att fungera med förtränade inbäddningar.\n",
"\n",
"I vårt fall kommer vi att använda en paddad dataladdare, så varje batch kommer att innehålla ett antal paddade sekvenser av samma längd. RNN-lagret kommer att ta sekvensen av inbäddningstensorer och producera två utgångar:\n",
"* $x$ är en sekvens av RNN-cellens utgångar vid varje steg\n",
"* $h$ är det slutliga dolda tillståndet för det sista elementet i sekvensen\n",
"\n",
"Vi applicerar sedan en fullt ansluten linjär klassificerare för att få antalet klasser.\n",
"\n",
"> **Note:** RNN:er är ganska svåra att träna, eftersom när RNN-cellerna rullas ut längs sekvenslängden blir det resulterande antalet lager som är involverade i backpropagering ganska stort. Därför behöver vi välja en liten inlärningshastighet och träna nätverket på en större dataset för att få bra resultat. Det kan ta ganska lång tid, så att använda GPU är att föredra.\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",
"Ett av de största problemen med klassiska RNN är det så kallade **vanishing gradients**-problemet. Eftersom RNN tränas från början till slut i en enda backpropagation-pass, har de svårt att föra vidare fel till de första lagren i nätverket, vilket gör att nätverket inte kan lära sig relationer mellan avlägsna tokens. Ett sätt att undvika detta problem är att införa **explicit tillståndshantering** genom att använda så kallade **grindar**. Det finns två mest kända arkitekturer av denna typ: **Long Short Term Memory** (LSTM) och **Gated Relay Unit** (GRU).\n",
"\n",
"![Bild som visar ett exempel på en long short term memory-cell](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"LSTM-nätverket är organiserat på ett sätt som liknar RNN, men det finns två tillstånd som skickas från lager till lager: det faktiska tillståndet $c$ och den dolda vektorn $h$. Vid varje enhet kombineras den dolda vektorn $h_i$ med input $x_i$, och de styr vad som händer med tillståndet $c$ via **grindar**. Varje grind är ett neuralt nätverk med sigmoid-aktivering (output i intervallet $[0,1]$), vilket kan ses som en bitmask när den multipliceras med tillståndsvektorn. Följande grindar finns (från vänster till höger på bilden ovan):\n",
"* **Forget-grinden** tar den dolda vektorn och avgör vilka komponenter i vektorn $c$ vi behöver glömma och vilka vi ska föra vidare.\n",
"* **Input-grinden** tar viss information från input och den dolda vektorn och lägger till den i tillståndet.\n",
"* **Output-grinden** transformerar tillståndet via ett linjärt lager med $\\tanh$-aktivering och väljer sedan vissa av dess komponenter med hjälp av den dolda vektorn $h_i$ för att producera ett nytt tillstånd $c_{i+1}$.\n",
"\n",
"Komponenterna i tillståndet $c$ kan ses som flaggor som kan slås på och av. Till exempel, när vi stöter på namnet *Alice* i sekvensen, kanske vi vill anta att det hänvisar till en kvinnlig karaktär och höja flaggan i tillståndet som indikerar att vi har ett kvinnligt substantiv i meningen. När vi senare stöter på frasen *och Tom*, kommer vi att höja flaggan som indikerar att vi har ett plural-substantiv. Genom att manipulera tillståndet kan vi alltså hålla reda på grammatiska egenskaper hos meningens delar.\n",
"\n",
"> **Note**: En utmärkt resurs för att förstå LSTM:s interna funktioner är den här fantastiska artikeln [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) av Christopher Olah.\n",
"\n",
"Även om den interna strukturen hos en LSTM-cell kan verka komplex, döljer PyTorch denna implementation i klassen `LSTMCell` och tillhandahåller objektet `LSTM` för att representera hela LSTM-lagret. Implementeringen av en LSTM-klassificerare kommer därför att vara ganska lik den enkla RNN som vi har sett ovan:\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": [
"## Packade sekvenser\n",
"\n",
"I vårt exempel var vi tvungna att fylla ut alla sekvenser i minibatchen med nollvektorer. Även om detta leder till viss minnesförlust, är det mer kritiskt med RNN att ytterligare RNN-celler skapas för de utfyllda inmatningsobjekten, som deltar i träningen men inte innehåller någon viktig inmatningsinformation. Det skulle vara mycket bättre att träna RNN endast på den faktiska sekvensstorleken.\n",
"\n",
"För att göra detta introduceras ett speciellt format för lagring av utfyllda sekvenser i PyTorch. Anta att vi har en utfylld minibatch som ser ut så här:\n",
"```\n",
"[[1,2,3,4,5],\n",
" [6,7,8,0,0],\n",
" [9,0,0,0,0]]\n",
"```\n",
"Här representerar 0 utfyllda värden, och den faktiska längdvektorn för inmatningssekvenserna är `[5,3,1]`.\n",
"\n",
"För att effektivt träna RNN med utfyllda sekvenser vill vi börja träna den första gruppen av RNN-celler med en stor minibatch (`[1,6,9]`), men sedan avsluta bearbetningen av den tredje sekvensen och fortsätta träningen med mindre minibatcher (`[2,7]`, `[3,8]`) och så vidare. Således representeras den packade sekvensen som en enda vektor - i vårt fall `[1,6,9,2,7,3,8,4,5]`, och längdvektorn (`[5,3,1]`), från vilken vi enkelt kan rekonstruera den ursprungliga utfyllda minibatchen.\n",
"\n",
"För att skapa en packad sekvens kan vi använda funktionen `torch.nn.utils.rnn.pack_padded_sequence`. Alla rekurrenta lager, inklusive RNN, LSTM och GRU, stöder packade sekvenser som inmatning och producerar packad utmatning, som kan dekodas med hjälp av `torch.nn.utils.rnn.pad_packed_sequence`.\n",
"\n",
"För att kunna skapa en packad sekvens måste vi skicka längdvektorn till nätverket, och därför behöver vi en annan funktion för att förbereda minibatcher:\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": [
"Det faktiska nätverket skulle vara mycket likt `LSTMClassifier` ovan, men `forward`-passet kommer att ta emot både den vadderade minibatchen och vektorn av sekvenslängder. Efter att ha beräknat inbäddningen, beräknar vi den packade sekvensen, skickar den till LSTM-lagret och packar sedan upp resultatet igen.\n",
"\n",
"> **Note**: Vi använder faktiskt inte det uppackade resultatet `x`, eftersom vi använder utdata från de dolda lagren i de följande beräkningarna. Därför kan vi ta bort uppackningen helt från denna kod. Anledningen till att vi placerar den här är för att du enkelt ska kunna modifiera denna kod, ifall du skulle behöva använda nätverkets utdata i vidare beräkningar.\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": [
"> **Observera:** Du kanske har märkt parametern `use_pack_sequence` som vi skickar till träningsfunktionen. För närvarande kräver funktionen `pack_padded_sequence` att längdsekvenstensoren är på CPU-enheten, och därför måste träningsfunktionen undvika att flytta längdsekvensdata till GPU under träning. Du kan titta på implementeringen av funktionen `train_emb` i filen [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bidirektionella och flerskiktade RNN:er\n",
"\n",
"I våra exempel har alla rekurrenta nätverk bearbetat data i en riktning, från början av en sekvens till slutet. Det känns naturligt, eftersom det liknar sättet vi läser och lyssnar på tal. Men i många praktiska fall har vi slumpmässig åtkomst till indata-sekvensen, och då kan det vara logiskt att köra rekurrent beräkning i båda riktningarna. Sådana nätverk kallas **bidirektionella** RNN:er, och de kan skapas genom att ange parametern `bidirectional=True` till konstruktören för RNN/LSTM/GRU.\n",
"\n",
"När vi arbetar med ett bidirektionellt nätverk behöver vi två vektor för dold tillstånd, en för varje riktning. PyTorch kodar dessa vektorer som en enda vektor med dubbelt så stor storlek, vilket är ganska praktiskt eftersom du normalt skickar det resulterande dolda tillståndet till ett fullt anslutet linjärt lager. Du behöver bara ta hänsyn till denna storleksökning när du skapar lagret.\n",
"\n",
"Ett rekurrent nätverk, oavsett om det är enkelriktat eller bidirektionellt, fångar vissa mönster inom en sekvens och kan lagra dem i en tillståndsvektor eller skicka dem vidare till utdata. Precis som med konvolutionella nätverk kan vi bygga ett annat rekurrent lager ovanpå det första för att fånga mönster på högre nivå, baserat på låg-nivå mönster som det första lagret har extraherat. Detta leder oss till begreppet **flerskiktad RNN**, som består av två eller fler rekurrenta nätverk, där utdata från det föregående lagret skickas som indata till nästa lager.\n",
"\n",
"![Bild som visar en flerskiktad lång-korttidsminne-RNN](../../../../../translated_images/sv/multi-layer-lstm.dd975e29bb2a59fe.webp)\n",
"\n",
"*Bild från [detta fantastiska inlägg](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) av Fernando López*\n",
"\n",
"PyTorch gör det enkelt att konstruera sådana nätverk, eftersom du bara behöver ange parametern `num_layers` till konstruktören för RNN/LSTM/GRU för att automatiskt bygga flera lager av rekurrens. Detta innebär också att storleken på den dolda tillståndsvektorn ökar proportionellt, och du måste ta hänsyn till detta när du hanterar utdata från de rekurrenta lagren.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN:er för andra uppgifter\n",
"\n",
"I denna enhet har vi sett att RNN:er kan användas för sekvensklassificering, men de kan faktiskt hantera många fler uppgifter, såsom textgenerering, maskinöversättning och mer. Vi kommer att titta på dessa uppgifter i nästa enhet.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Ansvarsfriskrivning**: \nDetta dokument har översatts med hjälp av AI-översättningstjänsten [Co-op Translator](https://github.com/Azure/co-op-translator). Även om vi strävar efter noggrannhet, bör du vara medveten om att automatiska översättningar kan innehålla fel eller inexaktheter. Det ursprungliga dokumentet på dess originalspråk bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för eventuella missförstånd eller feltolkningar som uppstår vid användning av denna översättning.\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-28T17:43:29+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "sv"
}
},
"nbformat": 4,
"nbformat_minor": 2
}