720 lines
27 KiB
Plaintext
720 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Innebygginger\n",
|
|
"\n",
|
|
"I vårt forrige eksempel jobbet vi med høydimensjonale bag-of-words-vektorer med lengde `vocab_size`, og vi konverterte eksplisitt fra lavdimensjonale posisjonsrepresentasjonsvektorer til sparsomme one-hot-representasjoner. Denne one-hot-representasjonen er ikke minneeffektiv, og i tillegg behandles hvert ord uavhengig av hverandre, dvs. one-hot-kodede vektorer uttrykker ingen semantisk likhet mellom ord.\n",
|
|
"\n",
|
|
"I denne enheten skal vi fortsette å utforske **News AG**-datasettet. For å begynne, la oss laste inn dataene og hente noen definisjoner fra den forrige notatboken.\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": [
|
|
"## Hva er embedding?\n",
|
|
"\n",
|
|
"Ideen med **embedding** er å representere ord ved lavdimensjonale tette vektorer, som på en eller annen måte reflekterer den semantiske betydningen av et ord. Senere skal vi diskutere hvordan man bygger meningsfulle ord-embeddings, men for nå kan vi bare tenke på embeddings som en måte å redusere dimensjonaliteten til en ordvektor. \n",
|
|
"\n",
|
|
"Så, en embedding-lag vil ta et ord som input og produsere en output-vektor med spesifisert `embedding_size`. På en måte er det veldig likt `Linear`-laget, men i stedet for å ta en én-hot kodet vektor, vil det kunne ta et ordnummer som input.\n",
|
|
"\n",
|
|
"Ved å bruke embedding-laget som det første laget i vårt nettverk, kan vi gå fra bag-of-words til **embedding bag**-modellen, hvor vi først konverterer hvert ord i teksten vår til tilsvarende embedding, og deretter beregner en aggregatfunksjon over alle disse embeddingene, som for eksempel `sum`, `average` eller `max`. \n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Vårt klassifiserings-nevrale nettverk vil starte med embedding-lag, deretter et aggregasjonslag, og en lineær klassifiserer på toppen av det:\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": [
|
|
"### Håndtering av variabel sekvensstørrelse\n",
|
|
"\n",
|
|
"Som et resultat av denne arkitekturen må minibatcher til nettverket vårt opprettes på en bestemt måte. I den forrige enheten, når vi brukte bag-of-words, hadde alle BoW-tensorer i en minibatch lik størrelse `vocab_size`, uavhengig av den faktiske lengden på tekstsekvensen vår. Når vi går over til ord-embedding, vil vi ende opp med et variabelt antall ord i hver tekstprøve, og når vi kombinerer disse prøvene til minibatcher, må vi legge til noe padding.\n",
|
|
"\n",
|
|
"Dette kan gjøres ved å bruke samme teknikk som å gi `collate_fn`-funksjonen til datakilden:\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": [
|
|
"### Trene embedding-klassifiserer\n",
|
|
"\n",
|
|
"Nå som vi har definert en passende dataloader, kan vi trene modellen ved å bruke treningsfunksjonen vi har definert i forrige enhet:\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": [
|
|
"> **Merk**: Vi trener kun for 25k poster her (mindre enn én full epoke) for tidsbesparelse, men du kan fortsette treningen, skrive en funksjon for å trene over flere epoker, og eksperimentere med læringsrateparameteren for å oppnå høyere nøyaktighet. Du bør kunne oppnå en nøyaktighet på rundt 90%.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### EmbeddingBag-lag og representasjon av sekvenser med variabel lengde\n",
|
|
"\n",
|
|
"I den tidligere arkitekturen måtte vi fylle alle sekvenser til samme lengde for å passe dem inn i en minibatch. Dette er ikke den mest effektive måten å representere sekvenser med variabel lengde på - en annen tilnærming ville være å bruke en **offset**-vektor, som holder offsetene til alle sekvenser lagret i én stor vektor.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: På bildet ovenfor viser vi en sekvens av tegn, men i vårt eksempel jobber vi med sekvenser av ord. Prinsippet for å representere sekvenser med en offset-vektor forblir imidlertid det samme.\n",
|
|
"\n",
|
|
"For å arbeide med offset-representasjon bruker vi [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)-laget. Det ligner på `Embedding`, men det tar innhold-vektor og offset-vektor som input, og inkluderer også et gjennomsnittslag, som kan være `mean`, `sum` eller `max`.\n",
|
|
"\n",
|
|
"Her er et modifisert nettverk som bruker `EmbeddingBag`:\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": [
|
|
"For å klargjøre datasettet for trening, må vi gi en konverteringsfunksjon som vil klargjøre offset-vektoren:\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": [
|
|
"Merk at i motsetning til alle tidligere eksempler, aksepterer nettverket vårt nå to parametere: datavektor og offsetvektor, som har forskjellige størrelser. På samme måte gir datalasteren vår oss også 3 verdier i stedet for 2: både tekst- og offsetvektorer blir gitt som funksjoner. Derfor må vi justere treningsfunksjonen vår litt for å ta hensyn til dette:\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": [
|
|
"## Semantiske Embeddinger: Word2Vec\n",
|
|
"\n",
|
|
"I vårt forrige eksempel lærte modellens embedding-lag å kartlegge ord til vektorrepresentasjoner, men denne representasjonen hadde ikke mye semantisk mening. Det hadde vært fint å lære en slik vektorrepresentasjon der lignende ord eller synonymer tilsvarer vektorer som er nær hverandre i henhold til en eller annen vektordistanse (f.eks. euklidisk distanse).\n",
|
|
"\n",
|
|
"For å oppnå dette må vi forhåndstrene vår embedding-modell på en stor samling tekst på en spesifikk måte. En av de første metodene for å trene semantiske embeddinger kalles [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Den er basert på to hovedarkitekturer som brukes for å produsere en distribuert representasjon av ord:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — i denne arkitekturen trener vi modellen til å forutsi et ord basert på den omkringliggende konteksten. Gitt ngrammet $(W_{-2},W_{-1},W_0,W_1,W_2)$, er målet for modellen å forutsi $W_0$ ut fra $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** er motsatt av CBoW. Modellen bruker det omkringliggende vinduet av kontekstord for å forutsi det nåværende ordet.\n",
|
|
"\n",
|
|
"CBoW er raskere, mens skip-gram er tregere, men gjør en bedre jobb med å representere sjeldne ord.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"For å eksperimentere med word2vec-embedding forhåndstrent på Google News-datasettet, kan vi bruke **gensim**-biblioteket. Nedenfor finner vi ordene som ligner mest på 'neural'\n",
|
|
"\n",
|
|
"> **Merk:** Når du først oppretter ordvektorer, kan nedlastingen ta litt tid!\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": [
|
|
"Vi kan også beregne vektorinnbygginger fra ordet, som skal brukes i treningsklassifiseringsmodellen (vi viser kun de første 20 komponentene av vektoren for klarhet):\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": []
|
|
},
|
|
{
|
|
"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": [
|
|
"Både CBoW og Skip-Grams er \"prediktive\" embeddinger, ettersom de kun tar lokale kontekster i betraktning. Word2Vec utnytter ikke global kontekst.\n",
|
|
"\n",
|
|
"**FastText** bygger videre på Word2Vec ved å lære vektorrepresentasjoner for hvert ord og tegn-n-grammene som finnes i hvert ord. Verdiene av representasjonene blir deretter gjennomsnittet til én vektor i hvert treningssteg. Selv om dette legger til mye ekstra beregning under forhåndstrening, gjør det at ordembeddinger kan kode informasjon på sub-ordnivå.\n",
|
|
"\n",
|
|
"En annen metode, **GloVe**, utnytter ideen om samforekomstmatriser og bruker nevrale metoder for å dekomponere samforekomstmatrisen til mer uttrykksfulle og ikke-lineære ordvektorer.\n",
|
|
"\n",
|
|
"Du kan eksperimentere med eksempelet ved å bytte embeddinger til FastText og GloVe, siden gensim støtter flere forskjellige modeller for ordembedding.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bruke forhåndstrente embeddings i PyTorch\n",
|
|
"\n",
|
|
"Vi kan endre eksemplet ovenfor for å forhåndsfylle matrisen i vår embedding-lag med semantiske embeddings, som Word2Vec. Vi må ta hensyn til at vokabularet til forhåndstrente embeddings og tekstkorpuset vårt sannsynligvis ikke vil samsvare, så vi vil initialisere vektene for de manglende ordene med tilfeldige verdier:\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": []
|
|
},
|
|
{
|
|
"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": [
|
|
"I vårt tilfelle ser vi ikke en stor økning i nøyaktighet, noe som sannsynligvis skyldes ganske forskjellige ordforråd. \n",
|
|
"For å løse problemet med ulike ordforråd, kan vi bruke en av følgende løsninger: \n",
|
|
"* Tren word2vec-modellen på nytt med vårt ordforråd \n",
|
|
"* Last inn datasettet vårt med ordforrådet fra den forhåndstrente word2vec-modellen. Ordforrådet som brukes til å laste inn datasettet kan spesifiseres under innlasting. \n",
|
|
"\n",
|
|
"Den sistnevnte tilnærmingen virker enklere, spesielt fordi PyTorch `torchtext`-rammeverket inneholder innebygd støtte for innebygginger. Vi kan for eksempel opprette et GloVe-basert ordforråd på følgende måte: \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": [
|
|
"Lastet ordforråd har følgende grunnleggende operasjoner:\n",
|
|
"* `vocab.stoi`-ordbok lar oss konvertere et ord til dets ordbokindeks\n",
|
|
"* `vocab.itos` gjør det motsatte - konverterer et tall til et ord\n",
|
|
"* `vocab.vectors` er arrayet av innebyggingsvektorer, så for å få innebyggingen av et ord `s` må vi bruke `vocab.vectors[vocab.stoi[s]]`\n",
|
|
"\n",
|
|
"Her er et eksempel på manipulering av innebygginger for å demonstrere ligningen **kind-man+woman = queen** (jeg måtte justere koeffisienten litt for å få det til å fungere):\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": [
|
|
"For å trene klassifisereren ved hjelp av disse embeddingene, må vi først kode datasettet vårt ved hjelp av GloVe-ordforråd:\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": [
|
|
"Som vi har sett ovenfor, lagres alle vektorinbeddinger i `vocab.vectors`-matrisen. Det gjør det superenkelt å laste inn disse vektene i vektene til innbeddingslaget ved hjelp av enkel kopiering:\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": []
|
|
},
|
|
{
|
|
"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": [
|
|
"En av grunnene til at vi ikke ser en betydelig økning i nøyaktighet er på grunn av at noen ord fra datasettet vårt mangler i den forhåndstrente GloVe-ordforrådet, og derfor blir de i hovedsak ignorert. For å overvinne dette kan vi trene våre egne innebygginger på datasettet vårt.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Kontekstuelle Embeddinger\n",
|
|
"\n",
|
|
"En viktig begrensning med tradisjonelle forhåndstrente embedding-representasjoner som Word2Vec er problemet med ordsanse-diskriminering. Selv om forhåndstrente embeddinger kan fange opp noe av betydningen av ord i kontekst, blir alle mulige betydninger av et ord kodet inn i samme embedding. Dette kan skape problemer i modeller som bruker disse embeddingene, siden mange ord, som ordet 'play', har forskjellige betydninger avhengig av konteksten de brukes i.\n",
|
|
"\n",
|
|
"For eksempel har ordet 'play' i disse to setningene ganske forskjellige betydninger:\n",
|
|
"- Jeg dro på et **skuespill** på teateret.\n",
|
|
"- John vil **leke** med vennene sine.\n",
|
|
"\n",
|
|
"De forhåndstrente embeddingene ovenfor representerer begge disse betydningene av ordet 'play' i samme embedding. For å overkomme denne begrensningen, må vi bygge embeddinger basert på **språkmodeller**, som er trent på et stort tekstkorpus og *vet* hvordan ord kan settes sammen i ulike kontekster. Å diskutere kontekstuelle embeddinger er utenfor rammen for denne opplæringen, men vi vil komme tilbake til dem når vi snakker om språkmodeller i neste enhet.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Ansvarsfraskrivelse**: \nDette dokumentet er oversatt ved hjelp av AI-oversettelsestjenesten [Co-op Translator](https://github.com/Azure/co-op-translator). Selv om vi streber etter nøyaktighet, vær oppmerksom på at automatiserte oversettelser kan inneholde feil eller unøyaktigheter. Det originale dokumentet på sitt opprinnelige språk bør anses som den autoritative kilden. For kritisk informasjon anbefales profesjonell menneskelig oversettelse. Vi er ikke ansvarlige for misforståelser eller feiltolkninger som oppstår ved bruk av denne oversettelsen.\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-28T17:51:49+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "no"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |