479 lines
24 KiB
Plaintext
479 lines
24 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Toistuvat neuroverkot\n",
|
|
"\n",
|
|
"Edellisessä moduulissa käytimme tekstin semanttisesti rikkaita esityksiä ja yksinkertaista lineaarista luokitinta upotusten päällä. Tämä arkkitehtuuri pystyy vangitsemaan sanojen yhdistetyn merkityksen lauseessa, mutta se ei ota huomioon sanojen **järjestystä**, koska upotusten päälle tehty yhdistämisoperaatio poistaa tämän tiedon alkuperäisestä tekstistä. Koska nämä mallit eivät pysty mallintamaan sanojen järjestystä, ne eivät voi ratkaista monimutkaisempia tai epäselvempiä tehtäviä, kuten tekstin generointia tai kysymyksiin vastaamista.\n",
|
|
"\n",
|
|
"Jotta voisimme ymmärtää tekstisekvenssin merkityksen, meidän täytyy käyttää toista neuroverkkoarkkitehtuuria, jota kutsutaan **toistuvaksi neuroverkoksi** eli RNN:ksi. RNN:ssä syötämme lauseen verkon läpi yksi symboli kerrallaan, ja verkko tuottaa jonkin **tilan**, jonka syötämme verkkoon uudelleen seuraavan symbolin kanssa.\n",
|
|
"\n",
|
|
"Annettua syötesequenssia $X_0,\\dots,X_n$ käyttäen RNN luo neuroverkkolohkojen sekvenssin ja opettaa tämän sekvenssin päästä päähän takaisinlevityksen avulla. Jokainen verkkolohko ottaa syötteenä parin $(X_i,S_i)$ ja tuottaa tuloksena $S_{i+1}$. Lopullinen tila $S_n$ tai ulostulo $X_n$ syötetään lineaariseen luokittimeen tuloksen tuottamiseksi. Kaikilla verkkolohkoilla on samat painot, ja ne opetetaan päästä päähän yhdellä takaisinlevityskierroksella.\n",
|
|
"\n",
|
|
"Koska tilavektorit $S_0,\\dots,S_n$ kulkevat verkon läpi, se pystyy oppimaan sanojen väliset sekventiaaliset riippuvuudet. Esimerkiksi, kun sana *not* esiintyy jossain sekvenssissä, se voi oppia kumoamaan tiettyjä elementtejä tilavektorissa, mikä johtaa negaatioon.\n",
|
|
"\n",
|
|
"> Koska kaikkien RNN-lohkojen painot kuvassa ovat jaettuja, sama kuva voidaan esittää yhtenä lohkona (oikealla) toistuvalla palautesilmukalla, joka syöttää verkon ulostulotilan takaisin syötteeseen.\n",
|
|
"\n",
|
|
"Katsotaanpa, kuinka toistuvat neuroverkot voivat auttaa meitä luokittelemaan uutisaineistomme.\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": [
|
|
"## Yksinkertainen RNN-luokitin\n",
|
|
"\n",
|
|
"Yksinkertaisessa RNN:ssä jokainen rekursiivinen yksikkö on yksinkertainen lineaarinen verkko, joka ottaa yhdistetyn syötevektorin ja tilavektorin ja tuottaa uuden tilavektorin. PyTorch edustaa tätä yksikköä `RNNCell`-luokalla, ja tällaisten solujen verkkoa - `RNN`-kerroksena.\n",
|
|
"\n",
|
|
"RNN-luokittimen määrittämiseksi käytämme ensin upotuskerrosta syötesanaston ulottuvuuden pienentämiseksi ja sen jälkeen RNN-kerrosta sen päällä:\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": [
|
|
"> **Huom:** Käytämme tässä yksinkertaisuuden vuoksi kouluttamatonta upotuskerrosta, mutta vielä parempien tulosten saavuttamiseksi voimme käyttää valmiiksi koulutettua upotuskerrosta, kuten Word2Vec- tai GloVe-upotuksia, jotka on kuvattu edellisessä osiossa. Ymmärtämisen parantamiseksi kannattaa mukauttaa tämä koodi toimimaan valmiiksi koulutettujen upotusten kanssa.\n",
|
|
"\n",
|
|
"Tässä tapauksessa käytämme täydennettyä datalaturia, joten jokaisessa erässä on sama määrä täydennettyjä sekvenssejä, jotka ovat samanpituisia. RNN-kerros ottaa upotustensoreiden sekvenssin ja tuottaa kaksi ulostuloa:\n",
|
|
"* $x$ on RNN-solujen ulostulojen sekvenssi jokaisessa vaiheessa\n",
|
|
"* $h$ on viimeisen sekvenssin elementin lopullinen piilotettu tila\n",
|
|
"\n",
|
|
"Sen jälkeen sovellamme täysin yhdistettyä lineaarista luokittelijaa saadaksemme luokkien määrän.\n",
|
|
"\n",
|
|
"> **Huom:** RNN:t ovat melko vaikeita kouluttaa, koska kun RNN-solut puretaan sekvenssin pituuden mukaan, takaisinkuljetuksessa mukana olevien kerrosten määrä kasvaa huomattavasti. Siksi meidän täytyy valita pieni oppimisnopeus ja kouluttaa verkkoa suuremmalla datamäärällä hyvien tulosten saavuttamiseksi. Tämä voi viedä melko paljon aikaa, joten GPU:n käyttö on suositeltavaa.\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": [
|
|
"## Pitkäkestoiset lyhytmuistiyksiköt (LSTM)\n",
|
|
"\n",
|
|
"Yksi klassisten RNN-verkkojen suurimmista ongelmista on niin sanottu **häviävien gradienttien** ongelma. Koska RNN-verkkoja koulutetaan päästä päähän yhdessä takaisinlevitysvaiheessa, niillä on vaikeuksia välittää virhettä verkon ensimmäisille kerroksille, minkä vuoksi verkko ei pysty oppimaan suhteita kaukaisten tokenien välillä. Yksi tapa välttää tämä ongelma on ottaa käyttöön **eksplisiittinen tilanhallinta** käyttämällä niin sanottuja **portteja**. Tämän tyyppisistä arkkitehtuureista tunnetuimmat ovat **pitkäkestoiset lyhytmuistiyksiköt** (LSTM) ja **porttiohjattu reläyksikkö** (GRU).\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"LSTM-verkko on järjestetty samalla tavalla kuin RNN, mutta siinä on kaksi tilaa, jotka siirtyvät kerrokselta toiselle: varsinainen tila $c$ ja piilovektori $h$. Jokaisessa yksikössä piilovektori $h_i$ yhdistetään syötteeseen $x_i$, ja ne ohjaavat, mitä tilalle $c$ tapahtuu **porttien** kautta. Jokainen portti on hermoverkko, jossa on sigmoid-aktivaatio (tuotos alueella $[0,1]$), ja sitä voidaan ajatella bittimaskina, kun se kerrotaan tilavektorilla. Kuvan yllä vasemmalta oikealle portit ovat seuraavat:\n",
|
|
"* **Unohtamisportti** ottaa piilovektorin ja määrittää, mitkä vektorin $c$ komponentit tulee unohtaa ja mitkä päästää läpi.\n",
|
|
"* **Syöteportti** ottaa tietoa syötteestä ja piilovektorista ja lisää sen tilaan.\n",
|
|
"* **Ulostuloportti** muuntaa tilan jonkin lineaarisen kerroksen kautta, jossa on $\\tanh$-aktivaatio, ja valitsee sitten joitakin sen komponentteja piilovektorin $h_i$ avulla tuottaakseen uuden tilan $c_{i+1}$.\n",
|
|
"\n",
|
|
"Tilan $c$ komponentteja voidaan ajatella eräänlaisina lippuina, joita voidaan kytkeä päälle ja pois päältä. Esimerkiksi, kun kohtaamme sekvenssissä nimen *Alice*, voimme olettaa, että se viittaa naispuoliseen hahmoon, ja nostaa tilassa lipun, joka ilmaisee, että lauseessa on naispuolinen substantiivi. Kun myöhemmin kohtaamme ilmauksen *and Tom*, nostamme lipun, joka ilmaisee, että kyseessä on monikollinen substantiivi. Näin manipuloimalla tilaa voimme mahdollisesti seurata lauseen osien kieliopillisia ominaisuuksia.\n",
|
|
"\n",
|
|
"> **Note**: Erinomainen resurssi LSTM:n sisäisen rakenteen ymmärtämiseen on Christopher Olahin loistava artikkeli [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n",
|
|
"\n",
|
|
"Vaikka LSTM-solun sisäinen rakenne saattaa näyttää monimutkaiselta, PyTorch piilottaa tämän toteutuksen `LSTMCell`-luokan sisälle ja tarjoaa `LSTM`-objektin koko LSTM-kerroksen esittämiseen. Näin ollen LSTM-luokittelijan toteutus on melko samanlainen kuin yksinkertaisen RNN:n, jonka näimme aiemmin:\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": [
|
|
"## Pakatut sekvenssit\n",
|
|
"\n",
|
|
"Esimerkissämme jouduimme täyttämään kaikki minibatchin sekvenssit nollavektoreilla. Vaikka tämä aiheuttaa jonkin verran muistin hukkaa, RNN:ien kohdalla on vielä kriittisempää, että täytetyt syötteet luovat ylimääräisiä RNN-soluja, jotka osallistuvat koulutukseen, mutta eivät sisällä mitään merkittävää syötetietoa. Olisi paljon parempi kouluttaa RNN vain todellisen sekvenssin pituuden mukaan.\n",
|
|
"\n",
|
|
"Tätä varten PyTorchissa on otettu käyttöön erityinen täytettyjen sekvenssien tallennusmuoto. Oletetaan, että meillä on täytetty minibatch, joka näyttää tältä:\n",
|
|
"```\n",
|
|
"[[1,2,3,4,5],\n",
|
|
" [6,7,8,0,0],\n",
|
|
" [9,0,0,0,0]]\n",
|
|
"```\n",
|
|
"Tässä 0 edustaa täytettyjä arvoja, ja syötteen sekvenssien todellinen pituusvektori on `[5,3,1]`.\n",
|
|
"\n",
|
|
"Jotta RNN voidaan kouluttaa tehokkaasti täytetyillä sekvensseillä, haluamme aloittaa ensimmäisen RNN-soluryhmän koulutuksen suurella minibatchilla (`[1,6,9]`), mutta sitten lopettaa kolmannen sekvenssin käsittelyn ja jatkaa lyhennetyillä minibatcheilla (`[2,7]`, `[3,8]`) ja niin edelleen. Näin ollen pakattu sekvenssi esitetään yhtenä vektorina - tässä tapauksessa `[1,6,9,2,7,3,8,4,5]`, ja pituusvektorina (`[5,3,1]`), jonka avulla alkuperäinen täytetty minibatch voidaan helposti rekonstruoida.\n",
|
|
"\n",
|
|
"Pakattujen sekvenssien tuottamiseen voidaan käyttää `torch.nn.utils.rnn.pack_padded_sequence`-funktiota. Kaikki rekursiiviset kerrokset, mukaan lukien RNN, LSTM ja GRU, tukevat pakattuja sekvenssejä syötteenä ja tuottavat pakatun ulostulon, joka voidaan purkaa käyttämällä `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
|
"\n",
|
|
"Jotta voimme tuottaa pakatun sekvenssin, meidän täytyy välittää pituusvektori verkolle, ja siksi tarvitsemme erilaisen funktion minibatchien valmisteluun:\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": [
|
|
"Todellinen verkko olisi hyvin samanlainen kuin yllä oleva `LSTMClassifier`, mutta `forward`-kutsussa vastaanotetaan sekä täytetty minibatch että sekvenssipituuksien vektori. Kun upotus on laskettu, muodostamme pakatun sekvenssin, välitämme sen LSTM-kerrokselle ja puramme tuloksen takaisin.\n",
|
|
"\n",
|
|
"> **Huomio**: Emme itse asiassa käytä purettua tulosta `x`, koska käytämme piilotettujen kerrosten tuottamaa ulostuloa seuraavissa laskelmissa. Näin ollen voimme poistaa purkamisen kokonaan tästä koodista. Syy, miksi se on tässä, on se, että voit helposti muokata tätä koodia, jos sinun tarvitsee käyttää verkon ulostuloa myöhemmissä laskelmissa.\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": [
|
|
"> **Huom:** Olet saattanut huomata parametrin `use_pack_sequence`, jonka välitämme koulutusfunktiolle. Tällä hetkellä `pack_padded_sequence`-funktio vaatii, että pituussekvenssin tensorin tulee olla CPU-laitteella, ja siksi koulutusfunktion täytyy välttää pituussekvenssidatan siirtämistä GPU:lle koulutuksen aikana. Voit tarkastella `train_emb`-funktion toteutusta [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py)-tiedostossa.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Kaksisuuntaiset ja monikerroksiset RNN:t\n",
|
|
"\n",
|
|
"Esimerkeissämme kaikki toistuvat verkot toimivat yhteen suuntaan, sekvenssin alusta loppuun. Tämä vaikuttaa luonnolliselta, koska se muistuttaa tapaa, jolla luemme ja kuuntelemme puhetta. Kuitenkin monissa käytännön tapauksissa meillä on satunnainen pääsy syötesekvenssiin, joten voi olla järkevää suorittaa toistuva laskenta molempiin suuntiin. Tällaisia verkkoja kutsutaan **kaksisuuntaisiksi** RNN:iksi, ja ne voidaan luoda lisäämällä `bidirectional=True` -parametri RNN/LSTM/GRU-rakentajaan.\n",
|
|
"\n",
|
|
"Kun käsittelemme kaksisuuntaista verkkoa, tarvitsemme kaksi piilotilavektoria, yhden kumpaankin suuntaan. PyTorch koodaa nämä vektorit yhdeksi vektoriksi, jonka koko on kaksinkertainen, mikä on varsin kätevää, koska yleensä välität tuloksena olevan piilotilan täysin yhdistettyyn lineaariseen kerrokseen, ja sinun tarvitsee vain ottaa tämä koon kasvu huomioon kerrosta luodessasi.\n",
|
|
"\n",
|
|
"Toistuva verkko, olipa se yksisuuntainen tai kaksisuuntainen, tunnistaa tiettyjä kuvioita sekvenssissä ja voi tallentaa ne tilavektoriin tai välittää ne ulostuloon. Kuten konvoluutioneuroverkoissa, voimme rakentaa toisen toistuvan kerroksen ensimmäisen päälle tunnistamaan korkeamman tason kuvioita, jotka on muodostettu ensimmäisen kerroksen tunnistamista matalan tason kuvioista. Tämä johtaa käsitteeseen **monikerroksinen RNN**, joka koostuu kahdesta tai useammasta toistuvasta verkosta, joissa edellisen kerroksen ulostulo välitetään seuraavan kerroksen syötteeksi.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Kuva [tästä upeasta artikkelista](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) kirjoittanut Fernando López*\n",
|
|
"\n",
|
|
"PyTorch tekee tällaisten verkkojen rakentamisesta helppoa, koska sinun tarvitsee vain lisätä `num_layers` -parametri RNN/LSTM/GRU-rakentajaan, jolloin useita toistuvia kerroksia luodaan automaattisesti. Tämä tarkoittaa myös sitä, että piilotila-/tilavektorin koko kasvaa suhteellisesti, ja sinun täytyy ottaa tämä huomioon käsitellessäsi toistuvien kerrosten ulostuloa.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN:t muihin tehtäviin\n",
|
|
"\n",
|
|
"Tässä osiossa olemme nähneet, että RNN:t voivat olla hyödyllisiä sekvenssiluokittelussa, mutta itse asiassa ne voivat käsitellä monia muitakin tehtäviä, kuten tekstin generointia, konekäännöstä ja paljon muuta. Tarkastelemme näitä tehtäviä seuraavassa osiossa.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Vastuuvapauslauseke**: \nTämä asiakirja on käännetty käyttämällä tekoälypohjaista käännöspalvelua [Co-op Translator](https://github.com/Azure/co-op-translator). Vaikka pyrimme tarkkuuteen, huomioithan, että automaattiset käännökset voivat sisältää virheitä tai epätarkkuuksia. Alkuperäistä asiakirjaa sen alkuperäisellä kielellä tulisi pitää ensisijaisena lähteenä. Kriittisen tiedon osalta suositellaan ammattimaista ihmiskäännöstä. Emme ole vastuussa väärinkäsityksistä tai virhetulkinnoista, jotka johtuvat tämän käännöksen käytöstä.\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-28T21:49:15+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
|
"language_code": "fi"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |