724 lines
29 KiB
Plaintext
724 lines
29 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Încapsulări\n",
|
|
"\n",
|
|
"În exemplul anterior, am lucrat cu vectori de tip bag-of-words de dimensiuni mari, având lungimea `vocab_size`, și am convertit explicit din vectori de reprezentare pozițională de dimensiuni mici în reprezentări sparse de tip one-hot. Această reprezentare one-hot nu este eficientă din punct de vedere al memoriei și, în plus, fiecare cuvânt este tratat independent de celelalte, adică vectorii codificați one-hot nu exprimă nicio similaritate semantică între cuvinte.\n",
|
|
"\n",
|
|
"În această unitate, vom continua să explorăm setul de date **News AG**. Pentru început, să încărcăm datele și să obținem câteva definiții din notebook-ul anterior.\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": [
|
|
"## Ce este embedding-ul?\n",
|
|
"\n",
|
|
"Ideea de **embedding** este de a reprezenta cuvintele prin vectori densi de dimensiuni mai mici, care reflectă într-un fel semnificația semantică a unui cuvânt. Vom discuta mai târziu cum să construim embedding-uri semnificative pentru cuvinte, dar deocamdată să ne gândim la embedding-uri ca la o modalitate de a reduce dimensiunea unui vector de cuvânt.\n",
|
|
"\n",
|
|
"Astfel, un strat de embedding ar lua un cuvânt ca intrare și ar produce un vector de ieșire cu dimensiunea specificată `embedding_size`. Într-un fel, este foarte similar cu stratul `Linear`, dar în loc să ia un vector codificat one-hot, va putea să primească un număr de cuvânt ca intrare.\n",
|
|
"\n",
|
|
"Folosind stratul de embedding ca prim strat în rețeaua noastră, putem trece de la modelul bag-of-words la modelul **embedding bag**, unde mai întâi convertim fiecare cuvânt din textul nostru în embedding-ul corespunzător, iar apoi calculăm o funcție de agregare peste toate aceste embedding-uri, cum ar fi `sum`, `average` sau `max`.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Rețeaua noastră neuronală de clasificare va începe cu un strat de embedding, urmat de un strat de agregare și un clasificator liniar deasupra acestuia:\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": [
|
|
"### Gestionarea dimensiunii variabile a secvenței\n",
|
|
"\n",
|
|
"Ca rezultat al acestei arhitecturi, minibatch-urile pentru rețea ar trebui să fie create într-un mod specific. În unitatea anterioară, când foloseam metoda bag-of-words, toate tensori BoW dintr-un minibatch aveau aceeași dimensiune `vocab_size`, indiferent de lungimea reală a secvenței text. Odată ce trecem la încorporări de cuvinte, ajungem să avem un număr variabil de cuvinte în fiecare eșantion de text, iar atunci când combinăm aceste eșantioane în minibatch-uri, va trebui să aplicăm un padding.\n",
|
|
"\n",
|
|
"Acest lucru poate fi realizat folosind aceeași tehnică de furnizare a funcției `collate_fn` sursei de date:\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": [
|
|
"### Antrenarea clasificatorului de embedding\n",
|
|
"\n",
|
|
"Acum că am definit un dataloader corespunzător, putem antrena modelul folosind funcția de antrenare pe care am definit-o în unitatea precedentă:\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": [
|
|
"> **Notă**: Aici antrenăm doar pentru 25k de înregistrări (mai puțin de un întreg epoc) pentru a economisi timp, dar puteți continua antrenamentul, scrie o funcție pentru a antrena pentru mai multe epoci și experimentați cu parametrul ratei de învățare pentru a obține o acuratețe mai mare. Ar trebui să puteți ajunge la o acuratețe de aproximativ 90%.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Stratul EmbeddingBag și Reprezentarea Secvențelor de Lungime Variabilă\n",
|
|
"\n",
|
|
"În arhitectura anterioară, a fost necesar să completăm toate secvențele la aceeași lungime pentru a le încadra într-un minibatch. Aceasta nu este cea mai eficientă metodă de a reprezenta secvențele de lungime variabilă - o altă abordare ar fi utilizarea unui vector de **offset**, care ar conține offset-urile tuturor secvențelor stocate într-un singur vector mare.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **Note**: În imaginea de mai sus, este prezentată o secvență de caractere, dar în exemplul nostru lucrăm cu secvențe de cuvinte. Totuși, principiul general de reprezentare a secvențelor cu un vector de offset rămâne același.\n",
|
|
"\n",
|
|
"Pentru a lucra cu reprezentarea prin offset, utilizăm stratul [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Este similar cu `Embedding`, dar ia ca intrare un vector de conținut și un vector de offset și include, de asemenea, un strat de agregare, care poate fi `mean`, `sum` sau `max`.\n",
|
|
"\n",
|
|
"Iată o rețea modificată care utilizează `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": [
|
|
"Pentru a pregăti setul de date pentru antrenare, trebuie să furnizăm o funcție de conversie care va pregăti vectorul de offset:\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": [
|
|
"Notă, că spre deosebire de toate exemplele anterioare, rețeaua noastră acceptă acum doi parametri: vectorul de date și vectorul de offset, care au dimensiuni diferite. În mod similar, loader-ul nostru de date ne oferă acum 3 valori în loc de 2: atât vectorii de text, cât și cei de offset sunt furnizați ca caracteristici. Prin urmare, trebuie să ajustăm ușor funcția noastră de antrenament pentru a ține cont de acest lucru:\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": [
|
|
"## Încapsulări Semantice: Word2Vec\n",
|
|
"\n",
|
|
"În exemplul nostru anterior, stratul de învățare al modelului a fost antrenat să mapeze cuvintele la o reprezentare vectorială, însă această reprezentare nu avea prea multă semnificație semantică. Ar fi util să învățăm o astfel de reprezentare vectorială, în care cuvintele similare sau sinonime să corespundă unor vectori care sunt apropiați unul de celălalt în funcție de o anumită distanță vectorială (de exemplu, distanța euclidiană).\n",
|
|
"\n",
|
|
"Pentru a realiza acest lucru, trebuie să pre-antrenăm modelul de învățare pe o colecție mare de texte într-un mod specific. Una dintre primele metode de antrenare a încapsulărilor semantice se numește [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Aceasta se bazează pe două arhitecturi principale utilizate pentru a produce o reprezentare distribuită a cuvintelor:\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — în această arhitectură, modelul este antrenat să prezică un cuvânt pe baza contextului din jur. Având ngrama $(W_{-2},W_{-1},W_0,W_1,W_2)$, scopul modelului este să prezică $W_0$ pe baza $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
|
" - **Continuous skip-gram** este opusul CBoW. Modelul folosește fereastra de cuvinte contextuale din jur pentru a prezice cuvântul curent.\n",
|
|
"\n",
|
|
"CBoW este mai rapid, în timp ce skip-gram este mai lent, dar oferă o reprezentare mai bună pentru cuvintele rare.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Pentru a experimenta cu încapsulările word2vec pre-antrenate pe setul de date Google News, putem folosi biblioteca **gensim**. Mai jos găsim cuvintele cele mai similare cu 'neural'.\n",
|
|
"\n",
|
|
"> **Note:** Când creați pentru prima dată vectori de cuvinte, descărcarea acestora poate dura ceva timp!\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": [
|
|
"Putem, de asemenea, să calculăm încorporările vectoriale din cuvânt, pentru a fi utilizate în antrenarea modelului de clasificare (afișăm doar primele 20 de componente ale vectorului pentru claritate):\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": [
|
|
"Lucrul grozav despre încorporările semantice este că poți manipula codificarea vectorială pentru a schimba semantica. De exemplu, putem cere să găsim un cuvânt, a cărui reprezentare vectorială să fie cât mai apropiată de cuvintele *rege* și *femeie*, și cât mai îndepărtată de cuvântul *bărbat*:\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": [
|
|
"Atât CBoW, cât și Skip-Grams sunt încorporări „predictive”, în sensul că iau în considerare doar contexte locale. Word2Vec nu profită de contextul global.\n",
|
|
"\n",
|
|
"**FastText** se bazează pe Word2Vec prin învățarea reprezentărilor vectoriale pentru fiecare cuvânt și pentru n-gramele de caractere găsite în interiorul fiecărui cuvânt. Valorile reprezentărilor sunt apoi mediate într-un singur vector la fiecare pas de antrenament. Deși acest lucru adaugă multă calculare suplimentară în etapa de pre-antrenare, permite încorporărilor de cuvinte să encodeze informații la nivel de sub-cuvânt.\n",
|
|
"\n",
|
|
"O altă metodă, **GloVe**, valorifică ideea matricei de co-apariție, utilizând metode neuronale pentru a descompune matricea de co-apariție în vectori de cuvinte mai expresivi și non-liniari.\n",
|
|
"\n",
|
|
"Poți experimenta cu exemplul schimbând încorporările în FastText și GloVe, deoarece gensim suportă mai multe modele diferite de încorporare a cuvintelor.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Utilizarea Embedding-urilor Pre-Antrenate în PyTorch\n",
|
|
"\n",
|
|
"Putem modifica exemplul de mai sus pentru a pre-popula matricea din stratul nostru de embedding cu embedding-uri semantice, cum ar fi Word2Vec. Trebuie să ținem cont de faptul că vocabularele embedding-urilor pre-antrenate și ale corpusului nostru de text probabil nu se vor potrivi, așa că vom inițializa greutățile pentru cuvintele lipsă cu valori random:\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": [
|
|
"Acum să antrenăm modelul nostru. Rețineți că timpul necesar pentru a antrena modelul este semnificativ mai mare decât în exemplul anterior, datorită dimensiunii mai mari a stratului de încorporare și, astfel, unui număr mult mai mare de parametri. De asemenea, din acest motiv, s-ar putea să fie nevoie să antrenăm modelul nostru pe mai multe exemple dacă dorim să evităm supraînvățarea.\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": [
|
|
"În cazul nostru, nu observăm o creștere semnificativă a acurateței, cel mai probabil din cauza vocabularului destul de diferit. \n",
|
|
"Pentru a depăși problema vocabularului diferit, putem folosi una dintre următoarele soluții: \n",
|
|
"* Re-antrenarea modelului word2vec pe vocabularul nostru \n",
|
|
"* Încărcarea setului nostru de date folosind vocabularul din modelul word2vec pre-antrenat. Vocabularul utilizat pentru încărcarea setului de date poate fi specificat în timpul încărcării. \n",
|
|
"\n",
|
|
"A doua abordare pare mai ușoară, mai ales deoarece cadrul `torchtext` din PyTorch conține suport integrat pentru embeddings. Putem, de exemplu, să instanțiem un vocabular bazat pe GloVe în următorul mod: \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": [
|
|
"Vocabularul încărcat are următoarele operațiuni de bază: \n",
|
|
"* Dicționarul `vocab.stoi` ne permite să convertim un cuvânt în indexul său din dicționar \n",
|
|
"* `vocab.itos` face opusul - convertește un număr în cuvânt \n",
|
|
"* `vocab.vectors` este matricea de vectori de încorporare, așa că pentru a obține încorporarea unui cuvânt `s`, trebuie să folosim `vocab.vectors[vocab.stoi[s]]` \n",
|
|
"\n",
|
|
"Iată un exemplu de manipulare a încorporărilor pentru a demonstra ecuația **kind-man+woman = queen** (a trebuit să ajustez puțin coeficientul pentru a funcționa): \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": [
|
|
"Pentru a antrena clasificatorul folosind acele embeddings, mai întâi trebuie să codificăm setul nostru de date folosind vocabularul GloVe:\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": [
|
|
"Așa cum am văzut mai sus, toate încorporările vectoriale sunt stocate în matricea `vocab.vectors`. Acest lucru face extrem de ușoară încărcarea acelor greutăți în greutățile stratului de încorporare folosind copierea simplă:\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": [
|
|
"Unul dintre motivele pentru care nu observăm o creștere semnificativă a acurateței este faptul că unele cuvinte din setul nostru de date lipsesc din vocabularul GloVe pre-antrenat și, prin urmare, sunt practic ignorate. Pentru a depăși acest fapt, putem antrena propriile noastre embedding-uri pe setul nostru de date.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Încadrări Contextuale\n",
|
|
"\n",
|
|
"O limitare importantă a reprezentărilor tradiționale de încadrare pre-antrenate, cum ar fi Word2Vec, este problema dezambiguizării sensului cuvintelor. Deși încadrările pre-antrenate pot surprinde o parte din sensul cuvintelor în context, fiecare posibil sens al unui cuvânt este codificat în aceeași încadrare. Acest lucru poate cauza probleme în modelele ulterioare, deoarece multe cuvinte, cum ar fi cuvântul „play”, au sensuri diferite în funcție de contextul în care sunt utilizate.\n",
|
|
"\n",
|
|
"De exemplu, cuvântul „play” în aceste două propoziții are sensuri destul de diferite:\n",
|
|
"- Am fost la o **piesă** de teatru.\n",
|
|
"- John vrea să se **joace** cu prietenii săi.\n",
|
|
"\n",
|
|
"Încadrările pre-antrenate de mai sus reprezintă ambele sensuri ale cuvântului „play” în aceeași încadrare. Pentru a depăși această limitare, trebuie să construim încadrări bazate pe **modelul lingvistic**, care este antrenat pe un corpus mare de text și *știe* cum pot fi utilizate cuvintele în contexte diferite. Discutarea încadrărilor contextuale depășește scopul acestui tutorial, dar vom reveni la ele atunci când vom vorbi despre modelele lingvistice în unitatea următoare.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Declinare de responsabilitate**: \nAcest document a fost tradus folosind serviciul de traducere AI [Co-op Translator](https://github.com/Azure/co-op-translator). Deși ne străduim să asigurăm acuratețea, vă rugăm să rețineți că traducerile automate pot conține erori sau inexactități. Documentul original în limba sa natală ar trebui considerat sursa autoritară. Pentru informații critice, se recomandă traducerea profesională realizată de un specialist uman. Nu ne asumăm responsabilitatea pentru eventualele neînțelegeri sau interpretări greșite care pot apărea din utilizarea acestei traduceri.\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-30T01:08:01+00:00",
|
|
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
|
"language_code": "ro"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |