AI-For-Beginners/translations/fi/lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb

726 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Upotukset\n",
"\n",
"Edellisessä esimerkissämme käsittelimme korkeulotteisia bag-of-words-vektoreita, joiden pituus oli `vocab_size`, ja muunsimme ne eksplisiittisesti matalaulotteisista sijaintiesityksistä harvaan yksi-kuuma-esitykseen. Tämä yksi-kuuma-esitys ei ole muistin kannalta tehokas, ja lisäksi jokainen sana käsitellään toisistaan riippumattomasti, eli yksi-kuuma-koodatut vektorit eivät ilmaise sanojen välistä semanttista samankaltaisuutta.\n",
"\n",
"Tässä osiossa jatkamme **News AG** -aineiston tutkimista. Aloitetaan lataamalla data ja hakemalla joitakin määritelmiä edellisestä muistikirjasta.\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": [
"## Mikä on upotus?\n",
"\n",
"**Upotuksen** idea on esittää sanoja matalampiulotteisina tiheinä vektoreina, jotka jollain tavalla heijastavat sanan semanttista merkitystä. Myöhemmin käsittelemme, kuinka rakentaa merkityksellisiä sanavektoreita, mutta toistaiseksi voimme ajatella upotuksia tapana pienentää sanavektorin ulottuvuuksia.\n",
"\n",
"Upotuskerros ottaa sanan syötteenä ja tuottaa ulostulovektorin, jonka koko on määritelty `embedding_size`. Tietyssä mielessä se on hyvin samanlainen kuin `Linear`-kerros, mutta sen sijaan, että se ottaisi yhden kuuman koodatun vektorin, se pystyy ottamaan sanan numeron syötteenä.\n",
"\n",
"Kun käytämme upotuskerrosta verkkomme ensimmäisenä kerroksena, voimme siirtyä bag-of-words-mallista **embedding bag** -malliin, jossa ensin muutamme jokaisen tekstimme sanan vastaavaksi upotukseksi ja sitten laskemme jonkin aggregaattifunktion kaikkien näiden upotusten yli, kuten `sum`, `average` tai `max`.\n",
"\n",
"![Kuva, joka näyttää upotusluokittelijan viidelle sanajonolle.](../../../../../translated_images/fi/embedding-classifier-example.b77f021a7ee67eee.webp)\n",
"\n",
"Luokittelijaneuroverkkomme alkaa upotuskerroksella, sitten aggregaatiokerroksella ja sen päällä lineaarisella luokittelijalla:\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": [
"### Käsittely muuttuvan kokoisia sekvenssejä\n",
"\n",
"Tämän arkkitehtuurin seurauksena minibatchit verkkoomme täytyy luoda tietyllä tavalla. Edellisessä osiossa, kun käytimme bag-of-words-menetelmää, kaikki BoW-tensorit minibatchissa olivat saman kokoisia, `vocab_size`, riippumatta tekstisekvenssin todellisesta pituudesta. Kun siirrymme sanavektoreihin, päädymme tilanteeseen, jossa jokaisessa tekstinäytteessä on vaihteleva määrä sanoja, ja näytteitä yhdistettäessä minibatcheiksi meidän täytyy käyttää täydennystä.\n",
"\n",
"Tämä voidaan tehdä käyttämällä samaa tekniikkaa, jossa `collate_fn`-funktio annetaan tietolähteelle:\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": [
"### Upotetun luokittelijan kouluttaminen\n",
"\n",
"Nyt kun olemme määrittäneet sopivan datalaturin, voimme kouluttaa mallin käyttämällä edellisessä osiossa määriteltyä koulutustoimintoa:\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": [
"> **Huom**: Tässä harjoittelemme vain 25 000 tietueella (alle yksi kokonainen epookki) ajan säästämiseksi, mutta voit jatkaa harjoittelua, kirjoittaa funktion useiden epookkien harjoitteluun ja kokeilla oppimisnopeuden parametria saavuttaaksesi korkeamman tarkkuuden. Sinun pitäisi pystyä saavuttamaan noin 90 % tarkkuus.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### EmbeddingBag-kerros ja muuttuvan pituisen sekvenssin esitys\n",
"\n",
"Edellisessä arkkitehtuurissa meidän täytyi täyttää kaikki sekvenssit samanpituisiksi, jotta ne sopisivat minibatchiin. Tämä ei ole tehokkain tapa esittää muuttuvan pituisia sekvenssejä toinen lähestymistapa olisi käyttää **offset**-vektoria, joka sisältää kaikkien yhteen suureen vektoriin tallennettujen sekvenssien offsetit.\n",
"\n",
"![Kuva, joka näyttää offset-sekvenssiesityksen](../../../../../translated_images/fi/offset-sequence-representation.eb73fcefb29b46ee.webp)\n",
"\n",
"> **Huom**: Yllä olevassa kuvassa esitetään merkkijonosekvenssi, mutta esimerkissämme työskentelemme sanasekvenssien kanssa. Yleinen periaate sekvenssien esittämisestä offset-vektorilla pysyy kuitenkin samana.\n",
"\n",
"Offset-esityksen kanssa työskentelyyn käytämme [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)-kerrosta. Se on samankaltainen kuin `Embedding`, mutta se ottaa syötteenä sisältövektorin ja offset-vektorin, ja siihen sisältyy myös keskiarvokerros, joka voi olla `mean`, `sum` tai `max`.\n",
"\n",
"Tässä on muokattu verkko, joka käyttää `EmbeddingBag`-kerrosta:\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": [
"Valmistaaksemme tietojoukon koulutusta varten, meidän täytyy tarjota muunnostoiminto, joka valmistaa offset-vektorin:\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": [
"Huomaa, että toisin kuin kaikissa aiemmissa esimerkeissä, verkkomme hyväksyy nyt kaksi parametria: datavektorin ja offset-vektorin, jotka ovat eri kokoisia. Samoin datalaturimme tarjoaa meille 3 arvoa 2 sijaan: sekä teksti- että offset-vektorit tarjotaan ominaisuuksina. Siksi meidän on hieman mukautettava koulutustoimintoamme huolehtimaan tästä:\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": [
"## Semanttiset upotukset: Word2Vec\n",
"\n",
"Edellisessä esimerkissämme mallin upotuskerros oppi kartoittamaan sanat vektorimuotoon, mutta tällä esityksellä ei ollut juurikaan semanttista merkitystä. Olisi hyödyllistä oppia sellainen vektorimuoto, jossa samankaltaiset sanat tai synonyymit vastaisivat toisiaan lähellä olevia vektoreita jonkin vektorietäisyyden (esim. euklidinen etäisyys) perusteella.\n",
"\n",
"Tämän saavuttamiseksi meidän täytyy esikouluttaa upotusmallimme suurella tekstikokoelmalla tietyllä tavalla. Yksi ensimmäisistä tavoista kouluttaa semanttisia upotuksia tunnetaan nimellä [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Se perustuu kahteen pääarkkitehtuuriin, joita käytetään sanojen hajautetun esityksen tuottamiseen:\n",
"\n",
" - **Jatkuva sanapussimalli** (CBoW) — tässä arkkitehtuurissa koulutamme mallin ennustamaan sanan ympäröivän kontekstin perusteella. Annetulla ngrammilla $(W_{-2},W_{-1},W_0,W_1,W_2)$ mallin tavoitteena on ennustaa $W_0$ käyttäen $(W_{-2},W_{-1},W_1,W_2)$.\n",
" - **Jatkuva skip-gram** on CBoW:n vastakohta. Malli käyttää ympäröivää kontekstisanan ikkunaa ennustaakseen nykyisen sanan.\n",
"\n",
"CBoW on nopeampi, kun taas skip-gram on hitaampi, mutta se edustaa harvinaisia sanoja paremmin.\n",
"\n",
"![Kuva, joka näyttää sekä CBoW- että Skip-Gram-algoritmit sanojen muuntamiseksi vektoreiksi.](../../../../../translated_images/fi/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\n",
"\n",
"Kokeillaksemme Word2Vec-upotusta, joka on esikoulutettu Google News -aineistolla, voimme käyttää **gensim**-kirjastoa. Alla etsimme sanoja, jotka ovat lähimpänä sanaa 'neural'.\n",
"\n",
"> **Note:** Kun luot sanavektoreita ensimmäistä kertaa, niiden lataaminen voi kestää jonkin aikaa!\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": [
"Voimme myös laskea sanasta vektorijoukoja, joita käytetään luokittelumallin koulutuksessa (näytämme selkeyden vuoksi vain vektorin ensimmäiset 20 komponenttia):\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": [
"Hieno asia semanttisissa upotuksissa on, että voit manipuloida vektoriin koodausta muuttaaksesi semantiikkaa. Esimerkiksi voimme pyytää löytämään sanan, jonka vektoriedustus olisi mahdollisimman lähellä sanoja *kuningas* ja *nainen*, ja mahdollisimman kaukana sanasta *mies*:\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": [
"Sekä CBoW että Skip-Grams ovat \"ennustavia\" upotuksia, koska ne ottavat huomioon vain paikalliset kontekstit. Word2Vec ei hyödynnä globaalia kontekstia.\n",
"\n",
"**FastText** rakentuu Word2Vecin pohjalle oppimalla vektoriedustuksia jokaiselle sanalle sekä sanan sisällä oleville merkkien n-grammeille. Näiden edustusten arvot keskiarvoistetaan yhdeksi vektoriksi jokaisessa harjoitusvaiheessa. Vaikka tämä lisää merkittävästi laskentaa esikoulutuksessa, se mahdollistaa sanaupotusten koodata osasanan tietoa.\n",
"\n",
"Toinen menetelmä, **GloVe**, hyödyntää yhteisesiintymismatriisin ideaa ja käyttää neuroverkkomenetelmiä hajottaakseen yhteisesiintymismatriisin ilmeikkäämmiksi ja epälineaarisiksi sanavektoreiksi.\n",
"\n",
"Voit kokeilla esimerkkiä vaihtamalla upotukset FastTextiin ja GloVeen, sillä gensim tukee useita erilaisia sanaupotusmalleja.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Esikoulutettujen upotusten käyttäminen PyTorchissa\n",
"\n",
"Voimme muokata yllä olevaa esimerkkiä esitäyttääksemme upotuskerroksen matriisin semanttisilla upotuksilla, kuten Word2Vecillä. Meidän on otettava huomioon, että esikoulutetun upotuksen ja tekstikorpuksemme sanastot eivät todennäköisesti vastaa toisiaan, joten alustamme puuttuvien sanojen painot satunnaisilla arvoilla:\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": [
"Nyt harjoitellaan malliamme. Huomaa, että mallin harjoittamiseen kuluva aika on huomattavasti pidempi kuin edellisessä esimerkissä, johtuen suuremmasta upotuskerroksen koosta ja siten paljon suuremmasta parametrien määrästä. Lisäksi tämän vuoksi saatamme joutua harjoittelemaan malliamme useammilla esimerkeillä, jos haluamme välttää ylioppimisen.\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": [
"Meidän tapauksessamme emme näe suurta tarkkuuden kasvua, mikä johtuu todennäköisesti hyvin erilaisista sanastoista. \n",
"Ongelman ratkaisemiseksi, joka liittyy erilaisiin sanastoihin, voimme käyttää jotakin seuraavista ratkaisuista: \n",
"* Kouluttaa word2vec-malli uudelleen meidän sanastollamme \n",
"* Ladata datasetimme sanastolla, joka on peräisin valmiiksi koulutetusta word2vec-mallista. Sanasto, jota käytetään datasetin lataamiseen, voidaan määrittää latauksen aikana. \n",
"\n",
"Jälkimmäinen lähestymistapa vaikuttaa helpommalta, erityisesti koska PyTorchin `torchtext`-kehys sisältää sisäänrakennetun tuen upotuksille. Voimme esimerkiksi luoda sanaston, joka perustuu GloVeen, seuraavalla tavalla: \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": [
"Ladattu sanasto sisältää seuraavat perustoiminnot:\n",
"* `vocab.stoi`-sanakirjan avulla voimme muuntaa sanan sen sanakirjaindeksiksi\n",
"* `vocab.itos` tekee päinvastoin - muuntaa numeron sanaksi\n",
"* `vocab.vectors` on upotusvektoreiden taulukko, joten saadaksemme sanan `s` upotuksen meidän täytyy käyttää `vocab.vectors[vocab.stoi[s]]`\n",
"\n",
"Tässä on esimerkki upotusten käsittelystä, joka havainnollistaa yhtälöä **kind-man+woman = queen** (jouduin säätämään kerrointa hieman, jotta se toimisi):\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": [
"Jotta voimme kouluttaa luokittelijan näiden upotusten avulla, meidän on ensin koodattava tietoaineistomme GloVe-sanakirjan avulla:\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": [
"Kuten olemme nähneet yllä, kaikki vektoriesitykset tallennetaan `vocab.vectors`-matriisiin. Tämä tekee painojen lataamisesta upotuskerroksen painoihin erittäin helppoa yksinkertaisella kopioinnilla:\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": [
"Nyt koulutetaan mallimme ja katsotaan, saammeko parempia tuloksia:\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": [
"Yksi syy siihen, miksi emme näe merkittävää tarkkuuden kasvua, johtuu siitä, että jotkin sanastomme sanat puuttuvat esikoulutetusta GloVe-sanakirjasta, ja siksi ne käytännössä jätetään huomiotta. Tämän ongelman voittamiseksi voimme kouluttaa omat upotuksemme omalla aineistollamme.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Kontekstuaaliset upotukset\n",
"\n",
"Yksi perinteisten esikoulutettujen upotusten, kuten Word2Vecin, merkittävä rajoitus on sanan merkityksen erottelun ongelma. Vaikka esikoulutetut upotukset voivat osittain vangita sanojen merkityksen kontekstissa, jokainen sanan mahdollinen merkitys koodataan samaan upotukseen. Tämä voi aiheuttaa ongelmia jatkomalleissa, koska monet sanat, kuten sana 'play', voivat tarkoittaa eri asioita riippuen siitä, missä kontekstissa niitä käytetään.\n",
"\n",
"Esimerkiksi sana 'play' tarkoittaa näissä kahdessa lauseessa hyvin eri asioita:\n",
"- Kävin katsomassa **näytelmää** teatterissa.\n",
"- John haluaa **leikkiä** ystäviensä kanssa.\n",
"\n",
"Yllä olevat esikoulutetut upotukset edustavat molempia sanan 'play' merkityksiä samassa upotuksessa. Tämän rajoituksen voittamiseksi meidän täytyy rakentaa upotuksia, jotka perustuvat **kielimalliin**, joka on koulutettu suurella tekstikorpuksella ja *tietää*, miten sanoja voidaan yhdistää eri konteksteissa. Kontekstuaalisten upotusten käsittely on tämän opetusmateriaalin ulkopuolella, mutta palaamme niihin, kun käsittelemme kielimalleja 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": "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-08-28T21:55:21+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "fi"
}
},
"nbformat": 4,
"nbformat_minor": 2
}