720 lines
42 KiB
Plaintext
720 lines
42 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Embeddings\n",
|
|
"\n",
|
|
"នៅក្នុងឧទាហរណ៍មុនរបស់យើង យើងបានប្រតិបត្តិលើវ៉ិចទ័រ bag-of-words ដែលមានខ្សែបណ្តោយខ្ពស់ `vocab_size` និងយើងបានបម្លែងយ៉ាងច្បាស់ពីវ៉ិចទ័រតំណរភាគទូលាយ ទាប ទៅជាតំណាងតែមួយ-ក្តៅដែលរាយបញ្ចាំងតិចតួច។ តំណាងតែមួយ-ក្តៅនេះមិនមានប្រសិទ្ធភាពក្នុងការផ្ទុកចងចាំទេ បន្ថែមពីនេះ រាល់ពាក្យត្រូវបានចាត់ទុកឯករាជ្យពីគ្នា ប្រែថាវ៉ិចទ័រដែលបាន encode តែមួយ-ក្តៅមិនបង្ហាញន័យស្រដៀងគ្នាណាមួយរវាងពាក្យទេ។\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": [
|
|
"## តើ embedding ជាអ្វី?\n",
|
|
"\n",
|
|
"គំនិតនៃ **embedding** គឺជារៀបរាប់ពាក្យជាវ៉ិចទ័រដ៏រឹងមាំដែលមានវិមាត្រកថ្លៃទាប ជាដើមដែលបញ្ជាក់អត្ថន័យយោងទៅលើពាក្យមួយ។ យើងនឹងពិភាក្សាពីរបៀបកសាង word embeddings មានន័យក្នុងពេលក្រោយ ប៉ុន្តែនាពេលនេះ hãyគិតថា embeddings ជាវិធីកាត់បន្ថយវិមាត្រនៃវ៉ិចទ័រពាក្យមួយ។\n",
|
|
"\n",
|
|
"ដូច្នេះ, ស្រទាប់ embedding នឹងទទួលពាក្យមួយជាឧបត្ថម្ភ និងបង្កើតវ៉ិចទ័រចេញមួយដែលមាន `embedding_size` បានកំណត់។ ក្នុងន័យមួយវាស្រដៀងទៅនឹងស្រទាប់ `Linear` តែជំនួសវិចទ័រពោលជា one-hot encoded វាអាចទទួលលេខពាក្យដោយផ្ទាល់ជាឧបត្ថម្ភបាន។\n",
|
|
"\n",
|
|
"ដោយប្រើស្រទាប់ embedding ជាស្រទាប់ដំបូងក្នុងបណ្តាញរបស់យើង យើងអាចបម្លែងពីគំរូ bag-of-words ទៅគំរូ **embedding bag** ដែលនៅទីនោះយើងបម្លែងពាក្យនីមួយៗក្នុងអត្ថបទជាវ៉ិចទ័រពាក់ព័ន្ធ embedding ហើយបន្ទាប់មកគណនាអនុគមន៍សរុបលើវ៉ិចទ័រទាំងនោះ ដូចជា `sum`, `average` ឬ `max`។\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"បណ្តាញនឺរ៉ាល់នៃ classifer របស់យើង នឹងចាប់ផ្តើមជាមួយស្រទាប់ embedding បន្ទាប់ស្រទាប់ aggregation និងចុងក្រោយជាស្រទាប់ linear classifier៖\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",
|
|
"ជាលទ្ធផលនៃស្ថាបត្យកម្មនេះ minibatches ទៅបណ្ដាញរបស់យើងនឹងត្រូវបានបង្កើតឡើងដោយវិធីជាក់លាក់មួយ។ នៅឯកតាមុននេះ នៅពេលប្រើ bag-of-words តង់ស័រទាំងអស់ក្នុង minibatch មានទំហំពេលស្មើគ្នា `vocab_size` មិនគិតទំហំជាក់លាក់នៃលំដាប់អត្ថបទរបស់យើងឡើយ។ ពេលយើងផ្លាស់ទៅប្រើបន្ទាត់ពាក្យ word embeddings យើងនឹងបានចំនួនពាក្យប្រែប្រួលនៅក្នុងគំរូអត្ថបទនីមួយៗ ហើយពេលបញ្ចូលគំរូទាំងនោះចូលក្នុង minibatches យើងនឹងត្រូវអនុវត្តការបញ្ចូលបន្ថែម padding មួយចំនួន។\n",
|
|
"\n",
|
|
"នេះអាចធ្វើបានដោយប្រើបច្ចេកទេសដដែលគឺផ្តល់មុខងារ `collate_fn` ទៅដល់ datasource៖\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": [
|
|
"### ការបណ្តុះបណ្តាលម៉ាស៊ីនចាត់ថ្នាក់ embedding\n",
|
|
"\n",
|
|
"ឥឡូវនេះដែលយើងបានកំណត់ dataloader ត្រឹមត្រូវហើយ យើងអាចបណ្តុះបណ្តាលម៉ូដែលដោយប្រើមុខងារ training ដែលយើងបានកំណត់នៅឯកតាមុន៖\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": [
|
|
"> **កំណត់សម្គាល់**: យើងកំពុង ONLY បណ្តុះបណ្តាលសម្រាប់កំណត់ត្រា 25,000 នៅទីនេះ (តិចជាងមួយស្ទង់ពេញ) ដើម្បីជួយការសន្សំសំចៃពេលវេលា ប៉ុន្តែអ្នកអាចបន្តការបណ្តុះបណ្តាល សរសេរពិធីសាស្រ្តមួយសម្រាប់បណ្តុះបណ្តាលជាច្រើនស្ទង់ និងសាកល្បងជាមួយប៉ារ៉ាម៉ែត្រការសិក្សា ដើម្បីទទួលបានភាពត្រឹមត្រូវខ្ពស់ជាងមុន។ អ្នកគួរតែអាចទៅដល់ភាពត្រឹមត្រូវប្រហែល ៩០%។\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### EmbeddingBag ស្រទាប់ និង តំណាងរាងលំដាប់រៀងពីរយៈវែរីយ៉ាប៊ុល\n",
|
|
"\n",
|
|
"នៅក្នុងស្ថาปត្យកម្មមុន យើងត្រូវការបន្ថែមចន្លោះទទេទៅលើលំដាប់ទាំងអស់ដើម្បីឲ្យវាមានប្រវែងដូចគ្នាដើម្បីដាក់វាទៅក្នុង minibatch។ វាមិនមែនជារបៀបមានប្រសិទ្ធភាពបំផុតក្នុងការតំណាងលំដាប់វែរីយ៉ាប៊ុលនោះទេ - វិធានមួយផ្សេងគឺប្រើវ៉ិចទ័រ **offset** ដែលនឹងរក្សាទុកចន្លោះ offset របស់លំដាប់ទាំងអស់ដែលបានរក្សាទុកក្នុងវ៉ិចទ័រធំបន្ទាត់មួយ។\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"> **ចំណាំ**: នៅក្នុងរូបភាពខាងលើ យើងបង្ហាញលំដាប់តួអក្សរ ប៉ុន្តែនៅក្នុងឧទាហរណ៍របស់យើង កំពុងដំណើរការជាលំដាប់ពាក្យ។ ទោះយ៉ាងណា 원칙ទូទៅក្នុងការតំណាងលំដាប់ជាមួយវ៉ិចទ័រ offset នៅតែដូចគ្នា។\n",
|
|
"\n",
|
|
"ដើម្បីធ្វើការជាមួយតំណាង offset យើងប្រើស្រទាប់ [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)។ វាស្រដៀងនឹង `Embedding` ប៉ុន្តែវាបានទទួលខ្សែវ៉ិចទ័រ និង offset វ៉ិចទ័រជាជាច្រើននូវការបញ្ចូល ហើយវាក៏រួមបញ្ចូលជាមួយស្រទាប់ averaging ដែលអាចជាផ្នែក `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": [
|
|
"សូមកត់សម្គាល់ថា ខុសពីគំរូទាំងអស់មុននេះ បណ្ដាញរបស់យើងឥឡូវនេះទទួលបានប៉ារាម៉ែត្រពីរចូលគ្នា៖ វិចទ័រទិន្នន័យ និងវិចទ័រជំហរ ដែលមានទំហំខុសគ្នា។ ដូចគ្នានេះ អ្នកដំណើរការទិន្នន័យរបស់យើងក៏ផ្ដល់ឲ្យយើងនូវតម្លៃចំនួន៣មិនមែនត្រឹមតែ២ទេ៖ វិចទ័រទាំងអត្ថបទ និងជំហរត្រូវបានផ្ដល់ឲ្យជាលក្ខណៈពិសេស។ ដូច្នេះ យើងត្រូវកែប្រែ slightly ប្រតិបត្ដិការបណ្តុះបណ្តាលរបស់យើង ដើម្បីដោះស្រាយបញ្ហានេះ៖\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": [
|
|
"## Semantic Embeddings: Word2Vec\n",
|
|
"\n",
|
|
"នៅក្នុងឧទាហរណ៍មុនរបស់យើង ស្រទាប់ embedding ម៉ូដែលបានរៀនផែនទីពាក្យទៅរាងតំណាងវ៉ិចទ័រ ទោះជាយ៉ាងណា រាងតំណាងនេះមិនមានន័យsemantic ច្រើនទេ។ វានឹងល្អបើរៀនរាងតំណាងវ៉ិចទ័រដូច្នេះ ដែលពាក្យស្រដៀងគ្នា ឬពាក្យប្រហាក់ប្រហែលនឹងផ្គូផ្គងទៅនឹងវ៉ិចទ័រដែលនៅជិតគ្នាប្រកបដោយចម្ងាយវ៉ិចទ័រ (ឧ. ចម្ងាយអ៊ីយូគ្ល៊ីដៀន) ។\n",
|
|
"\n",
|
|
"ដើម្បីធ្វើបានដូច្នេះ យើងត្រូវបណ្ដុះម៉ូដែល embedding របស់យើងលើការប្រមូលផ្តុំអត្ថបទធំៗដោយវិធីពិសេសមួយ។ វិធីដំបូងៗក្នុងការបណ្ដុះ semantic embeddings មួយឈ្មោះ [Word2Vec](https://en.wikipedia.org/wiki/Word2vec)។ វាមានព្រណិតប្រាជ្ញាផ្លូវការចម្បងពីរដែលប្រើប្រព័ន្ធផ្សព្វផ្សាយរាងតំណាងរាងពាក្យមួយ៖\n",
|
|
"\n",
|
|
" - **Continuous bag-of-words** (CBoW) — ក្នុងសំណុំប្រាជ្ញាផ្លូវការនេះ យើងបណ្ដុះម៉ូដែលឲ្យទស្សនាពាក្យមួយពីបរិបទជុំវិញ។ ផ្តល់អោយ ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$ គោលបំណងម៉ូដែលគឺទស្សនាពាក្យ $W_0$ ពី $(W_{-2},W_{-1},W_1,W_2)$។\n",
|
|
" - **Continuous skip-gram** គឺលើកលែងពី CBoW។ ម៉ូដែលប្រើបណ្តោយបរិបទវ៉ីនដូជុំវិញ ដើម្បីទស្សនាពាក្យបច្ចុប្បន្ន។\n",
|
|
"\n",
|
|
"CBoW ឆាប់រហ័ស ខណៈដែល skip-gram យឺតជាង ប៉ុន្តែប្រសើរជាងក្នុងការតំណាងពាក្យដែលមិនមានប្រជាជនច្រើន។\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"ដើម្បីសាកល្បង embedding word2vec ដែលបានបណ្ដុះរួចនៅលើឃ្លាំងទិន្នន័យ Google News អស់យើងអាចប្រើបណ្ណាល័យ **gensim** ។ ខាងក្រោម យើងស្វែងរកពាក្យដែលស្រដៀងគ្នាច្រើនជាងគេជាមួយពាក្យ 'neural'\n",
|
|
"\n",
|
|
"> **Note:** រស់រាន់ពេលអ្នកបង្កើតវ៉ិចទ័រពាក្យដំបូង ការទាញយកវានឹងចំណាយពេលខ្លះ!\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": [
|
|
"យើងអាចគណនាការតំណាងវ៉ែកទ័រពីពាក្យដើម្បីប្រើក្នុងការបណ្តុះមូឌែលចាត់ថ្នាក់ (យើងបង្ហាញតែ ២០ ធាតុដំបូងនៃវ៉ែកទ័រដើម្បីភាពច្បាស់):\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": [
|
|
"រឿងល្អណាស់អំពីសេចក្តីចងក្រងសម្ភារៈន័យគឺអ្នកអាចត្រួតពិនិត្យកូដចុះបញ្ជីវ៉ិចទ័រដើម្បីផ្លាស់ប្តូរអត្ថន័យ។ ឧទាហរណ៍ យើងអាចស្នើសុំស្វែងរកពាក្យមួយដែលការតំនាងវ៉ិចទ័របស់វានឹងនៅជិតពាក្យ *king* និង *woman* ចំបង ហើយឆ្ងាយពីពាក្យ *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": [
|
|
"ទាំង CBoW និង Skip-Grams ជា embedding ទាំងពីរដែល “ទាយ” គឺដោយពួកវាទទួលយកតែបរិបទក្នុងតំបន់ជិតៗ។ Word2Vec មិនប្រើប្រាស់អត្ថប្រយោជន៍ពីបរិបទសកលឡើយ។\n",
|
|
"\n",
|
|
"**FastText** សាងសង់លើ Word2Vec ដោយរៀនតំណាងវ៉ិកទ័រសម្រាប់ពាក្យនីមួយៗ និង n-gram តួអក្សរដែលមានក្នុងពាក្យនីមួយៗ។ តម្លៃនៃតំណាងទាំងនេះនឹងត្រូវបានរាប់បូកជាមធ្យមទៅវ៉ិកទ័រតែមួយនៅក្នុងជំហានបណ្តុះបណ្តាលមួយៗ។ បើទោះបីជាវានាំឲ្យមានការគណនាថែមទៀតច្រើនក្នុងការបណ្តុះបណ្តាលមុនក៏ដោយ វាអនុញ្ញាតឲ្យ embedding ពាក្យអាចបង្ហាញព័ត៌មានក្រោមពាក្យ។\n",
|
|
"\n",
|
|
"វិធីផ្សេងទៀត គឺ **GloVe** អនុវត្តគំនិតម៉ាត្រីសco-occurrence ហើយប្រើវិធីណឺរ៉ាល់ដើម្បីបំបែកម៉ាត្រីស co-occurrence ទៅជាវ៉ិកទ័រពាក្យដែលមានការបង្ហាញពត៌មានច្រើន និងមិនមែនជារូបមន្តលាញ់។\n",
|
|
"\n",
|
|
"អ្នកអាចលេងជាមួយឧទាហរណ៍ដោយផ្លាស់ប្តូរ embeddings ទៅជា FastText និង GloVe ព្រោះ gensim គាំទ្រម៉ូដែល embedding ពាក្យជាច្រើនផ្សេងទៀត។\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## ការប្រើប្រាស់ការកំណត់តម្លៃជាមុនក្នុង PyTorch\n",
|
|
"\n",
|
|
"យើងអាចកែប្រែឧទាហរណ៍ខាងលើដើម្បីបញ្ចូល matrix នៅលើស្រទាប់ embedding របស់យើងជាមុនជាមួយនឹងការកំណត់តម្លៃពាក្យដែលមានអត្ថន័យ ដូចជា 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` មានការគាំទ្របង្កើត embeddings ជាផ្នែកមួយក្នុងខ្លួន។ យើងអាច ឧទាហរណ៍ បង្កើតវចនានុក្រមមានមូលដ្ឋានលើ 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": [
|
|
"Loaded vocabulary has the following basic operations:\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": [
|
|
"ដើម្បីបណ្តុះបណ្តាលម៉ាស៊ីនចម្រាញ់ប្រភេទដោយប្រើ embeddings ទាំងនោះ សិនមុនយើងត្រូវបំលែងឱ្យប្រើពាក្យសម្គាល់ 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`។ វាធ្វើឲ្យមានភាពងាយស្រួលយ៉ាងខ្លាំងក្នុងការបញ្ចូលទំងន់ទាំងនោះទៅក្នុងទម្រង់ទំងន់នៃស្រទាប់ embedding ដោយប្រើការចម្លងធម្មតា៖\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": [
|
|
"ឥឡូវនេះរួចដំណើរការបណ្ដុះបណ្ដាលម៉ូដែលរបស់យើង ហើយមើលថាតើយើងទទួលបានលទ្ធផលកាន់តែល្អឬយ៉ាងដូចម្តេច៖\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.6271875\n",
|
|
"6400: acc=0.68078125\n",
|
|
"9600: acc=0.7030208333333333\n",
|
|
"12800: acc=0.71984375\n",
|
|
"16000: acc=0.7346875\n",
|
|
"19200: acc=0.7455729166666667\n",
|
|
"22400: acc=0.7529464285714286\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(35.53972978646833, 0.7575175943698017)"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
|
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"មួយក្នុងចំណោមហេតុផលដែលយើងមិនឃើញការកើនឡើងយ៉ាងសំខាន់នៃភាពទាន់សម័យគឺដោយសារតែកម្រិតពាក្យខ្លះពីទិន្នន័យរបស់យើងបានបាត់បង់ក្នុងវាក្យសព្ទ GloVe ដែលបានបណ្តុះមុន ហើយដូច្នេះពួកវាត្រូវបានរំលងផ្តាច់។ ដើម្បីទ្រាក់ទ្រង់លើការពិតនេះ យើងអាចបណ្តុះ embeddings ផ្ទាល់ខ្លួនលើទិន្នន័យរបស់យើងបាន។\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Contextual Embeddings\n",
|
|
"\n",
|
|
"ការពេញលេញចម្បងមួយនៃតំណាងការជួសជុលពីមុនដែលមានសមត្ថភាពនៃ pretrained embedding representations ដូចជា Word2Vec គឺបញ្ហានៃការប្រែប្រួលអត្ថន័យពាក្យ។ នៅពេល pretrained embeddings អាចចាប់យកអត្ថន័យខ្លះៗនៃពាក្យនៅក្នុងបរិបទជាមួយ ក៏ប៉ុន្តែអត្ថន័យដែលអាចមានទាំងអស់របស់ពាក្យត្រូវបានអចិន្រ្ត័យទៅក្នុង embedding ដូចគ្នា។ វាអាចបង្កើតបញ្ហាក្នុងម៉ូដែលក្រោយ, ពោលគឺពាក្យជាច្រើនដូចជា ពាក្យ 'play' មានអត្ថន័យខុសគ្នាច្រើនអាស្រ័យលើបរិបទដែលពួកវាត្រូវបានប្រើ។\n",
|
|
"\n",
|
|
"ឧទាហរណ៍ ពាក្យ 'play' នៅក្នុងប្រយោគទាំងពីរនោះមានអត្ថន័យខុសគ្នាក្រោមមួយ:\n",
|
|
"- ខ្ញុំបានទៅបង្ហាញ **play** នៅលើឆាកតេអាទឺរ។\n",
|
|
"- John ចង់ **play** ជាមួយមិត្តភក្តិរបស់គាត់។\n",
|
|
"\n",
|
|
"pretrained embeddings នៅលើនេះតំណាងឲ្យទាំងពីរអត្ថន័យនៃពាក្យ 'play' នៅក្នុង embedding ដូចគ្នា។ ដើម្បីដោះស្រាយកំណត់នេះ, យើងត្រូវតែបង្កើត embeddings បញ្ជាក់លើជារបៀប **language model**, ដែលបានបណ្តុះបណ្តាលលើនិទស្សន៍ធំមួយនៃអត្ថបទ ហើយ*ដឹង*ថាពាក្យអាចត្រូវបានដាក់ជាមួយគ្នារបៀបខុសៗគ្នានៅក្នុងបរិបទផ្សេងៗ។ ការពិភាក្សាពី contextual embeddings គឺក្រៅប្រធានបទសម្រាប់ មេរៀននេះ ប៉ុន្តែយើងនឹងត្រឡប់មកមើលពួកវាទៀតនៅពេលនិយាយអំពី language models ក្នុងឯកត្តបន្ទាប់។\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**ការព្រមាន**៖\nឯកសារនេះត្រូវបានបកប្រែដោយប្រើសេវាកម្មបកប្រែ AI [Co-op Translator](https://github.com/Azure/co-op-translator)។ ខណៈពេលយើងព្យាយាមរក្សាការពិតច្បាស់ សូមយល់ឲ្យបានថាការបកប្រែដោយស្វ័យប្រវត្តិសាច់បាច់អាចមានកំហុស ឬការមិនត្រឹមត្រូវនៅខ្លះ។ ឯកសារដើមដែលមានភាសាដើមគួរត្រូវបានគិតថាជាធនធានផ្លូវការជាចម្បង។ សម្រាប់ព័ត៌មានសំខាន់ ការបកប្រែដោយមនុស្សជំនាញត្រូវបានផ្តល់អាទិភាព។ យើងមិនទទួលខុសត្រូវចំពោះការយល់ច្រឡំ ឬការបំភ្លឺខុសចេញពីការប្រើប្រាស់ការបកប្រែនេះឡើយ។\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\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"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |