479 lines
23 KiB
Plaintext
479 lines
23 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Rekurentne neuronske mreže\n",
|
|
"\n",
|
|
"U prethodnom modulu koristili smo bogate semantičke reprezentacije teksta i jednostavan linearni klasifikator na vrhu ugrađenih vektora. Ova arhitektura hvata agregirano značenje riječi u rečenici, ali ne uzima u obzir **redoslijed** riječi, jer operacija agregacije na vrhu ugrađenih vektora uklanja tu informaciju iz izvornog teksta. Budući da ti modeli ne mogu modelirati redoslijed riječi, nisu sposobni riješiti složenije ili dvosmislene zadatke poput generiranja teksta ili odgovaranja na pitanja.\n",
|
|
"\n",
|
|
"Kako bismo uhvatili značenje sekvenci teksta, trebamo koristiti drugu arhitekturu neuronske mreže, koja se naziva **rekurentna neuronska mreža** ili RNN. U RNN-u, rečenicu prosljeđujemo kroz mrežu jedan simbol po simbol, a mreža proizvodi određeno **stanje**, koje zatim ponovno prosljeđujemo mreži zajedno sa sljedećim simbolom.\n",
|
|
"\n",
|
|
"S obzirom na ulaznu sekvencu tokena $X_0,\\dots,X_n$, RNN stvara sekvencu blokova neuronske mreže i trenira ovu sekvencu od početka do kraja koristeći povratnu propagaciju. Svaki blok mreže uzima par $(X_i,S_i)$ kao ulaz i proizvodi $S_{i+1}$ kao rezultat. Konačno stanje $S_n$ ili izlaz $X_n$ ide u linearni klasifikator kako bi se proizveo rezultat. Svi blokovi mreže dijele iste težine i treniraju se od početka do kraja koristeći jednu povratnu propagaciju.\n",
|
|
"\n",
|
|
"Budući da se vektori stanja $S_0,\\dots,S_n$ prosljeđuju kroz mrežu, ona može naučiti sekvencijalne ovisnosti između riječi. Na primjer, kada se riječ *ne* pojavi negdje u sekvenci, mreža može naučiti negirati određene elemente unutar vektora stanja, što rezultira negacijom.\n",
|
|
"\n",
|
|
"> Budući da svi blokovi RNN-a na slici dijele iste težine, ista slika može se prikazati kao jedan blok (desno) s povratnom petljom, koja prosljeđuje izlazno stanje mreže natrag na ulaz.\n",
|
|
"\n",
|
|
"Pogledajmo kako rekurentne neuronske mreže mogu pomoći u klasifikaciji našeg skupa podataka o vijestima.\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": [
|
|
"## Jednostavni RNN klasifikator\n",
|
|
"\n",
|
|
"Kod jednostavnog RNN-a, svaka rekurentna jedinica je jednostavna linearna mreža koja uzima spojeni ulazni vektor i vektor stanja te proizvodi novi vektor stanja. PyTorch predstavlja ovu jedinicu s klasom `RNNCell`, a mreže takvih jedinica - kao sloj `RNN`.\n",
|
|
"\n",
|
|
"Za definiranje RNN klasifikatora, prvo ćemo primijeniti sloj za ugrađivanje kako bismo smanjili dimenzionalnost ulaznog vokabulara, a zatim dodati RNN sloj na vrh:\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": [
|
|
"> **Napomena:** Ovdje koristimo neuvježbani sloj ugradnje radi jednostavnosti, ali za još bolje rezultate možemo koristiti unaprijed uvježbani sloj ugradnje s Word2Vec ili GloVe ugradnjama, kako je opisano u prethodnoj jedinici. Za bolje razumijevanje, možda biste željeli prilagoditi ovaj kod kako bi radio s unaprijed uvježbanim ugradnjama.\n",
|
|
"\n",
|
|
"U našem slučaju koristit ćemo učitavač podataka s popunjavanjem (padded data loader), tako da će svaki batch sadržavati određeni broj sekvenci iste duljine. RNN sloj će primiti sekvencu tenzora ugradnje i proizvesti dva izlaza:\n",
|
|
"* $x$ je sekvenca izlaza RNN ćelija na svakom koraku\n",
|
|
"* $h$ je konačno skriveno stanje za zadnji element sekvence\n",
|
|
"\n",
|
|
"Zatim primjenjujemo potpuno povezani linearni klasifikator kako bismo dobili broj klase.\n",
|
|
"\n",
|
|
"> **Napomena:** RNN-ove je prilično teško trenirati jer, kada se RNN ćelije razviju duž duljine sekvence, broj slojeva uključenih u povratnu propagaciju postaje prilično velik. Stoga trebamo odabrati malu stopu učenja i trenirati mrežu na većem skupu podataka kako bismo postigli dobre rezultate. To može potrajati dosta dugo, pa je preporučljivo koristiti 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": [
|
|
"## Long Short Term Memory (LSTM)\n",
|
|
"\n",
|
|
"Jedan od glavnih problema klasičnih RNN-a je takozvani problem **nestajućih gradijenata**. Budući da se RNN-i treniraju od kraja do početka u jednom prolazu unatrag, teško prenose pogrešku do prvih slojeva mreže, zbog čega mreža ne može naučiti odnose između udaljenih tokena. Jedan od načina za izbjegavanje ovog problema je uvođenje **eksplicitnog upravljanja stanjem** pomoću takozvanih **vrata**. Dvije najpoznatije arhitekture ovog tipa su: **Long Short Term Memory** (LSTM) i **Gated Relay Unit** (GRU).\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"LSTM mreža organizirana je na način sličan RNN-u, ali postoje dva stanja koja se prenose iz sloja u sloj: stvarno stanje $c$ i skriveni vektor $h$. U svakoj jedinici, skriveni vektor $h_i$ se spaja s ulazom $x_i$, i oni kontroliraju što se događa sa stanjem $c$ putem **vrata**. Svaka vrata su neuronska mreža sa sigmoidnom aktivacijom (izlaz u rasponu $[0,1]$), koja se može smatrati bitmaskom kada se pomnoži s vektorom stanja. Postoje sljedeća vrata (s lijeva na desno na slici iznad):\n",
|
|
"* **vrata zaborava** uzimaju skriveni vektor i određuju koje komponente vektora $c$ trebamo zaboraviti, a koje proslijediti dalje.\n",
|
|
"* **ulazna vrata** uzimaju neke informacije iz ulaza i skrivenog vektora te ih ubacuju u stanje.\n",
|
|
"* **izlazna vrata** transformiraju stanje putem nekog linearnog sloja s $\\tanh$ aktivacijom, zatim odabiru neke od njegovih komponenti koristeći skriveni vektor $h_i$ kako bi proizveli novo stanje $c_{i+1}$.\n",
|
|
"\n",
|
|
"Komponente stanja $c$ mogu se smatrati nekim zastavicama koje se mogu uključiti ili isključiti. Na primjer, kada u sekvenci naiđemo na ime *Alice*, možda ćemo pretpostaviti da se odnosi na ženski lik i podići zastavicu u stanju koja označava da imamo ženski imenicu u rečenici. Kada kasnije naiđemo na frazu *and Tom*, podići ćemo zastavicu koja označava da imamo množinsku imenicu. Tako manipulacijom stanja možemo navodno pratiti gramatička svojstva dijelova rečenice.\n",
|
|
"\n",
|
|
"> **Note**: Odličan resurs za razumijevanje unutarnje strukture LSTM-a je ovaj sjajan članak [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) Christophera Olaha.\n",
|
|
"\n",
|
|
"Iako unutarnja struktura LSTM ćelije može izgledati složeno, PyTorch skriva ovu implementaciju unutar klase `LSTMCell` i pruža objekt `LSTM` za predstavljanje cijelog LSTM sloja. Stoga će implementacija LSTM klasifikatora biti prilično slična jednostavnom RNN-u koji smo vidjeli ranije:\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": [
|
|
"## Pakirane sekvence\n",
|
|
"\n",
|
|
"U našem primjeru morali smo popuniti sve sekvence u minibatchu nulama. Iako to rezultira određenim gubitkom memorije, kod RNN-a je još kritičnije što se dodatne RNN ćelije stvaraju za popunjene ulazne stavke, koje sudjeluju u treningu, ali ne nose nikakve važne ulazne informacije. Bilo bi puno bolje trenirati RNN samo do stvarne duljine sekvence.\n",
|
|
"\n",
|
|
"Kako bismo to postigli, u PyTorchu je uveden poseban format za pohranu popunjenih sekvenci. Pretpostavimo da imamo ulazni popunjeni minibatch koji izgleda ovako:\n",
|
|
"```\n",
|
|
"[[1,2,3,4,5],\n",
|
|
" [6,7,8,0,0],\n",
|
|
" [9,0,0,0,0]]\n",
|
|
"```\n",
|
|
"Ovdje 0 predstavlja popunjene vrijednosti, a stvarni vektor duljina ulaznih sekvenci je `[5,3,1]`.\n",
|
|
"\n",
|
|
"Kako bismo učinkovito trenirali RNN s popunjenim sekvencama, želimo započeti trening prve grupe RNN ćelija s velikim minibatchom (`[1,6,9]`), ali zatim završiti obradu treće sekvence i nastaviti trening s kraćim minibatchevima (`[2,7]`, `[3,8]`), i tako dalje. Dakle, pakirana sekvenca je predstavljena kao jedan vektor - u našem slučaju `[1,6,9,2,7,3,8,4,5]`, i vektor duljina (`[5,3,1]`), iz kojeg lako možemo rekonstruirati originalni popunjeni minibatch.\n",
|
|
"\n",
|
|
"Za stvaranje pakirane sekvence možemo koristiti funkciju `torch.nn.utils.rnn.pack_padded_sequence`. Sve rekurzivne slojeve, uključujući RNN, LSTM i GRU, podržavaju pakirane sekvence kao ulaz i proizvode pakirani izlaz, koji se može dekodirati pomoću `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
|
"\n",
|
|
"Kako bismo mogli proizvesti pakiranu sekvencu, trebamo proslijediti vektor duljina mreži, i stoga nam je potrebna drugačija funkcija za pripremu minibatcheva:\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": [
|
|
"Stvarna mreža bila bi vrlo slična `LSTMClassifier` gore, ali `forward` prolaz će primiti i podskup s podacima (padded minibatch) i vektor duljina sekvenci. Nakon izračuna ugradnje (embedding), izračunavamo zapakiranu sekvencu, prosljeđujemo je LSTM sloju, a zatim rezultat ponovno raspakiramo.\n",
|
|
"\n",
|
|
"> **Napomena**: Zapravo ne koristimo raspakirani rezultat `x`, jer koristimo izlaz iz skrivenih slojeva u sljedećim izračunima. Stoga možemo u potpunosti ukloniti raspakiranje iz ovog koda. Razlog zašto ga ovdje uključujemo je taj da vam omogućimo jednostavno modificiranje ovog koda, u slučaju da trebate koristiti izlaz mreže u daljnjim izračunima.\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": [
|
|
"> **Napomena:** Možda ste primijetili parametar `use_pack_sequence` koji prosljeđujemo funkciji za treniranje. Trenutno, funkcija `pack_padded_sequence` zahtijeva da tensor duljine sekvence bude na CPU uređaju, te stoga funkcija za treniranje mora izbjeći premještanje podataka o duljini sekvence na GPU tijekom treniranja. Možete pogledati implementaciju funkcije `train_emb` u datoteci [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Dvosmjerni i višeslojni RNN-ovi\n",
|
|
"\n",
|
|
"U našim primjerima, sve rekurentne mreže radile su u jednom smjeru, od početka sekvence do kraja. To izgleda prirodno, jer podsjeća na način na koji čitamo i slušamo govor. Međutim, budući da u mnogim praktičnim slučajevima imamo nasumičan pristup ulaznoj sekvenci, moglo bi imati smisla pokrenuti rekurentne izračune u oba smjera. Takve mreže nazivaju se **dvosmjerni** RNN-ovi, a mogu se stvoriti dodavanjem parametra `bidirectional=True` konstruktoru RNN/LSTM/GRU.\n",
|
|
"\n",
|
|
"Kod rada s dvosmjernom mrežom, trebala bi nam dva vektora skrivenog stanja, po jedan za svaki smjer. PyTorch kodira te vektore kao jedan vektor dvostruko veće veličine, što je prilično praktično, jer biste obično proslijedili dobiveno skriveno stanje potpuno povezanoj linearnoj sloju, i samo trebate uzeti u obzir ovo povećanje veličine prilikom stvaranja sloja.\n",
|
|
"\n",
|
|
"Rekurentna mreža, bilo jednosmjerna ili dvosmjerna, hvata određene uzorke unutar sekvence i može ih pohraniti u vektor stanja ili proslijediti u izlaz. Kao i kod konvolucijskih mreža, možemo izgraditi još jedan rekurentni sloj na vrhu prvog kako bismo uhvatili uzorke višeg nivoa, izgrađene od uzoraka nižeg nivoa koje je izvukao prvi sloj. To nas dovodi do pojma **višeslojni RNN**, koji se sastoji od dvije ili više rekurentnih mreža, gdje se izlaz prethodnog sloja prosljeđuje sljedećem sloju kao ulaz.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Slika iz [ovog sjajnog posta](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autora Fernanda Lópeza*\n",
|
|
"\n",
|
|
"PyTorch olakšava izgradnju takvih mreža, jer samo trebate dodati parametar `num_layers` konstruktoru RNN/LSTM/GRU kako biste automatski izgradili nekoliko slojeva rekurencije. To bi također značilo da će se veličina vektora skrivenog stanja proporcionalno povećati, i trebate to uzeti u obzir prilikom rukovanja izlazom rekurentnih slojeva.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN-ovi za druge zadatke\n",
|
|
"\n",
|
|
"U ovoj jedinici vidjeli smo da se RNN-ovi mogu koristiti za klasifikaciju sekvenci, ali zapravo mogu obraditi mnogo više zadataka, poput generiranja teksta, strojnog prevođenja i drugih. Te zadatke ćemo razmotriti u sljedećoj jedinici.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Odricanje od odgovornosti**: \nOvaj dokument je preveden korištenjem AI usluge za prevođenje [Co-op Translator](https://github.com/Azure/co-op-translator). Iako nastojimo osigurati točnost, imajte na umu da automatski prijevodi mogu sadržavati pogreške ili netočnosti. Izvorni dokument na izvornom jeziku treba smatrati mjerodavnim izvorom. Za ključne informacije preporučuje se profesionalni prijevod od strane stručnjaka. Ne preuzimamo odgovornost za bilo kakva nesporazuma ili pogrešna tumačenja koja mogu proizaći iz korištenja ovog prijevoda.\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-30T08:10:46+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
|
"language_code": "hr"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |