နားလည်ထားရမယ့် အချက်
Attention မတိုင်ခင်က RNN (သို့မဟုတ် LSTM/GRU) အခြေခံ sequence model တွေက input sequence တစ်ခုလုံးကို output မထုတ်ခင် fixed-size hidden state vector တစ်ခုထဲကို squeeze လုပ်ထည့်ရတယ်။ ဒါက information bottleneck ကြီးတစ်ခုပဲ — sentence ရှည်ရှည် (သို့) document တစ်ခုအတွက်ဆိုရင် token ဦးဆုံးတွေကနေ model သင်ယူထားတာက နောက်ကလာတဲ့ token တိုင်းက overwrite ပြန်လုပ်တာကို ခံနေရလို့ model ဟာ sequence အဆုံးရောက်တဲ့အချိန်မှာ early context တွေက dilute ဖြစ်သွားတတ်တယ်၊ မေ့သွားနိုင်တယ်။ Attention က ဒီ bottleneck ကို ဖယ်ရှားပေးတယ် — output step တစ်ခုစီမှာ model က squeeze လုပ်ထားတဲ့ summary တစ်ခုတည်းကို အားကိုးမနေဘဲ input position တိုင်းကို တိုက်ရိုက် ပြန်ကြည့်နိုင်အောင် ခွင့်ပြုတယ်။ ဒါကို learned projection သုံးခုနဲ့ လုပ်တယ် — current step က ဘာကို ရှာနေလဲဆိုတာကို ကိုယ်စားပြုတဲ့ query vector တစ်ခု၊ input position တစ်ခုစီအတွက် key vector နဲ့ value vector။ Query ကို key တိုင်းနဲ့ (dot product နဲ့ ပုံမှန်) နှိုင်းယှဉ်ပြီး position တစ်ခုစီအတွက် relevance score တွေထုတ်တယ်၊ softmax က ဒီ score တွေကို ပေါင်းလို့ 1 ဖြစ်တဲ့ attention weight တွေအဖြစ် ပြောင်းပေးတယ်၊ output ကတော့ value vector တွေရဲ့ weighted sum ပဲ ဖြစ်တယ်။ Sequence ထဲက distance က အရေးမကြီးတော့ဘူး — position 1 ကို ရောက်နိုင်သလောက် position 1000 ကိုလည်း တူညီစွာ ရောက်နိုင်တယ်။
လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
Thuta ရဲ့ Tutorial Platform မှာ user ရဲ့ past lesson တွေထဲက ဘယ်ဟာက နောက် lesson ကို recommend လုပ်ဖို့ တကယ်သက်ဆိုင်လဲဆိုတာကို learned recommendation model တစ်ခုက ဆုံးဖြတ်ဖို့ attention ကိုပဲ လိုအပ်မှာဖြစ်တယ် — RNN လုပ်သလို learning history တစ်ခုလုံးကို summary vector တစ်ခုထဲ ညှစ်ထည့်စရာမလိုတော့ဘူး။ 'user အခုဘယ် topic မှာရှိနေလဲ' ဆိုတဲ့ query တစ်ခုက user ပြီးမြောက်ထားတဲ့ lesson တိုင်းက key တွေအပေါ် attend လုပ်ပြီး၊ တိုက်ရိုက်သက်ဆိုင်တဲ့ lesson တွေကို weight မြင့်မြင့်၊ မသက်ဆိုင်တာတွေကို 0 နီးပါးပေးနိုင်တယ် — lesson ဘယ်နှစ်ခု ကြားထားပါစေ၊ ဘယ်လောက်ကြာကြာ ပြီးထားပါစေ attention မှာ position (သို့) recency အပေါ်မူတည်ပြီး ကျဆင်းမှု မရှိလို့ဖြစ်တယ်။
အတူတူ စမ်းရေးကြည့်မယ်
import torch
import torch.nn.functional as F
torch.manual_seed(0)
seq_len, d_k = 4, 8
Q = torch.randn(seq_len, d_k)
K = torch.randn(seq_len, d_k)
V = torch.randn(seq_len, d_k)
scores = Q @ K.T / (d_k ** 0.5)
weights = F.softmax(scores, dim=-1)
output = weights @ V
print("attention weights shape:", weights.shape)
print("output shape:", output.shape)
print("weights row sums:", weights.sum(dim=-1))attention weights shape: torch.Size([4, 4])
output shape: torch.Size([4, 8])
weights row sums: tensor([1.0000, 1.0000, 1.0000, 1.0000])
weights matrix က query position 4 ခုစီအတွက် key position 4 ခုပေါ် attention ဘယ်လောက်ပေးမလဲဆိုတာကို ပြတယ်၊ row တစ်ခုစီက softmax ကြောင့် ပေါင်းလို့ 1 အမြဲရှိတယ်။ output shape က Q, V ရဲ့ (seq_len, d_k) shape အတိုင်းပဲ ပြန်ရလာတယ်။၅ မိနစ် စမ်းကြည့်
seq_len ကို 4 ကနေ 10 ကို ပြောင်းပြီး code ကို ပြန်run ကြည့်ပါ — attention weights matrix shape ဘယ်လို ပြောင်းလဲသွားလဲ။ ပြီးရင် d_k ကို 8 ကနေ 64 ကို တိုးပြီး scaling factor sqrt(d_k) ကို ဖယ်ထုတ်ကြည့်ပါ — softmax output ဘာဖြစ်သွားလဲ စမ်းကြည့်ပါ။
သတိလေးတစ်ချက်
sqrt(d_k) scaling ကို ချန်ထားလိုက်ရင် d_k ကြီးလာတာနဲ့အမျှ dot product score တွေက value ကြီးလာပြီး softmax က saturate ဖြစ်သွားလို့ gradient တွေ vanish သွားနိုင်တယ်
attention weights matrix shape ကို output shape လို့ မှားမှတ်တတ်ကြတယ် — weights ရဲ့ shape က (query_len, key_len) ဖြစ်ပြီး output ရဲ့ shape ကတော့ (query_len, value_dim) ဖြစ်တယ်၊ ဒီနှစ်ခု မတူတတ်ဘူး
Wikipedia — Attention (machine learning) — Deep Learning