724 lines
37 KiB
Plaintext
724 lines
37 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Вбудовування\n",
|
||
"\n",
|
||
"У нашому попередньому прикладі ми працювали з високовимірними векторами \"мішок слів\" довжиною `vocab_size`, і ми явно перетворювали низьковимірні вектори позиційного представлення у розріджене одноелементне представлення. Таке одноелементне представлення не є ефективним з точки зору пам'яті, до того ж кожне слово розглядається незалежно одне від одного, тобто одноелементні закодовані вектори не виражають жодної семантичної схожості між словами.\n",
|
||
"\n",
|
||
"У цьому розділі ми продовжимо досліджувати набір даних **News AG**. Для початку завантажимо дані та отримаємо деякі визначення з попереднього блокнота.\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": [
|
||
"## Що таке вбудовування?\n",
|
||
"\n",
|
||
"Ідея **вбудовування** полягає в тому, щоб представляти слова за допомогою низьковимірних щільних векторів, які певним чином відображають семантичне значення слова. Пізніше ми обговоримо, як створювати змістовні вбудовування слів, але наразі давайте просто розглядати вбудовування як спосіб зменшення розмірності векторів слів.\n",
|
||
"\n",
|
||
"Отже, шар вбудовування приймає слово як вхід і створює вихідний вектор заданого розміру `embedding_size`. У певному сенсі це дуже схоже на шар `Linear`, але замість того, щоб приймати вектор з одним активним елементом (one-hot encoded vector), він може приймати номер слова як вхід.\n",
|
||
"\n",
|
||
"Використовуючи шар вбудовування як перший шар у нашій мережі, ми можемо перейти від моделі \"мішок слів\" до моделі **мішок вбудовувань**, де спочатку кожне слово в тексті перетворюється на відповідне вбудовування, а потім обчислюється певна агрегатна функція для всіх цих вбудовувань, наприклад `sum`, `average` або `max`.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Наша нейронна мережа класифікатора починатиметься з шару вбудовування, потім шару агрегування, а зверху — лінійного класифікатора:\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": [
|
||
"### Робота зі змінним розміром послідовності змінних\n",
|
||
"\n",
|
||
"Через особливості цієї архітектури, мініпакети для нашої мережі потрібно створювати певним чином. У попередньому розділі, коли ми використовували метод \"мішок слів\" (bag-of-words), усі тензори BoW у мініпакеті мали однаковий розмір `vocab_size`, незалежно від фактичної довжини текстової послідовності. Коли ми переходимо до використання словникових векторів (word embeddings), кількість слів у кожному текстовому зразку стає змінною, і при об'єднанні цих зразків у мініпакети нам доведеться застосовувати заповнення (padding).\n",
|
||
"\n",
|
||
"Це можна зробити за допомогою тієї ж техніки, що й надання функції `collate_fn` джерелу даних:\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": [
|
||
"### Навчання класифікатора на основі вбудовування\n",
|
||
"\n",
|
||
"Тепер, коли ми визначили відповідний завантажувач даних, ми можемо навчити модель, використовуючи функцію навчання, яку ми визначили в попередньому розділі:\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": [
|
||
"> **Примітка**: Ми тренуємося лише на 25 тисячах записів (менше одного повного епоху) заради економії часу, але ви можете продовжити тренування, написати функцію для тренування протягом кількох епох і експериментувати з параметром швидкості навчання, щоб досягти вищої точності. Ви повинні бути здатні досягти точності близько 90%.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Шар EmbeddingBag та представлення послідовностей змінної довжини\n",
|
||
"\n",
|
||
"У попередній архітектурі нам потрібно було доповнювати всі послідовності до однакової довжини, щоб вони відповідали розміру мініпакету. Це не найефективніший спосіб представлення послідовностей змінної довжини — інший підхід полягає у використанні **вектора зсувів**, який містить зсуви всіх послідовностей, збережених в одному великому векторі.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"> **Примітка**: На зображенні вище показано послідовність символів, але в нашому прикладі ми працюємо з послідовностями слів. Однак загальний принцип представлення послідовностей за допомогою вектора зсувів залишається тим самим.\n",
|
||
"\n",
|
||
"Для роботи з представленням за допомогою зсувів ми використовуємо шар [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). Він схожий на `Embedding`, але приймає вектор вмісту та вектор зсувів як вхідні дані, а також включає шар усереднення, який може бути `mean`, `sum` або `max`.\n",
|
||
"\n",
|
||
"Ось модифікована мережа, яка використовує `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": [
|
||
"Щоб підготувати набір даних для навчання, нам потрібно надати функцію конвертації, яка підготує вектор зміщення:\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": [
|
||
"Зверніть увагу, що, на відміну від усіх попередніх прикладів, наша мережа тепер приймає два параметри: вектор даних і вектор зміщення, які мають різні розміри. Аналогічно, наш завантажувач даних також надає нам 3 значення замість 2: як текстові, так і вектори зміщення надаються як ознаки. Тому нам потрібно трохи змінити нашу функцію навчання, щоб врахувати це:\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": [
|
||
"## Семантичні Вбудовування: Word2Vec\n",
|
||
"\n",
|
||
"У нашому попередньому прикладі шар вбудовування моделі навчався відображати слова у векторне представлення, однак це представлення не мало значного семантичного значення. Було б добре навчитися створювати таке векторне представлення, де схожі слова або синоніми відповідали б векторами, які близькі один до одного за певною векторною відстанню (наприклад, евклідовою відстанню).\n",
|
||
"\n",
|
||
"Для цього нам потрібно попередньо навчити нашу модель вбудовування на великій колекції текстів у специфічний спосіб. Один із перших методів навчання семантичних вбудовувань називається [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Він базується на двох основних архітектурах, які використовуються для створення розподіленого представлення слів:\n",
|
||
"\n",
|
||
" - **Неперервний мішок слів** (CBoW) — у цій архітектурі ми навчаємо модель передбачати слово за навколишнім контекстом. Для даного n-граму $(W_{-2},W_{-1},W_0,W_1,W_2)$ мета моделі — передбачити $W_0$ за $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
||
" - **Неперервний skip-gram** є протилежністю до CBoW. Модель використовує навколишнє вікно контекстних слів, щоб передбачити поточне слово.\n",
|
||
"\n",
|
||
"CBoW працює швидше, тоді як skip-gram повільніший, але краще справляється з представленням рідковживаних слів.\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Щоб експериментувати з вбудовуванням word2vec, попередньо навченим на наборі даних Google News, ми можемо використовувати бібліотеку **gensim**. Нижче наведено приклад пошуку слів, найбільш схожих на 'neural'.\n",
|
||
"\n",
|
||
"> **Примітка:** Коли ви вперше створюєте векторні представлення слів, їх завантаження може зайняти деякий час!\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": [
|
||
"Ми також можемо обчислити векторні вбудовування зі слова, щоб використовувати їх у навчанні моделі класифікації (ми показуємо лише перші 20 компонентів вектора для ясності):\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": [
|
||
"Чудова річ у семантичних вбудовуваннях полягає в тому, що ви можете маніпулювати векторним кодуванням, щоб змінити семантику. Наприклад, ми можемо попросити знайти слово, чия векторна репрезентація буде якомога ближчою до слів *король* і *жінка*, і якомога далі від слова *чоловік*:\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": [
|
||
"Обидва методи, CBoW і Skip-Grams, є \"прогнозуючими\" векторними уявленнями, оскільки вони враховують лише локальні контексти. Word2Vec не використовує глобальний контекст.\n",
|
||
"\n",
|
||
"**FastText** розширює Word2Vec, навчаючи векторні уявлення для кожного слова та n-грам символів, які зустрічаються в межах кожного слова. Значення цих уявлень потім усереднюються в один вектор на кожному кроці навчання. Хоча це додає значну кількість додаткових обчислень під час попереднього навчання, це дозволяє векторним уявленням слів кодувати інформацію про частини слова.\n",
|
||
"\n",
|
||
"Інший метод, **GloVe**, використовує ідею матриці спільної зустрічальності, застосовуючи нейронні методи для розкладання цієї матриці на більш виразні та нелінійні векторні уявлення слів.\n",
|
||
"\n",
|
||
"Ви можете експериментувати з прикладом, змінюючи векторні уявлення на FastText і GloVe, оскільки gensim підтримує кілька різних моделей векторних уявлень слів.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Використання попередньо навчених векторів у PyTorch\n",
|
||
"\n",
|
||
"Ми можемо змінити наведений вище приклад, щоб попередньо заповнити матрицю у нашому шарі векторизації семантичними векторами, такими як Word2Vec. Потрібно врахувати, що словники попередньо навчених векторів і нашого текстового корпусу, ймовірно, не співпадатимуть, тому ми ініціалізуємо ваги для відсутніх слів випадковими значеннями:\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": [
|
||
"Тепер давайте навчимо нашу модель. Зверніть увагу, що час, необхідний для навчання моделі, значно більший, ніж у попередньому прикладі, через більший розмір шару вбудовування, а отже, значно більшу кількість параметрів. Також через це нам може знадобитися навчати нашу модель на більшій кількості прикладів, якщо ми хочемо уникнути перенавчання.\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",
|
||
"Щоб вирішити проблему різних словників, ми можемо скористатися одним із наступних рішень: \n",
|
||
"* Перенавчити модель word2vec на нашому словнику \n",
|
||
"* Завантажити наш набір даних із використанням словника з попередньо навченої моделі word2vec. Словник, який використовується для завантаження набору даних, можна вказати під час завантаження. \n",
|
||
"\n",
|
||
"Останній підхід здається простішим, особливо тому, що фреймворк PyTorch `torchtext` містить вбудовану підтримку для векторів слів. Наприклад, ми можемо створити словник на основі GloVe наступним чином: \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": [
|
||
"Завантажений словник має наступні базові операції:\n",
|
||
"* Словник `vocab.stoi` дозволяє перетворити слово на його індекс у словнику\n",
|
||
"* `vocab.itos` робить протилежне - перетворює число на слово\n",
|
||
"* `vocab.vectors` є масивом векторів вбудовування, тому, щоб отримати вбудовування слова `s`, нам потрібно використати `vocab.vectors[vocab.stoi[s]]`\n",
|
||
"\n",
|
||
"Ось приклад маніпуляції з вбудовуваннями, щоб продемонструвати рівняння **kind-man+woman = queen** (я трохи підкоригував коефіцієнт, щоб це спрацювало):\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": [
|
||
"Щоб навчити класифікатор за допомогою цих векторів, спочатку потрібно закодувати наш набір даних за допомогою словника 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": [
|
||
"Як ми бачили вище, всі векторні вбудовування зберігаються в матриці `vocab.vectors`. Це робить надзвичайно простим завантаження цих ваг у ваги шару вбудовування за допомогою простого копіювання:\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": [
|
||
"Однією з причин, чому ми не спостерігаємо значного підвищення точності, є те, що деякі слова з нашого набору даних відсутні у попередньо натренованому словнику GloVe, і тому вони фактично ігноруються. Щоб подолати це, ми можемо натренувати власні векторні представлення на нашому наборі даних.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Контекстуальні векторні представлення\n",
|
||
"\n",
|
||
"Одним із ключових обмежень традиційних попередньо навчених векторних представлень, таких як Word2Vec, є проблема розрізнення значень слів. Хоча попередньо навчені вектори можуть захоплювати певний зміст слів у контексті, усі можливі значення слова кодуються в одному й тому ж векторі. Це може спричиняти проблеми в подальших моделях, оскільки багато слів, наприклад слово \"play\", мають різні значення залежно від контексту, у якому вони використовуються.\n",
|
||
"\n",
|
||
"Наприклад, слово \"play\" у цих двох реченнях має зовсім різні значення:\n",
|
||
"- Я пішов на **п'єсу** в театрі.\n",
|
||
"- Джон хоче **грати** зі своїми друзями.\n",
|
||
"\n",
|
||
"Попередньо навчені вектори, згадані вище, представляють обидва ці значення слова \"play\" в одному й тому ж векторі. Щоб подолати це обмеження, потрібно створювати векторні представлення на основі **мовної моделі**, яка тренується на великому корпусі тексту і *знає*, як слова можуть поєднуватися в різних контекстах. Обговорення контекстуальних векторних представлень виходить за рамки цього уроку, але ми повернемося до них, коли будемо говорити про мовні моделі в наступному розділі.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Відмова від відповідальності**: \nЦей документ був перекладений за допомогою сервісу автоматичного перекладу [Co-op Translator](https://github.com/Azure/co-op-translator). Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.\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-30T10:38:21+00:00",
|
||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||
"language_code": "uk"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |