724 lines
28 KiB
Plaintext
724 lines
28 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Inbeddingen\n",
|
|
"\n",
|
|
"In ons vorige voorbeeld werkten we met hoog-dimensionale bag-of-words vectoren met een lengte van `vocab_size`, en we waren expliciet bezig met het omzetten van laag-dimensionale positionele representatievectoren naar schaarse one-hot representaties. Deze one-hot representatie is niet geheugen-efficiënt en bovendien wordt elk woord onafhankelijk van de andere behandeld, d.w.z. one-hot gecodeerde vectoren drukken geen enkele semantische gelijkenis tussen woorden uit.\n",
|
|
"\n",
|
|
"In deze eenheid gaan we verder met het verkennen van de **News AG** dataset. Om te beginnen, laten we de data laden en enkele definities uit het vorige notebook ophalen.\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": [
|
|
"## Wat is embedding?\n",
|
|
"\n",
|
|
"Het idee van **embedding** is om woorden te representeren door lagere-dimensionale, dense vectoren die op een bepaalde manier de semantische betekenis van een woord weerspiegelen. Later zullen we bespreken hoe we betekenisvolle woordembeddings kunnen bouwen, maar voor nu kunnen we embeddings zien als een manier om de dimensionaliteit van een woordvector te verlagen.\n",
|
|
"\n",
|
|
"Een embedding-laag neemt een woord als invoer en produceert een uitvoervector met een gespecificeerde `embedding_size`. In zekere zin lijkt het erg op een `Linear`-laag, maar in plaats van een one-hot encoded vector te gebruiken, kan het een woordnummer als invoer nemen.\n",
|
|
"\n",
|
|
"Door een embedding-laag als eerste laag in ons netwerk te gebruiken, kunnen we overschakelen van een bag-of-words naar een **embedding bag**-model, waarbij we eerst elk woord in onze tekst omzetten naar de bijbehorende embedding en vervolgens een aggregatiefunctie toepassen op al deze embeddings, zoals `sum`, `average` of `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Ons classifier-neuraal netwerk zal beginnen met een embedding-laag, gevolgd door een aggregatielaag en een lineaire classifier bovenop:\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": [
|
|
"### Omgaan met variabele sequentiegrootte\n",
|
|
"\n",
|
|
"Door deze architectuur moeten minibatches voor ons netwerk op een bepaalde manier worden gemaakt. In de vorige eenheid, bij het gebruik van bag-of-words, hadden alle BoW-tensors in een minibatch dezelfde grootte `vocab_size`, ongeacht de werkelijke lengte van onze tekstsequentie. Zodra we overstappen op woordembeddingen, krijgen we een variabel aantal woorden in elke tekstsample, en bij het combineren van die samples in minibatches moeten we enige padding toepassen.\n",
|
|
"\n",
|
|
"Dit kan worden gedaan door dezelfde techniek te gebruiken waarbij een `collate_fn`-functie aan de datasource wordt geleverd:\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": [
|
|
"### Classificatie van embeddings trainen\n",
|
|
"\n",
|
|
"Nu we een juiste dataloader hebben gedefinieerd, kunnen we het model trainen met behulp van de trainingsfunctie die we in de vorige eenheid hebben gedefinieerd:\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": [
|
|
"> **Opmerking**: We trainen hier slechts voor 25k records (minder dan één volledige epoch) om tijd te besparen, maar je kunt doorgaan met trainen, een functie schrijven om voor meerdere epochs te trainen, en experimenteren met de leersnelheidsparameter om een hogere nauwkeurigheid te bereiken. Je zou een nauwkeurigheid van ongeveer 90% moeten kunnen behalen.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### EmbeddingBag-laag en representatie van variabele-lengte sequenties\n",
|
|
"\n",
|
|
"In de vorige architectuur moesten we alle sequenties op dezelfde lengte opvullen om ze in een minibatch te passen. Dit is niet de meest efficiënte manier om sequenties met variabele lengte te representeren - een andere aanpak zou zijn om een **offset**-vector te gebruiken, die de offsets van alle sequenties in één grote vector bevat.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: Op de afbeelding hierboven tonen we een sequentie van karakters, maar in ons voorbeeld werken we met sequenties van woorden. Het algemene principe van het representeren van sequenties met een offset-vector blijft echter hetzelfde.\n",
|
|
"\n",
|
|
"Om met offset-representatie te werken, gebruiken we de [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)-laag. Deze lijkt op `Embedding`, maar neemt een content-vector en een offset-vector als invoer, en bevat ook een averaging-laag, die `mean`, `sum` of `max` kan zijn.\n",
|
|
"\n",
|
|
"Hier is een aangepaste netwerkarchitectuur die gebruikmaakt van `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": [
|
|
"Om de dataset voor training voor te bereiden, moeten we een conversiefunctie bieden die de offsetvector zal voorbereiden:\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 op dat, in tegenstelling tot alle eerdere voorbeelden, ons netwerk nu twee parameters accepteert: datavector en offsetvector, die van verschillende grootte zijn. Evenzo levert onze dataloader ons nu 3 waarden in plaats van 2: zowel tekst- als offsetvectoren worden als kenmerken geleverd. Daarom moeten we onze trainingsfunctie enigszins aanpassen om hiermee rekening te houden:\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": [
|
|
"## Semantische Embeddings: Word2Vec\n",
|
|
"\n",
|
|
"In ons vorige voorbeeld leerde de embeddinglaag van het model om woorden naar vectorrepresentaties te mappen, maar deze representatie had niet veel semantische betekenis. Het zou fijn zijn om een vectorrepresentatie te leren waarbij vergelijkbare woorden of synoniemen overeenkomen met vectoren die dicht bij elkaar liggen in termen van een bepaalde vectorafstand (bijv. euclidische afstand).\n",
|
|
"\n",
|
|
"Om dat te bereiken, moeten we ons embeddingmodel op een grote verzameling tekst op een specifieke manier voortrainen. Een van de eerste methoden om semantische embeddings te trainen wordt [Word2Vec](https://en.wikipedia.org/wiki/Word2vec) genoemd. Het is gebaseerd op twee hoofdarchitecturen die worden gebruikt om een gedistribueerde representatie van woorden te produceren:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — in deze architectuur trainen we het model om een woord te voorspellen op basis van de omliggende context. Gegeven de n-gram $(W_{-2},W_{-1},W_0,W_1,W_2)$, is het doel van het model om $W_0$ te voorspellen op basis van $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** is het tegenovergestelde van CBoW. Het model gebruikt het omliggende venster van contextwoorden om het huidige woord te voorspellen.\n",
|
|
"\n",
|
|
"CBoW is sneller, terwijl skip-gram langzamer is, maar beter presteert bij het representeren van zeldzame woorden.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Om te experimenteren met Word2Vec-embedding die is voorgetraind op de Google News dataset, kunnen we de **gensim**-bibliotheek gebruiken. Hieronder vinden we de woorden die het meest lijken op 'neural'.\n",
|
|
"\n",
|
|
"> **Opmerking:** Wanneer je voor het eerst woordvectoren aanmaakt, kan het downloaden ervan enige tijd duren!\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": [
|
|
"We kunnen ook vector-embeddings berekenen vanuit het woord, om te gebruiken bij het trainen van een classificatiemodel (we tonen alleen de eerste 20 componenten van de vector voor de duidelijkheid):\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": [
|
|
"Het geweldige aan semantische embeddings is dat je de vectorcodering kunt manipuleren om de semantiek te veranderen. Bijvoorbeeld, we kunnen vragen om een woord te vinden waarvan de vectorrepresentatie zo dicht mogelijk bij de woorden *koning* en *vrouw* ligt, en zo ver mogelijk van het woord *man*:\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": [
|
|
"Zowel CBoW als Skip-Grams zijn \"voorspellende\" embeddings, omdat ze alleen lokale contexten in aanmerking nemen. Word2Vec maakt geen gebruik van globale context.\n",
|
|
"\n",
|
|
"**FastText** bouwt voort op Word2Vec door vectorrepresentaties te leren voor elk woord en de karakter n-grams die binnen elk woord voorkomen. De waarden van de representaties worden vervolgens gemiddeld tot één vector bij elke trainingsstap. Hoewel dit veel extra berekeningen toevoegt aan de pre-training, stelt het woordembeddings in staat om subwoordinformatie te coderen.\n",
|
|
"\n",
|
|
"Een andere methode, **GloVe**, maakt gebruik van het idee van een co-occurrentie matrix en gebruikt neurale methoden om de co-occurrentie matrix te ontleden in meer expressieve en niet-lineaire woordvectoren.\n",
|
|
"\n",
|
|
"Je kunt met het voorbeeld spelen door de embeddings te veranderen naar FastText en GloVe, aangezien gensim verschillende modellen voor woordembeddings ondersteunt.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Gebruik van Voorgetrainde Embeddings in PyTorch\n",
|
|
"\n",
|
|
"We kunnen het bovenstaande voorbeeld aanpassen om de matrix in onze embedding-laag vooraf te vullen met semantische embeddings, zoals Word2Vec. We moeten er rekening mee houden dat de woordenschat van de voorgetrainde embedding en ons tekstcorpus waarschijnlijk niet overeenkomen, dus we zullen de gewichten voor de ontbrekende woorden initialiseren met willekeurige waarden:\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": [
|
|
"Laten we nu ons model trainen. Merk op dat de tijd die het kost om het model te trainen aanzienlijk groter is dan in het vorige voorbeeld, vanwege de grotere grootte van de inbeddingslaag en daardoor een veel hoger aantal parameters. Ook kunnen we hierdoor ons model op meer voorbeelden moeten trainen als we overfitting willen vermijden.\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": [
|
|
"In ons geval zien we geen enorme toename in nauwkeurigheid, wat waarschijnlijk te maken heeft met behoorlijk verschillende woordenschatten. \n",
|
|
"Om het probleem van verschillende woordenschatten te overwinnen, kunnen we een van de volgende oplossingen gebruiken: \n",
|
|
"* Het word2vec-model opnieuw trainen op onze woordenschat \n",
|
|
"* Onze dataset laden met de woordenschat van het vooraf getrainde word2vec-model. De woordenschat die wordt gebruikt om de dataset te laden, kan tijdens het laden worden gespecificeerd. \n",
|
|
"\n",
|
|
"De laatste aanpak lijkt eenvoudiger, vooral omdat het PyTorch `torchtext` framework ingebouwde ondersteuning voor embeddings bevat. We kunnen bijvoorbeeld een GloVe-gebaseerde woordenschat instantiëren op de volgende manier: \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": [
|
|
"Het geladen vocabulaire heeft de volgende basisbewerkingen:\n",
|
|
"* De `vocab.stoi`-woordenlijst stelt ons in staat om een woord om te zetten naar zijn index in de woordenlijst.\n",
|
|
"* `vocab.itos` doet het tegenovergestelde - het zet een nummer om naar een woord.\n",
|
|
"* `vocab.vectors` is de array van embedding-vectoren, dus om de embedding van een woord `s` te krijgen, moeten we `vocab.vectors[vocab.stoi[s]]` gebruiken.\n",
|
|
"\n",
|
|
"Hier is een voorbeeld van het manipuleren van embeddings om de vergelijking **kind-man+woman = queen** te demonstreren (ik moest de coëfficiënt een beetje aanpassen om het te laten werken):\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": [
|
|
"Om de classifier te trainen met behulp van die embeddings, moeten we eerst onze dataset coderen met behulp van de GloVe-woordenschat:\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": [
|
|
"Zoals we hierboven hebben gezien, worden alle vector-embeddings opgeslagen in de `vocab.vectors` matrix. Het maakt het super eenvoudig om die gewichten te laden in de gewichten van de embedding-laag door simpelweg te kopiëren:\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": [
|
|
"Een van de redenen waarom we geen significante toename in nauwkeurigheid zien, is het feit dat sommige woorden uit onze dataset ontbreken in de voorgetrainde GloVe-woordenschat en daardoor in wezen worden genegeerd. Om dit te verhelpen, kunnen we onze eigen embeddings trainen op onze dataset.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Contextuele Embeddings\n",
|
|
"\n",
|
|
"Een belangrijke beperking van traditionele vooraf getrainde embedding-representaties zoals Word2Vec is het probleem van woordbetekenis-onderscheiding. Hoewel vooraf getrainde embeddings een deel van de betekenis van woorden in context kunnen vastleggen, wordt elke mogelijke betekenis van een woord in dezelfde embedding gecodeerd. Dit kan problemen veroorzaken in downstream-modellen, aangezien veel woorden, zoals het woord 'play', verschillende betekenissen hebben afhankelijk van de context waarin ze worden gebruikt.\n",
|
|
"\n",
|
|
"Bijvoorbeeld, het woord 'play' heeft in deze twee verschillende zinnen een heel andere betekenis:\n",
|
|
"- Ik ging naar een **toneelstuk** in het theater.\n",
|
|
"- John wil **spelen** met zijn vrienden.\n",
|
|
"\n",
|
|
"De hierboven genoemde vooraf getrainde embeddings vertegenwoordigen beide betekenissen van het woord 'play' in dezelfde embedding. Om deze beperking te overwinnen, moeten we embeddings bouwen op basis van het **taalmodel**, dat is getraind op een grote hoeveelheid tekst en *begrijpt* hoe woorden in verschillende contexten kunnen worden gebruikt. Het bespreken van contextuele embeddings valt buiten de scope van deze tutorial, maar we zullen hierop terugkomen wanneer we taalmodellen bespreken in de volgende eenheid.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Disclaimer**: \nDit document is vertaald met behulp van de AI-vertalingsservice [Co-op Translator](https://github.com/Azure/co-op-translator). Hoewel we ons best doen voor nauwkeurigheid, dient u zich ervan bewust te zijn dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in zijn oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor cruciale informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor eventuele misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.\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:56:26+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "nl"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |