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

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",
"![រូបភាពបង្ហាញឧទាហរណ៍ embedding classifier សម្រាប់ពាក្យជាច្រើនក្នុងលំដាប់។](../../../../../translated_images/km/embedding-classifier-example.b77f021a7ee67eee.webp)\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",
"![Image showing an offset sequence representation](../../../../../translated_images/km/offset-sequence-representation.eb73fcefb29b46ee.webp)\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",
"![រូបភាពបង្ហាញអាល់ហ្គរីធึម CBoW និង Skip-Gram ដើម្បីបំលែងពាក្យទៅវ៉ិចទ័រ។](../../../../../translated_images/km/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)\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
}