479 lines
25 KiB
Plaintext
479 lines
25 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Rekurrens neurális hálózatok\n",
|
||
"\n",
|
||
"Az előző modulban gazdag szemantikai reprezentációkat használtunk a szövegekhez, és egy egyszerű lineáris osztályozót helyeztünk az embeddingek fölé. Ez az architektúra a mondatokban szereplő szavak összesített jelentését ragadja meg, de nem veszi figyelembe a szavak **sorrendjét**, mivel az embeddingek fölötti aggregációs művelet eltávolította ezt az információt az eredeti szövegből. Mivel ezek a modellek nem képesek a szavak sorrendjét modellezni, nem tudnak megoldani összetettebb vagy kétértelmű feladatokat, például szövegalkotást vagy kérdés-megválaszolást.\n",
|
||
"\n",
|
||
"Ahhoz, hogy a szövegszekvencia jelentését megragadjuk, egy másik neurális hálózati architektúrát kell használnunk, amelyet **rekurrens neurális hálózatnak** (RNN) nevezünk. Az RNN-ben mondatunkat egy szimbólumonként haladva adjuk át a hálózaton, és a hálózat egy **állapotot** hoz létre, amelyet aztán a következő szimbólummal együtt ismét átadunk a hálózatnak.\n",
|
||
"\n",
|
||
"Adott $X_0,\\dots,X_n$ tokenekből álló bemeneti szekvencia esetén az RNN egy neurális hálózati blokkokból álló szekvenciát hoz létre, és ezt a szekvenciát végponttól végpontig tanítja visszaterjesztés segítségével. Minden hálózati blokk egy $(X_i,S_i)$ párt kap bemenetként, és $S_{i+1}$-et állít elő eredményként. A végső állapot $S_n$ vagy a kimenet $X_n$ egy lineáris osztályozóba kerül, amely előállítja az eredményt. Az összes hálózati blokk ugyanazokat a súlyokat osztja meg, és egyetlen visszaterjesztési lépés során végponttól végpontig tanítják őket.\n",
|
||
"\n",
|
||
"Mivel az állapotvektorok $S_0,\\dots,S_n$ átmennek a hálózaton, képes megtanulni a szavak közötti szekvenciális függőségeket. Például, ha a *nem* szó valahol megjelenik a szekvenciában, megtanulhatja bizonyos elemek tagadását az állapotvektoron belül, ami tagadást eredményez.\n",
|
||
"\n",
|
||
"> Mivel az RNN blokkok súlyai a képen megosztottak, ugyanaz a kép egyetlen blokként is ábrázolható (a jobb oldalon) egy rekurrens visszacsatolási hurokkal, amely a hálózat kimeneti állapotát visszavezeti a bemenethez.\n",
|
||
"\n",
|
||
"Nézzük meg, hogyan segíthetnek a rekurrens neurális hálózatok a hírek adatállományának osztályozásában.\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": [
|
||
"## Egyszerű RNN osztályozó\n",
|
||
"\n",
|
||
"Egyszerű RNN esetén minden rekurrens egység egy egyszerű lineáris hálózat, amely a bemeneti vektor és az állapotvektor összefűzött változatát veszi, és egy új állapotvektort hoz létre. A PyTorch ezt az egységet az `RNNCell` osztállyal képviseli, míg az ilyen cellák hálózatát az `RNN` rétegként.\n",
|
||
"\n",
|
||
"Egy RNN osztályozó definiálásához először egy beágyazási réteget alkalmazunk, hogy csökkentsük a bemeneti szókincs dimenzióját, majd erre építjük az RNN réteget:\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": [
|
||
"> **Megjegyzés:** Itt egyszerűség kedvéért nem használtunk előre betanított beágyazási réteget, de még jobb eredmények érdekében használhatunk előre betanított beágyazási réteget Word2Vec vagy GloVe beágyazásokkal, ahogy az előző egységben leírtuk. A jobb megértés érdekében érdemes lehet ezt a kódot úgy módosítani, hogy előre betanított beágyazásokat használjon.\n",
|
||
"\n",
|
||
"Esetünkben párnázott adatbetöltőt fogunk használni, így minden batch azonos hosszúságú párnázott szekvenciákat fog tartalmazni. Az RNN réteg beágyazási tenzorok szekvenciáját veszi, és két kimenetet állít elő:\n",
|
||
"* $x$ az RNN cellák kimeneteinek szekvenciája minden lépésnél\n",
|
||
"* $h$ a szekvencia utolsó elemének végső rejtett állapota\n",
|
||
"\n",
|
||
"Ezután egy teljesen összekapcsolt lineáris osztályozót alkalmazunk, hogy megkapjuk az osztályok számát.\n",
|
||
"\n",
|
||
"> **Megjegyzés:** Az RNN-eket elég nehéz betanítani, mivel amikor az RNN cellák a szekvencia hosszán keresztül kibonthatók, a visszaterjesztésben részt vevő rétegek száma meglehetősen nagy lesz. Ezért kis tanulási rátát kell választanunk, és nagyobb adathalmazon kell betanítanunk a hálózatot, hogy jó eredményeket érjünk el. Ez elég hosszú időt vehet igénybe, ezért ajánlott GPU-t használni.\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": [
|
||
"## Hosszú és rövid távú memória (LSTM)\n",
|
||
"\n",
|
||
"A klasszikus RNN-ek egyik fő problémája az úgynevezett **eltűnő gradiens** probléma. Mivel az RNN-eket egyetlen visszaterjesztési lépésben, végponttól végpontig tanítják, nehézséget okoz a hiba propagálása a hálózat első rétegeihez, így a hálózat nem tudja megtanulni a távoli tokenek közötti kapcsolatokat. Ennek a problémának az elkerülésére az egyik megoldás az **explicit állapotkezelés** bevezetése úgynevezett **kapuk** használatával. Két legismertebb ilyen architektúra: a **Hosszú és rövid távú memória** (LSTM) és a **Kapuzott reléegység** (GRU).\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Az LSTM hálózat felépítése hasonló az RNN-hez, de két állapotot továbbítanak rétegről rétegre: az aktuális állapotot $c$, és a rejtett vektort $h$. Minden egységnél a rejtett vektor $h_i$ összefűződik a bemenettel $x_i$, és ezek irányítják, hogy mi történik az állapottal $c$ a **kapuk** segítségével. Minden kapu egy neurális hálózat szigmoid aktivációval (kimenet tartománya $[0,1]$), amely bitmaszkként értelmezhető, amikor megszorozzuk az állapotvektorral. Az alábbi kapuk léteznek (a fenti képen balról jobbra):\n",
|
||
"* **felejtő kapu** veszi a rejtett vektort, és meghatározza, hogy az $c$ vektor mely komponenseit kell elfelejteni, és melyeket kell továbbadni.\n",
|
||
"* **bemeneti kapu** vesz némi információt a bemenetből és a rejtett vektorból, majd beilleszti az állapotba.\n",
|
||
"* **kimeneti kapu** átalakítja az állapotot egy lineáris rétegen keresztül $\\tanh$ aktivációval, majd kiválasztja annak néhány komponensét a rejtett vektor $h_i$ segítségével, hogy új állapotot hozzon létre $c_{i+1}$.\n",
|
||
"\n",
|
||
"Az állapot $c$ komponensei úgy tekinthetők, mint bizonyos jelzők, amelyeket be- és kikapcsolhatunk. Például, amikor egy *Alice* nevű szót találunk a sorozatban, feltételezhetjük, hogy női karakterre utal, és bekapcsolhatjuk az állapotban azt a jelzőt, hogy nőnemű főnév van a mondatban. Amikor később találkozunk az *és Tom* kifejezéssel, bekapcsolhatjuk azt a jelzőt, hogy többes számú főnév van jelen. Így az állapot manipulálásával nyomon követhetjük a mondatrészek nyelvtani tulajdonságait.\n",
|
||
"\n",
|
||
"> **Note**: Egy kiváló forrás az LSTM belső működésének megértéséhez Christopher Olah cikke, [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n",
|
||
"\n",
|
||
"Bár az LSTM cella belső szerkezete bonyolultnak tűnhet, a PyTorch elrejti ezt a megvalósítást az `LSTMCell` osztályban, és az `LSTM` objektumot biztosítja az egész LSTM réteg reprezentálására. Így az LSTM osztályozó megvalósítása nagyon hasonló lesz az egyszerű RNN-hez, amelyet korábban láttunk:\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": [
|
||
"## Csomagolt szekvenciák\n",
|
||
"\n",
|
||
"Példánkban minden szekvenciát nullvektorokkal kellett kitöltenünk a minibatch-ben. Ez némi memóriapazarlást eredményez, de az RNN-ek esetében még kritikusabb, hogy a kitöltött bemeneti elemekhez további RNN cellák jönnek létre, amelyek részt vesznek a tanításban, de nem hordoznak semmilyen fontos bemeneti információt. Sokkal jobb lenne, ha az RNN-t csak a tényleges szekvenciahosszra képeznénk.\n",
|
||
"\n",
|
||
"Ehhez a PyTorch-ban bevezettek egy speciális formátumot a kitöltött szekvenciák tárolására. Tegyük fel, hogy van egy kitöltött minibatch bemenetünk, amely így néz ki:\n",
|
||
"```\n",
|
||
"[[1,2,3,4,5],\n",
|
||
" [6,7,8,0,0],\n",
|
||
" [9,0,0,0,0]]\n",
|
||
"```\n",
|
||
"Itt a 0 a kitöltött értékeket jelöli, és a bemeneti szekvenciák tényleges hossza `[5,3,1]`.\n",
|
||
"\n",
|
||
"Ahhoz, hogy hatékonyan képezzük az RNN-t kitöltött szekvenciával, azt szeretnénk, hogy az első RNN cellacsoportot nagy minibatch-csel (`[1,6,9]`) kezdjük el tanítani, majd befejezzük a harmadik szekvencia feldolgozását, és rövidebb minibatch-ekkel (`[2,7]`, `[3,8]`) folytassuk a tanítást, és így tovább. Így a csomagolt szekvencia egyetlen vektorként van ábrázolva – esetünkben `[1,6,9,2,7,3,8,4,5]`, valamint egy hosszvektorral (`[5,3,1]`), amelyből könnyen visszaállíthatjuk az eredeti kitöltött minibatch-et.\n",
|
||
"\n",
|
||
"A csomagolt szekvencia előállításához használhatjuk a `torch.nn.utils.rnn.pack_padded_sequence` függvényt. Minden rekurrens réteg, beleértve az RNN-t, LSTM-et és GRU-t, támogatja a csomagolt szekvenciákat bemenetként, és csomagolt kimenetet állít elő, amelyet a `torch.nn.utils.rnn.pad_packed_sequence` segítségével dekódolhatunk.\n",
|
||
"\n",
|
||
"Ahhoz, hogy csomagolt szekvenciát tudjunk előállítani, át kell adnunk a hosszvektort a hálózatnak, ezért egy másik függvényre van szükségünk a minibatch-ek előkészítéséhez:\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": [
|
||
"A tényleges hálózat nagyon hasonló lenne a fentebb említett `LSTMClassifier`-hez, de a `forward` metódus mind a kitöltött minibatch-et, mind a szekvenciahosszak vektorát megkapja bemenetként. Az embedding kiszámítása után létrehozzuk a csomagolt szekvenciát, továbbítjuk az LSTM rétegnek, majd az eredményt visszacsomagoljuk.\n",
|
||
"\n",
|
||
"> **Megjegyzés**: Valójában nem használjuk a visszacsomagolt `x` eredményt, mivel a következő számításokban a rejtett rétegek kimenetét használjuk. Ezért ezt a visszacsomagolást teljesen el is hagyhatjuk ebből a kódból. Azért helyeztük ide, hogy könnyen módosíthasd a kódot, ha a hálózat kimenetére lenne szükséged további számítások során.\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": [
|
||
"> **Megjegyzés:** Észrevehetted a `use_pack_sequence` paramétert, amelyet az oktatási függvénynek adunk át. Jelenleg a `pack_padded_sequence` függvény megköveteli, hogy a hosszúsági szekvencia tensor a CPU eszközön legyen, ezért az oktatási függvénynek el kell kerülnie, hogy a hosszúsági szekvencia adatokat a GPU-ra mozgassa az oktatás során. Megnézheted a `train_emb` függvény implementációját a [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) fájlban.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Kétirányú és többrétegű RNN-ek\n",
|
||
"\n",
|
||
"Példáinkban minden rekurrens hálózat egy irányban működött, a szekvencia elejétől a végéig. Ez természetesnek tűnik, mivel hasonlít arra, ahogyan olvasunk vagy beszédet hallgatunk. Azonban sok gyakorlati esetben véletlenszerűen hozzáférhetünk a bemeneti szekvenciához, így érdemes lehet a rekurrens számítást mindkét irányban futtatni. Az ilyen hálózatokat **kétirányú** RNN-eknek nevezzük, és létrehozhatók az `bidirectional=True` paraméter átadásával az RNN/LSTM/GRU konstruktorának.\n",
|
||
"\n",
|
||
"Kétirányú hálózat esetén két rejtett állapotvektorra van szükségünk, egy-egy az egyes irányokhoz. A PyTorch ezeket a vektorokat egy kétszer nagyobb méretű vektorként kódolja, ami elég kényelmes, mivel általában a kapott rejtett állapotot egy teljesen összekapcsolt lineáris réteghez továbbítjuk, és csak figyelembe kell venni ezt a méretnövekedést a réteg létrehozásakor.\n",
|
||
"\n",
|
||
"A rekurrens hálózat, legyen az egyirányú vagy kétirányú, bizonyos mintázatokat rögzít egy szekvencián belül, és ezeket tárolhatja az állapotvektorban vagy továbbíthatja a kimenetbe. Akárcsak a konvolúciós hálózatok esetében, egy másik rekurrens réteget építhetünk az első fölé, hogy magasabb szintű mintázatokat rögzítsünk, amelyeket az első réteg által kinyert alacsony szintű mintázatokból építünk fel. Ez vezet el minket a **többrétegű RNN** fogalmához, amely két vagy több rekurrens hálózatból áll, ahol az előző réteg kimenete bemenetként kerül a következő réteghez.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"*Kép Fernando López [ezen csodálatos bejegyzéséből](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3)*\n",
|
||
"\n",
|
||
"A PyTorch megkönnyíti az ilyen hálózatok létrehozását, mivel csak az `num_layers` paramétert kell átadni az RNN/LSTM/GRU konstruktorának, hogy automatikusan több rekurrens réteget építsen. Ez azt is jelenti, hogy a rejtett/állapotvektor mérete arányosan növekedni fog, és ezt figyelembe kell venni a rekurrens rétegek kimenetének kezelésénél.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## RNN-ek más feladatokra\n",
|
||
"\n",
|
||
"Ebben az egységben láttuk, hogy az RNN-ek használhatók szekvenciaosztályozásra, de valójában sok más feladatot is képesek kezelni, például szöveggenerálást, gépi fordítást és még sok mást. Ezeket a feladatokat a következő egységben fogjuk megvizsgálni.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Felelősség kizárása**: \nEz a dokumentum az AI fordítási szolgáltatás, a [Co-op Translator](https://github.com/Azure/co-op-translator) segítségével lett lefordítva. Bár törekszünk a pontosságra, kérjük, vegye figyelembe, hogy az automatikus fordítások hibákat vagy pontatlanságokat tartalmazhatnak. Az eredeti dokumentum az eredeti nyelvén tekintendő hiteles forrásnak. Kritikus információk esetén javasolt professzionális emberi fordítást igénybe venni. Nem vállalunk felelősséget semmilyen félreértésért vagy téves értelmezésért, amely a fordítás használatából eredhet.\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-29T16:20:29+00:00",
|
||
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
||
"language_code": "hu"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |