477 lines
38 KiB
Plaintext
477 lines
38 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# ပြန်လည်လုပ်ဆောင်နိုင်သော နယူးရယ်နက်ဝက်\n",
|
|
"\n",
|
|
"ယခင် module မှာ ကျွန်တော်တို့ rich semantic representations of text ကို အသုံးပြုပြီး embeddings အပေါ်မှာ simple linear classifier ကို ထည့်သုံးခဲ့ပါတယ်။ ဒီ architecture က စာကြောင်းတစ်ကြောင်းအတွင်းမှာ စကားလုံးတွေ၏ အဓိပ္ပါယ်ကို စုပေါင်းဖမ်းယူပေးနိုင်ပေမယ့် စကားလုံးတွေရဲ့ **အစီအစဉ်** ကို မထည့်သွင်းစဉ်းစားပါဘူး။ အကြောင်းကတော့ embeddings အပေါ်မှာ aggregation operation လုပ်တဲ့အခါမှာ original text ထဲက အစီအစဉ်ဆိုင်ရာ အချက်အလက်တွေကို ဖယ်ရှားလိုက်တဲ့အတွက်ပါ။ ဒီ model တွေက စကားလုံးအစီအစဉ်ကို မဖော်ဆောင်နိုင်တဲ့အတွက် text generation, question answering လို ပိုမိုရှုပ်ထွေးတဲ့ task တွေကို မဖြေရှင်းနိုင်ပါဘူး။\n",
|
|
"\n",
|
|
"စာကြောင်းအစီအစဉ်ရဲ့ အဓိပ္ပါယ်ကို ဖမ်းယူနိုင်ဖို့အတွက် **recurrent neural network** (RNN) လို့ခေါ်တဲ့ neural network architecture တစ်ခုကို အသုံးပြုရပါမယ်။ RNN မှာ ကျွန်တော်တို့ရဲ့ စာကြောင်းကို network အတွင်းသို့ symbol တစ်ခုစီအလိုက် ဖြတ်သွားပြီး network က **state** တစ်ခုကို ထုတ်ပေးပါတယ်။ ဒီ state ကို နောက် symbol နဲ့အတူ network ထဲကို ပြန်လည်ထည့်သွင်းပေးပါတယ်။\n",
|
|
"\n",
|
|
"Given the input sequence of tokens $X_0,\\dots,X_n$, RNN က neural network blocks တွေကို အစီအစဉ်အလိုက် ဖန်တီးပြီး ဒီ sequence ကို end-to-end အနေနဲ့ back propagation အသုံးပြုပြီး training လုပ်ပါတယ်။ Network block တစ်ခုစီက $(X_i,S_i)$ pair ကို input အနေနဲ့ ယူပြီး $S_{i+1}$ ကို output အနေနဲ့ ထုတ်ပေးပါတယ်။ နောက်ဆုံး state $S_n$ သို့မဟုတ် output $X_n ကို linear classifier ထဲသို့ ထည့်သွင်းပြီး ရလဒ်ကို ထုတ်ပေးပါတယ်။ Network block အားလုံးမှာ weight တွေကို share လုပ်ထားပြီး back propagation pass တစ်ခုတည်းနဲ့ end-to-end training လုပ်ပါတယ်။\n",
|
|
"\n",
|
|
"State vectors $S_0,\\dots,S_n$ တွေကို network အတွင်းဖြတ်သွားတဲ့အတွက် စကားလုံးတွေရဲ့ အစီအစဉ်ဆိုင်ရာ အချင်းချင်းပေါ်မူတည်မှုတွေကို သင်ယူနိုင်ပါတယ်။ ဥပမာအားဖြင့် *not* ဆိုတဲ့ စကားလုံးတစ်လုံးက sequence တစ်ခုမှာ တစ်နေရာမှာပေါ်လာတဲ့အခါ state vector အတွင်းမှာ အချို့ element တွေကို negate လုပ်နိုင်ဖို့ သင်ယူနိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"> RNN blocks တွေမှာ weight တွေကို share လုပ်ထားတဲ့အတွက် ပုံမှာ RNN blocks အားလုံးကို block တစ်ခုအနေနဲ့ (ညာဘက်မှာ) recurrent feedback loop နဲ့ ပြန်လည်ဖော်ပြနိုင်ပါတယ်။ ဒီ loop က network ရဲ့ output state ကို input အနေနဲ့ ပြန်လည်ပေးပို့ပါတယ်။\n",
|
|
"\n",
|
|
"အခု ကျွန်တော်တို့ရဲ့ news dataset ကို classify လုပ်ဖို့ recurrent neural networks က ဘယ်လို အကူအညီပေးနိုင်မလဲဆိုတာ ကြည့်လိုက်ရအောင်။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n",
|
|
"Building vocab...\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import torch\n",
|
|
"import torchtext\n",
|
|
"from torchnlp import *\n",
|
|
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
|
"vocab_size = len(vocab)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## ရိုးရှင်းသော RNN ခွဲခြားသူ\n",
|
|
"\n",
|
|
"ရိုးရှင်းသော RNN ၏အခြေအနေတွင်၊ တစ်ခုချင်းစီသော recurrent unit သည် ရိုးရှင်းသော linear network တစ်ခုဖြစ်ပြီး၊ input vector နှင့် state vector ကိုပေါင်းစပ်ပြီး အသစ်သော state vector တစ်ခုကို ထုတ်လုပ်ပေးသည်။ PyTorch သည် ဤ unit ကို `RNNCell` class ဖြင့် ကိုယ်စားပြုပြီး၊ ဤ cells များ၏ network များကို `RNN` layer အဖြစ် ကိုယ်စားပြုသည်။\n",
|
|
"\n",
|
|
"RNN ခွဲခြားသူတစ်ခုကို သတ်မှတ်ရန်အတွက်၊ အရင်ဆုံး input vocabulary ၏ dimensionality ကို လျှော့ချရန် embedding layer တစ်ခုကို အသုံးပြုပြီး၊ ထို့နောက် RNN layer ကို ထပ်တင်ပါမည်။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class RNNClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,h = self.rnn(x)\n",
|
|
" return self.fc(x.mean(dim=1))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Note:** ဒီနေရာမှာ ကျွန်တော်တို့ Untrained embedding layer ကို အသုံးပြုထားပါတယ်၊ ဒါပေမယ့် ပိုမိုကောင်းမွန်တဲ့ရလဒ်ရဖို့ Word2Vec သို့မဟုတ် GloVe embeddings နဲ့ အတူ Pre-trained embedding layer ကို အသုံးပြုနိုင်ပါတယ်၊ အရင်ယူနစ်မှာ ဖော်ပြထားသလိုပဲ။ ပိုမိုနားလည်စေရန် ဒီကုဒ်ကို Pre-trained embeddings နဲ့ အလုပ်လုပ်အောင် ပြောင်းလဲကြည့်နိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"ဒီအခါမှာတော့ ကျွန်တော်တို့ Padded data loader ကို အသုံးပြုမှာဖြစ်ပြီး၊ အဲ့ဒီလိုဖြစ်တဲ့အခါမှာ တစ်ခုချင်းစီ batch မှာ အရှည်တူတဲ့ Padded sequences အရေအတွက် ပါဝင်မှာဖြစ်ပါတယ်။ RNN layer က embedding tensors ရဲ့ အစဉ်အတိုင်း sequence ကို ယူပြီး output နှစ်ခုကို ထုတ်ပေးပါမယ်။\n",
|
|
"* $x$ ကတော့ RNN cell outputs တွေကို တစ်ဆင့်ချင်းစီမှာ sequence အတိုင်း ထုတ်ပေးမှာဖြစ်ပါတယ်။\n",
|
|
"* $h$ ကတော့ sequence ရဲ့ နောက်ဆုံး element အတွက် final hidden state ဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"ဒီနောက်မှာတော့ Fully-connected linear classifier ကို အသုံးပြုပြီး အတန်းအရေအတွက်ကို ထုတ်ယူမှာဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"> **Note:** RNN တွေကို သင်ကြားဖို့ အတော်ခက်ခဲပါတယ်၊ အကြောင်းကတော့ RNN cells တွေကို sequence length အတိုင်း unroll လုပ်လိုက်တဲ့အခါမှာ Back propagation အတွက် ပါဝင်တဲ့ Layers အရေအတွက် အတော်များလာတတ်ပါတယ်။ ဒါကြောင့် Learning rate ကို သေးငယ်စွာရွေးချယ်ရမယ်၊ နဲ့ အကြီးမားတဲ့ Dataset ပေါ်မှာ Network ကို သင်ကြားရမယ်၊ ဒါမှသာ ကောင်းမွန်တဲ့ရလဒ်ရနိုင်ပါတယ်။ အချိန်အတော်ကြာတတ်တာကြောင့် GPU ကို အသုံးပြုရင် ပိုမိုအဆင်ပြေပါတယ်။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.3090625\n",
|
|
"6400: acc=0.38921875\n",
|
|
"9600: acc=0.4590625\n",
|
|
"12800: acc=0.511953125\n",
|
|
"16000: acc=0.5506875\n",
|
|
"19200: acc=0.57921875\n",
|
|
"22400: acc=0.6070089285714285\n",
|
|
"25600: acc=0.6304296875\n",
|
|
"28800: acc=0.6484027777777778\n",
|
|
"32000: acc=0.66509375\n",
|
|
"35200: acc=0.6790056818181818\n",
|
|
"38400: acc=0.6929166666666666\n",
|
|
"41600: acc=0.7035817307692308\n",
|
|
"44800: acc=0.7137276785714286\n",
|
|
"48000: acc=0.72225\n",
|
|
"51200: acc=0.73001953125\n",
|
|
"54400: acc=0.7372794117647059\n",
|
|
"57600: acc=0.7436631944444444\n",
|
|
"60800: acc=0.7503947368421052\n",
|
|
"64000: acc=0.75634375\n",
|
|
"67200: acc=0.7615773809523809\n",
|
|
"70400: acc=0.7662642045454545\n",
|
|
"73600: acc=0.7708423913043478\n",
|
|
"76800: acc=0.7751822916666666\n",
|
|
"80000: acc=0.7790625\n",
|
|
"83200: acc=0.7825\n",
|
|
"86400: acc=0.7858564814814815\n",
|
|
"89600: acc=0.7890513392857142\n",
|
|
"92800: acc=0.7920474137931034\n",
|
|
"96000: acc=0.7952708333333334\n",
|
|
"99200: acc=0.7982258064516129\n",
|
|
"102400: acc=0.80099609375\n",
|
|
"105600: acc=0.8037594696969697\n",
|
|
"108800: acc=0.8060569852941176\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
|
|
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## ရေရှည်တိုတောင်းမှတ်ဉာဏ် (Long Short Term Memory - LSTM)\n",
|
|
"\n",
|
|
"ရိုးရာ RNNs တွေမှာ အဓိကပြဿနာတစ်ခုက **vanishing gradients** ပြဿနာဖြစ်ပါတယ်။ RNNs တွေကို တစ်ကြိမ်တည်းမှာ အဆုံး-to-အဆုံး back-propagation နည်းလမ်းနဲ့ သင်ကြားရတဲ့အတွက် error ကို network ရဲ့ ပထမဆုံး layer တွေထိ ပြန်ပို့ဖို့ အခက်အခဲရှိပါတယ်။ ဒီကြောင့် network က အဝေးက token တွေကြားက ဆက်နွယ်မှုတွေကို သင်ယူလို့မရနိုင်ပါဘူး။ ဒီပြဿနာကို ရှောင်ရှားဖို့နည်းလမ်းတစ်ခုက **explicit state management** ကို **gates** လို့ခေါ်တဲ့ နည်းလမ်းနဲ့ အသုံးပြုဖို့ ဖြစ်ပါတယ်။ ဒီအမျိုးအစားမှာ အထင်ရှားဆုံး architecture နှစ်ခုရှိပါတယ် - **Long Short Term Memory** (LSTM) နဲ့ **Gated Relay Unit** (GRU) ဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"LSTM Network ကို RNN နဲ့ ဆင်တူတဲ့ပုံစံနဲ့ စီမံထားပြီး layer တစ်ခုမှ layer တစ်ခုဆီကို state နှစ်ခုကို ပို့ပေးပါတယ် - အမှန်တကယ် state $c$ နဲ့ hidden vector $h$ ဖြစ်ပါတယ်။ Unit တစ်ခုစီမှာ hidden vector $h_i$ ကို input $x_i$ နဲ့ concatenation လုပ်ပြီး **gates** တွေက state $c$ ကို ဘယ်လိုထိန်းချုပ်မယ်ဆိုတာကို ဆုံးဖြတ်ပါတယ်။ Gate တစ်ခုစီက sigmoid activation (output $[0,1]$ အတွင်း) ပါရှိတဲ့ neural network ဖြစ်ပြီး state vector ကို မျှတစွာ mask လုပ်တဲ့အခါမှာ bitwise mask အနေနဲ့ တွေးနိုင်ပါတယ်။ အပေါ်ပုံမှာ (ဘယ်မှညာ) gates တွေက:\n",
|
|
"* **forget gate** က hidden vector ကိုယူပြီး vector $c$ ရဲ့ ဘယ် components တွေကို မေ့ပစ်ရမလဲ၊ ဘယ် components တွေကို ဆက်လက်အသုံးပြုရမလဲဆိုတာ ဆုံးဖြတ်ပါတယ်။\n",
|
|
"* **input gate** က input နဲ့ hidden vector မှ အချက်အလက်တစ်ချို့ကိုယူပြီး state ထဲကို ထည့်သွင်းပါတယ်။\n",
|
|
"* **output gate** က state ကို $\\tanh$ activation နဲ့ linear layer တစ်ခုကနေ ပြောင်းပြီး hidden vector $h_i$ ကို အသုံးပြုကာ state အသစ် $c_{i+1}$ ကို ထုတ်လုပ်ပါတယ်။\n",
|
|
"\n",
|
|
"State $c$ ရဲ့ components တွေကို flag တွေလို switch on/off လုပ်နိုင်တဲ့အရာတွေလို တွေးနိုင်ပါတယ်။ ဥပမာအားဖြင့် sequence မှာ *Alice* ဆိုတဲ့နာမည်ကိုတွေ့တဲ့အခါ female character ကိုရည်ညွှန်းတယ်လို့ယူဆပြီး state မှာ female noun ရှိတယ်ဆိုတဲ့ flag ကိုတင်နိုင်ပါတယ်။ နောက်ပိုင်းမှာ *and Tom* ဆိုတဲ့ phrase ကိုတွေ့တဲ့အခါ plural noun ရှိတယ်ဆိုတဲ့ flag ကိုတင်နိုင်ပါတယ်။ ဒီလို state ကို ထိန်းချုပ်ခြင်းအားဖြင့် sentence ရဲ့ grammar properties တွေကို ထိန်းသိမ်းနိုင်တယ်လို့ ယူဆနိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"> **Note**: LSTM ရဲ့ အတွင်းပိုင်းကို နားလည်ဖို့အတွက် Christopher Olah ရဲ့ [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) ဆိုတဲ့ ဆောင်းပါးက အလွန်ကောင်းမွန်တဲ့ resource ဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"LSTM cell ရဲ့ အတွင်းပိုင်းဖွဲ့စည်းပုံက ရှုပ်ထွေးနေသလိုပဲ PyTorch က `LSTMCell` class ထဲမှာ ဒီ implementation ကို ဖုံးကွယ်ထားပြီး `LSTM` object ကို LSTM layer အဖြစ် ကိုယ်စားပြုထားပါတယ်။ ဒါကြောင့် LSTM classifier ရဲ့ implementation က အထက်မှာ ကြည့်ခဲ့တဲ့ ရိုးရှင်းတဲ့ RNN နဲ့ ဆင်တူနေမှာဖြစ်ပါတယ်:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,(h,c) = self.rnn(x)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.259375\n",
|
|
"6400: acc=0.25859375\n",
|
|
"9600: acc=0.26177083333333334\n",
|
|
"12800: acc=0.2784375\n",
|
|
"16000: acc=0.313\n",
|
|
"19200: acc=0.3528645833333333\n",
|
|
"22400: acc=0.3965625\n",
|
|
"25600: acc=0.4385546875\n",
|
|
"28800: acc=0.4752777777777778\n",
|
|
"32000: acc=0.505375\n",
|
|
"35200: acc=0.5326704545454546\n",
|
|
"38400: acc=0.5557552083333334\n",
|
|
"41600: acc=0.5760817307692307\n",
|
|
"44800: acc=0.5954910714285714\n",
|
|
"48000: acc=0.6118333333333333\n",
|
|
"51200: acc=0.62681640625\n",
|
|
"54400: acc=0.6404779411764706\n",
|
|
"57600: acc=0.6520138888888889\n",
|
|
"60800: acc=0.662828947368421\n",
|
|
"64000: acc=0.673546875\n",
|
|
"67200: acc=0.6831547619047619\n",
|
|
"70400: acc=0.6917897727272727\n",
|
|
"73600: acc=0.6997146739130434\n",
|
|
"76800: acc=0.707109375\n",
|
|
"80000: acc=0.714075\n",
|
|
"83200: acc=0.7209134615384616\n",
|
|
"86400: acc=0.727037037037037\n",
|
|
"89600: acc=0.7326674107142858\n",
|
|
"92800: acc=0.7379633620689655\n",
|
|
"96000: acc=0.7433645833333333\n",
|
|
"99200: acc=0.7479032258064516\n",
|
|
"102400: acc=0.752119140625\n",
|
|
"105600: acc=0.7562405303030303\n",
|
|
"108800: acc=0.76015625\n",
|
|
"112000: acc=0.7641339285714286\n",
|
|
"115200: acc=0.7677777777777778\n",
|
|
"118400: acc=0.7711233108108108\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.03487814127604167, 0.7728)"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## အစုအဝေးများကို ထုပ်ပိုးခြင်း\n",
|
|
"\n",
|
|
"ဥပမာမှာတော့ minibatch ထဲမှာ sequence တွေကို zero vector တွေနဲ့ padding လုပ်ဖို့လိုအပ်ခဲ့ပါတယ်။ ဒါဟာ memory ကို အချို့အပိုသုံးစွဲရတာဖြစ်ပေမယ့် RNN တွေမှာတော့ padding လုပ်ထားတဲ့ input item တွေကြောင့် RNN cell အသစ်တွေကို ဖန်တီးရတာပိုအရေးကြီးပါတယ်။ အဲဒီ cell တွေဟာ training မှာ ပါဝင်ပေမယ့် အရေးကြီးတဲ့ input အချက်အလက်တွေကို မပါဝင်စေပါဘူး။ အဲဒီအစား RNN ကို အမှန်တကယ် sequence size အတိုင်း training လုပ်တာပိုကောင်းပါတယ်။\n",
|
|
"\n",
|
|
"ဒါကိုလုပ်ဖို့ PyTorch မှာ padding လုပ်ထားတဲ့ sequence တွေကို သိမ်းဆည်းဖို့ အထူး format တစ်ခုကို မိတ်ဆက်ထားပါတယ်။ ဥပမာအားဖြင့် input padded minibatch က ဒီလိုပုံစံရှိတယ်ဆိုပါစို့:\n",
|
|
"```\n",
|
|
"[[1,2,3,4,5],\n",
|
|
" [6,7,8,0,0],\n",
|
|
" [9,0,0,0,0]]\n",
|
|
"```\n",
|
|
"ဒီမှာ 0 တွေဟာ padding လုပ်ထားတဲ့ value တွေကို ကိုယ်စားပြုပြီး၊ input sequence တွေရဲ့ အမှန်တကယ် length vector က `[5,3,1]` ဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"padding လုပ်ထားတဲ့ sequence တွေနဲ့ RNN ကို ထိရောက်စွာ training လုပ်ဖို့အတွက် RNN cell တွေကို ပထမအကြိမ် training လုပ်တဲ့အခါမှာ minibatch ကြီး (`[1,6,9]`) ကို စတင်သုံးပြီး၊ အဲဒီနောက်မှာတော့ တတိယ sequence ကို processing ပြီးဆုံးပြီး၊ minibatch သေးသေးလေးတွေ (`[2,7]`, `[3,8]`) နဲ့ training ကို ဆက်လက်လုပ်ဆောင်သင့်ပါတယ်။ ဒီလိုနည်းဖြင့် packed sequence ကို vector တစ်ခုအနေနဲ့ ကိုယ်စားပြုထားပါတယ် - ဥပမာအားဖြင့် `[1,6,9,2,7,3,8,4,5]` ဖြစ်ပြီး၊ length vector (`[5,3,1]`) ကနေ အစမှတ် minibatch ကို ပြန်လည်တည်ဆောက်နိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"packed sequence ကို ထုတ်လုပ်ဖို့ `torch.nn.utils.rnn.pack_padded_sequence` function ကို အသုံးပြုနိုင်ပါတယ်။ RNN, LSTM, GRU အပါအဝင် recurrent layer အားလုံးဟာ packed sequence တွေကို input အနေနဲ့ support လုပ်ပြီး၊ packed output ကို ထုတ်လုပ်နိုင်ပါတယ်။ ထို output ကို `torch.nn.utils.rnn.pad_packed_sequence` ကို အသုံးပြုပြီး decode လုပ်နိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"packed sequence ကို ထုတ်လုပ်နိုင်ဖို့ network ကို length vector ကို pass လုပ်ဖို့လိုအပ်ပါတယ်၊ ဒါကြောင့် minibatch တွေကို ပြင်ဆင်ဖို့ function တစ်ခုကို အသုံးပြုဖို့လိုအပ်ပါတယ်:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def pad_length(b):\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # compute max length of a sequence in this minibatch and length sequence itself\n",
|
|
" len_seq = list(map(len,v))\n",
|
|
" l = max(len_seq)\n",
|
|
" return ( # tuple of three tensors - labels, padded features, length sequence\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",
|
|
" torch.tensor(len_seq)\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"အမှန်တကယ်က `LSTMClassifier` နမူနာနဲ့ အလွန်ဆင်တူတဲ့ network ဖြစ်မှာပါ၊ ဒါပေမယ့် `forward` pass ကတော့ padded minibatch နဲ့ sequence lengths ရဲ့ vector နှစ်ခုလုံးကို လက်ခံရမှာဖြစ်ပါတယ်။ Embedding ကိုတွက်ချက်ပြီးနောက်မှာ packed sequence ကိုတွက်ချက်ပြီး LSTM layer ကိုပို့ပြီးနောက်မှာ ပြန်လည် unpack လုပ်ပါမယ်။\n",
|
|
"\n",
|
|
"> **Note**: အမှန်တကယ်တော့ unpack လုပ်ထားတဲ့ `x` ရလဒ်ကို မသုံးပါဘူး၊ အကြောင်းက hidden layers က output ကို နောက်ထပ်တွက်ချက်မှုတွေမှာ သုံးမယ့်အတွက်ပါ။ ဒါကြောင့် ဒီ code မှာ unpacking ကို အပြည့်အဝ ဖယ်ရှားနိုင်ပါတယ်။ ဒါပေမယ့် ဒီ code ကို နောက်ထပ်တွက်ချက်မှုတွေမှာ network output ကို သုံးဖို့ လိုအပ်လာရင် အလွယ်တကူ ပြင်ဆင်နိုင်ဖို့အတွက် ဒီနေရာမှာ ထားထားတာပါ။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMPackClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x, lengths):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
|
|
" pad_x,(h,c) = self.rnn(pad_x)\n",
|
|
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.285625\n",
|
|
"6400: acc=0.33359375\n",
|
|
"9600: acc=0.3876041666666667\n",
|
|
"12800: acc=0.44078125\n",
|
|
"16000: acc=0.4825\n",
|
|
"19200: acc=0.5235416666666667\n",
|
|
"22400: acc=0.5559821428571429\n",
|
|
"25600: acc=0.58609375\n",
|
|
"28800: acc=0.6116666666666667\n",
|
|
"32000: acc=0.63340625\n",
|
|
"35200: acc=0.6525284090909091\n",
|
|
"38400: acc=0.668515625\n",
|
|
"41600: acc=0.6822596153846154\n",
|
|
"44800: acc=0.6948214285714286\n",
|
|
"48000: acc=0.7052708333333333\n",
|
|
"51200: acc=0.71521484375\n",
|
|
"54400: acc=0.7239889705882353\n",
|
|
"57600: acc=0.7315277777777778\n",
|
|
"60800: acc=0.7388486842105263\n",
|
|
"64000: acc=0.74571875\n",
|
|
"67200: acc=0.7518303571428572\n",
|
|
"70400: acc=0.7576988636363636\n",
|
|
"73600: acc=0.7628940217391305\n",
|
|
"76800: acc=0.7681510416666667\n",
|
|
"80000: acc=0.7728125\n",
|
|
"83200: acc=0.7772235576923077\n",
|
|
"86400: acc=0.7815393518518519\n",
|
|
"89600: acc=0.7857700892857142\n",
|
|
"92800: acc=0.7895043103448276\n",
|
|
"96000: acc=0.7930520833333333\n",
|
|
"99200: acc=0.7959072580645161\n",
|
|
"102400: acc=0.798994140625\n",
|
|
"105600: acc=0.802064393939394\n",
|
|
"108800: acc=0.8051378676470589\n",
|
|
"112000: acc=0.8077857142857143\n",
|
|
"115200: acc=0.8104600694444445\n",
|
|
"118400: acc=0.8128293918918919\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.029785829671223958, 0.8138166666666666)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bidirectional နှင့် multilayer RNNs\n",
|
|
"\n",
|
|
"ကျွန်တော်တို့ရဲ့ ဥပမာတွေမှာ၊ အားလုံး recurrent networks တွေဟာ direction တစ်ခုတည်းမှာ လုပ်ဆောင်ခဲ့ပါတယ်၊ sequence ရဲ့အစမှ အဆုံးအထိ။ ဒါဟာ သဘာဝကျတဲ့အရာလိုပဲ၊ အကြောင်းကတော့ ကျွန်တော်တို့ စာဖတ်တာနဲ့ စကားနားထောင်တာကို တူတူပဲ။ သို့သော်လည်း၊ အများအပြား အကဲဖြတ်မှုတွေမှာ input sequence ကို random access လုပ်နိုင်တဲ့အခြေအနေရှိတဲ့အခါ၊ recurrent computation ကို direction နှစ်ခုလုံးမှာ လုပ်ဆောင်တာ make sense ဖြစ်နိုင်ပါတယ်။ ဒီလို networks တွေကို **bidirectional** RNNs လို့ခေါ်ပြီး၊ RNN/LSTM/GRU constructor ကို `bidirectional=True` parameter ဖြင့် pass လုပ်ခြင်းအားဖြင့် ဖန်တီးနိုင်ပါတယ်။\n",
|
|
"\n",
|
|
"Bidirectional network ကို handle လုပ်တဲ့အခါ၊ direction တစ်ခုစီအတွက် hidden state vector နှစ်ခုလိုအပ်ပါမယ်။ PyTorch က ဒီ vectors တွေကို twice larger size ရှိတဲ့ vector တစ်ခုအဖြစ် encode လုပ်ပေးပါတယ်၊ ဒါဟာ အဆင်ပြေပါတယ်၊ အကြောင်းကတော့ normally hidden state ကို fully-connected linear layer ကို pass လုပ်မှာဖြစ်ပြီး၊ layer ဖန်တီးတဲ့အခါ size တိုးလာတာကို သတိထားရုံပဲ လိုအပ်ပါတယ်။\n",
|
|
"\n",
|
|
"Recurrent network, one-directional ဖြစ်စေ bidirectional ဖြစ်စေ၊ sequence တစ်ခုအတွင်းမှာ pattern အချို့ကို capture လုပ်နိုင်ပြီး၊ state vector ထဲမှာ သိမ်းဆည်းနိုင်သလို output ကို pass လုပ်နိုင်ပါတယ်။ Convolutional networks တွေနဲ့တူတူပဲ၊ ပထမ layer က low-level patterns တွေကို extract လုပ်ပြီး၊ အဲ့ဒီ patterns တွေကို အသုံးပြုပြီး higher level patterns တွေကို capture လုပ်ဖို့ ပထမ layer ရဲ့အပေါ်မှာ recurrent layer တစ်ခုတိုးဖွဲ့နိုင်ပါတယ်။ ဒီအရာက **multi-layer RNN** ဆိုတဲ့အယူအဆကို ရောက်လာစေပြီး၊ ဒါဟာ recurrent networks နှစ်ခု သို့မဟုတ် အများကြီးပါဝင်ပြီး၊ အရင် layer ရဲ့ output ကို နောက် layer ရဲ့ input အဖြစ် pass လုပ်ပေးတဲ့ network ဖြစ်ပါတယ်။\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Fernando López ရဲ့ [ဒီ post](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) မှာရရှိတဲ့ပုံ*\n",
|
|
"\n",
|
|
"PyTorch က ဒီလို networks တွေကို ဖန်တီးဖို့ လွယ်ကူစေပါတယ်၊ အကြောင်းကတော့ `num_layers` parameter ကို RNN/LSTM/GRU constructor ကို pass လုပ်ရုံနဲ့ recurrence layers အများကြီးကို အလိုအလျောက် ဖန်တီးနိုင်ပါတယ်။ ဒါဟာ hidden/state vector ရဲ့ size တိုးလာမယ်ဆိုတာကိုလည်း အဓိကထားရမယ်၊ recurrent layers ရဲ့ output ကို handle လုပ်တဲ့အခါ ဒီအချက်ကို သတိထားဖို့လိုပါတယ်။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN များကို အခြားအလုပ်များအတွက် အသုံးပြုခြင်း\n",
|
|
"\n",
|
|
"ဤယူနစ်တွင် RNN များကို အစီအစဉ်အလိုက် အမျိုးအစားခွဲခြင်းအတွက် အသုံးပြုနိုင်သည်ကို တွေ့ရှိခဲ့ပြီး၊ အမှန်တကယ်တွင် RNN များသည် စာသားထုတ်လုပ်ခြင်း၊ စက်ဖြင့်ဘာသာပြန်ခြင်းနှင့် အခြားအလုပ်များစွာကိုလည်း ကိုင်တွယ်နိုင်ပါသည်။ ဤအလုပ်များကို နောက်ယူနစ်တွင် ဆွေးနွေးမည်ဖြစ်သည်။\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**အကြောင်းကြားချက်**: \nဤစာရွက်စာတမ်းကို AI ဘာသာပြန်ဝန်ဆောင်မှု [Co-op Translator](https://github.com/Azure/co-op-translator) ကို အသုံးပြု၍ ဘာသာပြန်ထားပါသည်။ ကျွန်ုပ်တို့သည် တိကျမှုအတွက် ကြိုးစားနေသော်လည်း၊ အလိုအလျောက် ဘာသာပြန်မှုများတွင် အမှားများ သို့မဟုတ် မမှန်ကန်မှုများ ပါဝင်နိုင်သည်ကို သတိပြုပါ။ မူရင်းဘာသာစကားဖြင့် ရေးသားထားသော စာရွက်စာတမ်းကို အာဏာရှိသော ရင်းမြစ်အဖြစ် သတ်မှတ်သင့်ပါသည်။ အရေးကြီးသော အချက်အလက်များအတွက် လူ့ဘာသာပြန်ပညာရှင်များမှ ပရော်ဖက်ရှင်နယ် ဘာသာပြန်မှုကို အကြံပြုပါသည်။ ဤဘာသာပြန်မှုကို အသုံးပြုခြင်းမှ ဖြစ်ပေါ်လာသော အလွဲအမှားများ သို့မဟုတ် အနားယူမှုများအတွက် ကျွန်ုပ်တို့သည် တာဝန်မယူပါ။\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3.8.12 ('py38')",
|
|
"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": "522ee52ae3d5ae933e283286254e9a55",
|
|
"translation_date": "2025-08-30T10:32:23+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
|
"language_code": "my"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |