{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Rekurentiniai neuroniniai tinklai\n", "\n", "Ankstesniame modulyje naudojome turtingas semantines tekstų reprezentacijas ir paprastą linijinį klasifikatorių virš įterpimų. Ši architektūra padeda užfiksuoti agreguotą žodžių prasmę sakinyje, tačiau ji neatsižvelgia į **žodžių tvarką**, nes agregavimo operacija virš įterpimų pašalina šią informaciją iš pradinio teksto. Kadangi šie modeliai negali modeliuoti žodžių tvarkos, jie negali spręsti sudėtingesnių ar dviprasmiškų užduočių, tokių kaip teksto generavimas ar klausimų atsakymas.\n", "\n", "Norėdami užfiksuoti teksto sekos prasmę, turime naudoti kitą neuroninių tinklų architektūrą, vadinamą **rekurentiniu neuroniniu tinklu** arba RNN. RNN tinklu mes perduodame savo sakinį per tinklą po vieną simbolį, o tinklas sukuria tam tikrą **būseną**, kurią vėliau perduodame tinklui kartu su kitu simboliu.\n", "\n", "Duotai įvesties sekai $X_0,\\dots,X_n$, RNN sukuria neuroninių tinklų blokų seką ir treniruoja šią seką nuo pradžios iki pabaigos naudodamas atgalinę sklaidą. Kiekvienas tinklo blokas kaip įvestį gauna porą $(X_i,S_i)$ ir kaip rezultatą sukuria $S_{i+1}$. Galutinė būsena $S_n$ arba išvestis $X_n$ perduodama linijiniam klasifikatoriui, kad būtų gautas rezultatas. Visi tinklo blokai dalijasi tais pačiais svoriais ir yra treniruojami nuo pradžios iki pabaigos per vieną atgalinės sklaidos etapą.\n", "\n", "Kadangi būsenos vektoriai $S_0,\\dots,S_n$ perduodami per tinklą, jis gali išmokti sekos priklausomybes tarp žodžių. Pavyzdžiui, kai žodis *ne* pasirodo kažkur sekoje, tinklas gali išmokti paneigti tam tikrus elementus būsenos vektoriuje, sukeldamas neigimą.\n", "\n", "> Kadangi visų RNN blokų svoriai paveikslėlyje yra bendri, tas pats paveikslėlis gali būti pavaizduotas kaip vienas blokas (dešinėje) su rekursiniu grįžtamojo ryšio ciklu, kuris perduoda tinklo išvesties būseną atgal į įvestį.\n", "\n", "Pažiūrėkime, kaip rekurentiniai neuroniniai tinklai gali padėti klasifikuoti mūsų naujienų duomenų rinkinį.\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": [ "## Paprastas RNN klasifikatorius\n", "\n", "Naudojant paprastą RNN, kiekvienas pasikartojantis vienetas yra paprastas linijinis tinklas, kuris priima sujungtą įvesties vektorių ir būsenos vektorių, o tada sukuria naują būsenos vektorių. PyTorch šį vienetą atvaizduoja naudodamas `RNNCell` klasę, o tokių ląstelių tinklą - kaip `RNN` sluoksnį.\n", "\n", "Norėdami apibrėžti RNN klasifikatorių, pirmiausia pritaikysime įterpimo sluoksnį, kad sumažintume įvesties žodyno dimensiją, o tada virš jo pridėsime RNN sluoksnį:\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": [ "> **Pastaba:** Čia naudojame netreniruotą įterpimo sluoksnį dėl paprastumo, tačiau dar geresniems rezultatams galime naudoti iš anksto apmokytą įterpimo sluoksnį su Word2Vec arba GloVe įterpimais, kaip aprašyta ankstesniame skyriuje. Norėdami geriau suprasti, galite pritaikyti šį kodą darbui su iš anksto apmokytais įterpimais.\n", "\n", "Mūsų atveju naudosime užpildytą duomenų kaupiklį, todėl kiekvienas paketas turės tam tikrą skaičių užpildytų sekų, kurios bus vienodo ilgio. RNN sluoksnis priims įterpimo tensorių seką ir sugeneruos du išvesties rezultatus:\n", "* $x$ yra RNN ląstelių išvesties seka kiekviename žingsnyje\n", "* $h$ yra galutinė paslėpta būsena paskutiniam sekos elementui\n", "\n", "Tada pritaikome pilnai sujungtą linijinį klasifikatorių, kad gautume klasių skaičių.\n", "\n", "> **Pastaba:** RNN yra gana sunku treniruoti, nes kai RNN ląstelės yra išskleistos pagal sekos ilgį, sluoksnių, dalyvaujančių atgalinėje propagacijoje, skaičius tampa labai didelis. Todėl reikia pasirinkti mažą mokymosi greitį ir treniruoti tinklą su didesniu duomenų rinkiniu, kad būtų pasiekti geri rezultatai. Tai gali užtrukti gana ilgai, todėl rekomenduojama naudoti GPU.\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": [ "## Ilgalaikė trumpalaikė atmintis (LSTM)\n", "\n", "Viena iš pagrindinių klasikinių RNN problemų yra vadinamoji **nykstančių gradientų** problema. Kadangi RNN yra mokomi nuo pradžios iki pabaigos vienu atgalinio sklidimo etapu, jiems sunku perduoti klaidą į pirmuosius tinklo sluoksnius, todėl tinklas negali išmokti ryšių tarp tolimų žodžių. Vienas iš būdų išvengti šios problemos yra įvesti **aiškų būsenos valdymą** naudojant vadinamuosius **vartus**. Yra dvi žinomiausios tokio tipo architektūros: **Ilgalaikė trumpalaikė atmintis** (LSTM) ir **Vartų relės vienetas** (GRU).\n", "\n", "![Paveikslėlis, rodantis ilgalaikės trumpalaikės atminties ląstelės pavyzdį](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n", "\n", "LSTM tinklas yra organizuotas panašiai kaip RNN, tačiau yra dvi būsenos, kurios perduodamos iš sluoksnio į sluoksnį: faktinė būsena $c$ ir paslėptas vektorius $h$. Kiekviename vienete paslėptas vektorius $h_i$ yra sujungiamas su įvestimi $x_i$, ir jie kontroliuoja, kas vyksta su būsena $c$ per **vartus**. Kiekvienas vartas yra neuroninis tinklas su sigmoidine aktyvacija (rezultatas intervale $[0,1]$), kurį galima įsivaizduoti kaip bitų kaukę, kai jis dauginamas iš būsenos vektoriaus. Yra šie vartai (iš kairės į dešinę paveikslėlyje aukščiau):\n", "* **užmaršumo vartai** priima paslėptą vektorių ir nustato, kuriuos vektoriaus $c$ komponentus reikia pamiršti, o kuriuos perduoti toliau.\n", "* **įvesties vartai** paima tam tikrą informaciją iš įvesties ir paslėpto vektoriaus bei įterpia ją į būseną.\n", "* **išvesties vartai** transformuoja būseną per tam tikrą linijinį sluoksnį su $\\tanh$ aktyvacija, tada pasirenka kai kuriuos jos komponentus naudodami paslėptą vektorių $h_i$, kad sukurtų naują būseną $c_{i+1}$.\n", "\n", "Būsenos $c$ komponentus galima įsivaizduoti kaip tam tikrus vėliavėles, kurias galima įjungti ir išjungti. Pavyzdžiui, kai sekoje sutinkame vardą *Alice*, galime manyti, kad jis nurodo moterišką veikėją, ir pakelti vėliavėlę būsenoje, kad sakinyje turime moterišką daiktavardį. Kai toliau sutinkame frazę *ir Tom*, pakelsime vėliavėlę, kad turime daugiskaitinį daiktavardį. Taigi, manipuliuodami būsena, galime, tikėtina, sekti sakinio dalių gramatines savybes.\n", "\n", "> **Note**: Puikus šaltinis, padedantis suprasti LSTM vidinę struktūrą, yra šis puikus Christopher Olah straipsnis [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n", "\n", "Nors LSTM ląstelės vidinė struktūra gali atrodyti sudėtinga, PyTorch slepia šią įgyvendinimą `LSTMCell` klasėje ir pateikia `LSTM` objektą, skirtą visam LSTM sluoksniui atvaizduoti. Todėl LSTM klasifikatoriaus įgyvendinimas bus gana panašus į paprasto RNN, kurį matėme aukščiau:\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": [ "## Supakuotos sekos\n", "\n", "Mūsų pavyzdyje turėjome užpildyti visas mini partijos sekas nulio vektoriais. Nors tai šiek tiek švaisto atmintį, su RNN dar svarbiau, kad papildomos RNN ląstelės yra sukuriamos užpildytoms įvesties reikšmėms, kurios dalyvauja mokyme, tačiau neturi jokios svarbios įvesties informacijos. Būtų daug geriau, jei RNN būtų mokoma tik pagal tikrąją sekos ilgį.\n", "\n", "Tam PyTorch įveda specialų užpildytų sekų saugojimo formatą. Tarkime, turime užpildytą įvesties mini partiją, kuri atrodo taip:\n", "```\n", "[[1,2,3,4,5],\n", " [6,7,8,0,0],\n", " [9,0,0,0,0]]\n", "```\n", "Čia 0 reiškia užpildytas reikšmes, o tikrasis įvesties sekų ilgio vektorius yra `[5,3,1]`.\n", "\n", "Kad efektyviai mokytume RNN su užpildyta seka, norime pradėti mokymą su pirmąja RNN ląstelių grupe, turinčia didelę mini partiją (`[1,6,9]`), tačiau tada baigti trečios sekos apdorojimą ir tęsti mokymą su mažesnėmis mini partijomis (`[2,7]`, `[3,8]`) ir taip toliau. Taigi, supakuota seka yra pateikiama kaip vienas vektorius – mūsų atveju `[1,6,9,2,7,3,8,4,5]`, ir ilgio vektorius (`[5,3,1]`), iš kurio galime lengvai atkurti pradinę užpildytą mini partiją.\n", "\n", "Norėdami sukurti supakuotą seką, galime naudoti funkciją `torch.nn.utils.rnn.pack_padded_sequence`. Visos rekursinės sluoksnių rūšys, įskaitant RNN, LSTM ir GRU, palaiko supakuotas sekas kaip įvestį ir sukuria supakuotą išvestį, kurią galima dekoduoti naudojant `torch.nn.utils.rnn.pad_packed_sequence`.\n", "\n", "Kad galėtume sukurti supakuotą seką, turime perduoti ilgio vektorių tinklui, todėl mums reikia kitos funkcijos mini partijoms paruošti:\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": [ "Tikrasis tinklas būtų labai panašus į aukščiau pateiktą `LSTMClassifier`, tačiau `forward` perdavimas gaus tiek užpildytą mini partiją, tiek sekų ilgių vektorių. Po įterpimo apskaičiavimo, mes apskaičiuojame supakuotą seką, perduodame ją LSTM sluoksniui ir tada išpakuojame rezultatą atgal.\n", "\n", "> **Pastaba**: Iš tikrųjų mes nenaudojame išpakuoto rezultato `x`, nes tolesniuose skaičiavimuose naudojame iš paslėptų sluoksnių gautą išvestį. Todėl šį išpakavimą galima visiškai pašalinti iš šio kodo. Priežastis, kodėl jį čia pateikiame, yra ta, kad jums būtų lengviau modifikuoti šį kodą, jei prireiktų naudoti tinklo išvestį tolimesniuose skaičiavimuose.\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": [ "> **Pastaba:** Galbūt pastebėjote parametrą `use_pack_sequence`, kurį perduodame mokymo funkcijai. Šiuo metu funkcija `pack_padded_sequence` reikalauja, kad ilgio sekos tensorius būtų CPU įrenginyje, todėl mokymo funkcija turi vengti perkelti ilgio sekos duomenis į GPU mokymo metu. Galite peržiūrėti `train_emb` funkcijos įgyvendinimą [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) faile.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dvikryptės ir daugiapakopės RNN\n", "\n", "Mūsų pavyzdžiuose visos rekursinės tinklų operacijos vyko viena kryptimi – nuo sekos pradžios iki pabaigos. Tai atrodo natūralu, nes primena būdą, kaip skaitome ar klausomės kalbos. Tačiau daugelyje praktinių atvejų turime atsitiktinę prieigą prie įvesties sekos, todėl gali būti prasminga vykdyti rekursinį skaičiavimą abiem kryptimis. Tokie tinklai vadinami **dvikrypčiais** RNN, ir juos galima sukurti perduodant `bidirectional=True` parametrą RNN/LSTM/GRU konstruktoriui.\n", "\n", "Dirbant su dvikrypčiu tinklu, mums reikės dviejų paslėptų būsenų vektorių – po vieną kiekvienai krypčiai. PyTorch koduoja šiuos vektorius kaip vieną dvigubai didesnio dydžio vektorių, kas yra gana patogu, nes paprastai galutinę paslėptą būseną perduodate pilnai sujungtam linijiniam sluoksniui, ir jums tereikia atsižvelgti į šį dydžio padidėjimą kuriant sluoksnį.\n", "\n", "Rekursinis tinklas, vienkryptis ar dvikryptis, fiksuoja tam tikrus sekos modelius ir gali juos išsaugoti būsenos vektoriuje arba perduoti į išvestį. Kaip ir konvoliuciniuose tinkluose, galime sukurti kitą rekursinį sluoksnį ant pirmojo, kad fiksuotume aukštesnio lygio modelius, sudarytus iš žemesnio lygio modelių, kuriuos ištraukė pirmasis sluoksnis. Tai veda mus prie **daugiapakopės RNN** sąvokos, kurią sudaro du ar daugiau rekursinių tinklų, kur ankstesnio sluoksnio išvestis perduodama kitam sluoksniui kaip įvestis.\n", "\n", "![Vaizdas, rodantis daugiapakopį ilgalaikės-trumpalaikės atminties RNN](../../../../../lessons/5-NLP/16-RNN/images/multi-layer-lstm.jpg)\n", "\n", "*Paveikslas iš [šio nuostabaus įrašo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autoriaus Fernando López*\n", "\n", "PyTorch palengvina tokių tinklų konstravimą, nes jums tereikia perduoti `num_layers` parametrą RNN/LSTM/GRU konstruktoriui, kad automatiškai sukurtumėte kelis rekursijos sluoksnius. Tai taip pat reiškia, kad paslėpto/būsenos vektoriaus dydis proporcingai padidės, ir jums reikės atsižvelgti į tai tvarkant rekursinių sluoksnių išvestį.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## RNN kitiems užduotims\n", "\n", "Šiame skyriuje matėme, kad RNN gali būti naudojami sekų klasifikavimui, tačiau iš tiesų jie gali atlikti daug daugiau užduočių, tokių kaip teksto generavimas, mašininis vertimas ir kt. Šias užduotis aptarsime kitame skyriuje.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T14:00:44+00:00", "source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb", "language_code": "lt" } }, "nbformat": 4, "nbformat_minor": 2 }