726 lines
28 KiB
Plaintext
726 lines
28 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Indlejring\n",
|
|
"\n",
|
|
"I vores tidligere eksempel arbejdede vi med høj-dimensionelle bag-of-words vektorer med længden `vocab_size`, og vi konverterede eksplicit fra lav-dimensionelle positionsrepræsentationsvektorer til sparsomme one-hot repræsentationer. Denne one-hot repræsentation er ikke hukommelseseffektiv, og derudover behandles hvert ord uafhængigt af hinanden, dvs. one-hot kodede vektorer udtrykker ikke nogen semantisk lighed mellem ord.\n",
|
|
"\n",
|
|
"I denne enhed vil vi fortsætte med at udforske **News AG** datasættet. For at starte, lad os indlæse dataene og hente nogle definitioner fra den tidligere notebook.\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": [
|
|
"## Hvad er embedding?\n",
|
|
"\n",
|
|
"Ideen med **embedding** er at repræsentere ord som lavdimensionelle tætte vektorer, der på en eller anden måde afspejler et ords semantiske betydning. Vi vil senere diskutere, hvordan man bygger meningsfulde word embeddings, men for nu kan vi bare tænke på embeddings som en måde at reducere dimensionaliteten af en ordvektor.\n",
|
|
"\n",
|
|
"Så en embedding-lag vil tage et ord som input og producere en outputvektor med en specificeret `embedding_size`. På en måde minder det meget om et `Linear` lag, men i stedet for at tage en one-hot kodet vektor, vil det kunne tage et ordnummer som input.\n",
|
|
"\n",
|
|
"Ved at bruge embedding-laget som det første lag i vores netværk kan vi skifte fra bag-of-words til en **embedding bag**-model, hvor vi først konverterer hvert ord i vores tekst til den tilsvarende embedding og derefter beregner en eller anden aggregeringsfunktion over alle disse embeddings, såsom `sum`, `average` eller `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Vores klassifikator-neurale netværk vil starte med et embedding-lag, derefter et aggregeringslag og en lineær klassifikator ovenpå:\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 af variabel sekvensstørrelse\n",
|
|
"\n",
|
|
"Som et resultat af denne arkitektur skal minibatches til vores netværk oprettes på en bestemt måde. I den tidligere enhed, hvor vi brugte bag-of-words, havde alle BoW-tensore i en minibatch samme størrelse `vocab_size`, uanset den faktiske længde af vores tekstsekvens. Når vi skifter til ordindlejring, vil vi ende med et variabelt antal ord i hver tekstprøve, og når vi kombinerer disse prøver til minibatches, bliver vi nødt til at anvende noget padding.\n",
|
|
"\n",
|
|
"Dette kan gøres ved hjælp af den samme teknik, hvor vi leverer `collate_fn`-funktionen 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": [
|
|
"### Træning af embedding-klassifikator\n",
|
|
"\n",
|
|
"Nu hvor vi har defineret en passende dataloader, kan vi træne modellen ved hjælp af den træningsfunktion, vi har defineret i den forrige enhed:\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": [
|
|
"> **Bemærk**: Vi træner kun på 25k poster her (mindre end en fuld epoke) for at spare tid, men du kan fortsætte træningen, skrive en funktion til at træne over flere epoker og eksperimentere med læringsrateparameteren for at opnå højere nøjagtighed. Du burde kunne nå en nøjagtighed på omkring 90%.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### EmbeddingBag-lag og repræsentation af sekvenser med variabel længde\n",
|
|
"\n",
|
|
"I den tidligere arkitektur var vi nødt til at udfylde alle sekvenser til samme længde for at passe dem ind i en minibatch. Dette er ikke den mest effektive måde at repræsentere sekvenser med variabel længde på - en anden tilgang ville være at bruge en **offset**-vektor, som indeholder offsets for alle sekvenser, der er gemt i én stor vektor.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: På billedet ovenfor viser vi en sekvens af tegn, men i vores eksempel arbejder vi med sekvenser af ord. Dog forbliver det generelle princip om at repræsentere sekvenser med en offset-vektor det samme.\n",
|
|
"\n",
|
|
"For at arbejde med offset-repræsentation bruger vi [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)-laget. Det ligner `Embedding`, men det tager indholdvektor og offset-vektor som input, og det inkluderer også et gennemsnitslag, som kan være `mean`, `sum` eller `max`.\n",
|
|
"\n",
|
|
"Her er et modificeret netværk, der bruger `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 at forberede datasættet til træning, skal vi levere en konverteringsfunktion, der vil forberede 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": [
|
|
"Bemærk, at i modsætning til alle tidligere eksempler accepterer vores netværk nu to parametre: datavektor og offsetvektor, som har forskellige størrelser. Tilsvarende giver vores dataloader os også 3 værdier i stedet for 2: både tekst- og offsetvektorer leveres som funktioner. Derfor skal vi justere vores træningsfunktion en smule for at tage højde for 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 Embeddings: Word2Vec\n",
|
|
"\n",
|
|
"I vores tidligere eksempel lærte modelens embedding-lag at kortlægge ord til vektorrepræsentation, men denne repræsentation havde ikke meget semantisk betydning. Det ville være rart at lære en sådan vektorrepræsentation, hvor lignende ord eller synonymer svarer til vektorer, der er tæt på hinanden i forhold til en eller anden vektordistance (f.eks. euklidisk distance).\n",
|
|
"\n",
|
|
"For at opnå dette skal vi fortræne vores embedding-model på en stor samling af tekst på en specifik måde. En af de første metoder til at træne semantiske embeddings kaldes [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Det er baseret på to hovedarkitekturer, der bruges til at producere en distribueret repræsentation af ord:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — i denne arkitektur træner vi modellen til at forudsige et ord ud fra den omkringliggende kontekst. Givet ngrammet $(W_{-2},W_{-1},W_0,W_1,W_2)$ er målet for modellen at forudsige $W_0$ ud fra $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** er det modsatte af CBoW. Modellen bruger det omkringliggende vindue af kontekstord til at forudsige det aktuelle ord.\n",
|
|
"\n",
|
|
"CBoW er hurtigere, mens skip-gram er langsommere, men gør et bedre stykke arbejde med at repræsentere sjældne ord.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"For at eksperimentere med word2vec embedding fortrænet på Google News-datasættet kan vi bruge **gensim**-biblioteket. Nedenfor finder vi de ord, der minder mest om 'neural'.\n",
|
|
"\n",
|
|
"> **Note:** Når du først opretter ordvektorer, kan det tage noget tid at downloade dem!\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 vektorindlejringer fra ordet, som skal bruges til at træne klassifikationsmodellen (vi viser kun de første 20 komponenter af vektoren for klarhed):\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": [
|
|
"Den fantastiske ting ved semantiske indlejringer er, at du kan manipulere vektor-kodningen for at ændre semantikken. For eksempel kan vi bede om at finde et ord, hvis vektorrepræsentation ville være så tæt som muligt på ordene *konge* og *kvinde*, og så langt væk som muligt fra ordet *mand*:\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": [
|
|
"Både CBoW og Skip-Grams er \"forudsigende\" indlejringer, da de kun tager lokale kontekster i betragtning. Word2Vec udnytter ikke global kontekst.\n",
|
|
"\n",
|
|
"**FastText** bygger videre på Word2Vec ved at lære vektorrepræsentationer for hvert ord og de karakter-n-grammer, der findes inden for hvert ord. Værdierne af repræsentationerne gennemsnitsberegnes derefter til én vektor ved hvert træningsskridt. Selvom dette tilføjer en del ekstra beregning til fortræningen, gør det det muligt for ordindlejringer at kode information på sub-ordniveau.\n",
|
|
"\n",
|
|
"En anden metode, **GloVe**, udnytter ideen om en samforekomstmatrix og bruger neurale metoder til at dekomponere samforekomstmatricen til mere udtryksfulde og ikke-lineære ordvektorer.\n",
|
|
"\n",
|
|
"Du kan eksperimentere med eksemplet ved at ændre indlejringer til FastText og GloVe, da gensim understøtter flere forskellige modeller for ordindlejring.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Brug af Forudtrænede Embeddinger i PyTorch\n",
|
|
"\n",
|
|
"Vi kan ændre eksemplet ovenfor for at forudfylde matricen i vores embedding-lag med semantiske embeddinger, såsom Word2Vec. Vi skal tage højde for, at ordforrådene fra de forudtrænede embeddinger og vores tekstkorpus sandsynligvis ikke vil matche, så vi vil initialisere vægtene for de manglende ord med tilfældige værdier:\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": [
|
|
"Nu lad os træne vores model. Bemærk, at den tid, det tager at træne modellen, er betydeligt længere end i det tidligere eksempel, på grund af den større størrelse på indlejringslaget og dermed et meget højere antal parametre. Desuden kan vi på grund af dette være nødt til at træne vores model på flere eksempler, hvis vi vil undgå overtilpasning.\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": [
|
|
"I vores tilfælde ser vi ikke en stor stigning i nøjagtighed, hvilket sandsynligvis skyldes meget forskellige ordforråd. \n",
|
|
"For at løse problemet med forskellige ordforråd kan vi bruge en af følgende løsninger: \n",
|
|
"* Gen-træne word2vec-modellen på vores ordforråd \n",
|
|
"* Indlæse vores datasæt med ordforrådet fra den forudtrænede word2vec-model. Det ordforråd, der bruges til at indlæse datasættet, kan specificeres under indlæsningen. \n",
|
|
"\n",
|
|
"Den sidstnævnte tilgang virker lettere, især fordi PyTorch `torchtext`-frameworket indeholder indbygget support til embeddings. Vi kan for eksempel oprette et GloVe-baseret ordforråd på følgende måde: \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": [
|
|
"Indlæst ordforråd har følgende grundlæggende operationer:\n",
|
|
"* `vocab.stoi`-ordbogen giver os mulighed for at konvertere et ord til dets ordbogsindeks\n",
|
|
"* `vocab.itos` gør det modsatte - konverterer et tal til et ord\n",
|
|
"* `vocab.vectors` er arrayet af indlejringsvektorer, så for at få indlejringen af et ord `s` skal vi bruge `vocab.vectors[vocab.stoi[s]]`\n",
|
|
"\n",
|
|
"Her er et eksempel på manipulation af indlejringer for at demonstrere ligningen **kind-man+woman = queen** (jeg var nødt til at justere koefficienten en smule for at få det til at 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 at træne klassifikatoren ved hjælp af disse indlejringer, skal vi først kode vores datasæt ved hjælp af GloVe-ordforrådet:\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 set ovenfor, gemmes alle vektorembeddinger i `vocab.vectors` matrixen. Det gør det supernemt at indlæse disse vægte i vægtene for embeddingslaget ved hjælp af simpel 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": [
|
|
"Lad os nu træne vores model og se, om vi får bedre resultater:\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": [
|
|
"En af grundene til, at vi ikke ser en betydelig stigning i nøjagtighed, skyldes, at nogle ord fra vores datasæt mangler i den forudtrænede GloVe-ordforråd, og derfor bliver de i det væsentlige ignoreret. For at overvinde dette kan vi træne vores egne indlejringer på vores datasæt.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Kontekstuelle Embeddinger\n",
|
|
"\n",
|
|
"En af de væsentlige begrænsninger ved traditionelle forudtrænede embedding-repræsentationer som Word2Vec er problemet med ords betydningsafklaring. Selvom forudtrænede embeddinger kan fange noget af ordenes betydning i kontekst, bliver alle mulige betydninger af et ord kodet ind i den samme embedding. Dette kan skabe problemer i efterfølgende modeller, da mange ord, som for eksempel ordet 'play', har forskellige betydninger afhængigt af den kontekst, de bruges i.\n",
|
|
"\n",
|
|
"For eksempel har ordet 'play' i disse to forskellige sætninger ret forskellige betydninger:\n",
|
|
"- Jeg gik til et **skuespil** i teatret.\n",
|
|
"- John vil gerne **lege** med sine venner.\n",
|
|
"\n",
|
|
"De forudtrænede embeddinger ovenfor repræsenterer begge disse betydninger af ordet 'play' i den samme embedding. For at overvinde denne begrænsning skal vi bygge embeddinger baseret på **sproglige modeller**, som er trænet på et stort tekstkorpus og *forstår*, hvordan ord kan sættes sammen i forskellige kontekster. At diskutere kontekstuelle embeddinger er uden for rammerne af denne tutorial, men vi vender tilbage til dem, når vi taler om sproglige modeller i den næste enhed.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Ansvarsfraskrivelse**: \nDette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten [Co-op Translator](https://github.com/Azure/co-op-translator). Selvom vi bestræber os på nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi er ikke ansvarlige for eventuelle misforståelser eller fejltolkninger, der opstår som følge af brugen af denne oversættelse.\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:50:44+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "da"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |