726 lines
27 KiB
Plaintext
726 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Sisestused\n",
|
|
"\n",
|
|
"Eelmises näites töötasime kõrgedimensionaalsete sõnakottide vektoritega, mille pikkus oli `vocab_size`, ja teisendasime madaladimensionaalsed positsiooniesituse vektorid selgesõnaliselt hõredaks ühekuumaks esitusviisiks. See ühekuum esitusviis ei ole mälusäästlik, lisaks käsitletakse iga sõna üksteisest sõltumatult, st ühekuum kodeeritud vektorid ei väljenda sõnade vahel semantilist sarnasust.\n",
|
|
"\n",
|
|
"Selles osas jätkame **News AG** andmestiku uurimist. Alustuseks laadime andmed ja võtame mõned määratlused eelmisest märkmikust.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
|
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
|
]
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Building vocab...\n",
|
|
"Vocab size = 95812\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import torch\n",
|
|
"import torchtext\n",
|
|
"import numpy as np\n",
|
|
"from torchnlp import *\n",
|
|
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(\"Vocab size = \",vocab_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Mis on sisestamine?\n",
|
|
"\n",
|
|
"**Sisestamise** idee seisneb selles, et sõnu esitatakse madalama dimensiooniga tihedate vektoritega, mis mingil moel kajastavad sõna semantilist tähendust. Hiljem arutame, kuidas luua tähenduslikke sõna sisestusi, kuid praegu mõtleme sisestustest lihtsalt kui viisist vähendada sõna vektori dimensioonilisust.\n",
|
|
"\n",
|
|
"Sisestuskiht võtaks sõna sisendina ja annaks väljundvektori määratud `embedding_size` suurusega. Teatud mõttes on see väga sarnane `Linear` kihiga, kuid selle asemel, et võtta ühekuumkodeeritud vektor, suudab see võtta sisendiks sõna numbri.\n",
|
|
"\n",
|
|
"Kasutades sisestuskihti meie võrgu esimesena kihina, saame liikuda sõnakoti mudelilt **sisestuskoti** mudelile, kus esmalt teisendame iga sõna tekstis vastavaks sisestuseks ja seejärel arvutame nende sisestuste üle mingi koondfunktsiooni, näiteks `sum`, `average` või `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Meie klassifikaatori närvivõrk algab sisestuskihiga, millele järgneb koondamiskihiga ja lineaarne klassifikaator selle peal:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" x = self.embedding(x)\n",
|
|
" x = torch.mean(x,dim=1)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Töötamine muutuva järjestuse suurusega\n",
|
|
"\n",
|
|
"Sellise arhitektuuri tulemusena tuleb meie võrgule minibatche luua kindlal viisil. Eelmises osas, kui kasutasime sõnakottide meetodit (bag-of-words), olid kõik BoW tensorid minibatchis ühesuurused, `vocab_size`, sõltumata meie tekstijärjestuse tegelikust pikkusest. Kui liigume sõnaembeddingsite kasutamisele, siis jõuame olukorda, kus igas tekstinäidises on erinev arv sõnu, ja nende näidiste minibatchidesse kombineerimisel peame rakendama täitmist (padding).\n",
|
|
"\n",
|
|
"Seda saab teha, kasutades sama tehnikat, pakkudes andmeallikale `collate_fn` funktsiooni:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def padify(b):\n",
|
|
" # b is the list of tuples of length batch_size\n",
|
|
" # - first element of a tuple = label, \n",
|
|
" # - second = feature (text sequence)\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # first, compute max length of a sequence in this minibatch\n",
|
|
" l = max(map(len,v))\n",
|
|
" return ( # tuple of two tensors - labels and features\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",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Embedding klassifikaatori treenimine\n",
|
|
"\n",
|
|
"Nüüd, kui oleme määratlenud sobiva andmete laadija, saame mudelit treenida, kasutades treenimisfunktsiooni, mille määratlesime eelmises osas:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6415625\n",
|
|
"6400: acc=0.6865625\n",
|
|
"9600: acc=0.7103125\n",
|
|
"12800: acc=0.726953125\n",
|
|
"16000: acc=0.739375\n",
|
|
"19200: acc=0.75046875\n",
|
|
"22400: acc=0.7572321428571429\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.889799795315499, 0.7623160588611644)"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Märkus**: Treenime siin ainult 25 000 kirje jaoks (vähem kui üks täistsükkel) aja säästmiseks, kuid võite jätkata treenimist, kirjutada funktsiooni mitme tsükli treenimiseks ja katsetada õppemäära parameetriga, et saavutada suurem täpsus. Peaksite suutma saavutada umbes 90% täpsuse.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### EmbeddingBag kiht ja muutuva pikkusega järjestuste esitus\n",
|
|
"\n",
|
|
"Eelmises arhitektuuris pidime kõik järjestused täitma sama pikkuseni, et need minibatch'i sobiksid. See ei ole kõige tõhusam viis muutuva pikkusega järjestuste esitamiseks - teine lähenemine oleks kasutada **nihke** vektorit, mis sisaldaks kõigi järjestuste nihked, mis on salvestatud ühes suures vektoris.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: Ülaloleval pildil on näidatud tähemärkide järjestus, kuid meie näites töötame sõnade järjestustega. Siiski jääb üldine põhimõte järjestuste esitamiseks nihkevektoriga samaks.\n",
|
|
"\n",
|
|
"Nihkevektori esitusega töötamiseks kasutame [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) kihti. See on sarnane `Embedding`-iga, kuid võtab sisendiks sisuvektori ja nihkevektori ning sisaldab ka keskmistamise kihti, mis võib olla `mean`, `sum` või `max`.\n",
|
|
"\n",
|
|
"Siin on muudetud võrk, mis kasutab `EmbeddingBag`-i:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class EmbedClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
|
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, text, off):\n",
|
|
" x = self.embedding(text, off)\n",
|
|
" return self.fc(x)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Ettevalmistamaks andmekogumit treenimiseks, peame pakkuma teisendusfunktsiooni, mis valmistab ette nihkevektori:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Pange tähele, et erinevalt kõigist eelmistest näidetest võtab meie võrk nüüd vastu kaks parameetrit: andmevektori ja nihkevektori, mis on erineva suurusega. Samamoodi annab meie andmete laadija meile 3 väärtust 2 asemel: nii teksti- kui ka nihkevektorid antakse funktsioonidena. Seetõttu peame oma treeningfunktsiooni veidi kohandama, et sellega arvestada:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6153125\n",
|
|
"6400: acc=0.6615625\n",
|
|
"9600: acc=0.6932291666666667\n",
|
|
"12800: acc=0.715078125\n",
|
|
"16000: acc=0.7270625\n",
|
|
"19200: acc=0.7382291666666667\n",
|
|
"22400: acc=0.7486160714285715\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(22.771553103007037, 0.7551983365323096)"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
|
"\n",
|
|
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
|
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
|
" loss_fn = loss_fn.to(device)\n",
|
|
" net.train()\n",
|
|
" total_loss,acc,count,i = 0,0,0,0\n",
|
|
" for labels,text,off in dataloader:\n",
|
|
" optimizer.zero_grad()\n",
|
|
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
|
" out = net(text, off)\n",
|
|
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
|
" loss.backward()\n",
|
|
" optimizer.step()\n",
|
|
" total_loss+=loss\n",
|
|
" _,predicted = torch.max(out,1)\n",
|
|
" acc+=(predicted==labels).sum()\n",
|
|
" count+=len(labels)\n",
|
|
" i+=1\n",
|
|
" if i%report_freq==0:\n",
|
|
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
|
" if epoch_size and count>epoch_size:\n",
|
|
" break\n",
|
|
" return total_loss.item()/count, acc.item()/count\n",
|
|
"\n",
|
|
"\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Semantilisedastused: Word2Vec\n",
|
|
"\n",
|
|
"Eelmises näites õppis mudeli sisestuskiht kaardistama sõnu vektoriesitusteks, kuid sellel esitlusel puudus suurem semantiline tähendus. Oleks kasulik õppida sellist vektoriesitust, kus sarnased sõnad või sünonüümid vastaksid vektoritele, mis on teatud vektorkauguse (nt eukleidiline kaugus) mõttes üksteisele lähedal.\n",
|
|
"\n",
|
|
"Selle saavutamiseks peame oma sisestusmudeli eelnevalt treenima suure tekstikogu peal kindlal viisil. Üks esimesi meetodeid semantiliste esinduste treenimiseks on [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). See põhineb kahel peamisel arhitektuuril, mida kasutatakse sõnade hajutatud esituse loomiseks:\n",
|
|
"\n",
|
|
" - **Järjepidev sõnakott** (CBoW) — selles arhitektuuris treenime mudelit ennustama sõna ümbritseva konteksti põhjal. Arvestades ngrammi $(W_{-2},W_{-1},W_0,W_1,W_2)$, on mudeli eesmärk ennustada $W_0$ $(W_{-2},W_{-1},W_1,W_2)$ põhjal.\n",
|
|
" - **Järjepidev vahegramm** (Continuous skip-gram) on CBoW vastand. Mudel kasutab ümbritsevat kontekstiakent, et ennustada praegust sõna.\n",
|
|
"\n",
|
|
"CBoW on kiirem, samas kui vahegramm on aeglasem, kuid esindab haruldasi sõnu paremini.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Et katsetada Word2Vec-i sisestust, mis on eelnevalt treenitud Google News andmekogul, saame kasutada **gensim** teeki. Allpool otsime sõnu, mis on kõige sarnasemad sõnale 'neural'.\n",
|
|
"\n",
|
|
"> **Märkus:** Kui loote esimest korda sõnavektoreid, võib nende allalaadimine võtta aega!\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import gensim.downloader as api\n",
|
|
"w2v = api.load('word2vec-google-news-300')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"neuronal -> 0.7804799675941467\n",
|
|
"neurons -> 0.7326500415802002\n",
|
|
"neural_circuits -> 0.7252851724624634\n",
|
|
"neuron -> 0.7174385190010071\n",
|
|
"cortical -> 0.6941086649894714\n",
|
|
"brain_circuitry -> 0.6923246383666992\n",
|
|
"synaptic -> 0.6699118614196777\n",
|
|
"neural_circuitry -> 0.6638563275337219\n",
|
|
"neurochemical -> 0.6555314064025879\n",
|
|
"neuronal_activity -> 0.6531826257705688\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"for w,p in w2v.most_similar('neural'):\n",
|
|
" print(f\"{w} -> {p}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Saame arvutada ka sõna vektorite sisestusi, mida kasutatakse klassifitseerimismudeli treenimisel (selguse huvides näitame ainult vektori esimesi 20 komponenti):\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
|
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
|
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
|
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
|
" dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.word_vec('play')[:20]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Suurepärane asi semantiliste sisestuste juures on see, et saate vektorkodeeringut manipuleerida, et muuta semantikat. Näiteks võime paluda leida sõna, mille vektorrepresentatsioon oleks võimalikult lähedal sõnadele *kuningas* ja *naine*, ning võimalikult kaugel sõnast *mees*:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"('queen', 0.7118192911148071)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nii CBoW kui ka Skip-Grams on \"ennustavad\" sõnaesitused, kuna need arvestavad ainult kohalikke kontekste. Word2Vec ei kasuta ära globaalset konteksti.\n",
|
|
"\n",
|
|
"**FastText** põhineb Word2Vec-il, õppides vektoriesitusi iga sõna ja sõnas leiduvate tähemärkide n-grammide jaoks. Esituste väärtused keskmistatakse igal treeningusammul üheks vektoriks. Kuigi see lisab eeltreeningule palju lisaarvutusi, võimaldab see sõnaesitustel kodeerida alam-sõna teavet.\n",
|
|
"\n",
|
|
"Teine meetod, **GloVe**, kasutab koosesinemise maatriksi ideed ja rakendab närvivõrkude meetodeid, et lagundada koosesinemise maatriks väljendusrikkamateks ja mittelineaarseteks sõnavektoriteks.\n",
|
|
"\n",
|
|
"Saate näitega mängida, muutes sõnaesitusi FastTextiks ja GloVe'iks, kuna gensim toetab mitmeid erinevaid sõnaesitusmudeleid.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Eelnevalt treenitud sisendvektorite kasutamine PyTorchis\n",
|
|
"\n",
|
|
"Saame ülaltoodud näidet muuta nii, et täidame oma sisendvektori kihi maatriksi semantiliste sisendvektoritega, nagu Word2Vec. Tuleb arvestada, et eelnevalt treenitud sisendvektorite ja meie tekstikorpuse sõnavarad ei pruugi kokku langeda, mistõttu algväärtustame puuduvate sõnade kaalud juhuslike väärtustega:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Embedding size: 300\n",
|
|
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"embed_size = len(w2v.get_vector('hello'))\n",
|
|
"print(f'Embedding size: {embed_size}')\n",
|
|
"\n",
|
|
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
|
"\n",
|
|
"print('Populating matrix, this will take some time...',end='')\n",
|
|
"found, not_found = 0,0\n",
|
|
"for i,w in enumerate(vocab.get_itos()):\n",
|
|
" try:\n",
|
|
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
|
" found+=1\n",
|
|
" except:\n",
|
|
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
|
" not_found+=1\n",
|
|
"\n",
|
|
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nüüd treenime oma mudelit. Pange tähele, et mudeli treenimiseks kuluv aeg on oluliselt pikem kui eelmises näites, kuna sisestuskihi suurus on suurem ja seega ka parameetrite arv palju suurem. Samuti, selle tõttu võib olla vajalik treenida mudelit rohkemate näidete peal, kui soovime vältida üleõppimist.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6359375\n",
|
|
"6400: acc=0.68109375\n",
|
|
"9600: acc=0.7067708333333333\n",
|
|
"12800: acc=0.723671875\n",
|
|
"16000: acc=0.73625\n",
|
|
"19200: acc=0.7463541666666667\n",
|
|
"22400: acc=0.7560714285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(214.1013875559821, 0.7626759436980166)"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Meie puhul ei näe me suurt täpsuse kasvu, mis tõenäoliselt tuleneb üsna erinevatest sõnavaradest. \n",
|
|
"Erinevate sõnavarade probleemi lahendamiseks saame kasutada ühte järgmistest lahendustest:\n",
|
|
"* Treenida word2vec mudel uuesti meie sõnavara põhjal\n",
|
|
"* Laadida meie andmestik eelnevalt treenitud word2vec mudeli sõnavaraga. Sõnavara, mida kasutatakse andmestiku laadimiseks, saab määrata laadimise ajal.\n",
|
|
"\n",
|
|
"Viimane lähenemine tundub lihtsam, eriti kuna PyTorch `torchtext` raamistik sisaldab sisseehitatud tuge sisendvektoritele. Näiteks saame GloVe-põhise sõnavara luua järgmiselt:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stderr",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Laaditud sõnavaral on järgmised põhitoimingud:\n",
|
|
"* `vocab.stoi` sõnastik võimaldab meil muuta sõna selle sõnastiku indeksiks\n",
|
|
"* `vocab.itos` teeb vastupidist - muudab numbri sõnaks\n",
|
|
"* `vocab.vectors` on maatriks sisendvektoritest, seega sõna `s` sisendvektori saamiseks peame kasutama `vocab.vectors[vocab.stoi[s]]`\n",
|
|
"\n",
|
|
"Siin on näide sisendvektoritega manipuleerimisest, et näidata võrrandit **kind-man+woman = queen** (pidin koefitsienti veidi kohandama, et see toimiks):\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'queen'"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# get the vector corresponding to kind-man+woman\n",
|
|
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
|
"# find the index of the closest embedding vector \n",
|
|
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
|
"min_idx = torch.argmin(d)\n",
|
|
"# find the corresponding word\n",
|
|
"vocab.itos[min_idx]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Klassifikaatori treenimiseks nende sisendite abil peame esmalt kodeerima oma andmekogu GloVe'i sõnavara abil:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def offsetify(b):\n",
|
|
" # first, compute data tensor from all sequences\n",
|
|
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
|
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
|
" o = [0] + [len(t) for t in x]\n",
|
|
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
|
" return ( \n",
|
|
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
|
" torch.cat(x), # text \n",
|
|
" o\n",
|
|
" )"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nagu me eespool nägime, salvestatakse kõik vektori manused `vocab.vectors` maatriksisse. See teeb nende kaalude laadimise manustamiskihi kaaludesse lihtsa kopeerimise abil väga lihtsaks:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
|
"net.embedding.weight.data = vocab.vectors\n",
|
|
"net = net.to(device)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nüüd treenime oma mudelit ja vaatame, kas saame paremaid tulemusi:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6271875\n",
|
|
"6400: acc=0.68078125\n",
|
|
"9600: acc=0.7030208333333333\n",
|
|
"12800: acc=0.71984375\n",
|
|
"16000: acc=0.7346875\n",
|
|
"19200: acc=0.7455729166666667\n",
|
|
"22400: acc=0.7529464285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(35.53972978646833, 0.7575175943698017)"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Üks põhjusi, miks me ei näe märkimisväärset täpsuse suurenemist, on see, et mõned meie andmekogumi sõnad puuduvad eeltreenitud GloVe'i sõnavaras ja seetõttu neid sisuliselt ignoreeritakse. Selle probleemi ületamiseks saame treenida omaenda sõnaembeddingsid meie andmekogumi põhjal.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Kontekstuaalsed sisendvektorid\n",
|
|
"\n",
|
|
"Üks peamisi traditsiooniliste eeltreenitud sisendvektorite, nagu Word2Vec, piiranguid on sõna tähenduse eristamise probleem. Kuigi eeltreenitud sisendvektorid suudavad mingil määral tabada sõnade tähendust kontekstis, kodeeritakse iga sõna võimalikud tähendused samasse sisendvektorisse. See võib põhjustada probleeme järgnevatel mudelitel, kuna paljudel sõnadel, näiteks sõnal 'play', on erinevad tähendused sõltuvalt kontekstist, milles neid kasutatakse.\n",
|
|
"\n",
|
|
"Näiteks sõnal 'play' on nendes kahes lauses üsna erinev tähendus:\n",
|
|
"- Ma käisin teatris **etendust** vaatamas.\n",
|
|
"- John tahab oma sõpradega **mängida**.\n",
|
|
"\n",
|
|
"Eeltreenitud sisendvektorid esindavad mõlemat tähendust sõnast 'play' samas sisendvektoris. Selle piirangu ületamiseks peame looma sisendvektorid, mis põhinevad **keelemudelil**, mis on treenitud suurel tekstikorpusel ja *teab*, kuidas sõnu saab erinevates kontekstides kokku panna. Kontekstuaalsete sisendvektorite arutelu jääb selle õpetuse raamidest välja, kuid me tuleme nende juurde tagasi, kui räägime keelemudelitest järgmises osas.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Lahtiütlus**: \nSee dokument on tõlgitud AI tõlketeenuse [Co-op Translator](https://github.com/Azure/co-op-translator) abil. Kuigi püüame tagada täpsust, palume arvestada, et automaatsed tõlked võivad sisaldada vigu või ebatäpsusi. Algne dokument selle algses keeles tuleks pidada autoriteetseks allikaks. Olulise teabe puhul soovitame kasutada professionaalset inimtõlget. Me ei vastuta selle tõlke kasutamisest tulenevate arusaamatuste või valesti tõlgenduste eest.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_pytorch",
|
|
"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": "f50b026abce5cf36783a560ea72cb9b1",
|
|
"translation_date": "2025-10-11T12:41:38+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "et"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |