{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Korduvad närvivõrgud\n", "\n", "Eelmises moodulis kasutasime tekstide rikkalikke semantilisi esitusi ja lihtsat lineaarset klassifikaatorit, mis põhines nendele sisenditel. Selline arhitektuur suudab tabada lause sõnade koondatud tähendust, kuid ei arvesta sõnade **järjekorda**, kuna sisendite koondamine eemaldab selle informatsiooni algsest tekstist. Kuna need mudelid ei suuda modelleerida sõnade järjestust, ei ole nad võimelised lahendama keerukamaid või mitmetähenduslikke ülesandeid, nagu teksti genereerimine või küsimustele vastamine.\n", "\n", "Tekstijärjestuse tähenduse tabamiseks peame kasutama teistsugust närvivõrgu arhitektuuri, mida nimetatakse **korduvaks närvivõrguks** ehk RNN-iks. RNN-is edastame oma lause läbi võrgu ühe sümboli kaupa, ja võrk genereerib mingi **oleku**, mille edastame võrku uuesti koos järgmise sümboliga.\n", "\n", "\"RNN\"\n", "\n", "Arvestades sisendjärjestust $X_0,\\dots,X_n$, loob RNN järjestuse närvivõrgu plokkidest ja treenib seda järjestust otsast lõpuni tagasileviku meetodil. Iga võrguplokk võtab sisendiks paari $(X_i,S_i)$ ja genereerib tulemuseks $S_{i+1}$. Lõplik olek $S_n$ või väljund $X_n$ suunatakse lineaarsele klassifikaatorile, et toota tulemus. Kõik võrguplokid jagavad samu kaalusid ja neid treenitakse otsast lõpuni ühe tagasileviku käigu abil.\n", "\n", "Kuna olekuvektorid $S_0,\\dots,S_n$ edastatakse läbi võrgu, suudab see õppida sõnade järjestusevahelisi sõltuvusi. Näiteks, kui sõna *not* ilmub kuskil järjestuses, võib võrk õppida teatud elemente olekuvektoris eitama, mis viib eitamiseni.\n", "\n", "> Kuna kõik RNN plokkide kaalud pildil on jagatud, saab sama pilti kujutada ühe plokina (paremal), millel on korduv tagasisideahel, mis edastab võrgu väljundoleku tagasi sisendisse.\n", "\n", "Vaatame, kuidas korduvad närvivõrgud aitavad meil klassifitseerida meie uudiste andmekogumit.\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": [ "## Lihtne RNN klassifikaator\n", "\n", "Lihtsa RNN-i puhul on iga korduvüksus lihtne lineaarne võrk, mis võtab sisendvektori ja olekuvektori ühendatud kujul ning toodab uue olekuvektori. PyTorch esindab seda üksust `RNNCell` klassiga, ja selliste rakkude võrku - `RNN` kihina.\n", "\n", "RNN klassifikaatori määratlemiseks rakendame esmalt sisendvokaabli dimensioonide vähendamiseks sisestuskihi (embedding layer) ja seejärel lisame sellele RNN kihi:\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": [ "> **Märkus:** Siin kasutame lihtsuse huvides treenimata sisendkihti, kuid veelgi paremate tulemuste saavutamiseks võiks kasutada eelnevalt treenitud sisendkihti, mis põhineb Word2Vec või GloVe vektoritel, nagu kirjeldatud eelmises osas. Paremaks arusaamiseks võiksite kohandada seda koodi, et see töötaks eelnevalt treenitud vektoritega.\n", "\n", "Meie puhul kasutame täiendatud andmete laadijat, nii et iga partii sisaldab sama pikkusega täiendatud järjestusi. RNN-kiht võtab sisendiks järjestuse vektorite tensoritest ja annab kaks väljundit:\n", "* $x$ on RNN-raku väljundite järjestus igal sammul\n", "* $h$ on viimane varjatud olek järjestuse viimase elemendi jaoks\n", "\n", "Seejärel rakendame täielikult ühendatud lineaarse klassifikaatori, et saada klasside arv.\n", "\n", "> **Märkus:** RNN-e on üsna keeruline treenida, sest kui RNN-rakud lahti rullitakse järjestuse pikkuse ulatuses, on tagasipropagatsioonis osalevate kihtide arv üsna suur. Seetõttu peame valima väikese õppemäära ja treenima võrku suurema andmekogumi peal, et saavutada häid tulemusi. See võib võtta üsna kaua aega, seega on eelistatud GPU kasutamine.\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": [ "## Pika- ja lühimälu (LSTM)\n", "\n", "Üks klassikaliste korduvate närvivõrkude (RNN) peamisi probleeme on nn **hajuvate gradientide probleem**. Kuna RNN-e treenitakse otsast lõpuni ühe tagasileviku käigus, on neil keeruline viga võrgu esimestesse kihtidesse edasi kanda, mistõttu ei suuda võrk õppida kaugemate tokenite vahelisi seoseid. Üks viis selle probleemi vältimiseks on **eksplitsiitse oleku haldamise** kasutuselevõtt, kasutades nn **väravaid**. Selle tüüpi arhitektuuridest on kaks kõige tuntumat: **Pika- ja lühimälu** (LSTM) ja **Gated Relay Unit** (GRU).\n", "\n", "![Pilt, mis näitab näidet pika- ja lühimälu rakust](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n", "\n", "LSTM-võrk on organiseeritud sarnaselt RNN-iga, kuid seal on kaks olekut, mis edastatakse kihilt kihile: tegelik olek $c$ ja peidetud vektor $h$. Igas üksuses ühendatakse peidetud vektor $h_i$ sisendiga $x_i$, ja need kontrollivad, mis juhtub olekuga $c$ läbi **väravate**. Iga värav on närvivõrk sigmoidse aktivatsiooniga (väljund vahemikus $[0,1]$), mida võib käsitleda kui bitimaski, kui see korrutatakse olekuvektoriga. Järgnevad väravad on olemas (ülaltoodud pildil vasakult paremale):\n", "* **unustamisvärav** võtab peidetud vektori ja määrab, millised komponendid vektorist $c$ tuleb unustada ja millised edasi anda.\n", "* **sisendvärav** võtab osa teavet sisendist ja peidetud vektorist ning lisab selle olekusse.\n", "* **väljundvärav** teisendab oleku läbi mingi lineaarse kihi $\\tanh$ aktivatsiooniga, seejärel valib mõned selle komponendid, kasutades peidetud vektorit $h_i$, et toota uus olek $c_{i+1}$.\n", "\n", "Olekukomponente $c$ võib käsitleda kui lippe, mida saab sisse ja välja lülitada. Näiteks, kui kohtame järjestuses nime *Alice*, võime eeldada, et see viitab naissoost tegelasele, ja tõsta olekus lippu, mis näitab, et lauses on naissoost nimisõna. Kui kohtame hiljem fraasi *ja Tom*, tõstame lippu, mis näitab, et meil on mitmuse nimisõna. Seega, olekuga manipuleerides saame väidetavalt jälgida lause osade grammatilisi omadusi.\n", "\n", "> **Note**: Suurepärane ressurss LSTM-i sisemuse mõistmiseks on Christopher Olah' suurepärane artikkel [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n", "\n", "Kuigi LSTM-raku sisemine struktuur võib tunduda keeruline, peidab PyTorch selle teostuse `LSTMCell` klassi sisse ja pakub `LSTM` objekti, et esindada tervet LSTM kihti. Seega on LSTM-klassifikaatori teostus üsna sarnane lihtsa RNN-iga, mida me eespool nägime:\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": [ "Nüüd treenime oma võrku. Pange tähele, et LSTM-i treenimine on samuti üsna aeglane ja treeningu alguses ei pruugi täpsus märkimisväärselt tõusta. Samuti peate võib-olla katsetama `lr` õppemäära parameetriga, et leida õppemäär, mis tagab mõistliku treeningkiiruse, kuid ei põhjusta mälu raiskamist.\n" ] }, { "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": [ "## Pakitud järjestused\n", "\n", "Meie näites pidime täitma kõik minibatch'i järjestused nullvektoritega. Kuigi see põhjustab mõningast mälukasutuse raiskamist, on RNN-ide puhul veelgi olulisem, et täiendavad RNN-rakud luuakse täidetud sisendite jaoks, mis osalevad treeningus, kuid ei kanna endas olulist sisendinfot. Oleks palju parem treenida RNN-i ainult tegeliku järjestuse pikkuse ulatuses.\n", "\n", "Selleks on PyTorchis kasutusele võetud spetsiaalne täidetud järjestuste salvestusvorming. Oletame, et meil on sisendiks täidetud minibatch, mis näeb välja selline:\n", "```\n", "[[1,2,3,4,5],\n", " [6,7,8,0,0],\n", " [9,0,0,0,0]]\n", "```\n", "Siin tähistab 0 täidetud väärtusi ja sisendjärjestuste tegelik pikkusvektor on `[5,3,1]`.\n", "\n", "Selleks, et RNN-i tõhusalt treenida täidetud järjestustega, tahame alustada esimese RNN-rakkude grupi treenimist suure minibatch'iga (`[1,6,9]`), kuid seejärel lõpetada kolmanda järjestuse töötlemine ja jätkata treenimist lühemate minibatch'idega (`[2,7]`, `[3,8]`) ja nii edasi. Seega on pakitud järjestus esitatud ühe vektorina - meie näites `[1,6,9,2,7,3,8,4,5]` ja pikkusvektorina (`[5,3,1]`), mille abil saame hõlpsasti taastada algse täidetud minibatch'i.\n", "\n", "Pakitud järjestuse loomiseks saame kasutada funktsiooni `torch.nn.utils.rnn.pack_padded_sequence`. Kõik korduvad kihid, sealhulgas RNN, LSTM ja GRU, toetavad pakitud järjestusi sisendina ja toodavad pakitud väljundi, mida saab dekodeerida funktsiooni `torch.nn.utils.rnn.pad_packed_sequence` abil.\n", "\n", "Selleks, et oleks võimalik luua pakitud järjestust, peame võrgule edastama pikkusvektori ja seetõttu vajame minibatch'ide ettevalmistamiseks teistsugust funktsiooni:\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": [ "Tegelik võrk oleks väga sarnane ülaltoodud `LSTMClassifier`-iga, kuid `forward`-protsess võtab vastu nii täidetud minibatch'i kui ka järjestuste pikkuste vektori. Pärast sisendi teisendamist arvutame pakitud järjestuse, edastame selle LSTM-kihile ja seejärel pakime tulemuse tagasi lahti.\n", "\n", "> **Märkus**: Tegelikult me ei kasuta lahtipakitud tulemust `x`, kuna kasutame järgnevates arvutustes peidetud kihtide väljundit. Seega võime selle koodist lahtipakkimise täielikult eemaldada. Põhjus, miks me selle siia lisame, on see, et teil oleks lihtsam koodi muuta, kui peaksite võrgustiku väljundit edaspidistes arvutustes kasutama.\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": [ "Nüüd alustame treeningut:\n" ] }, { "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": [ "> **Märkus:** Võite olla märganud parameetrit `use_pack_sequence`, mida edastame treeningfunktsioonile. Hetkel nõuab `pack_padded_sequence` funktsioon, et pikkuse järjestuse tensor oleks CPU seadmel, mistõttu peab treeningfunktsioon vältima pikkuse järjestuse andmete GPU-le liigutamist treeningu ajal. Võite vaadata `train_emb` funktsiooni teostust [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) failis.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Kahepoolne ja mitmekihiline RNN\n", "\n", "Meie näidetes töötasid kõik korduvad võrgud ühes suunas, järjestuse algusest lõpuni. See tundub loomulik, kuna see sarnaneb viisiga, kuidas me loeme ja kuulame kõnet. Kuid paljudel praktilistel juhtudel on meil juhuslik juurdepääs sisendjärjestusele, mistõttu võib olla mõistlik käivitada korduv arvutus mõlemas suunas. Selliseid võrke nimetatakse **kahepoolseteks** RNN-ideks ning neid saab luua, lisades RNN/LSTM/GRU konstruktorile parameetri `bidirectional=True`.\n", "\n", "Kahepoolse võrgu puhul vajame kahte varjatud oleku vektorit, üks iga suuna jaoks. PyTorch kodeerib need vektorid üheks kaks korda suurema suurusega vektoriks, mis on üsna mugav, kuna tavaliselt edastatakse saadud varjatud olek täielikult ühendatud lineaarsele kihile. Selle kihi loomisel tuleb lihtsalt arvestada suuruse suurenemisega.\n", "\n", "Korduv võrk, olgu see ühe- või kahepoolne, tuvastab teatud mustrid järjestuses ja suudab need salvestada oleku vektorisse või edastada väljundisse. Nagu konvolutsioonivõrkude puhul, saame ehitada esimese kihi peale teise korduva kihi, et tuvastada kõrgema taseme mustreid, mis on loodud esimese kihi poolt tuvastatud madalama taseme mustritest. See viib meid **mitmekihilise RNN-i** mõisteni, mis koosneb kahest või enamast korduvast võrgust, kus eelmise kihi väljund edastatakse järgmisele kihile sisendina.\n", "\n", "![Pilt, mis näitab mitmekihilist pika-lühiajalise-mälu RNN-i](../../../../../translated_images/et/multi-layer-lstm.dd975e29bb2a59fe.webp)\n", "\n", "*Pilt [sellest suurepärasest postitusest](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autorilt Fernando López*\n", "\n", "PyTorch muudab selliste võrkude loomise lihtsaks, kuna piisab, kui lisada RNN/LSTM/GRU konstruktorile parameeter `num_layers`, et automaatselt ehitada mitu korduvate kihtide taset. See tähendab ka, et varjatud oleku vektori suurus suureneb proportsionaalselt, ja seda tuleb arvestada korduvate kihtide väljundiga töötamisel.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## RNN-id muude ülesannete jaoks\n", "\n", "Selles osas oleme näinud, et RNN-e saab kasutada järjestuste klassifitseerimiseks, kuid tegelikult suudavad need toime tulla paljude teiste ülesannetega, nagu teksti genereerimine, masintõlge ja palju muud. Neid ülesandeid käsitleme järgmises osas.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Lahtiütlus**: \nSee dokument on tõlgitud AI tõlketeenuse [Co-op Translator](https://github.com/Azure/co-op-translator) abil. Kuigi püüame tagada täpsust, palume arvestada, et automaatsed tõlked võivad sisaldada vigu või ebatäpsusi. Algne dokument selle algses keeles tuleks pidada autoriteetseks allikaks. Olulise teabe puhul soovitame kasutada professionaalset inimtõlget. Me ei vastuta selle tõlke kasutamisest tulenevate arusaamatuste või valesti tõlgenduste eest.\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-10-11T12:56:43+00:00", "source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb", "language_code": "et" } }, "nbformat": 4, "nbformat_minor": 2 }