এই পর্বে যা শিখবেন
- Query, key আর value দিয়ে একটি attention হাতে আর NumPy-তে হিসাব করা
- Dot-product আর additive score-এর সূত্র, parameter আর দুর্বলতা
- Padding mask কেন softmax-এর আগে, আর −inf-ই কেন
- GRU seq2seq-এ attention বসানো, প্রতিটি ধাপের shape সহ
- দৈর্ঘ্য অনুযায়ী উন্নতি আর দাম মাপা, আর alignment heatmap সতর্কভাবে পড়া
পর্ব ৩০-এ দেখেছেন, একটি context vector দিয়ে সব তথ্য পার করলে লম্বা input ভেঙে পড়ে। Padding আর attention mask এসেছে পর্ব ১১-এ, স্থিতিশীল softmax পর্ব ৩-এ। Dot product-এর জ্যামিতিক অর্থ AI-এর ভিত্তি কোর্সের পর্ব ৩৩-এ, embedding দিয়ে অর্থ ধরে খোঁজা পর্ব ৬৯-এ, আর query, key, value-র প্রথম পরিচয় পর্ব ৭০-এ। সেখানে হিসাবটি একবার দেখেছেন; এবার ভেতরে ঢুকে মেপে দেখব।
১ · একটি চিরকুট থেকে পুরো খাতা — attention কেন
পর্ব ৩০-এর দোভাষীকে মনে করুন, যাঁর হাতে ছোট একটি চিরকুট। এবার নিয়ম বদলান: বক্তা যা বলছেন, প্রতিটি কথার নোট একটি খাতায় লেখা থাকবে, আর অনুবাদের সময় দোভাষী যেকোনো পাতা খুলে দেখতে পারবেন। প্রতিটি শব্দ বলার আগে তিনি ভাবেন, “এখন কোন পাতাটি লাগবে?” চিরকুটের আকার নিয়ে আর দুশ্চিন্তা নেই। মুদি দোকানের হিসাব খাতাতেও আমরা তাই করি: পুরো খাতা মুখস্থ না করে দরকারি পাতাটি খুলি।
এই “দরকারি পাতা খোলা”-ই attention. Encoder-এর শেষ state-এর বদলে আমরা তার সব state জমা রাখি: h1, …, hT. Decoder-এর প্রতিটি ধাপ t নিজের একটি context ct পায়:
etj = score(st−1, hj) · atj = softmaxj(et) · ct = Σj atj hj
এখানে st−1 decoder-এর আগের state, hj j-তম input token-এর encoder state, etj তাদের alignment score, atj সেই ঘরের attention weight, আর ct এই ধাপের context. Weight-গুলো ধনাত্মক, যোগফল ১, প্রতি ধাপে নতুন করে হিসাব হয়। লক্ষ্য করুন: ct-এর আকার এখনো (B, H), কিন্তু তাকে আর একবারে সব তথ্য বহন করতে হয় না। প্রতি ধাপে শুধু সেই ধাপের দরকারি অংশ বহন করলেই চলে।
ইতিহাস: Bahdanau, Cho ও Bengio-র “Neural Machine Translation by Jointly Learning to Align and Translate” (arXiv ২০১৪, ICLR ২০১৫) অনুবাদে এই ধারণা জনপ্রিয় করে; সেখানে encoder ছিল bidirectional RNN, আর ছোট একটি network score দিত। তবে সম্পূর্ণ নতুন ধারণা নয়: Graves-এর হাতের লেখা তৈরির কাজে (২০১৩) নরম পড়ার ব্যবস্থা ছিল। Luong, Pham ও Manning (২০১৫) সরল score-গুলো তুলনা করেন। “Query, key, value” নামগুলো মূলত ২০১৭ সালের Transformer প্রবন্ধ থেকে ছড়ায়; Bahdanau-র প্রবন্ধে এই নাম ছিল না।
২ · Query, key, value — নরম খোঁজার তিন ধাপ
Python-এর dict একটি শক্ত খোঁজা: d["ডাল"] হয় হুবহু মেলে, নয় error. এতে gradient চলে না, কারণ বাছাইটি লাফিয়ে বদলায়। Soft lookup সব key-এর সঙ্গেই মিল মাপে, আর সব value মেশায় মিলের অনুপাতে। হিসাবটি মসৃণ, তাই backpropagation চলে।
- Query (q): এই মুহূর্তে কী খুঁজছি। Seq2seq-এ decoder-এর আগের state.
- Key (kj): প্রতিটি ঘরের লেবেল, যার সঙ্গে query মেলানো হয়।
- Value (vj): ঘরটি বেছে নিলে যা পাওয়া যায়।
এই পর্বের GRU model-এ key আর value একই জিনিস: hj. নামগুলো আলাদা রাখার কারণ, পরের পর্বে Transformer-এ এরা আলাদা হবে। নিচে পুরো পথটি দেখুন।
Decoder-এর একটি ধাপে attention
Query সব key-এর সঙ্গে মেলে, score থেকে weight আসে, weight দিয়ে value মিশিয়ে এই ধাপের context হয়। ডান পাশের পথে নতুন state হয় পরের ধাপের query.
এবার একটি হিসাব হাতে। ৩টি token, key দুই মাপের, value একটি সংখ্যা। এই ধরনের অঙ্ক পরীক্ষায়ও আসতে পারে, তাই প্রতিটি ধাপ খাতায় মিলিয়ে নিন।
| ধাপ | |
| token ১ | |
| token ২ | |
| token ৩ | |
| ফল |
- Score. Query আর প্রতিটি key-এর dot product. Token ৩ সবচেয়ে বেশি মেলে: ৩. এগুলো এখনো সম্ভাবনা নয়; ঋণাত্মকও হতে পারত।
- Exp. প্রতিটি score-কে e-এর ঘাতে তোলা হয়, ফলে সব মান ধনাত্মক। পার্থক্যও বড় হয়: score-এ ১ বেশি মানে e গুণ বেশি।
- যোগফল. তিনটি মান যোগ করে ৩০.১৯২৯. এটিই হর।
- Weight. ভাগ করলে ০.২৪৪৭, ০.০৯০০, ০.৬৬৫২. Token ৩ বেশি পেল, কিন্তু একা সব নয়। এটিই “নরম” কথাটির মানে।
- পরীক্ষা. Weight-এর যোগফল ১. হাতের হিসাবে এই পরীক্ষা ভুল ধরার সবচেয়ে সস্তা উপায়।
- Context. Value-র ওজনসহ যোগ: ২৪.২০৫১. Token ৩-এর ৩০-এর দিকে ঝোঁকা, কিন্তু বাকিরাও কিছুটা মিশেছে। মনে রাখবেন: key ঠিক করে weight, মেশানো হয় value.
একই কাজ NumPy-তে, ৪টি token নিয়ে। বাজারঘরের চারটি পণ্য; key বানানো লেবেল, value হলো দাম আর মজুত। শেষ অংশে query-কে বড় করে দেখব বাছাই কড়া হয় কি না।
import numpy as np
np.set_printoptions(precision=3, suppress=True, linewidth=60)
items = ["চাল", "ডাল", "তেল", "লবণ"] # ৪টি token
K = np.array([[1.0, 0.0, 0.5], # keys: লেবেল, (4, 3)
[0.8, 0.6, 0.0],
[0.0, 1.0, 0.2],
[-0.5, 0.0, 1.0]])
V = np.array([[70.0, 12.0], # values: দাম আর মজুত, (4, 2)
[120.0, 5.0],
[180.0, 8.0],
[40.0, 30.0]])
q = np.array([1.0, 0.5, 0.0]) # query, (3,)
def softmax(z):
e = np.exp(z - z.max()) # পর্ব ৩-এর স্থিতিশীল রূপ
return e / e.sum()
def attend(q, K, V):
scores = K @ q # dot product, (4,)
weights = softmax(scores) # যোগফল 1, (4,)
return scores, weights, weights @ V # weighted sum, (2,)
s, a, c = attend(q, K, V)
print("shape: q", q.shape, "K", K.shape, "V", V.shape)
for name, si, ai in zip(items, s, a):
print(f"{name}: score {si:5.2f} weight {ai:.3f}")
print("weight-এর যোগফল:", round(float(a.sum()), 6))
print("context c =", c, "shape", c.shape)
for beta in (1, 3, 10): # query যত বড়, বাছাই তত কড়া
_, a2, c2 = attend(beta * q, K, V)
print(f"query × {beta:2d}: weight {a2} c {c2}")আউটপুট (পাতার Python-এ চালানো):
shape: q (3,) K (4, 3) V (4, 2)
চাল: score 1.00 weight 0.341
ডাল: score 1.10 weight 0.377
তেল: score 0.50 weight 0.207
লবণ: score -0.50 weight 0.076
weight-এর যোগফল: 1.0
context c = [109.281 9.906] shape (2,)
query × 1: weight [0.341 0.377 0.207 0.076] c [109.281 9.906]
query × 3: weight [0.387 0.522 0.086 0.004] c [105.488 8.075]
query × 10: weight [0.268 0.73 0.002 0. ] c [106.686 6.885]
Query “ডাল”-এর সবচেয়ে কাছে, তবু weight মাত্র ০.৩৭৭: score-এর ফাঁক ছোট। Context-এর দাম ১০৯.২৮, যা চার পণ্যের কোনো দামই নয়, একটি মিশ্রণ। এই মিশ্রণকেই decoder পড়ে। Query ১০ গুণ করলে ডাল ০.৭৩ পায়, চাল ০.২৬৮: score-এর মাপই বাছাইয়ের কড়াকড়ি ঠিক করে। তাই vector-এর মাপ বড় হলে score সংযত রাখার প্রশ্ন ওঠে, যা √dk নিয়ে পর্ব ৩৩ (Self-attention আর multi-head attention)-এর বিষয়।
৩ · Score দুই রকম — dot-product আর additive
Softmax আর weighted sum সব attention-এ প্রায় একই; আলাদা হয় score. দুটি সূত্র সবচেয়ে পরিচিত:
dot: e = sTh · additive: e = vT tanh(Wqs + Wkh)
Dot-product attention-এ শেখার কিছু নেই: query আর key-এর dot product. Additive attention-এ Wq, Wk matrix আর v একটি vector, যা শেখা হয়; এটিই একটি লুকানো স্তরের ছোট network. এই ধরনের score Bahdanau-র প্রবন্ধে ছিল, আর Luong-রা dot-সহ কয়েকটি সরল রূপ তুলনা করেন।
নিচের কোডে দুটি score একই vector-এর ওপর। Additive-এর parameter এলোমেলো, না-শেখা, তাই তার সংখ্যার কোনো “অর্থ” খুঁজবেন না; দেখার বিষয় আকার, সীমা আর একটি বড় key-এর প্রভাব।
import numpy as np
np.set_printoptions(precision=3, suppress=True, linewidth=60)
rng = np.random.default_rng(42)
K = np.array([[1.0, 0.0, 0.5], [0.8, 0.6, 0.0],
[0.0, 1.0, 0.2], [-0.5, 0.0, 1.0]])
q = np.array([1.0, 0.5, 0.0])
d, da = 3, 4 # key-এর মাপ, additive-এর ভেতরের মাপ
Wq = rng.normal(size=(da, d)) * 0.5 # না-শেখা, এলোমেলো parameter
Wk = rng.normal(size=(da, d)) * 0.5
v = rng.normal(size=da) * 0.5
def softmax(z):
e = np.exp(z - z.max()); return e / e.sum()
def dot(q, K):
return K @ q
def additive(q, K):
return np.tanh(Wq @ q + K @ Wk.T) @ v # (4, da) @ (da,) → (4,)
print("dot score", dot(q, K), "weight", softmax(dot(q, K)))
print("additive score", additive(q, K), "weight", softmax(additive(q, K)))
print("dot-এর parameter: 0")
print("additive-এর parameter:", Wq.size + Wk.size + v.size)
K2 = K.copy(); K2[3] = K2[3] * -10 # লবণের key উল্টে ১০ গুণ বড়
print("\nএকটি key ১০ গুণ বড় করলে:")
print("dot score", dot(q, K2), "weight", softmax(dot(q, K2)))
print("additive score", additive(q, K2), "weight", softmax(additive(q, K2)))
print("additive score-এর সীমা: ±", round(float(np.abs(v).sum()), 3))
cos = K2 @ q / (np.linalg.norm(K2, axis=1) * np.linalg.norm(q))
print("cosine (দৈর্ঘ্য বাদ) ", cos)আউটপুট (পাতার Python-এ চালানো):
dot score [ 1. 1.1 0.5 -0.5] weight [0.341 0.377 0.207 0.076]
additive score [ 0.096 0.052 -0.06 -0.08 ] weight [0.274 0.262 0.234 0.23 ]
dot-এর parameter: 0
additive-এর parameter: 28
একটি key ১০ গুণ বড় করলে:
dot score [1. 1.1 0.5 5. ] weight [0.017 0.019 0.011 0.953]
additive score [ 0.096 0.052 -0.06 0.152] weight [0.258 0.247 0.221 0.273]
additive score-এর সীমা: ± 0.839
cosine (দৈর্ঘ্য বাদ) [0.8 0.984 0.439 0.4 ]
এখানে একটি key শুধু উল্টে ১০ গুণ লম্বা করলাম। Dot-এ সেই ঘর weight ০.৯৫৩ পেয়ে গেল, যদিও cosine বলছে সে query-র সবচেয়ে কম কাছের (০.৪০০). Dot product দৈর্ঘ্যও গোনে। Additive-এ tanh মান বেঁধে রাখে: score কখনো ±০.৮৩৯-এর বাইরে যায় না, তাই একটি বড় key একাই সব weight নিতে পারে না। দাম হলো parameter আর হিসাব। মনে রাখবেন: এটি এক জোড়া বানানো সংখ্যার ছবি, কোনটি “ভালো”, তার রায় নয়।
| বিষয় | Dot-product | Additive |
|---|---|---|
| নিজের parameter (q, k মাপ d, ভেতরে da) | ০ | ২·d·da + da |
| Query আর key-এর মাপ | সমান হতেই হবে | আলাদা হতে পারে |
| Score-এর সীমা | নেই, দৈর্ঘ্য অনুযায়ী বাড়ে | tanh-এ বাঁধা |
| Batched হিসাব | একটি matrix গুণ | একটি ছোট network |
| এক ধাপে T token-এর জন্য | T·d গুণ | Wkh আগেই রেখে দিলে প্রায় d·da + T·da গুণ, সঙ্গে T·da-টি tanh |
৪ · Mask — pad-কে ঠিক শূন্য weight দেওয়া
একটি batch-এ ৪ অঙ্ক আর ২ অঙ্কের নম্বর একসঙ্গে থাকলে ছোটটিতে pad বসে (পর্ব ১১). Pad ডেটা নয়, তাই attention যেন সেখানে না তাকায়। এটিই padding mask-এর কাজ। আর একটি জিনিস প্রায়ই ভুল হয়: pad ঘরের state শূন্য হলে score-ও শূন্য, কিন্তু শূন্য score মানে শূন্য weight নয়।
| পদ্ধতি | |
| score | |
| exp | |
| weight | |
| যোগফল |
- শুরু. প্রথম দুটি ঘর আসল, শেষ দুটি pad. আমরা চাই pad ঘরের weight ঠিক ০ হোক, আর আসল দুটির যোগফল ১.
- Mask নেই. e0 = ১, তাই প্রতিটি pad ০.০৮৩ পায়। মোট ০.১৬৫ weight ডেটা নয় এমন জায়গায়।
- Score × mask. AI-এর কোডেও দেখা যায় এমন ভুল। Score ০ করলেও e0 = ১, ফল হুবহু আগের মতো। শূন্য score “নিরপেক্ষ”, “বাতিল” নয়।
- −inf. e−∞ = ০, তাই pad ঠিক শূন্য, আর হর শুধু আসল ঘরের যোগফল ১০.১০৭. আসল দুটি পায় ০.৭৩১ আর ০.২৬৯: pad না থাকলে যা হতো, হুবহু তাই।
- Softmax-এর পরে শূন্য. যোগফল ০.৮৩৫, ১ নয়। Context তাই ছোট হয়ে যায়, আর ছোট হওয়ার মাপ pad-এর সংখ্যার ওপর নির্ভর করে। আবার ভাগ দিলে ফল −inf-এর সমান হয়, তবে −inf-ই সরল।
- সব ঘর mask. সতর্কতা: যদি একটি সারির সব ঘর mask হয়, যেমন ফাঁকা input, তাহলে ০/০ থেকে NaN আসে, যা পুরো batch-এর loss নষ্ট করতে পারে। ফাঁকা sequence আগেই বাদ দিতে হয়।
এবার batch আকারে, এমন সংখ্যায় যেখানে pad-এর শূন্য score আসল score-এর চেয়েও বড়।
import numpy as np
np.set_printoptions(precision=3, suppress=True, linewidth=60)
PAD = 0
src = np.array([[5, 7, 6, 9], # ৪ অঙ্ক
[8, 3, PAD, PAD]]) # ২ অঙ্ক + ২ pad
H = np.zeros((2, 4, 2)) # encoder states, (B, T, d)
H[0, :4] = [[1, 0], [0, 1], [-1, 1], [1, 1]]
H[1, :2] = [[-1, 0.5], [-0.5, -1]] # pad ঘরে শূন্য vector
s = np.array([[1.0, 0.5], [1.0, 0.5]]) # query, (B, d)
mask = src != PAD # True = আসল token
def softmax(z):
e = np.exp(z - np.max(z, -1, keepdims=True))
return e / e.sum(-1, keepdims=True)
scores = (H @ s[..., None])[..., 0] # (B, T)
print("mask:\n", mask.astype(int))
print("scores:\n", scores)
print("১. mask নেই:\n", softmax(scores))
print("২. score × mask (ভুল পথ), সারি ২:", softmax(scores * mask)[1])
masked = np.where(mask, scores, -np.inf) # softmax-এর আগে −inf
w = softmax(masked)
print("৩. −inf:\n", w)
print("প্রতি সারির যোগফল:", w.sum(-1))
c = (w[..., None] * H).sum(1) # context, (B, d)
print("batch-এ context, সারি ২:", c[1])
one = softmax(H[1, :2] @ s[1]) # pad ছাড়া একা চালালে
print("একা context:", one @ H[1, :2])
print("সব ঘর mask হলে:", softmax(np.full((1, 3), -np.inf)))আউটপুট (পাতার Python-এ চালানো):
mask:
[[1 1 1 1]
[1 1 0 0]]
scores:
[[ 1. 0.5 -0.5 1.5 ]
[-0.75 -1. 0. 0. ]]
১. mask নেই:
[[0.287 0.174 0.064 0.474]
[0.166 0.13 0.352 0.352]]
২. score × mask (ভুল পথ), সারি ২: [0.166 0.13 0.352 0.352]
৩. −inf:
[[0.287 0.174 0.064 0.474]
[0.562 0.438 0. 0. ]]
প্রতি সারির যোগফল: [1. 1.]
batch-এ context, সারি ২: [-0.781 -0.157]
একা context: [-0.781 -0.157]
সব ঘর mask হলে: [[nan nan nan]]
দ্বিতীয় সারির আসল score ঋণাত্মক, তাই mask ছাড়া দুটি pad মিলে ০.৭০৪ weight নিয়ে নেয়: বেশির ভাগ মনোযোগ যায় ফাঁকা ঘরে। −inf দিলে batch-এর context আর একা চালানো context হুবহু এক। এই “একা আর batch-এ একই ফল” যাচাইটি mask-এর সবচেয়ে ভালো পরীক্ষা, যা §৭-এ আসল model-এ করব।
৫ · Decoder-এ attention বসানো — shape ধরে
পর্ব ৩০-এর GRU model-এ তিনটি বদল। Encoder এখন সব state ফেরত দেয়। Decoder-এর ইনপুটে আগের token-এর embedding-এর পাশে context যোগ হয়। আর output layer পড়ে নতুন state আর context, দুটিই। নকশাটি Bahdanau ধরনের (আগের state হলো query), তবে encoder একমুখী, আর দুই score-ই পরীক্ষা করা যায়। নিচে আসল class-এর মূল অংশ, সংক্ষেপে; greedy আর constructor-এর বাকি অংশ বাদ, packing আগের মতোই।
class AttnSeq2Seq(nn.Module):
# self.dec = nn.GRUCell(emb + hid, hid); self.out = nn.Linear(2 * hid, n_tgt)
def encode(self, src, lens):
packed = nn.utils.rnn.pack_padded_sequence(
self.src_emb(src), lens.cpu(), batch_first=True, enforce_sorted=False)
H, h_n = self.enc(packed)
H, _ = nn.utils.rnn.pad_packed_sequence(H, batch_first=True)
return H, h_n[0] # H: (B, T, hid), keys ও values
def attend(self, s, H, mask): # s: (B, hid) হলো query
if self.score == "dot":
e = (H @ s.unsqueeze(-1)).squeeze(-1) # (B, T)
else:
e = self.v(torch.tanh(self.Wq(s).unsqueeze(1) + self.Wk(H))).squeeze(-1)
e = e.masked_fill(~mask, float("-inf"))
a = e.softmax(-1) # (B, T), প্রতি সারির যোগফল 1
c = (a.unsqueeze(1) @ H).squeeze(1) # (B, hid)
return c, a
def step(self, inp, s, H, mask):
c, a = self.attend(s, H, mask)
s = self.dec(torch.cat([self.tgt_emb(inp), c], -1), s)
return self.out(torch.cat([s, c], -1)), s, aShape পড়ুন, B = ১২৮, T = ৪০, hid = ১২৮ ধরে। H (128, 40, 128); s.unsqueeze(-1) (128, 128, 1); গুণফল (128, 40, 1), squeeze করে (128, 40). a.unsqueeze(1) (128, 1, 40), তার সঙ্গে H গুণ করলে (128, 1, 128), squeeze করে context (128, 128). masked_fill ঘরগুলোকে −inf বসায়, আর সেই ঘর থেকে gradient আসে না। Batched matmul এসেছে পর্ব ৭-এ।
Parameter গুনি। পর্ব ৩০-এর h = ১২৮ model-এ ১,২৮,২৩৯. GRUCell-এর ইনপুট ৩২ থেকে ১৬০ হওয়ায় বাড়ে ৩ × ১২৮ × ১২৮ = ৪৯,১৫২. Output layer-এ বাড়ে ১২৮ × ১৫ = ১,৯২০. Dot-এর মোট ১,৭৯,৩১১, attention-এর নিজের কোনো parameter ছাড়াই। Additive-এ আরও ২ × ১২৮ × ১২৮ + ১২৮ = ৩২,৮৯৬, মোট ২,১২,২০৭. PyTorch-এর গোনার সঙ্গে সংখ্যাগুলো মেলে।
৬ · সত্যিকারের run — পর্ব ৩০-এর একই কাজ, attention সহ আর ছাড়া
কাজ পর্ব ৩০-এর: এলোমেলো বাংলা অঙ্কের অর্ডার নম্বর, দৈর্ঘ্য ১ থেকে ৪০, output ইংরেজি অঙ্কে। একই ২০,০০০ train জোড়া, একই seed-এর ৪০০টি করে test প্রতিটি দৈর্ঘ্যের ভাগে, batch ১২৮, ২০ epoch, teacher forcing ratio ১.০, Adam ০.০০২, মূল্যায়ন greedy. সত্যিকারের run, CPU-তে; torch 2.15.0, Python 3.14.4. আগে attention ছাড়া model আবার চালিয়ে নিলাম, পর্ব ৩০-এর সংখ্যা ফিরে আসে কি না দেখতে:
~/bazarghar-dl$ uv run python run_attn.py none 128 0
none hid 128 seed 0 parameters 128239
epoch 1 train loss 2.1908 | L=36-40 exact 0.000 | 12s
epoch 5 train loss 1.3105 | L=36-40 exact 0.000 | 65s
epoch 20 train loss 0.8449 | L=36-40 exact 0.000 | 335s
L= 1- 5 exact 1.000 CER 0.0000
L= 6-10 exact 0.792 CER 0.0679
L=11-15 exact 0.223 CER 0.2437
L=16-20 exact 0.007 CER 0.3658
…
L=36-40 exact 0.000 CER 0.5789
~/bazarghar-dl$ uv run python run_attn.py dot 128 0
dot hid 128 seed 0 parameters 179311
epoch 1 train loss 1.5400 | L=36-40 exact 0.013 | 25s
epoch 3 train loss 0.1260 | L=36-40 exact 0.393 | 82s
epoch 5 train loss 0.0291 | L=36-40 exact 0.897 | 147s
epoch 10 train loss 0.0170 | L=36-40 exact 0.950 | 338s
epoch 20 train loss 0.0215 | L=36-40 exact 0.850 | 677s
L= 1- 5 exact 0.998 CER 0.0008
L= 6-10 exact 0.993 CER 0.0019
L=11-15 exact 0.975 CER 0.0031
L=16-20 exact 0.980 CER 0.0032
L=21-25 exact 0.973 CER 0.0035
L=26-30 exact 0.945 CER 0.0048
L=31-35 exact 0.920 CER 0.0140
L=36-40 exact 0.850 CER 0.0181
প্রথম লগের প্রতিটি সংখ্যা পর্ব ৩০-এর সঙ্গে হুবহু মেলে (train loss ০.৮৪৪৯; ৬–১০ আর ১১–১৫ অঙ্কে exact ০.৭৯২, ০.২২৩), তাই তুলনাটি সত্যিই একই ডেটায়। সেকেন্ডের সংখ্যায় ভরসা করবেন না: কয়েকটি run একসঙ্গে চলছিল; সময় আলাদা করে মেপেছি নিচে। সব মিলিয়ে, প্রতিটি ঘরে exact-match / CER:
| দৈর্ঘ্য | ছাড়া, h = ১২৮ | ছাড়া, h = ৫১২ | Dot, h = ৬৪ | Dot, h = ১২৮ | Additive, h = ১২৮ |
|---|---|---|---|---|---|
| ১–৫ | ১.০০০ / ০.০০০ | ১.০০০ / ০.০০০ | ০.৯৯৭ / ০.০০১ | ০.৯৯৯ / ০.০০০ | ১.০০০ / ০.০০০ |
| ৬–১০ | ০.৭৮৬ / ০.০৭০ | ০.৯৪৫ / ০.০১৩ | ০.৯৮৯ / ০.০০২ | ০.৯৯৪ / ০.০০২ | ১.০০০ / ০.০০০ |
| ১১–১৫ | ০.২১২ / ০.২৩৭ | ০.৮০৭ / ০.০৩৩ | ০.৯৮৯ / ০.০০১ | ০.৯৮৩ / ০.০০২ | ১.০০০ / ০.০০০ |
| ১৬–২০ | ০.০০৭ / ০.৩৫৭ | ০.৫৭০ / ০.০৬৫ | ০.৯৯৩ / ০.০০১ | ০.৯৮৯ / ০.০০২ | ১.০০০ / ০.০০০ |
| ২১–২৫ | ০.০০০ / ০.৪৪৭ | ০.২৯০ / ০.১০৮ | ০.৯৮৯ / ০.০০১ | ০.৯৮৬ / ০.০০২ | ১.০০০ / ০.০০০ |
| ২৬–৩০ | ০.০০০ / ০.৫০৫ | ০.০৯০ / ০.১৫২ | ০.৯৮৩ / ০.০০১ | ০.৯৭০ / ০.০০৩ | ১.০০০ / ০.০০০ |
| ৩১–৩৫ | ০.০০০ / ০.৫৪৭ | ০.০১০ / ০.২২৭ | ০.৯৭৯ / ০.০০১ | ০.৯৫৯ / ০.০০৭ | ১.০০০ / ০.০০০ |
| ৩৬–৪০ | ০.০০০ / ০.৫৭২ | ০.০০০ / ০.২৯৪ | ০.৯৫১ / ০.০০৬ | ০.৯২২ / ০.০০৯ | ০.৯৯৭ / ০.০০০ |
| Parameter | ১,২৮,২৩৯ | ১৬,৮৬,৮৯৫ | ৫৩,৭৪৩ | ১,৭৯,৩১১ | ২,১২,২০৭ |
প্রতিটি attention কলাম seed ০ আর ১-এর গড়; attention ছাড়া কলাম দুটি পর্ব ৩০-এর টেবিল থেকে (h = ১২৮ দুই seed, h = ৫১২ এক seed). পড়ার সময় চারটি কথা মনে রাখুন।
- পতন প্রায় নেই. h = ১২৮-এ ১৬–২০ অঙ্কে ০.০০৭ থেকে ০.৯৮৯, ৩৬–৪০ অঙ্কে ০.০০০ থেকে ০.৯২২. পর্ব ৩০-এর পাশে-পাশে নিয়ন্ত্রণের ১.০০০-এর খুব কাছে।
- বড় model লাগেনি. h = ৬৪-এর attention model h = ৫১২-এর attention-ছাড়া model-এর প্রায় ৩১ ভাগের এক ভাগ parameter নিয়েও ৩৬–৪০ অঙ্কে ০.৯৫১ পায়, যেখানে অন্যটি ০.০০০.
- Dot h = ৬৪ কি h = ১২৮-এর চেয়ে ভালো? এই টেবিল তা বলে না। Dot h = ১২৮, seed ০-এর লগে ৩৬–৪০ অঙ্কে ১০ম epoch-এ ০.৯৫০, ২০তম-তে ০.৮৫০; একই model-এর seed ১ পেল ০.৯৯৩. Learning rate স্থির, তাই শেষ epoch-এর ফল ওঠানামা করে। দুই seed দিয়ে এই ফাঁকের রায় দেওয়া যায় না।
- Additive এখানে স্থির. দুই seed-এই ৩৬–৪০ অঙ্কে ০.৯৯৫ আর ০.৯৯৮. এটি এই কাজের একটি স্থানীয় পরীক্ষা, “additive সবসময় ভালো” নয়।
দামও মাপা দরকার। আলাদা করে, পরপর, CPU-তে ২০টি train batch আর ৪০ অঙ্কের একটি greedy decode (৫০ বারের গড়); দ্বিতীয় রাউন্ডেও প্রায় একই:
~/bazarghar-dl$ uv run python run_extra.py
run 0 none : ২০ train batch 0.99 s | ৪০ অঙ্কের greedy 3.4 ms
run 0 dot : ২০ train batch 1.89 s | ৪০ অঙ্কের greedy 5.6 ms
run 0 additive : ২০ train batch 4.84 s | ৪০ অঙ্কের greedy 7.1 ms
Dot-এ training প্রায় দ্বিগুণ, additive-এ প্রায় পাঁচ গুণ সময় নিল, এই কোডে। কারণ decoder-এর প্রতি ধাপে T-টি ঘর দেখতে হয়: output-এর দৈর্ঘ্য T′ হলে মোট কাজ T·T′-এর সমানুপাতিক। Additive-এর দামের একটি অংশ আমাদের কোডেরই: সে প্রতি ধাপে Wkh আবার হিসাব করে, যা আগে থেকে রেখে দেওয়া যেত। তবে একই বাজেটে ফলের ফারাক বিশাল: পর্ব ৩০-এ দৈর্ঘ্যের সঙ্গে দৌড়াতে parameter ৪২ গুণ করেও ৩৬–৪০ অঙ্কে শূন্য ছিল।
Attention সব সারিয়ে দেয় না। Train-এ ৪০ অঙ্কের বেশি ছিল না। তার চেয়ে লম্বা input দিলে (প্রতি ভাগে ২০০টি, seed ০):
dot h=128: L=41-60 (train-এ ছিল না) exact 0.430 CER 0.1020
dot h=128: L=61-80 (train-এ ছিল না) exact 0.080 CER 0.2762
dot h=128: L=81-120 (train-এ ছিল না) exact 0.005 CER 0.5084
additive h=128: L=41-60 (train-এ ছিল না) exact 0.310 CER 0.1062
additive h=128: L=61-80 (train-এ ছিল না) exact 0.000 CER 0.3586
…
dot h=64: L=41-60 (train-এ ছিল না) exact 0.620 CER 0.1002
…
Context-এর পথ খোলা, কিন্তু “এখন কোন ঘর” তা decoder-এর state-কে গুনে রাখতে হয়, আর সেই গোনা ৪০-এর পরে শেখা হয়নি। ৪০ অঙ্কের ০.৯৯৭ থেকে ৮১–১২০-এ প্রায় শূন্য। আর অঙ্কের এই সরল কাজ অনুবাদের মতো কঠিন নয়: টেবিলের ১.০০০ কোনো ভাষার model-এর গুণ বলে ধরবেন না।
৭ · Mask ছাড়া শেখালে কী হয় — একই input, ভিন্ন output
Dot h = ১২৮, seed ০ model-টি mask ছাড়াও শেখালাম, বাকি সব এক। Pad ঘরে encoder-এর state শূন্য, তাই score ০. ধারণা ছিল সামান্য খারাপ হবে। হলো অন্য কিছু:
~/bazarghar-dl$ uv run python run_align.py; uv run python run_extra.py # লগ থেকে সংক্ষেপ করা
mask আছে: ১–১০ অঙ্ক, একা exact 0.998 | ৪০ অঙ্কের পাশে exact 0.998
mask নেই: ১–১০ অঙ্ক, একা exact 0.693 | ৪০ অঙ্কের পাশে exact 0.998
mask নেই: gold 1674 | একা 16741 | ৪০ অঙ্কের পাশে 1674
mask নেই: gold 1954 | একা 19541 | ৪০ অঙ্কের পাশে 1954
mask আছে: </s> লেখার ধাপে pad ঘরের গড় weight 0.000
mask নেই: </s> লেখার ধাপে pad ঘরের গড় weight 0.866
একই model, একই input “১৬৭৪”. একা দিলে “16741”, পাশে একটি ৪০ অঙ্কের নম্বর থাকলে “1674”. কেন? </s> লেখার ধাপে এই model weight-এর ০.৮৬৬ দিচ্ছে pad ঘরে। অর্থাৎ সে শিখেছে, “pad-এর শূন্য vector দেখলে থামতে হবে”। Pad হয়ে গেছে থামার সংকেত। Batch-এ pad না থাকলে সংকেতও নেই, আর model বাড়তি অঙ্ক লেখে। তার test টেবিলেও (প্রায় একই দৈর্ঘ্যের batch, তাই pad কম) ১–৫ অঙ্কে exact ০.৮৯০.
Loss-এর লগে এই সমস্যা ধরা পড়ে না: ৩৬–৪০ অঙ্কে mask-ছাড়া model ০.৯২৭ পায়, দেখতে ভালোই। ধরা পড়ে শুধু “একা বনাম batch” পরীক্ষায়। এটি একটি run; কিন্তু শিক্ষাটি সাধারণ: model ডেটার যেকোনো নকশা কাজে লাগাতে পারে, ডেটা-ভরাটের নকশাও। Live ব্যবস্থায় batch-এর সঙ্গী বদলালে ফল বদলাবে, এমন model চালানো যায় না।
৮ · Alignment heatmap পড়া
প্রতিটি output ধাপের weight সারি করে সাজালে হয় alignment matrix: সারি output-এর ধাপ, কলাম input-এর ঘর, প্রতি সারির যোগফল ১. আগে হাতে বানানো একটি ছবি দেখি, যেখানে উত্তর আমরা জানি। Key হলো অবস্থানের one-hot, value হলো অঙ্কের one-hot; query আমরাই বলে দিই কোন ঘর চাই। “4” দুবার আছে, ইচ্ছা করেই।
import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision=2, suppress=True, linewidth=60)
digits = [4, 7, 0, 4, 2, 9] # ৪৭০৪২৯, “4” দুবার আছে
T = len(digits)
V = np.eye(10)[digits] # value: অঙ্কের one-hot, (T, 10)
K = np.eye(T) # key: অবস্থানের one-hot, (T, T)
def softmax(z):
e = np.exp(z - np.max(z, -1, keepdims=True))
return e / e.sum(-1, keepdims=True)
def align(order, beta=4.0): # order[t] = t ধাপে যে ঘর চাই
Q = beta * np.eye(T)[order] # query, (T_out, T)
A = softmax(Q @ K.T) # alignment, (T_out, T)
C = A @ V # context, (T_out, 10)
return A, C.argmax(-1)
A1, copy = align(np.arange(T)) # সোজা নকল
A2, back = align(np.arange(T)[::-1]) # উল্টো ক্রম
print("কপি :", copy)
print("উল্টো:", back)
print("A1, প্রথম ২ সারি:\n", A1[:2])
print("প্রতি সারির যোগফল:", A1.sum(1))
fig, axes = plt.subplots(1, 2, figsize=(7.2, 3.4))
for ax, A, name in zip(axes, (A1, A2), ("copy", "reverse")):
ax.imshow(A, cmap="Blues", vmin=0, vmax=1)
ax.set_title(name)
ax.set_xticks(range(T), labels=digits)
ax.set_yticks(range(T))
ax.set_xlabel("input digit (key position)")
ax.set_ylabel("output step t")
plt.show()আউটপুট (পাতার Python-এ চালানো, সঙ্গে ছবি):
কপি : [4 7 0 4 2 9]
উল্টো: [9 2 4 0 7 4]
A1, প্রথম ২ সারি:
[[0.92 0.02 0.02 0.02 0.02 0.02]
[0.02 0.92 0.02 0.02 0.02 0.02]]
প্রতি সারির যোগফল: [1. 1. 1. 1. 1. 1.]
নকলে উজ্জ্বল ঘরগুলো বাঁ-ওপর থেকে ডান-নিচে কর্ণ বরাবর, উল্টোয় ডান-ওপর থেকে বাঁ-নিচে। প্রকৃত অনুবাদে কর্ণ থেকে সরে যাওয়া ঘরগুলো শব্দক্রমের বদল দেখাতে পারে। আর এই ছবিতে key অবস্থান জানে, অঙ্ক জানে না; তাই একই “4” দুই জায়গায় দুই আলাদা ঘর।
এবার আসল শেখা model-এর weight. উদাহরণটি test-এর ছাঁচের ৮ অঙ্ক, “৬৮০৬৭৩৬২”. সারির বাঁয়ে ধাপ আর লেখা অক্ষর, $ মানে </s>. সত্যিকারের run, ২ ঘর দশমিকে:
dot h=128 seed 0 | input ৬৮০৬৭৩৬২ | output 68067362
6 8 0 6 7 3 6 2
0 6 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00
1 8 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00
2 0 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00
3 6 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00
4 7 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00
5 3 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00
6 6 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00
7 2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00
8 $ 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00
additive h=128 seed 0 | input ৬৮০৬৭৩৬২ | output 68067362
6 8 0 6 7 3 6 2
0 6 0.93 0.05 0.01 0.00 0.00 0.00 0.00 0.00
1 8 0.16 0.66 0.15 0.02 0.00 0.00 0.00 0.00
2 0 0.01 0.11 0.80 0.07 0.01 0.00 0.00 0.00
3 6 0.01 0.02 0.14 0.77 0.05 0.01 0.00 0.00
4 7 0.00 0.00 0.00 0.09 0.81 0.07 0.01 0.02
5 3 0.00 0.00 0.00 0.01 0.06 0.82 0.11 0.00
6 6 0.00 0.00 0.00 0.00 0.01 0.07 0.89 0.03
7 2 0.00 0.00 0.00 0.00 0.00 0.00 0.02 0.97
8 $ 0.00 0.00 0.00 0.00 0.01 0.02 0.31 0.66
| প্রশ্ন | |
| Model | |
| সারি | |
| Weight | |
| পড়া |
- অক্ষ. আগে দেখুন কোন দিক কী। এখানে সারি হলো output-এর ধাপ, কলাম হলো input-এর ঘর। অনেক ছবিতে উল্টো থাকে, তাই লেবেল পড়া প্রথম কাজ।
- যোগফল. প্রতিটি সারির যোগফল ১, কলামের নয়। কোনো ঘরের কলাম খালি থাকলে মানে হতে পারে সেই token কেউ পড়েনি, আবার ভরা কলাম মানে একই ঘর বারবার পড়া।
- চূড়া. t = ২-এ তৃতীয় ঘরে ০.৮০, আর সেখানে অঙ্ক “0”, যা লেখাও হলো। এমন মিল দেখে আমরা বলি alignment কর্ণ বরাবর।
- পাশে ছড়ানো. t = ১-এ ০.৬৬ চূড়া, পাশে ০.১৬ আর ০.১৫. দেখে মনে হয় model পাশের অঙ্কও “কাজে লাগাচ্ছে”। এটি শুধু অনুমান, §৯-এর পরীক্ষায় দেখুন।
- অদ্ভুত সারি. Dot model প্রথম ধাপে পুরো ১.০০ দিচ্ছে শেষ অঙ্ক “2”-এ, অথচ লিখছে “6”. চোখ বুজে পড়লে সিদ্ধান্ত হতো “2 পড়ে 6 লিখেছে”। Query এখানে encoder-এর শেষ state নিজেই, তাই সম্ভবত সে নিজের সঙ্গেই সবচেয়ে মেলে।
- থামার সারি. </s> লেখার সময় চূড়া শেষ ঘরে। “আর ঘর নেই” এমন কোনো সংকেত এখানে সরাসরি নেই, তাই এই সারি থেকে থামার কারণ পড়া যায় না।
৯ · Attention কি ব্যাখ্যা? — একটি বিতর্ক আর আমাদের পরীক্ষা
Heatmap এত সুন্দর যে তাকে “model কেন এটি বলল”-এর ব্যাখ্যা বলে ফেলা সহজ। Jain ও Wallace-এর “Attention is not Explanation” (২০১৯) জানায়, তাঁদের দেখা কিছু NLP classification model-এ weight প্রায়ই gradient-ভিত্তিক গুরুত্বের সঙ্গে ভালো মেলে না, আর অনেক আলাদা weight দিয়েও প্রায় একই অনুমান পাওয়া যায়। Wiegreffe ও Pinter-এর “Attention is not not Explanation” (২০১৯) পাল্টা বলে, এই সিদ্ধান্ত নির্ভর করে “ব্যাখ্যা” বলতে কী বোঝানো হচ্ছে তার ওপর, আর পরীক্ষার ধরনের ওপর; কিছু পরিস্থিতিতে weight কাজের তথ্য দিতে পারে। বিতর্কটি শেষ হয়নি। সাধারণ রায় না দিয়ে আমরা নিজের model-এ একটি সরল পরীক্ষা করি: এক ধাপে weight জোর করে সরিয়ে দেখি, output token বদলায় কি না। সংখ্যাগুলো token অপরিবর্তিত থাকার ভাগ, ২১–২৫ অঙ্কের ৪০০টি input-এ।
~/bazarghar-dl$ uv run python run_align.py
dot h=128 seed 0 {'hard': 1.000, 'same': 0.722, 'other': 0.050, 'uniform': 0.152}
additive h=128 seed 0 {'hard': 1.000, 'same': 0.965, 'other': 0.002, 'uniform': 0.192}
~/bazarghar-dl$ uv run python run_extra.py
dot h=128 seed 0: t=0-এ argmax শেষ ঘরে 1.000 | t=0-এর token ঠিক 1.000 | weight অন্য অঙ্কের ঘরে সরালে token একই 0.994
dot h=128 seed 1: t=0-এ argmax শেষ ঘরে 1.000 | t=0-এর token ঠিক 1.000 | weight অন্য অঙ্কের ঘরে সরালে token একই 1.000
additive h=128 seed 0: t=0-এ argmax শেষ ঘরে 0.000 | t=0-এর token ঠিক 1.000 | weight অন্য অঙ্কের ঘরে সরালে token একই 0.000
(প্রথম দুই সারি লগ থেকে সংক্ষেপ করা, উদাহরণের সংখ্যা বাদ।) “hard” মানে চূড়ার ঘরে পুরো weight; “same” মানে একই অঙ্ক আছে এমন অন্য ঘরে; “other” মানে ভিন্ন অঙ্কের ঘরে; “uniform” মানে সব ঘরে সমান। পড়ে দেখুন:
- এই কাজে weight বেশির ভাগ ধাপে সত্যিই প্রভাব ফেলে. ভিন্ন অঙ্কের ঘরে সরালে token প্রায় সবসময় বদলায় (additive-এ অপরিবর্তিত মাত্র ০.০০২).
- ছড়ানো weight দরকারি ছিল না. Additive-এর গড় চূড়া ০.৭৮৪, পাশে অনেকটা ছড়ানো। অথচ সব weight চূড়ায় আনলে একটি token-ও বদলায় না। “পাশের অঙ্ক কাজে লাগছে” অনুমানটি এই পরীক্ষায় টিকল না।
- এক output, অনেক সম্ভাব্য ছবি. একই অঙ্কের অন্য ঘরে সরালেও token অনেকটাই একই থাকে (০.৭২২, ০.৯৬৫). তাই heatmap একমাত্র “সঠিক” ব্যাখ্যা নয়।
- প্রথম সারিটি dot-এ বিভ্রান্তিকর. শেষ ঘরে পুরো weight, কিন্তু সরালে প্রায় কিছুই বদলায় না। প্রথম অঙ্কটি সম্ভবত আসে decoder-এর শুরুর state থেকে। Additive-এ এই ধাপ আলাদা: সেখানে weight সরালে token বদলে যায়।
দুই model-এর accuracy কাছাকাছি, অথচ প্রথম সারির গল্প সম্পূর্ণ আলাদা। ঠিক এই কারণে একটি ছবি থেকে “model এই কারণে বলেছে” দাবি করা যায় না। কোনো অনুমানের ব্যাখ্যা দরকার হলে মেশিন লার্নিং কোর্সের পর্ব ৫৭-এর পদ্ধতি আর এই ধরনের হস্তক্ষেপ-পরীক্ষা পাশাপাশি রাখুন। আর আমাদের পরীক্ষা শুধু এই ছোট কাজের, এই দুটি model-এর।
আরও গভীরে: Luong ও সহকর্মীরা (২০১৫) সব ঘর দেখা “global” আর কিছু ঘর দেখা “local” attention আলাদা করেন, আর Xu ও সহকর্মীরা (২০১৫) ছবির caption-এ soft ও hard attention তুলনা করেন। একই token বারবার পড়া ঠেকাতে coverage ধারণা এসেছে (Tu ও সহকর্মীরা, ২০১৬)। Pointer network (Vinyals ও সহকর্মীরা, ২০১৫) weight-কেই সরাসরি output বানায়। Serrano ও Smith (২০১৯) হস্তক্ষেপ-পরীক্ষা দিয়ে weight-এর ব্যাখ্যাশক্তি নিয়ে আরেকটি দিক দেখান। Query, key, value আলাদা করে শেখা আর একই sequence-এর ওপর attention পর্ব ৩৩ (Self-attention আর multi-head attention)-এ।
পরীক্ষা ও ইন্টারভিউতে: Attention-এর তিন ধাপ? Score, softmax, weighted sum. Key আর value-র কাজ? Key দিয়ে weight, value দিয়ে মিশ্রণ। Mask −inf কেন, শূন্য নয়? e0 = ১ কিন্তু e−∞ = ০. Dot আর additive-এর পার্থক্য? Dot-এর parameter নেই আর দ্রুত, মাপ সমান চায়; additive শেখে, tanh-এ বাঁধা। Weight কি ব্যাখ্যা? নিশ্চিত করে নয়; হস্তক্ষেপ করে যাচাই করতে হয়। Seq2seq attention-এর খরচ? প্রতি ধাপে T, মোট T·T′.
১০ · AI-এর যুগে — attention-এর কোড যাচাই
AI assistant attention-এর কোড সহজে লেখে, চলেও। ভুলগুলো সাধারণত error দেয় না, চুপচাপ সংখ্যা বদলায়।
| যে ভুল প্রায়ই দেখা যায় | ফল | কীভাবে ধরবেন |
|---|---|---|
| Score × mask, বা softmax-এর পরে শূন্য | Pad weight পায়, বা যোগফল ১ থাকে না | প্রতি সারির যোগফল আর pad-এর weight ছাপানো |
| Mask-এর অর্থ উল্টো | আসল ঘর বাদ, pad থেকে যায় | Docs পড়া: key_padding_mask-এ True মানে বাদ, scaled_dot_product_attention-এর bool mask-এ True মানে অংশ নেবে |
| ভুল dim-এ softmax | Weight T জুড়ে নয়, batch জুড়ে | একটি সারির যোগফল ১ কি না, আর shape দেখা |
unsqueeze/squeeze-এ ভুল | Broadcasting-এ error ছাড়াই ভুল গুণ | সব ধাপের shape হাতে মেলানো, B ≠ T ≠ H দিয়ে |
| “একা বনাম batch” পরীক্ষা নেই | §৭-এর মতো model pad-কে সংকেত বানাতে পারে | একই input দুভাবে চালিয়ে output মেলানো |
| Heatmap দেখে কারণ দাবি | বিশ্বাসযোগ্য কিন্তু ভুল গল্প | Weight সরিয়ে output-এর বদল মাপা |
| বানানো বা ভুল-বছরের citation | ভুল ইতিহাস | মূল প্রবন্ধ খুঁজে মিলিয়ে দেখা |
AI-কে কোডের সঙ্গে প্রমাণও চাইতে বলুন:
AI-এর দেওয়া attention হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Attention কি fixed context-এর সমস্যা সত্যিই মিটিয়ে দিল, নাকি সমস্যাটিকে অন্য জায়গায় সরিয়ে দিল?
পর্ব ৩০-এর সমস্যা ছিল তথ্যের সরু পথ। Bahdanau, Cho আর Bengio-র ২০১৪ সালের প্রবন্ধ সেই পথটি খুলে দেয়: decoder প্রতি ধাপে encoder-এর সব state দেখে। প্রবন্ধটি জানায়, লম্বা বাক্যে তাদের model-এর ফল আগের encoder–decoder-এর মতো দ্রুত পড়ে না। আমাদের ছোট পরীক্ষাও একই দিকে: ৩৬–৪০ অঙ্কে exact-match ০.০০০ থেকে ০.৯২২.
কিন্তু তিনটি নতুন দাম আসে। প্রথম, স্মৃতি আর সময়: সব state ধরে রাখতে হয়, আর প্রতি output ধাপে T ঘর দেখতে হয়। মোট কাজ T·T′-এর সমানুপাতিক। আমাদের run-এ এই কোডে training প্রায় দ্বিগুণ (dot) বা প্রায় পাঁচ গুণ (additive) সময় নিল। ছোট input-এ এটি নগণ্য, কিন্তু খুব লম্বা context-এর model-এ এই হিসাবই বড় প্রশ্ন, যা পর্ব ৩৭ (দক্ষ attention)-এর বিষয়।
দ্বিতীয়, জায়গা ঠিক রাখা। Model-কে জানতে হয় “এখন কোন ঘর”। আমাদের GRU সেটি state-এ গোনে, আর train-এর সীমা ৪০ পেরোলে সেই গোনা ভেঙে পড়ল: ৬১–৮০ অঙ্কে dot h = ১২৮-এ exact ০.০৮০. তাই bottleneck-এর একটি অংশ আসলে সরে গেছে অবস্থান ধরে রাখার কাজে। অবস্থানের তথ্য কীভাবে দেওয়া হয়, তা পর্ব ৩৪ (Positional encoding)-এর আলোচ্য। তৃতীয়, RNN-টি রয়েই গেল: encoder এখনো একটি একটি করে পড়ে, যা সমান্তরাল হিসাবের পথে বাধা, আর দূরের token-এর তথ্য এখনো ধাপে ধাপে আসে।
২০১৭ সালের Transformer প্রবন্ধ এই শেষ কথাটি থেকেই একটি বড় লাফ দেয়: যদি attention-ই সব পড়ার কাজ করে, RNN আদৌ লাগবে কি? সে উত্তর পরের পর্বগুলোতে। তবে “সমস্যা মিটেছে” বলা যায় না, বিশেষ করে ভাষায়: আমাদের কাজে input আর output এক-এক করে মেলে, যা attention-এর জন্য সবচেয়ে সহজ পরিস্থিতিগুলোর একটি।
মূল উপলব্ধি: Attention তথ্যের পথ খোলে, দাম হিসেবে আনে বাড়তি হিসাব আর অবস্থান ধরে রাখার নতুন সমস্যা; একটি bottleneck গেলে পরেরটি দেখা দেয়।
প্র ০২একজন ব্যবস্থাপক একটি review classifier-এর heatmap দেখে বললেন, “দেখুন, ‘দেরি’ শব্দে attention বেশি, তাই model দেরির জন্য খারাপ বলেছে।” আপনি কী বলবেন?
কথাটি সত্যি হতে পারে, কিন্তু heatmap একা তা প্রমাণ করে না। এখানে দুটি আলাদা গুণ গুলিয়ে যায়। একটি হলো বিশ্বাসযোগ্যতা, মানে ব্যাখ্যাটি মানুষের কাছে যুক্তিসঙ্গত লাগে কি না। অন্যটি হলো বিশ্বস্ততা (faithfulness), মানে ব্যাখ্যাটি model-এর আসল হিসাবকে ঠিকমতো ধরে কি না। Jacovi ও Goldberg ২০২০ সালের এক প্রবন্ধে এই পার্থক্যের ওপর জোর দেন। “দেরি”-তে উজ্জ্বল ঘর আমাদের কাছে স্বাভাবিক লাগে, আর ঠিক এই কারণে আমরা বেশি পরীক্ষা না করেই মেনে নিই।
Jain ও Wallace (২০১৯) দেখান, তাঁদের পরীক্ষার কিছু মডেলে অনেক ভিন্ন weight দিয়েও প্রায় একই অনুমান পাওয়া যায়। Wiegreffe ও Pinter (২০১৯) মনে করিয়ে দেন, একই model আর ডেটায় স্বাধীনভাবে শেখা বিকল্প model দিয়ে যাচাই না করলে সেই সিদ্ধান্ত অতিরিক্ত হতে পারে; তাই “কখনোই ব্যাখ্যা নয়” বলাও ঠিক নয়। আমাদের অর্ডার নম্বরের পরীক্ষায় দুই দিকের কথাই দেখা গেল: বেশির ভাগ ধাপে weight সরালে output বদলায়, কিন্তু dot model-এর প্রথম সারিতে পুরো weight শেষ ঘরে, অথচ সরালে ০.৯৯৪ ভাগ ক্ষেত্রে token একই থাকে।
ব্যবস্থাপককে তাই প্রত্যাখ্যান নয়, একটি পরীক্ষা প্রস্তাব করবেন। “দেরি” শব্দটি বাদ দিয়ে বা সমার্থক শব্দ দিয়ে কিছু review আবার চালান; অনুমান কতটা বদলায়? একই model আলাদা seed-এ শেখালে উজ্জ্বল ঘর একই থাকে কি? একটি আলাদা পদ্ধতিতে (SHAP, মেশিন লার্নিং কোর্সের পর্ব ৫৭, বা gradient-ভিত্তিক) ফল মেলে কি? আর সিদ্ধান্তটি যদি কাউকে প্রভাবিত করে, যেমন একজন বিক্রেতার মূল্যায়ন, তবে শুধু ছবিতে ভরসা না করে নমুনা হাতে দেখুন। একটি মনে রাখার জিনিস: Encoder-এর state-এ আশেপাশের শব্দের তথ্যও মিশে থাকে, তাই একটি ঘরের weight মানেই শুধু সেই শব্দটির প্রভাব নয়।
মূল উপলব্ধি: Attention weight model-এর ভেতরের একটি সংখ্যা, প্রমাণিত কারণ নয়; ব্যাখ্যার দাবি করতে হলে input বা weight বদলে ফল মাপতে হয়।
প্র ০৩আমাদের run-এ additive একটু বেশি স্থির ছিল। তাহলে Transformer কেন dot-product বেছে নিল?
প্রথমে আমাদের সংখ্যার সীমা মানুন: দুটি seed, একটি কৃত্রিম কাজ, h = ১২৮, স্থির learning rate. এখান থেকে সাধারণ রায় চলে না। Dot-এর seed ১-ও ৩৬–৪০ অঙ্কে ০.৯৯৩ পেয়েছে; ফারাকের বড় অংশ এসেছে একটি seed-এর শেষ epoch থেকে।
গবেষণার ছবিও এক রকম নয়। Luong ও সহকর্মীরা ২০১৫ সালে অনুবাদে কয়েকটি score তুলনা করে জানান, তাঁদের global attention-এ সরল dot ভালো ফল দিয়েছে। Vaswani ও সহকর্মীদের ২০১৭ সালের প্রবন্ধ জানায়, তত্ত্বে দুটির জটিলতা কাছাকাছি হলেও dot-product বাস্তবে অনেক দ্রুত আর জায়গা কম নেয়, কারণ অত্যন্ত অপ্টিমাইজ করা matrix গুণ দিয়ে হিসাব হয়। সেখানে তাঁরা এটিও বলেন, dk বড় হলে scale ছাড়া dot-এর চেয়ে additive ভালো করে; তাঁদের সন্দেহ, বড় dot product softmax-কে এমন জায়গায় ঠেলে দেয় যেখানে gradient খুব ছোট। তাই তাঁরা √dk দিয়ে ভাগ করেন।
এখানে হার্ডওয়্যারের যুক্তিটি বড়। Transformer-এ প্রতিটি token প্রতিটি token-কে দেখে, আর একাধিক head, বহু layer: score হিসাবের কাজ বিপুল। একটি matrix গুণ Q KT GPU-তে খুব দক্ষ। Additive-এ T × T জোড়ার জন্য tanh আর আরেকটি গুণ লাগে। আমাদের ছোট CPU মাপেও পার্থক্য চোখে পড়ে: ২০ batch-এ ১.৮৯ s বনাম ৪.৮৪ s, যদিও আমাদের কোড Wkh জমিয়ে না রাখায় ফাঁকটি কিছুটা বেশি দেখায়।
Dot-এ যে দুর্বলতা §৩-এ দেখলাম, একটি লম্বা key-এর weight দখল করে নেওয়া, Transformer-এ কিছুটা সামলানো হয় শেখা projection, scaling আর LayerNorm দিয়ে। এসব পর্ব ৩৩ আর পর্ব ৩৫ (Transformer block)-এর বিষয়। ছোট, পুরোনো ধাঁচের RNN model-এ additive এখনো ভালো বিকল্প হতে পারে; সিদ্ধান্ত নেবেন নিজের কাজে মেপে।
মূল উপলব্ধি: স্থাপত্যের বাছাই অনেক সময় শুধু accuracy নয়, হিসাবের দাম আর হার্ডওয়্যারেরও রায়; dot-product-এর দুর্বলতা scaling দিয়ে সামলে তার গতি কাজে লাগানো হয়েছে।
প্র ০৪“সব ঘর মেশানো” কেন, একটি ঘর বেছে নিলেই তো হয়? আমাদের পরীক্ষায় চূড়ায় সব weight দিলেও একটি token বদলায়নি।
প্রশ্নটি পুরোনো। Xu ও সহকর্মীদের “Show, Attend and Tell” (২০১৫) ছবির caption-এ দুটি পথই দেখায়। Soft attention সব ঘর মেশায়, মসৃণ, সরাসরি backpropagation চলে। Hard attention একটি ঘর বেছে নেয়; বাছাইটি differentiable নয়, তাই শেখাতে sampling-ভিত্তিক gradient অনুমান লাগে (REINFORCE ধরনের), যার variance বেশি, তাই training ধীর আর অস্থির হতে পারে।
আমাদের পরীক্ষাটি লক্ষ্য করুন: শেখা শেষে চূড়ায় সব weight দিলে token একই থাকে। এটি বলে না যে শুরু থেকে hard বাছাই দিয়ে শেখা যেত। Training-এর শুরুতে model জানে না কোন ঘর ঠিক; নরম weight থেকে gradient সব ঘরেই যায়, তাই ভুল ঘরের থেকে ঠিক ঘরের দিকে ধীরে সরা সম্ভব। মসৃণতার দাম শেখার সময় লাগে, ব্যবহারের সময় না-ও লাগতে পারে। আমাদের additive-এর গড় চূড়া ০.৭৮৪, আর dot-এর প্রায় ০.৯৯৯: শেখা শেষে বাছাই অনেকটাই কড়া হয়ে যায়, আর সংখ্যাটি score-এর ধরনের ওপরও নির্ভর করে।
আবার বাস্তবের কাজে সত্যিই একটি জায়গা বাছা হয়। Pointer network-এ (Vinyals ও সহকর্মীরা, ২০১৫) weight-কেই output ধরা হয়: input-এর কোন ঘরটি। Vector search-এ (AI-এর ভিত্তি কোর্সের পর্ব ৬৯) top-k খোঁজা একটি শক্ত বাছাই, কারণ key-এর সংখ্যা বিশাল, সব মেশানো সম্ভব নয়; এই ধরনের বাছাইয়ের ভেতর দিয়ে সাধারণত gradient যায় না, তাই embedding আলাদা করে শেখানো হয়। তাই “soft বনাম hard” কোনো চূড়ান্ত বিজয়ের প্রশ্ন নয়, কাজের আকারের প্রশ্ন।
আরও একটি ব্যবহারিক কথা: ব্যাখ্যা বা গতির জন্য শেখা-শেষের weight ছেঁটে ফেলা (sparse attention) সম্ভব, তবে সেটি ভিন্ন model হয়ে যায়, আবার মাপা দরকার। আমাদের এক টুকরো পরীক্ষা ২১–২৫ অঙ্কের একটি কাজের, সাধারণ নিয়ম নয়।
মূল উপলব্ধি: Soft attention-এর মূল্য শেখার সময়ের gradient-এ; শেষ model-এ বাছাই কড়া হলেও শুরু থেকে কড়া বাছাই দিয়ে শেখা অনেক কঠিন।
প্র ০৫Mask-ছাড়া model pad-কে থামার সংকেত বানিয়ে ফেলল। এই ঘটনাটি অন্য কোথায় দেখা যায়, আর কীভাবে আটকাবেন?
এটি একটি shortcut: কাজের সঙ্গে সম্পর্কিত কিন্তু উদ্দেশ্যহীন একটি সংকেত, যা training-এর পরিবেশে নির্ভরযোগ্য, বাইরে নয়। Geirhos ও সহকর্মীরা ২০২০ সালের “Shortcut Learning in Deep Neural Networks” প্রবন্ধে এমন অনেক উদাহরণ একসঙ্গে আলোচনা করেন, যেমন ছবির পেছনের দৃশ্য দেখে বস্তু চেনা। মেশিন লার্নিং কোর্সেও data leakage-এর একই গন্ধ পেয়েছেন: সংকেতটি ডেটা তৈরির প্রক্রিয়া থেকে আসে, বিষয় থেকে নয়।
আমাদের ক্ষেত্রে সংকেতটি বিশেষভাবে বিপজ্জনক, কারণ সে নির্ভর করে batch-এর সঙ্গীর ওপর। Training-এ দৈর্ঘ্য ১ থেকে ৪০ মিশে থাকে, তাই প্রায় সব ছোট উদাহরণের পরে pad থাকে। Test-এর ভাগে একই দৈর্ঘ্যের কাছাকাছি উদাহরণ একসঙ্গে, তাই pad কম, আর ১–৫ অঙ্কে exact ০.৮৯০; ১–১০ অঙ্কের input একটি ৪০ অঙ্কের পাশে ০.৯৯৮, একা ০.৬৯৩. Live ব্যবস্থায় একা অনুরোধে, ভিন্ন batch-এর আকারে, বা দৈর্ঘ্য অনুযায়ী সাজানো batch-এ ফল বদলে যেতে পারত, আর সেই ভুল ধরা খুব কঠিন, কারণ একই input অন্য batch-এ অন্য ফল দেয়।
আটকানোর পথ কয়েকটি। প্রথম, একটি নিয়ম হিসেবে invariance test: একই input একা, ছোট batch-এ আর লম্বা সঙ্গীর পাশে চালিয়ে output আর weight মেলানো। এটি একটি সরল unit test, CI-তে থাকতে পারে (সফটওয়্যার ইঞ্জিনিয়ারিং-এর সাধারণ অভ্যাস)। দ্বিতীয়, train আর test-এর batch-এর গঠন ব্যবহারের পরিস্থিতির সঙ্গে মেলানো। তৃতীয়, padding, packing আর mask কোথায় কী করছে তা স্পষ্ট লিখে রাখা, যেমন পর্ব ৩০-এর সতর্কবার্তায়। চতুর্থ, heatmap-কে তদন্তের হাতিয়ার হিসেবে ব্যবহার: এখানে ব্যাখ্যা হিসেবে নয়, সন্দেহ জাগানোর উপায় হিসেবে সে সত্যিই কাজ দেয়; pad ঘরের ০.৮৬৬ weight-ই সূত্রটি ধরিয়ে দেয়।
একই ধরনের ফাঁদ Transformer-এর অন্য জায়গায়ও আছে, যেমন generation-এর সময় batch-এ left padding, যা পর্ব ৩৭-এ আসবে।
মূল উপলব্ধি: Model সুযোগ পেলে ডেটা-ভরাটের নকশাও কাজে লাগায়; “একা আর batch-এ একই ফল” পরীক্ষাটি কয়েক লাইনের, কিন্তু অনেক গভীর ভুল ধরে।
অনুশীলন
প্রথমে খাতায় করুন, তারপর উত্তর মেলান।
-
পূর্ণ numerical: q = [1, 1]; k1 = [1, 0], k2 = [0, 2], k3 = [1, −1]; value ৪, ৮, ১২. (ক) Dot-product attention-এর score, weight আর context বের করুন। (খ) Token ৩ pad হলে −inf mask দিয়ে weight আর context কত? (গ) Additive attention-এ q, k-এর মাপ ১২৮ আর ভেতরের মাপ ১২৮ হলে তার নিজের parameter কত (bias নেই)?
(ক) Score: ১, ২, ০. escore: ২.৭১৮৩, ৭.৩৮৯১, ১; যোগফল ১১.১০৭৩. Weight: ০.২৪৪৭, ০.৬৬৫২, ০.০৯০০. Context = ০.২৪৪৭×৪ + ০.৬৬৫২×৮ + ০.০৯০০×১২ ≈ ৭.৩৮১. (খ) e-গুলো ২.৭১৮৩, ৭.৩৮৯১, ০; যোগফল ১০.১০৭৩. Weight ০.২৬৮৯, ০.৭৩১১, ০; context ≈ ৬.৯২৪. (গ) Wq ১২৮ × ১২৮, Wk ১২৮ × ১২৮, v ১২৮: ১৬,৩৮৪ + ১৬,৩৮৪ + ১২৮ = ৩২,৮৯৬, যা PyTorch-এর গোনার পার্থক্যের সঙ্গে মেলে (২,১২,২০৭ − ১,৭৯,৩১১).
-
সমালোচনা ও নকশা: একজন AI assistant দিয়েছে:
a = F.softmax(e * mask.float(), dim=0), যেখানে e-এর আকার (B, T). বলছে, “শেষে heatmap দেখলেই বোঝা যাবে model কোন শব্দ দেখে সিদ্ধান্ত নিচ্ছে।” কী কী ভুল?এক, score × mask: pad-এর score ০ হয়, কিন্তু e0 = ১, তাই pad weight পায়। সমাধান
e.masked_fill(~mask, float("-inf")). দুই,dim=0মানে batch জুড়ে softmax, যা উদাহরণগুলোকে একে অপরের সঙ্গে মিশিয়ে ফেলে। হওয়া উচিতdim=-1(T). একটি সারির যোগফল ছাপালেই ধরা পড়ে। তিন, ব্যাখ্যার দাবিটি প্রমাণ ছাড়া: weight সরিয়ে বা শব্দ বদলে output-এর পরিবর্তন মাপতে হয়। যাচাই: B ≠ T দিয়ে একটি ছোট batch, হাতে হিসাব করা ৩ token-এর উত্তর, আর “একা বনাম batch” পরীক্ষা। -
আউটপুট অনুমান করুন: কী ছাপা হবে?
s = np.array([0., np.log(3), -np.inf]);w = np.exp(s) / np.exp(s).sum();print(w.round(2), round(float(w @ np.array([4., 8., 100.])), 2))[0.25 0.75 0. ] 7.0. e0 = ১, eln 3 = ৩, e−∞ = ০; যোগফল ৪, তাই weight ০.২৫, ০.৭৫, ০. Context = ১ + ৬ + ০ = ৭. তৃতীয় value ১০০ যত বড়ই হোক, mask-এর জন্য কোনো প্রভাব নেই। -
নকশা: পর্ব ৩০-এর বাজারঘরের তারিখের model-এ attention যোগ করতে চান। Input “১২ মার্চ ২০২৫”, output “2025-03-12”. Heatmap কেমন হওয়ার কথা? সার্থকতা কীভাবে যাচাই করবেন, আর এখানে attention কি আদৌ দরকার?
Heatmap: কর্ণ হবে না; সাল আগে লেখা, তাই প্রথম সারিগুলোর চূড়া input-এর শেষ দিকে, “03” লেখার সময় “মার্চ”-এর অক্ষরে, “12”-এর সময় শুরুতে। এটি অনুমান, যাচাই দরকার। দরকার কি? পর্ব ৩০-এ h = ১২৮-এর সাধারণ model-ই এই ছোট input-এ ০.৭২০ থেকে ১.০০০ পেয়েছিল (ratio অনুযায়ী), তাই accuracy-র জন্য হয়তো নয়; ছোট h-এ লাভ হতে পারে, আর regular expression এখনো একটি বাস্তব baseline. যাচাই: attention সহ আর ছাড়া একই ডেটায়, ৩টি seed, কম hidden size-এও; exact-match আর CER, দৈর্ঘ্য অনুযায়ী। Mask-এর একা-বনাম-batch পরীক্ষা, কারণ তিন ধরনের লেখার দৈর্ঘ্য আলাদা। Heatmap-এর পরীক্ষা: মাসের অক্ষরের ওপর weight সরিয়ে output-এর মাস বদলায় কি না দেখা। সীমা: train-এর বাইরের ধরন (“১২ই মার্চ”) আলাদা করে পরীক্ষা করা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমSeq2seq-এর decoder-এর একটি ধাপে attention-এর হিসাব সঠিক ক্রমে সাজান।
- Decoder-এর আগের state-কে query হিসেবে নেওয়া
- Query আর প্রতিটি key থেকে alignment score
- Pad ঘরের score −inf করা
- Score-এর softmax থেকে weight
- Weight দিয়ে value-র যোগফল, মানে context
- Context আর আগের token দিয়ে decoder-এর নতুন state আর output
Dot-product attention-এ q = [1, 2], key k1 = [1, 0], k2 = [0, 1]; value 0 আর 10. Context প্রায় কত?
- প্রায় ৫.০
- প্রায় ৭.৩১
- প্রায় ৬.৬৭
- ১০.০
Score [2, 1, 0, 0]-এর শেষ দুই ঘর pad. Softmax-এর আগে score-কে mask [1, 1, 0, 0] দিয়ে গুণ করলে pad দুটির মোট weight কত?
- ঠিক ০
- প্রায় ০.৫০০
- প্রায় ০.১৬৫
- প্রায় ০.৭৩১
একটি GRU seq2seq-এ hidden size ৬৪, একটি batch-এর ৩২টি উদাহরণের সবচেয়ে লম্বা input ৫০. Dot-product attention-এ এক decoder ধাপের weight আর context-এর আকার কী?
- Weight (৩২, ৫০), context (৩২, ৬৪)
- Weight (৩২, ৫০, ৬৪), context (৩২, ৬৪)
- Weight (৩২, ৫০), context (৩২, ৫০, ৬৪)
- Weight (৩২, ৬৪), context (৩২, ৫০)
Additive attention-এ query ও key-এর মাপ ১০০, ভেতরের মাপ ৫০, bias নেই। Score-এর অংশের parameter কত?
- ০
- ৫,০০০
- ১০,০০০
- ১০,০৫০
একটি seq2seq model-কে একা এক উদাহরণ দিলে এক output, আর একটি অনেক লম্বা উদাহরণের সঙ্গে batch-এ দিলে অন্য output আসে। সবচেয়ে সম্ভাব্য প্রথম সন্দেহ কোনটি?
- Learning rate খুব বেশি ছিল
- Attention বা encoder-এ pad ঠিকমতো mask হচ্ছে না
- Teacher forcing ratio ভুল
- Vocabulary খুব ছোট
একটি মডেলে একটি output ধাপে input-এর একটি ঘরে weight ০.৯৯. সবচেয়ে সঠিক সিদ্ধান্ত কোনটি?
- Output-টির কারণ নিশ্চিতভাবে ওই ঘরের token
- Weight-এর কোনো অর্থই নেই, তাই দেখার দরকার নেই
- এটি একটি সূত্র; weight বা input বদলে output-এর পরিবর্তন মেপে যাচাই করতে হয়
- Model-এর accuracy ৯৯%
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে।
মনে রাখুন
- হিসাব: score, softmax, weighted sum. Key ঠিক করে weight, মেশানো হয় value. Seq2seq-এ query হলো decoder-এর আগের state, key আর value encoder-এর সব state.
- দুই score: dot-এর নিজের parameter নেই, দ্রুত, দৈর্ঘ্যে সংবেদনশীল; additive শেখে, tanh-এ বাঁধা, বেশি খরচের।
- Mask: softmax-এর আগে −inf. Score ০ করলে e0 = ১, কাজ হয় না। সব ঘর mask হলে NaN. “একা বনাম batch” পরীক্ষাটি রাখুন।
- মাপা ফল: একই ডেটায় ৩৬–৪০ অঙ্কে ০.০০০ থেকে ০.৯২২ (dot) আর ০.৯৯৭ (additive), দুই seed. দাম: T·T′ হিসাব। Train-এর দৈর্ঘ্যের বাইরে আবার ভাঙে।
- Heatmap: সারি যোগ ১, কর্ণ মানে একই ক্রম; তবে ছবিটি সূত্র, প্রমাণ নয়। ব্যাখ্যা দাবি করলে weight সরিয়ে মাপুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৩ · Self-attention আর multi-head attention — matrix দিয়ে হাতে-কলমেপরবর্তী পর্বQ, K, V, √d_k, দুই রকম mask আর multi-head-এর shape হাতে-কলমে শেখা।
- ডিপ লার্নিং, NLP আর Generative AI · কোর্স হোম৯১টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ।
- পর্ব ৩০ · Seq2seq আর encoder–decoderআগের পর্বযে bottleneck এখানে মাপা হলো, তার উৎস।
- পর্ব ১১ · Dataset আর DataLoaderআগের পর্বPadding, collate_fn আর attention mask.
- AI-এর ভিত্তি · পর্ব ৭০ · LLM কীভাবে কাজ করেআগের ভিত্তিQuery, key, value-র প্রথম পরিচয়।
- AI-এর ভিত্তি · পর্ব ৩৩ · Vector আর similarityআগের ভিত্তিDot product আর cosine-এর জ্যামিতি।
- মেশিন লার্নিং · পর্ব ৫৭ · SHAP আর LIMEপাশের ট্র্যাকএকটি অনুমানের ব্যাখ্যার অন্য পথ।