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

724 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

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

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Osadzenia\n",
"\n",
"W naszym poprzednim przykładzie operowaliśmy na wysokowymiarowych wektorach typu bag-of-words o długości `vocab_size`, a także jawnie konwertowaliśmy niskowymiarowe wektory reprezentacji pozycyjnej na rzadkie reprezentacje typu one-hot. Taka reprezentacja one-hot nie jest efektywna pod względem pamięci, a dodatkowo każde słowo jest traktowane niezależnie od innych, tzn. zakodowane wektory one-hot nie wyrażają żadnego semantycznego podobieństwa między słowami.\n",
"\n",
"W tej jednostce będziemy kontynuować eksplorację zbioru danych **News AG**. Na początek załadujmy dane i pobierzmy kilka definicji z poprzedniego notatnika.\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": [
"## Czym jest embedding?\n",
"\n",
"Idea **embeddingu** polega na reprezentowaniu słów za pomocą gęstych wektorów o niższej wymiarowości, które w pewien sposób odzwierciedlają semantyczne znaczenie słowa. Później omówimy, jak tworzyć znaczące embeddingi słów, ale na razie potraktujmy embeddingi jako sposób na zmniejszenie wymiarowości wektora słowa.\n",
"\n",
"Warstwa embeddingu przyjmuje słowo jako dane wejściowe i generuje wektor wyjściowy o określonym `embedding_size`. W pewnym sensie jest to bardzo podobne do warstwy `Linear`, ale zamiast przyjmować wektor zakodowany w formie one-hot, może przyjąć numer słowa jako dane wejściowe.\n",
"\n",
"Używając warstwy embeddingu jako pierwszej warstwy w naszej sieci, możemy przejść od modelu bag-of-words do modelu **embedding bag**, w którym najpierw konwertujemy każde słowo w naszym tekście na odpowiadający mu embedding, a następnie obliczamy jakąś funkcję agregującą dla wszystkich tych embeddingów, taką jak `sum`, `average` lub `max`.\n",
"\n",
"![Obraz przedstawiający klasyfikator embeddingu dla pięciu słów w sekwencji.](../../../../../lessons/5-NLP/14-Embeddings/images/embedding-classifier-example.png)\n",
"\n",
"Nasza sieć neuronowa klasyfikatora rozpocznie się od warstwy embeddingu, następnie warstwy agregacji, a na końcu liniowego klasyfikatora:\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": [
"### Radzenie sobie z różną długością sekwencji zmiennych\n",
"\n",
"W wyniku tej architektury, minibatche dla naszej sieci muszą być tworzone w określony sposób. W poprzedniej części, podczas korzystania z bag-of-words, wszystkie tensory BoW w minibatchu miały jednakowy rozmiar `vocab_size`, niezależnie od rzeczywistej długości sekwencji tekstowej. Gdy przechodzimy do osadzania słów (word embeddings), kończymy z różną liczbą słów w każdej próbce tekstowej, a podczas łączenia tych próbek w minibatche musimy zastosować pewne wypełnienie (padding).\n",
"\n",
"Można to zrobić, stosując tę samą technikę, polegającą na dostarczeniu funkcji `collate_fn` do źródła danych:\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": [
"### Trenowanie klasyfikatora osadzeń\n",
"\n",
"Teraz, gdy zdefiniowaliśmy odpowiedni ładowacz danych, możemy wytrenować model, korzystając z funkcji treningowej, którą zdefiniowaliśmy w poprzedniej jednostce:\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": [
"> **Uwaga**: Trenujemy tutaj tylko na 25 tysiącach rekordów (mniej niż jedna pełna epoka) ze względu na czas, ale możesz kontynuować trening, napisać funkcję do trenowania przez kilka epok i eksperymentować z parametrem szybkości uczenia, aby osiągnąć wyższą dokładność. Powinieneś być w stanie osiągnąć dokładność na poziomie około 90%.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Warstwa EmbeddingBag i reprezentacja sekwencji o zmiennej długości\n",
"\n",
"W poprzedniej architekturze musieliśmy uzupełniać wszystkie sekwencje do tej samej długości, aby dopasować je do minibatcha. Nie jest to jednak najbardziej efektywny sposób reprezentowania sekwencji o zmiennej długości innym podejściem byłoby użycie wektora **offset**, który przechowuje przesunięcia wszystkich sekwencji zapisanych w jednym dużym wektorze.\n",
"\n",
"![Obraz przedstawiający reprezentację sekwencji za pomocą przesunięć](../../../../../lessons/5-NLP/14-Embeddings/images/offset-sequence-representation.png)\n",
"\n",
"> **Note**: Na powyższym obrazku pokazano sekwencję znaków, ale w naszym przykładzie pracujemy z sekwencjami słów. Jednak ogólna zasada reprezentowania sekwencji za pomocą wektora przesunięć pozostaje taka sama.\n",
"\n",
"Aby pracować z reprezentacją przesunięć, używamy warstwy [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Jest ona podobna do `Embedding`, ale przyjmuje jako wejście wektor zawartości oraz wektor przesunięć, a także zawiera warstwę uśredniającą, która może być `mean`, `sum` lub `max`.\n",
"\n",
"Oto zmodyfikowana sieć wykorzystująca `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": [
"Aby przygotować zestaw danych do treningu, musimy dostarczyć funkcję konwersji, która przygotuje wektor przesunięcia:\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": [
"Należy zauważyć, że w przeciwieństwie do wszystkich poprzednich przykładów, nasza sieć teraz akceptuje dwa parametry: wektor danych i wektor przesunięcia, które mają różne rozmiary. Podobnie, nasz ładowacz danych dostarcza nam 3 wartości zamiast 2: zarówno wektory tekstowe, jak i przesunięcia są dostarczane jako cechy. Dlatego musimy nieco dostosować naszą funkcję treningową, aby to uwzględnić:\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": [
"## Semantyczne osadzenia: Word2Vec\n",
"\n",
"W naszym poprzednim przykładzie warstwa osadzenia modelu nauczyła się mapować słowa na reprezentacje wektorowe, jednak ta reprezentacja nie miała dużego znaczenia semantycznego. Byłoby dobrze nauczyć się takiej reprezentacji wektorowej, w której podobne słowa lub synonimy odpowiadałyby wektorom bliskim sobie pod względem jakiejś odległości wektorowej (np. odległości euklidesowej).\n",
"\n",
"Aby to osiągnąć, musimy wstępnie wytrenować nasz model osadzenia na dużym zbiorze tekstów w specyficzny sposób. Jednym z pierwszych sposobów trenowania semantycznych osadzeń jest metoda [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Opiera się ona na dwóch głównych architekturach, które są używane do tworzenia rozproszonej reprezentacji słów:\n",
"\n",
" - **Ciągły worek słów** (CBoW) — w tej architekturze trenujemy model, aby przewidywał słowo na podstawie otaczającego kontekstu. Mając n-gram $(W_{-2},W_{-1},W_0,W_1,W_2)$, celem modelu jest przewidzenie $W_0$ na podstawie $(W_{-2},W_{-1},W_1,W_2)$.\n",
" - **Ciągły skip-gram** jest przeciwieństwem CBoW. Model wykorzystuje otaczające okno słów kontekstowych, aby przewidzieć bieżące słowo.\n",
"\n",
"CBoW działa szybciej, podczas gdy skip-gram jest wolniejszy, ale lepiej radzi sobie z reprezentacją rzadkich słów.\n",
"\n",
"![Obraz przedstawiający algorytmy CBoW i Skip-Gram do konwersji słów na wektory.](../../../../../lessons/5-NLP/14-Embeddings/images/example-algorithms-for-converting-words-to-vectors.png)\n",
"\n",
"Aby eksperymentować z osadzeniem word2vec wstępnie wytrenowanym na zbiorze danych Google News, możemy użyć biblioteki **gensim**. Poniżej znajdujemy słowa najbardziej podobne do 'neural'.\n",
"\n",
"> **Note:** Przy pierwszym tworzeniu wektorów słów ich pobieranie może zająć trochę czasu!\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": [
"Możemy również obliczyć osadzenia wektorowe z wyrazu, które będą używane do trenowania modelu klasyfikacyjnego (pokazujemy tylko pierwsze 20 komponentów wektora dla przejrzystości):\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": [
"Wspaniałą cechą osadzeń semantycznych jest to, że można manipulować kodowaniem wektorowym, aby zmieniać semantykę. Na przykład możemy poprosić o znalezienie słowa, którego reprezentacja wektorowa byłaby jak najbliższa słowom *król* i *kobieta*, a jak najdalsza od słowa *mężczyzna*:\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": [
"Zarówno CBoW, jak i Skip-Grams to osadzenia „predykcyjne”, ponieważ uwzględniają jedynie lokalne konteksty. Word2Vec nie wykorzystuje globalnego kontekstu.\n",
"\n",
"**FastText** rozwija Word2Vec, ucząc się reprezentacji wektorowych dla każdego słowa oraz n-gramów znakowych zawartych w każdym słowie. Wartości tych reprezentacji są następnie uśredniane do jednego wektora na każdym kroku treningu. Chociaż dodaje to dużo dodatkowych obliczeń podczas wstępnego treningu, umożliwia osadzeniom słów kodowanie informacji o pod-słowach.\n",
"\n",
"Inna metoda, **GloVe**, opiera się na idei macierzy współwystępowania i wykorzystuje metody neuronowe do dekompozycji macierzy współwystępowania na bardziej ekspresyjne i nieliniowe wektory słów.\n",
"\n",
"Możesz eksperymentować, zmieniając osadzenia na FastText i GloVe, ponieważ gensim obsługuje kilka różnych modeli osadzeń słów.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Korzystanie z wstępnie wytrenowanych osadzeń w PyTorch\n",
"\n",
"Możemy zmodyfikować powyższy przykład, aby wstępnie wypełnić macierz w naszej warstwie osadzeń semantycznymi osadzeniami, takimi jak Word2Vec. Musimy jednak pamiętać, że słowniki wstępnie wytrenowanych osadzeń i naszego korpusu tekstowego prawdopodobnie nie będą się pokrywać, więc zainicjalizujemy wagi dla brakujących słów losowymi wartościami:\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": [
"Teraz przejdźmy do trenowania naszego modelu. Zauważ, że czas potrzebny na trenowanie modelu jest znacznie dłuższy niż w poprzednim przykładzie, ze względu na większy rozmiar warstwy osadzania, a co za tym idzie, znacznie większą liczbę parametrów. Ponadto, z tego powodu możemy potrzebować trenować nasz model na większej liczbie przykładów, jeśli chcemy uniknąć przeuczenia.\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": [
"W naszym przypadku nie zauważamy dużego wzrostu dokładności, co prawdopodobnie wynika z dość różnych słownictw. \n",
"Aby rozwiązać problem różnic w słownictwie, możemy zastosować jedno z następujących rozwiązań: \n",
"* Ponowne wytrenowanie modelu word2vec na naszym słownictwie \n",
"* Załadowanie naszego zbioru danych z użyciem słownictwa z wstępnie wytrenowanego modelu word2vec. Słownictwo używane do załadowania zbioru danych można określić podczas ładowania. \n",
"\n",
"Drugie podejście wydaje się łatwiejsze, zwłaszcza że framework `torchtext` w PyTorch zawiera wbudowaną obsługę osadzeń. Możemy na przykład utworzyć słownictwo oparte na GloVe w następujący sposób: \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": [
"Załadowany słownik posiada następujące podstawowe operacje:\n",
"* Słownik `vocab.stoi` pozwala nam zamienić słowo na jego indeks w słowniku\n",
"* `vocab.itos` działa odwrotnie - zamienia liczbę na słowo\n",
"* `vocab.vectors` to tablica wektorów osadzeń, więc aby uzyskać osadzenie dla słowa `s`, musimy użyć `vocab.vectors[vocab.stoi[s]]`\n",
"\n",
"Oto przykład manipulacji osadzeniami, aby zademonstrować równanie **kind-man+woman = queen** (musiałem nieco dostosować współczynnik, aby to zadziałało):\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": [
"Aby wytrenować klasyfikator za pomocą tych osadzeń, najpierw musimy zakodować nasz zbiór danych za pomocą słownictwa 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": [
"Jak widzieliśmy powyżej, wszystkie osadzenia wektorów są przechowywane w macierzy `vocab.vectors`. Dzięki temu niezwykle łatwo jest załadować te wagi do wag warstwy osadzenia, używając prostego kopiowania:\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": [
"Jednym z powodów, dla których nie obserwujemy znaczącego wzrostu dokładności, jest fakt, że niektóre słowa z naszego zbioru danych są nieobecne w wstępnie wytrenowanym słowniku GloVe, a zatem są zasadniczo ignorowane. Aby przezwyciężyć ten fakt, możemy wytrenować własne osadzenia na naszym zbiorze danych.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Kontekstowe osadzenia\n",
"\n",
"Jednym z kluczowych ograniczeń tradycyjnych wstępnie wytrenowanych reprezentacji osadzeń, takich jak Word2Vec, jest problem rozróżniania znaczeń słów. Chociaż wstępnie wytrenowane osadzenia mogą uchwycić część znaczenia słów w kontekście, każde możliwe znaczenie danego słowa jest kodowane w tym samym osadzeniu. Może to powodować problemy w modelach wykorzystujących te osadzenia, ponieważ wiele słów, takich jak słowo „grać”, ma różne znaczenia w zależności od kontekstu, w którym są używane.\n",
"\n",
"Na przykład słowo „grać” w tych dwóch zdaniach ma zupełnie inne znaczenie:\n",
"- Poszedłem na **sztukę** do teatru.\n",
"- Jan chce **grać** ze swoimi przyjaciółmi.\n",
"\n",
"Wstępnie wytrenowane osadzenia powyżej reprezentują oba te znaczenia słowa „grać” w tym samym osadzeniu. Aby przezwyciężyć to ograniczenie, musimy budować osadzenia oparte na **modelu językowym**, który jest wytrenowany na dużym korpusie tekstu i *wie*, jak słowa mogą być zestawiane w różnych kontekstach. Omówienie kontekstowych osadzeń wykracza poza zakres tego samouczka, ale wrócimy do tego tematu, gdy będziemy omawiać modele językowe w następnej jednostce.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Zastrzeżenie**: \nTen dokument został przetłumaczony za pomocą usługi tłumaczenia AI [Co-op Translator](https://github.com/Azure/co-op-translator). Chociaż dokładamy wszelkich starań, aby tłumaczenie było precyzyjne, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za autorytatywne źródło. W przypadku informacji o kluczowym znaczeniu zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.\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-31T14:08:16+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "pl"
}
},
"nbformat": 4,
"nbformat_minor": 2
}