এই পর্বে যা শিখবেন
- Training ডিবাগের একটি ক্রম, আর প্রতিটি ধাপ কোন ধরনের ভুল ধরে
- শুরুর loss −ln(1/C) মিলিয়ে দেখা আর এক batch overfit করানো
- Learning rate, gradient norm, dead ReLU আর NaN-এর উৎস খোঁজা
- Loss-activation-এর ভুল জোড়, broadcasting আর duplicate leakage-এর মতো চুপচাপ ভুল চেনা
- Learning curve-এর আকার দেখে underfitting, overfitting, plateau আর অস্থিরতা চেনা
আগের পর্বগুলোতে training loop লিখেছেন (পর্ব ১০), DataLoader বানিয়েছেন (পর্ব ১১), আর এই মডিউলে loss, initialization, optimizer, regularization আর gradient-এর সমস্যা দেখছেন (পর্ব ১৩ থেকে ১৭)। কিন্তু বাস্তবে প্রথম চেষ্টায় model প্রায় কখনো ঠিকমতো শেখে না। এই পর্বে শিখব কীভাবে খুঁজবেন ভুলটা কোথায়। একই বাজারঘর customer segment task-এ (৫টি feature, ৩টি segment, ৪০০টি train আর ২০০টি validation উদাহরণ) ছয়টি পরিকল্পিত বাগ বসিয়ে প্রতিটির সত্যিকারের লক্ষণ মাপা হয়েছে (ডেস্কটপ, CPU, লেখার সময়ে torch 2.15.0, Python 3.14.4). পাতায় চালানো যায় এমন চারটি NumPy ব্লক একই ধারণা দেখায় (প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে)।
১ · কেন training-এর বাগ চুপচাপ থাকে — একটি ক্রম
ডাক্তারের রোগনির্ণয়ের কথা ভাবুন। রোগী জ্বর নিয়ে এলে ভালো ডাক্তার প্রথমেই MRI করান না। আগে তাপমাত্রা, নাড়ি আর রক্তচাপ দেখেন: সস্তা, দ্রুত, আর উত্তর জানা থাকে স্বাভাবিক মান কত। সেগুলো ঠিক থাকলে তবেই বড় পরীক্ষা। Training ডিবাগও তাই: প্রথমে এমন ছোট পরীক্ষা, যার সঠিক উত্তর আগে থেকেই জানেন।
সাধারণ প্রোগ্রামে ভুল হলে প্রায়ই error আসে। Neural network-এ বেশিরভাগ ভুল silent bug: shape মিলে যায়, loss নামে, accuracy-ও কিছুটা আসে। Label এলোমেলো, ইনপুট normalise করা নেই, loss-এর ভুল জোড়, এর কোনোটাই error দেয় না। তার ওপর ফল খারাপ হওয়ার "স্বাভাবিক" কারণও অনেক (কম ডেটা, ভুল hyperparameter), তাই বাগকে সহজেই "model-টা হয়তো দুর্বল" বলে ভুল বোঝা যায়।
সমাধান হলো একটি ক্রম, যেখানে প্রতিটি ধাপ একটি sanity check: সস্তা পরীক্ষা, যার সঠিক উত্তর জানা। Andrej Karpathy তাঁর ২০১৯ সালের ব্লগ লেখা “A Recipe for Training Neural Networks”-এ এই ধরনের ক্রম জনপ্রিয় করেন; নিচের ছবি সেই ধারার একটি সরল রূপ।
Training ডিবাগের সিঁড়ি
Cheap checks with known answers first; scale up only after each one passes
এই ক্রম কেন কাজ করে, আর আমাদের ছয়টি বাগের কোনটি কোন ধাপে ধরা পড়ল, তা নিচের stepper-এ। সংখ্যাগুলো পরের অংশগুলোর সত্যিকারের run থেকে।
| ধাপ | |
| কী করবেন | |
| যা ধরা পড়ল |
- ডেটা আর label. সবচেয়ে সস্তা পরীক্ষা, আর সবচেয়ে বেশি বাগ এখানেই। শ্রেণি অনুযায়ী feature-এর গড় সব এক হলে label আর ইনপুটের সম্পর্ক ভেঙে গেছে; কোনো কলামের sd ৬৪৭ আর অন্যটির ০.৩২ হলে normalise বাকি।
- শুরুর loss. এলোমেলো weight-এর classifier কিছু জানে না, তাই প্রতিটি শ্রেণিকে প্রায় ১/C দেয়, loss প্রায় ln C. আমাদের ১৮.৬৭ এসেছে কারণ কাঁচা "basket_tk" কলাম শত শত টাকার ঘরে।
- এক batch overfit. Model, loss আর gradient-এর কোড ঠিক থাকলে ৩২টি উদাহরণ মুখস্থ করা সহজ, loss প্রায় ০. Softmax-এর পরে NLLLoss দিলে loss −১-এর দিকে নামে: cross-entropy কখনো ঋণাত্মক হয় না, তাই এখানেই ধরা।
- ছোট পূর্ণ run. এখন পুরো ডেটায় কয়েক epoch, আর প্রতি ধাপে gradient norm আর NaN দেখা। খুব বড় learning rate হয় ReLU মেরে ফেলে (loss ln ৩-এ আটকে), নয়তো NaN দেয়।
- Learning curve আর leakage. Train আর val-এর ফাঁক পড়া, আর "অবিশ্বাস্য ভালো" ফলকে সন্দেহ করা: duplicate সারি val-কে ০.৮৯৩ দেখাল, অথচ নতুন ক্রেতায় ০.৭০৮.
- বড় করা. এতক্ষণে model বড় করা, regularization (পর্ব ১৬) আর schedule (পর্ব ১৫) নিয়ে খেলা। একবারে একটি জিনিস বদলান, যাতে কোন বদলে কী হলো বোঝা যায়।
২ · প্রথমে ডেটা আর label — একটি batch নিজের চোখে
Karpathy-র রেসিপির প্রথম কথা ছিল: model-এ হাত দেওয়ার আগে ডেটার সঙ্গে সময় কাটান। Tabular ডেটায় এর মানে তিনটি ছোট হিসাব: প্রতিটি শ্রেণিতে কতটি উদাহরণ, প্রতিটি feature-এর গড় আর sd, আর শ্রেণি অনুযায়ী কয়েকটি feature-এর গড়। ছবির ডেটায় একটি batch ছবিসহ label আঁকা, লেখার ডেটায় কয়েকটি উদাহরণ পড়া। এগুলো পরীক্ষা করা উচিত DataLoader থেকে বেরোনোর পরে, ঠিক যে tensor model-এ ঢুকছে সেটাতে, কারণ ভুল প্রায়ই pipeline-এর মাঝে ঢোকে।
প্রথম বাগ: কোনো pipeline-এ X আর y আলাদাভাবে এলোমেলো করা হয়েছে (যেমন দুটি আলাদা sort বা দুটি আলাদা seed-এর shuffle), তাই প্রতিটি সারির label অন্য কারও। সত্যিকারের run:
~/bazarghar-dl$ uv run python data_check.py
label count: [124, 121, 155]
ঠিক orders_pm/app_share per class: 2.31/0.31 | 5.87/0.68 | 4.10/0.56
এলোমেলো orders_pm/app_share per class: 4.33/0.55 | 4.08/0.52 | 3.88/0.50
feature mean: [4.08, 976.05, 18.94, 0.35, 0.52]
feature sd: [2.76, 647.08, 29.87, 0.32, 0.35]
ঠিক ডেটায় তিনটি segment স্পষ্ট আলাদা: "occasional" ক্রেতা মাসে গড়ে ২.৩১টি অর্ডার দেন, "regular" ৫.৮৭টি। এলোমেলো label-এ তিন শ্রেণির গড় প্রায় এক (৪.৩৩, ৪.০৮, ৩.৮৮), কারণ প্রতিটি শ্রেণিতে এখন সব ধরনের ক্রেতা মিশে আছে। Label-এর গোনা দুই ক্ষেত্রেই হুবহু এক, তাই শুধু গোনা দেখলে বাগটি ধরা পড়ত না। শেষ দুই লাইন আরেকটি সমস্যা দেখায়: basket_tk-এর sd ৬৪৭, discount_share-এর ০.৩২, অর্থাৎ feature-গুলো তুলনীয় মাপে নেই। এর ফল পরের অংশে।
এলোমেলো label নিয়ে ২০ epoch train করলে কী হয়? (পুরো script অংশ ৩-এ।) Train loss ১.১ থেকে ধীরে ০.৯৭০২-এ নামে, কিন্তু val accuracy ০.৩০০, তিন শ্রেণিতে অনুমানের চেয়েও খারাপ, আর val loss ১.৩১২. Train loss তবু নামছে কেন? কারণ বড় network এলোমেলো label-ও ধীরে ধীরে মুখস্থ করতে পারে: memorization. এই তথ্য অংশ ৪-এ খুব জরুরি হবে।
ডেটা পরীক্ষা model-এ ঢোকার ঠিক আগের tensor-এ করুন, আর শুধু গোনা নয়, শ্রেণি অনুযায়ী feature-এর গড়ও দেখুন। ভুল label-এর বিস্তারিত (label noise, ভুল encoding) AI-এর ভিত্তি কোর্সের পর্ব ৫৬-এ আর এই কোর্সের পর্ব ১১-এ।
৩ · শুরুর loss যাচাই — −ln(1/C)
Training শুরুর আগে, এলোমেলো weight-এ, একবার loss মাপুন। এটিই initial loss. যুক্তি সরল: ঠিকঠাক শুরু করা classifier-এর logits ছোট আর প্রায় সমান, তাই softmax প্রতিটি শ্রেণিকে প্রায় ১/C probability দেয়। Cross-entropy হলো সঠিক শ্রেণির probability-র ঋণাত্মক log (AI-এর ভিত্তি কোর্সের পর্ব ৩৮), তাই:
Lশুরু ≈ −ln(1/C) = ln C
এখানে C শ্রেণির সংখ্যা। ৩ শ্রেণিতে ln ৩ = ১.০৯৮৬, ১০ শ্রেণিতে ২.৩০২৬, ১০০ শ্রেণিতে ৪.৬০৫২. দুই শ্রেণির BCE-তে ln ২ = ০.৬৯৩১. শ্রেণিগুলো অসমান হলে ভালো শুরু করা model-এর loss এর চেয়ে একটু বেশি বা কম হতে পারে, কিন্তু অনেক গুণ বেশি নয়। শুরুর loss অনেক বেশি হলে logits বড়: weight-এর শুরু খুব বড় (পর্ব ১৪ Weight initialization আর normalization), বা ইনপুট normalise করা নেই। নিচের ব্লকে একটি NumPy MLP তিন রকম weight-এর মাপ আর কাঁচা ইনপুটে শুরু করা হলো:
import numpy as np
rng = np.random.default_rng(42)
X = rng.normal(0, 1, (256, 5)) # standardize করা ৫টি feature
def initial_loss(C, scale, x_scale=1.0):
W1 = rng.normal(0, scale / np.sqrt(5), (5, 64)) # scale: init কত বড়
W2 = rng.normal(0, scale / np.sqrt(64), (64, C))
y = rng.integers(0, C, len(X)) # শ্রেণি সমানভাবে ছড়ানো
Z = np.maximum((X * x_scale) @ W1, 0) @ W2 # logits
Z = Z - Z.max(1, keepdims=True)
logp = Z - np.log(np.exp(Z).sum(1, keepdims=True))
return -logp[np.arange(len(y)), y].mean()
print(" C | -ln(1/C) | init 0.01 | init 1 | init 10 | x×1000")
for C in (3, 10, 100):
row = [initial_loss(C, s) for s in (0.01, 1.0, 10.0)] + [initial_loss(C, 1.0, 1000)]
print(f"{C:3d} | {np.log(C):8.4f} | " + " | ".join(f"{v:7.3f}" for v in row))আউটপুট (পাতার Python-এ চালানো):
C | -ln(1/C) | init 0.01 | init 1 | init 10 | x×1000
3 | 1.0986 | 1.099 | 1.213 | 47.001 | 534.885
10 | 2.3026 | 2.303 | 2.555 | 103.172 | 1048.795
100 | 4.6052 | 4.605 | 4.759 | 135.022 | 1548.914
খুব ছোট init (০.০১)-এ logits প্রায় শূন্য, তাই loss হুবহু ln C. সাধারণ init (১)-এ একটু বেশি, ১.২১৩ বনাম ১.০৯৯: স্বাভাবিক। Init দশ গুণ বড় হলে ৩ শ্রেণিতেই ৪৭: model শুরু থেকেই ভুল উত্তরে অতি-আত্মবিশ্বাসী, আর প্রথম কয়েকটি ধাপ খরচ হবে শুধু সেই আত্মবিশ্বাস ভাঙতে। কাঁচা ইনপুট (×১০০০) আরও খারাপ। এক লাইনের এই পরীক্ষা তাই init আর scaling দুটোই যাচাই করে।
এখন দ্বিতীয় বাগ, PyTorch-এ সত্যিকারের run: বাজারঘরের ডেটা normalise না করে একই model (৫ → ৬৪ → ৬৪ → ৩, ReLU), SGD momentum ০.৯, learning rate ০.১, ২০ epoch. ছয়টি বাগের জন্য একটি script, যার মূল অংশ:
def fit(bug=None, epochs=20, lr=0.1):
torch.manual_seed(0)
Xtr, ytr, Xva, yva = load(norm=(bug != "no_norm")) # শুধু train-এর গড় আর sd দিয়ে
if bug == "shuffled": ytr = ytr[torch.randperm(len(ytr))] # X আর y আলাদা ক্রমে
if bug == "lr10": lr = lr * 10
if bug == "lr100": lr = lr * 100
model = make(); opt = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
dl = DataLoader(TensorDataset(Xtr, ytr), batch_size=64, shuffle=True,
generator=torch.Generator().manual_seed(0))
def lossf(out, yb):
if bug == "softmax_nll": return F.nll_loss(torch.softmax(out, 1), yb) # চায় log-prob
return F.cross_entropy(out, yb)
with torch.no_grad(): init = lossf(model(Xtr), ytr).item() # শুরুর loss
# … ২০ epoch training, তারপর val loss, accuracy আর dead ReLU-এর ভাগ~/bazarghar-dl$ uv run python bugs.py
torch 2.15.0+cpu | ln(3) = 1.0986
None init 1.114 | train ep1/2/10/20: 1.022 0.6463 0.492 0.4264
val loss 0.7472 acc 0.755 | dead ReLU L1 0.02 L2 0.08
shuffled init 1.099 | train ep1/2/10/20: 1.1 1.091 1.044 0.9702
val loss 1.312 acc 0.300 | dead ReLU L1 0.00 L2 0.06
no_norm init 18.67 | train ep1/2/10/20: 1.585e+33 1.124 1.094 1.094
val loss 1.119 acc 0.300 | dead ReLU L1 0.00 L2 1.00
softmax_nll init -0.33 | train ep1/2/10/20: -0.3408 -0.4187 -0.799 -0.8112
val loss 1.959 acc 0.770 | dead ReLU L1 0.00 L2 0.06
lr10 init 1.114 | train ep1/2/10/20: 0.8936 1.052 1.072 1.103
val loss 1.193 acc 0.380 | dead ReLU L1 0.12 L2 0.94
lr100 init 1.114 | train ep1/2/10/20: 1.042e+19 nan nan nan
val loss nan acc 0.320 | dead ReLU L1 0.03 L2 0.00
সঠিক run-এর শুরুর loss ১.১১৪, ln ৩-এর খুব কাছে। Normalise ছাড়া শুরু ১৮.৬৭, তারপর প্রথম epoch-এ গড় loss ১.৫৮৫ × ১০৩৩: বিশাল gradient weight ছুড়ে ফেলেছে। পরে loss ১.০৯৪-এ, প্রায় ln ৩-এ, আটকে গেল আর দ্বিতীয় layer-এর সব ReLU মৃত (L2 1.00): network এখন সব ক্রেতার জন্য একই উত্তর দেয়। শুরুর loss দেখলে training-এর আগেই এই বাগ ধরা যেত। বাকি সারিগুলো পরের অংশে একে একে দেখব। Standardization-এর নিয়ম মনে রাখুন: গড় আর sd শুধু train থেকে।
৪ · এক batch overfit — কেন সবার আগে
Overfit one batch মানে একটি ছোট batch (১৬ থেকে ৬৪টি উদাহরণ) বেছে বারবার শুধু তাতেই train করা। যথেষ্ট বড় network-এর পক্ষে কয়েক ডজন উদাহরণ মুখস্থ করা খুব সহজ, তাই কোড ঠিক থাকলে loss প্রায় ০-তে নামবে আর batch accuracy ১.০০ হবে। না নামলে সমস্যা ডেটার পরিমাণ, regularization বা hyperparameter-এ নয়, কোডে: model, loss, gradient বা optimizer-এর সংযোগে। নিচের ব্লকে NumPy-তে হাতে লেখা backward (পর্ব ০৪-এর P − Y) চারটি অবস্থায়:
import numpy as np
rng = np.random.default_rng(42)
X = rng.normal(0, 1, (16, 5)); y = rng.integers(0, 3, 16) # এক batch: ১৬টি উদাহরণ
def overfit(y, bug=None, steps=500, lr=0.5):
r = np.random.default_rng(0)
W1 = r.normal(0, np.sqrt(2 / 5), (5, 32)); W2 = r.normal(0, 0.1, (32, 3))
out = []
for t in range(steps):
H = np.maximum(X @ W1, 0); Z = H @ W2
Z = Z - Z.max(1, keepdims=True); S = np.exp(Z).sum(1, keepdims=True); P = np.exp(Z) / S
loss = (np.log(S[:, 0]) - Z[np.arange(16), y]).mean() # স্থির cross-entropy
G = P.copy(); G[np.arange(16), y] -= 1; G /= 16 # ∂L/∂Z = (P − Y)/N
dH = G @ W2.T
if bug != "no_mask": dH = dH * (H > 0) # ReLU-এর ঢাল
gW1, gW2 = X.T @ dH, H.T @ G
if bug == "sign": gW1, gW2 = -gW1, -gW2 # ভুল দিকে ধাপ
W1 -= lr * gW1; W2 -= lr * gW2
if t in (0, 99, 499): out.append(loss)
acc = (Z.argmax(1) == y).mean()
return " → ".join(f"{v:.4g}" for v in out) + f" | batch acc {acc:.2f}"
print("ঠিক কোড :", overfit(y))
print("এলোমেলো label :", overfit(rng.permutation(y)))
print("ReLU mask নেই :", overfit(y, "no_mask"))
print("gradient-এর চিহ্ন:", overfit(y, "sign"))আউটপুট (পাতার Python-এ চালানো; loss ধাপ ১ → ১০০ → ৫০০):
ঠিক কোড : 1.042 → 0.03095 → 0.003118 | batch acc 1.00
এলোমেলো label : 1.265 → 0.05818 → 0.004591 | batch acc 1.00
ReLU mask নেই : 1.042 → 0.1177 → 0.01712 | batch acc 1.00
gradient-এর চিহ্ন: 1.042 → 1.364e+19 → 1.2e+99 | batch acc 0.25
ঠিক কোডে loss ০.০০৩১-এ নামে। চিহ্ন উল্টানো gradient প্রতি ধাপে loss বাড়ায়, ১০৯৯-এ পৌঁছায়: পরীক্ষাটি এক নজরে ধরে। দুটি ফল সতর্ক করে। এলোমেলো label-এও loss ০.০০৪৬: ১৬টি এলোমেলো জোড়া মুখস্থ করা সহজ, তাই এই পরীক্ষা ডেটার ভুল ধরে না। আর ReLU-এর mask বাদ দেওয়া ভুল gradient-ও ধীরে ০.০১৭-এ নামে: পরীক্ষাটি প্রয়োজনীয়, যথেষ্ট নয়। হাতে লেখা backward-এর জন্য gradient check (পর্ব ০৪) আলাদা দরকার।
একই পরীক্ষা PyTorch-এ, বাজারঘরের প্রথম ৩২টি train উদাহরণে, প্রতিটি বাগসহ ৩০০ ধাপ (সত্যিকারের run):
~/bazarghar-dl$ uv run python one_batch.py
None loss step 1/50/300: 1.118 → 0.001514 → 0.0001714 | batch acc 1.00
shuffled loss step 1/50/300: 1.1 → 0.1552 → 0.001291 | batch acc 1.00
no_norm loss step 1/50/300: 14.8 → 1.104 → 1.075 | batch acc 0.44
softmax_nll loss step 1/50/300: -0.3278 → -0.9385 → -0.9997 | batch acc 1.00
lr100 loss step 1/50/300: 1.118 → nan → nan | batch acc 0.28
| অবস্থা | |
| loss ধাপ ১ → ৫০ → ৩০০ | |
| batch acc | |
| রায় |
- ঠিক কোড. ৫০ ধাপে loss ০.০০১৫, ৩০০ ধাপে ০.০০০১৭. Forward, loss, backward আর step-এর সংযোগ ঠিক। এখন পূর্ণ ডেটায় গেলে যা দেখবেন তা model আর ডেটার প্রশ্ন, কোডের নয়।
- এলোমেলো label. এটিও পাস করে, শুধু একটু ধীরে (৫০ ধাপে ০.১৫৫২)। তাই এই পরীক্ষা ডেটা আর label-এর শুদ্ধতা যাচাই করে না; সেই কাজ ধাপ ১-এর।
- Normalise নেই. শুরু ১৪.৮, তারপর ১.০৭৫-এ আটকে, মানে প্রায় ln ৩: network প্রায় সবাইকে একই উত্তর দিচ্ছে। ৩২টি উদাহরণও মুখস্থ হলো না, তাই এখানেই থামুন।
- Softmax + NLLLoss. Accuracy ১.০০, কিন্তু loss −০.৯৯৯৭.
nll_lossধরে নেয় ইনপুট log-probability, তাই শুধু −(ইনপুট)[y] নেয়; probability দিলে loss = −py, যা −১-এর দিকে যায়। Cross-entropy কখনো ঋণাত্মক নয়, তাই ঋণাত্মক loss নিশ্চিত বাগ। - LR ১০০ গুণ. ৫০ ধাপের আগেই NaN. এক batch-এর পরীক্ষা কয়েক সেকেন্ডে এটাও দেখায়। কোন ধাপে আর কেন, অংশ ৭-এ।
সংক্ষেপে, এক batch overfit সবার আগে কারণ এটি সবচেয়ে সস্তা পরীক্ষা যা "কোড আদৌ শিখতে পারে কি না" প্রশ্নের উত্তর দেয়। এটি পাস না করলে পূর্ণ ডেটায় ঘণ্টার পর ঘণ্টা চালানো, hyperparameter খোঁজা বা model বদলানো সব অর্থহীন। এক batch-এ dropout বা weight decay থাকলে loss হয়তো পুরো ০ পর্যন্ত নামবে না; পরীক্ষার সময় সেগুলো বন্ধ রাখাই ভালো।
৫ · Loss-এর হিসাবে চুপচাপ ভুল — ভুল জোড় আর broadcasting
অংশ ৩-এর run-এ softmax_nll সারিটি আবার দেখুন: val accuracy ০.৭৭০, সঠিক run-এর ০.৭৫৫-এর চেয়েও বেশি! শুধু accuracy দেখলে বাগটি কখনো ধরা পড়ত না। কিন্তু আসল cross-entropy-তে val loss ১.৯৫৯, সঠিক run-এর ০.৭৪৭২-এর আড়াই গুণ: −py কমাতে গিয়ে model ভুল উত্তরগুলোতে অতি-আত্মবিশ্বাসী হয়েছে। Probability দিয়ে সিদ্ধান্ত (কোন ক্রেতাকে কত ছাড় দেওয়া) নিলে বিপদ। PyTorch-এ সঠিক জোড়গুলো:
| শেষ layer-এর output | সঠিক loss | ভুল জোড়ের লক্ষণ |
|---|---|---|
| কাঁচা logits | CrossEntropyLoss | আগে softmax: loss-এর নিচের সীমা ০.৫৫১৪ (৩ শ্রেণি, পর্ব ১০) |
log_softmax | NLLLoss | softmax দিলে ঋণাত্মক loss, −১-এর দিকে |
| একটি logit, দুই শ্রেণি | BCEWithLogitsLoss | আগে sigmoid: বড় logit-এ loss আটকে যায় |
| সংখ্যা (regression) | MSELoss, L1Loss | shape না মিললে broadcasting (নিচে) |
একটি অদ্ভুত ব্যতিক্রম: log_softmax-এর পরে CrossEntropyLoss দিলে কোনো ক্ষতি হয় না, কারণ log-probability-র log-sum-exp ঠিক ০, তাই দ্বিতীয় log-softmax কিছুই বদলায় না। তবু পড়তে বিভ্রান্তিকর, এড়িয়ে চলুন। বিভিন্ন loss-এর বিস্তারিত পর্ব ১৩ (Deep learning-এর loss)-এ।
Broadcasting-এর ফাঁদ: (N, 1) − (N,)
পঞ্চম বাগটি সবচেয়ে লুকানো। Regression model-এর শেষ layer nn.Linear(64, 1) output দেয় আকার (N, 1), আর target প্রায়ই আকার (N,). হাতে লেখা ((out - y) ** 2).mean()-এ broadcasting (এই কোর্সের পর্ব ০৭, অংশ ৫) দুটিকে (N, N) বানিয়ে দেয়: প্রতিটি অনুমান প্রত্যেক ক্রেতার target-এর সঙ্গে তুলনা হয়। কোনো error নেই। গণিতে, i-তম অনুমান pi আর target-এর গড় ȳ হলে:
(1/N²) Σi Σj (pi − yj)² = (1/N) Σi (pi − ȳ)² + Var(y)
ডান দিকের প্রথম অংশ ছোট হয় যখন সব অনুমান গড়ের সমান। অর্থাৎ ভুল loss model-কে শেখায় যে সবাইকে গড় বলাই সবচেয়ে ভালো। নিখুঁত অনুমানে (pi = yi) এই loss হয় ২ × Var(y), গড় বলায় মাত্র Var(y): ভুল loss নিখুঁত model-কে শাস্তি দেয়।
import numpy as np
rng = np.random.default_rng(42)
y = rng.normal(5, 2, 6) # ৬ জনের আসল খরচ, আকার (6,)
pred = y.reshape(-1, 1) # model-এর output: নিখুঁত, কিন্তু আকার (6, 1)
diff = pred - y # (6,1) − (6,) → broadcasting → (6, 6)
print("diff-এর আকার:", diff.shape)
print("ভুল loss, নিখুঁত অনুমানে :", round((diff ** 2).mean(), 4))
const = np.full((6, 1), y.mean()) # সবার জন্য একই সংখ্যা: গড়
print("ভুল loss, শুধু গড় বললে :", round(((const - y) ** 2).mean(), 4))
print("y-এর variance, 2×variance:", round(y.var(), 4), round(2 * y.var(), 4))
print("ঠিক loss, নিখুঁত অনুমানে :", round(((pred[:, 0] - y) ** 2).mean(), 4))
assert pred.squeeze(1).shape == y.shape # এই এক লাইন বাগটি আগেই ধরতআউটপুট (পাতার Python-এ চালানো):
diff-এর আকার: (6, 6)
ভুল loss, নিখুঁত অনুমানে : 9.6597
ভুল loss, শুধু গড় বললে : 4.8298
y-এর variance, 2×variance: 4.8298 9.6597
ঠিক loss, নিখুঁত অনুমানে : 0.0
সূত্রের সঙ্গে হুবহু মিল: নিখুঁত অনুমানে ভুল loss ৯.৬৫৯৭ = ২ × ৪.৮২৯৮, গড় বললে ৪.৮২৯৮ = Var(y). তাই gradient descent ভুল loss কমাতে গিয়ে model-কে গড়ের দিকে ঠেলে দেয়। শেষ লাইনের assert অভ্যাসটি সস্তা আর কার্যকর: loss-এর আগে দুই দিকের shape মিলিয়ে নিন।
বাজারঘরে সত্যিকারের run: একই ৪০০ ক্রেতার পরের মাসের খরচ (হাজার টাকা, অর্ডার × basket থেকে বানানো target, noise-সহ), ৫ → ৬৪ → ১-এর MLP, Adam, ৩০০ ধাপ:
~/bazarghar-dl$ uv run python broadcast.py
target sd 4.763 | var 22.6312
bug=False | diff (400,) | train loss 0.2547 | val R2 0.981 | pred sd 4.697
bug=True | diff (400, 400) | train loss 22.6325 | val R2 -0.003 | pred sd 0.050
nn.MSELoss: Using a target size (torch.Size([4])) that is different to the input size (torch.Size([4, 1])). …
বাগসহ train loss ২২.৬৩২৫, target-এর variance ২২.৬৩১২-এর প্রায় সমান, আর অনুমানের sd মাত্র ০.০৫০: model সবাইকে প্রায় একই সংখ্যা বলছে, val R² −০.০০৩. ঠিক কোডে R² ০.৯৮১. লক্ষ করুন, PyTorch-এর নিজের nn.MSELoss এই অমিলে UserWarning দেয়; হাতে লেখা loss কিছুই বলে না। তাই warning কখনো উপেক্ষা করবেন না, আর হাতে loss লিখলে shape নিজে যাচাই করুন। Loss-এর মান target-এর variance-এর সমান হয়ে আটকে থাকা এই বাগের চেনা লক্ষণ।
৬ · Learning rate, gradient norm আর dead ReLU
Learning rate সবচেয়ে সংবেদনশীল hyperparameter (পর্ব ১৫ Optimizer আর learning-rate schedule). খুব ছোট হলে loss ধীরে নামে, দেখতে underfitting-এর মতো। খুব বড় হলে দুই রকম ব্যর্থতা হয়, আর অংশ ৩-এর run দুটোই দেখিয়েছে:
LR ১০ গুণ (১.০): চুপচাপ মৃত্যু
প্রথম epoch-এ loss ০.৮৯৩৬, তারপর বেড়ে ১.১০৩-এ, প্রায় ln ৩. কোনো NaN নেই, কোনো error নেই। কারণ: দ্বিতীয় layer-এর ৯৪% ReLU unit কোনো train উদাহরণেই আর সক্রিয় হয় না। বড় ধাপ তাদের bias এত ঋণাত্মক করেছে যে ঢাল সব জায়গায় ০, তাই gradient তাদের আর ফেরাতে পারে না।
LR ১০০ গুণ (১০): বিস্ফোরণ
প্রথম epoch-এর গড় loss ১.০৪২ × ১০১৯, তারপর সব NaN. Weight প্রতি ধাপে বহুগুণ বাড়ে, এটি এক রকম exploding gradient, শেষে float32-এর সীমা পেরিয়ে inf, আর inf − inf = NaN. অংশ ৭-এ ধাপে ধাপে।
দুটিই ধরা যায় দুটি সংখ্যা নিয়মিত লগ করে। প্রথমটি gradient norm: সব parameter-এর gradient এক লম্বা vector ধরে তার দৈর্ঘ্য। torch.nn.utils.clip_grad_norm_(model.parameters(), float("inf")) কিছু কাটে না, শুধু মোট norm ফেরত দেয়, তাই মাপার সহজ উপায়। সুস্থ run-এ এটি মোটামুটি স্থির পরিসরে থাকে; ধাপে ধাপে দশ গুণ বাড়লে বিস্ফোরণ, প্রায় ০ হলে শেখা থেমে গেছে। Clipping দিয়ে বিস্ফোরণ সামলানো আর gradient-এর মাপ layer ধরে দেখা পর্ব ১৭ (Vanishing আর exploding gradient)-এর বিষয়।
দ্বিতীয়টি dead ReLU-এর ভাগ: প্রতিটি hidden unit-এর output সব train উদাহরণে দেখে, যার সর্বোচ্চ মানও ০, সে মৃত। আমাদের script-এ মাপার কোড দুই লাইন:
def dead_frac(model, X): # সব train input-এ ০ দেয় এমন ReLU unit
with torch.no_grad():
h1 = model[1](model[0](X)); h2 = model[3](model[2](h1))
return (h1.amax(0) == 0).float().mean().item(), (h2.amax(0) == 0).float().mean().item()সঠিক run-এ ভাগ ২% আর ৮%: কিছু মৃত unit স্বাভাবিক। LR ১০ গুণে ১২% আর ৯৪%, normalise ছাড়া দ্বিতীয় layer-এ ১০০%. বড় model-এ একই হিসাব forward hook দিয়ে করা যায় (পর্ব ০৮-এ hook). প্রতিকার সাধারণত ছোট learning rate, ঠিক normalise আর init; Leaky ReLU বা GELU-এর মতো activation-এ ঢাল কখনো পুরো ০ হয় না (পর্ব ০৩)। Learning rate কত হওয়া উচিত তা খুঁজতে LR range test আর schedule পর্ব ১৫-এ; optimizer-গুলোর গণিত মেশিন লার্নিং কোর্সের পর্ব ১৪-এ।
৭ · NaN শিকার — isfinite আর set_detect_anomaly
NaN একবার ঢুকলে প্রতিটি হিসাবে ছড়ায়: NaN-এর সঙ্গে যেকোনো যোগ বা গুণ NaN. তাই শেষে NaN দেখে লাভ নেই; খুঁজতে হবে প্রথম কোথায় এল। দুটি হাতিয়ার:
torch.isfiniteপরীক্ষা: প্রতি ধাপে loss আর gradient norm সসীম কি না দেখা, না হলে সেই ধাপেই থামা আর সব লগ রাখা। খরচ প্রায় নেই, তাই production training-এও রাখা যায়।- Anomaly detection:
torch.autograd.set_detect_anomaly(True)চালু করলে backward-এ কোনো function NaN ফেরত দিলেই PyTorch error তোলে, আর সেই function যে forward লাইনে তৈরি হয়েছিল তার traceback দেখায়। এটি প্রতিটি ধাপ ধীর করে, তাই শুধু ডিবাগের সময়।
LR ১০০ গুণ (SGD lr ১০, momentum ০.৯) দিয়ে প্রতি ধাপে loss, gradient norm আর সবচেয়ে বড় weight লগ করা হলো:
torch.autograd.set_detect_anomaly("--anomaly" in sys.argv)
for xb, yb in dl:
loss = F.cross_entropy(model(xb), yb)
opt.zero_grad(); loss.backward()
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), float("inf")) # শুধু মাপা
wmax = max(p.abs().max().item() for p in model.parameters())
print(f"step {step:2d} loss {loss.item():.4g} grad_norm {gn.item():.4g} max|w| {wmax:.4g}")
if not (torch.isfinite(loss) and torch.isfinite(gn)):
print(f"অসীম/NaN ধরা পড়ল step {step}-এ; থামছি"); sys.exit(1)
opt.step(); step += 1~/bazarghar-dl$ uv run python nan_hunt.py
step 0 loss 1.125 grad_norm 0.5253 max|w| 0.447
step 1 loss 3.627 grad_norm 4.966 max|w| 0.9057
step 2 loss 161.1 grad_norm 61.42 max|w| 9.012
step 3 loss 1.18e+05 grad_norm 7997 max|w| 132.3
step 4 loss 4.93e+04 grad_norm 2.072e+04 max|w| 4.091e+04
step 5 loss 2.236e+09 grad_norm 1.88e+08 max|w| 7.773e+04
step 6 loss 2.606e+20 grad_norm 1.366e+15 max|w| 9.557e+08
step 7 loss nan grad_norm nan max|w| 6.311e+15
অসীম/NaN ধরা পড়ল step 7-এ; থামছি
| ধাপ | |
| loss | |
| grad norm | |
| max │w│ | |
| যা ঘটছে |
- ধাপ ০ আর ১. শুরুর loss ১.১২৫, ln ৩-এর কাছে: init ঠিক আছে। কিন্তু এক ধাপ পরেই loss ৩.৬২৭ আর gradient norm প্রায় ১০ গুণ। সুস্থ training-এ প্রথম ধাপগুলোতে loss বাড়ে না; এটিই প্রথম সংকেত।
- ধাপ ২. Loss ১৬১.১, gradient norm ৬১.৪২, সবচেয়ে বড় weight ০.৪৪৭ থেকে ৯.০১২. প্রতি ধাপে প্রায় দশ গুণ: momentum-সহ বড় ধাপ নিজেকে বাড়িয়ে চলেছে।
- ধাপ ৩ আর ৪. Loss ১.১৮ × ১০৫ থেকে ৪.৯৩ × ১০৪-এ একটু কমল। শুধু loss দেখলে মনে হতে পারে "ঠিক হয়ে আসছে"। কিন্তু max │w│ ১৩২ থেকে ৪০,৯১০: weight দেখলে বিপদ স্পষ্ট।
- ধাপ ৫ আর ৬. Loss ২.৬ × ১০২০, gradient norm ১.৩৭ × ১০১৫. Float32-এর সীমা প্রায় ৩.৪ × ১০৩৮; logits এখন এত বড় যে পরের ধাপে কোনো হিসাব সীমা পেরোবে।
- ধাপ ৭. Weight ৬.৩ × ১০১৫, logits inf ছুঁয়েছে, আর log-softmax-এর ভেতরে inf − inf = NaN.
isfiniteপরীক্ষা এই ধাপেই থামাল, NaN weight-এ ঢোকার আগে। - Anomaly মোড.
--anomalyদিয়ে আবার চালালে একই ধাপ ৭-এ backward error তোলে:LogSoftmaxBackward0NaN দিয়েছে, forward-এরF.cross_entropyলাইন থেকে। মূল কারণ অবশ্য সেই লাইন নয়, learning rate; anomaly মোড দেখায় NaN কোথায় জন্মাল, কেন তা আপনাকে ভাবতে হয়।
Anomaly মোডের সত্যিকারের আউটপুট (পথ ছোট করা, traceback-এর মাঝের লাইন বাদ):
~/bazarghar-dl$ uv run python -u nan_hunt.py --anomaly
step 0 loss 1.125 grad_norm 0.5253 max|w| 0.447
…
step 6 loss 2.606e+20 grad_norm 1.366e+15 max|w| 9.557e+08
…/torch/autograd/graph.py:1113: UserWarning: Error detected in LogSoftmaxBackward0. Traceback of forward call that caused the error:
File "~/bazarghar-dl/nan_hunt.py", line 16, in <module>
loss = F.cross_entropy(model(xb), yb)
File "…/torch/nn/functional.py", line 3561, in cross_entropy
return torch._C._nn.cross_entropy_loss(
…
Traceback (most recent call last):
File "~/bazarghar-dl/nan_hunt.py", line 17, in <module>
opt.zero_grad(); loss.backward()
…
RuntimeError: Function 'LogSoftmaxBackward0' returned nan values in its 0th output.
NaN-এর সাধারণ উৎসগুলোর একটি তালিকা রাখুন: খুব বড় learning rate; ০ বা ঋণাত্মক সংখ্যার log বা sqrt (যেমন হাতে লেখা loss-এ log(p), যেখানে p ০ হতে পারে); ০ দিয়ে ভাগ (sd ০ এমন কলাম normalise করা); ডেটাতেই NaN (খালি ঘর); আর mixed precision-এ float16-এর ছোট পরিসর (পর্ব ১২)। ডেটার NaN ধরতে প্রথম batch-এ torch.isfinite(xb).all() একবার দেখে নিন।
৮ · খুব ভালো ফলও বাগ — duplicate আর leakage
বেশিরভাগ বাগ ফল খারাপ করে। কিন্তু data leakage ফল ভালো দেখায়, তাই সবচেয়ে বিপজ্জনক: কেউ সেটি খোঁজে না। একটি পরিচিত রূপ duplicate: একই উদাহরণ ডেটাসেটে একাধিকবার আছে, আর এলোমেলো split-এ একটি কপি train-এ, আরেকটি validation-এ পড়ে। তখন validation আর "নতুন" ডেটা নয়, model শুধু মুখস্থ উত্তর বলে। Split আর leakage-এর ভিত্তি AI-এর ভিত্তি কোর্সের পর্ব ৫৬-এ।
বাজারঘরে ধরা যাক export-এর ভুলে (যেমন ক্রেতার টেবিল অর্ডারের টেবিলের সঙ্গে join করায়) প্রতিটি ক্রেতার সারি ১ থেকে ৩ বার এসেছে। তারপর এলোমেলো split, বড় একটি MLP (৫ → ২৫৬ → ২৫৬ → ৩), ৩০০ ধাপ, আর আসল পরীক্ষা হিসেবে ১,০০০ জন একদম নতুন ক্রেতা (সত্যিকারের run):
~/bazarghar-dl$ uv run python leak.py
leaky: rows 1235 | val rows seen in train 65% | train acc 1.000 | val acc 0.893 | new-customer test acc 0.708
dedup: rows 600 | val rows seen in train 0% | train acc 1.000 | val acc 0.750 | new-customer test acc 0.732
Duplicate-সহ val accuracy ০.৮৯৩, অথচ নতুন ক্রেতায় ০.৭০৮: ১৮ শতাংশ-বিন্দুর মিথ্যা আশা। Val-এর ৬৫% সারি হুবহু train-এও আছে, আর model train-এ ১.০০০ accuracy পেয়ে সেগুলো মুখস্থ করেছে, ১০% ভুল label-সহ। Duplicate সরিয়ে split করলে val ০.৭৫০, নতুন ক্রেতার ০.৭৩২-এর কাছে: এখন val সৎ। লক্ষণটি মনে রাখুন: val-এর ফল train-এর খুব কাছে, আর আগের অভিজ্ঞতা বা সহজ baseline-এর চেয়ে অবিশ্বাস্য ভালো।
ধরার উপায়: split-এর আগে duplicate খোঁজা (script-এ প্রতিটি সারির byte দিয়ে একটি set, r.tobytes() in seen), আর একই ক্রেতা, একই রোগী বা একই ছবির বিভিন্ন কপি থাকলে সারি নয়, ক্রেতা ধরে split (group split). ছবি আর লেখায় হুবহু নয়, প্রায়-একই কপিও সমস্যা; সেখানে embedding-এর দূরত্ব দিয়ে খোঁজা হয় (পরের মডিউলগুলোতে embedding আসবে)। আরও কয়েকটি leakage-এর উৎস: পুরো ডেটার গড় দিয়ে normalise করে তারপর split, ভবিষ্যতের তথ্য থেকে বানানো feature, আর test set দেখে বারবার hyperparameter বদলানো।
৯ · Learning curve পড়া — আর বাগ বারবার দেখানো
Learning curve মানে epoch বা ধাপ ধরে train আর validation loss-এর লেখচিত্র। এর আকার দেখে বেশিরভাগ সমস্যা চেনা যায়, অনেকটা ক্রিকেটের ওভার-প্রতি রানের ছবি দেখে ইনিংসের গল্প বোঝার মতো। Validation আর train loss-এর পার্থক্য generalization gap; সেটি বড় আর বাড়তে থাকলে overfitting (তত্ত্ব AI-এর ভিত্তি কোর্সের পর্ব ৬১-এ)। নিচের ব্লক ছয়টি বানানো curve তৈরি করে, আর একটি ছোট নিয়ম-ভিত্তিক function প্রতিটির ধরন বলে:
import numpy as np, matplotlib.pyplot as plt
rng = np.random.default_rng(42); e = np.arange(1, 61) # ৬০ epoch
n = lambda s: rng.normal(0, s, 60) # epoch-এর ছোট ওঠানামা
dec = lambda k: np.exp(-e / k)
curves = { # (train loss, val loss)
"A": (0.9 * dec(8) + 0.25 + n(.01), 0.9 * dec(8) + 0.32 + n(.01)),
"B": (0.95 - 0.12 * (1 - dec(15)) + n(.005), 0.97 - 0.12 * (1 - dec(15)) + n(.005)),
"C": (1.0 * dec(10) + 0.05, 0.45 + 0.6 * dec(6) + 0.0004 * (e - 12).clip(0) ** 2),
"D": (np.where(e < 25, 1.1 * 1.9 ** (e - 1), np.nan), np.where(e < 25, 1.2 * 1.9 ** (e - 1), np.nan)),
"E": (np.where(e < 25, 1.10, 1.10 - 0.8 * (1 - np.exp(-(e - 25) / 4))) + n(.004),
np.where(e < 25, 1.11, 1.11 - 0.75 * (1 - np.exp(-(e - 25) / 4))) + n(.004)),
"F": (0.8 * dec(8) + 0.35 + n(.08), 0.8 * dec(8) + 0.42 + n(.1)),
}
def diagnose(tr, va):
if not np.all(np.isfinite(tr)) or tr[-1] > tr[0]: return "diverged: LR too high"
if np.std(np.diff(tr[-20:])) > 0.05: return "noisy: small batch / high LR"
if np.ptp(tr[:15]) < 0.03 * tr[0] and tr[-1] < 0.7 * tr[0]: return "plateau, then escape"
if va[-1] - va.min() > 0.1 and va[-1] - tr[-1] > 0.2: return "overfitting"
if tr[-1] > 0.6 * tr[0]: return "underfitting"
return "healthy"
fig, axs = plt.subplots(2, 3, figsize=(10, 5.6))
for ax, (k, (tr, va)) in zip(axs.flat, curves.items()):
label = diagnose(tr, va); print(k, "→", label)
ax.plot(e, tr, label="train"); ax.plot(e, va, label="val"); ax.set_title(f"{k}: {label}", fontsize=9)
ax.set_xlim(0, 61)
if "diverged" in label: ax.set_yscale("log")
else: ax.set_ylim(0, 1.5)
for ax in axs[1]: ax.set_xlabel("epoch")
for ax in axs[:, 0]: ax.set_ylabel("loss")
axs[0, 0].legend(); fig.subplots_adjust(hspace=0.35, wspace=0.3); plt.show()আউটপুট (পাতার Python-এ চালানো; নিচে ছয় খোপের একটি চিত্রও আঁকা হয়):
A → healthy
B → underfitting
C → overfitting
D → diverged: LR too high
E → plateau, then escape
F → noisy: small batch / high LR
A, সুস্থ: দুটিই নেমে থিতু, ফাঁক ছোট। B, underfitting: দুটিই উঁচুতে আর পাশাপাশি; model যথেষ্ট শিখছে না: বড় model, বেশি epoch বা বড় learning rate চেষ্টা করুন। C, overfitting: train নামতেই থাকে, val epoch ১২-এর দিকে সবচেয়ে নিচে, তারপর ওঠে; early stopping, regularization বা বেশি ডেটা (পর্ব ১৬)। D, বিস্ফোরণ: log স্কেলে সরল রেখা মানে প্রতি epoch-এ গুণ হারে বৃদ্ধি, তারপর NaN (অংশ ৭)। E, loss plateau: ২৪ epoch প্রায় সমতল, তারপর হঠাৎ নামা; খারাপ init বা খুব ছোট learning rate-এ দেখা যায়, আর অধৈর্য হয়ে আগে থামালে ভালো model হারাতেন। F, অস্থির: নিচের দিকে যাচ্ছে, কিন্তু epoch-ভেদে বড় ঝাঁকুনি; ছোট batch বা একটু বড় learning rate. নিয়মগুলো হাতে বানানো আর এই ছয়টি curve-এর জন্য সাজানো; বাস্তবে এগুলো চোখের সাহায্য, স্বয়ংক্রিয় বিচারক নয়।
দুটি সতর্কতা। প্রথম, train loss সাধারণত epoch চলার সময় dropout-সহ মাপা গড়, val loss epoch শেষে eval mode-এ; তাই শুরুর দিকে val loss train-এর চেয়ে কম দেখাতে পারে, সেটি বাগ নয়। দ্বিতীয়, শুধু loss নয়, যে metric আপনার কাছে জরুরি (accuracy, F1) সেটিও আঁকুন; অংশ ৫-এ accuracy ঠিক থেকেও loss ভুল ছিল।
বাগ বারবার একইভাবে দেখানো
অংশ ৭-এর NaN প্রতিবার ঠিক ধাপ ৭-এ এসেছে, কারণ script-এ torch.manual_seed(0) আর DataLoader-এর নিজস্ব seed দেওয়া Generator আছে (পর্ব ১০)। যে বাগ মাঝে মাঝে আসে, seed ঠিক না করলে তাকে ধরা প্রায় অসম্ভব: ঠিক করার পরে সমস্যা চলে গেল, নাকি এবার ভাগ্য ভালো ছিল, বোঝা যায় না। তাই বাগ পেলে প্রথমে seed, ডেটার ক্রম আর library version লিখে রাখুন, সম্ভব হলে বাগটি একটি ছোট script-এ ছোট ডেটায় আলাদা করুন, তারপর ঠিক করুন। Seed-এর পুরো তালিকা আর deterministic মোড পর্ব ১৯ (Deep learning experiment)-এ, আর experiment লেখার অভ্যাস মেশিন লার্নিং কোর্সের পর্ব ৪-এ।
| বাগ | সত্যিকারের লক্ষণ | কোন পরীক্ষায় ধরা |
|---|---|---|
| X আর y আলাদা ক্রমে | val acc ০.৩০০, শ্রেণির গড় সব এক | ডেটা দেখা (এক batch-এ ধরা পড়ে না) |
| Normalise নেই | শুরুর loss ১৮.৬৭, তারপর ln ৩-এ আটকে | শুরুর loss, feature-এর sd |
| Softmax + NLLLoss | ঋণাত্মক loss, val CE ১.৯৫৯ | শুরুর loss (−০.৩৩), এক batch |
| LR ১০ / ১০০ গুণ | ৯৪% ReLU মৃত / ধাপ ৭-এ NaN | dead ReLU-এর ভাগ, gradient norm, isfinite |
| (N, 1) − (N,) | loss = Var(y), R² −০.০০৩ | shape assert, loss-এর মান |
| Duplicate split-এর দুই পাশে | val ০.৮৯৩, নতুন ডেটায় ০.৭০৮ | duplicate খোঁজা, নতুন ডেটায় test |
আরও গভীরে: Zhang ও সহকর্মীদের “Understanding deep learning requires rethinking generalization” (ICLR ২০১৭) দেখায়, প্রচলিত CNN এলোমেলো label-ও শূন্য train error পর্যন্ত মুখস্থ করতে পারে, যা অংশ ৪-এর ফলের তাত্ত্বিক পটভূমি। Benchmark-এ duplicate-এর একটি পরিচিত উদাহরণ Barz আর Denzler-এর “Do we train on test data? Purging CIFAR of near-duplicates” (২০২০), যেখানে রিপোর্ট অনুযায়ী CIFAR-100-এর test ছবির প্রায় ১০%-এর কাছাকাছি-কপি train-এ পাওয়া যায়। NaN-এর আচরণ IEEE 754 মানদণ্ডে (১৯৮৫, পরে সংশোধিত) নির্দিষ্ট; PyTorch-এর anomaly মোডের খুঁটিনাটি তার নিজের docs-এ torch.autograd.detect_anomaly অংশে দেখে নিন।
পরীক্ষা ও ইন্টারভিউতে:
- ১০ শ্রেণির classifier-এর শুরুর loss কত হওয়া উচিত? −ln(১/১০) = ln ১০ ≈ ২.৩০২৬; অনেক বেশি হলে init বা ইনপুটের মাপ দেখুন।
- নতুন model-এ প্রথম পরীক্ষা কী? একটি ছোট batch overfit; loss প্রায় ০ না হলে কোডে ভুল।
- Loss হঠাৎ NaN, কীভাবে খুঁজবেন? প্রতি ধাপে isfinite আর gradient norm লগ, প্রথম খারাপ ধাপে set_detect_anomaly, তারপর learning rate, log/ভাগ আর ডেটার NaN দেখা।
- Val accuracy train-এর প্রায় সমান আর অস্বাভাবিক ভালো, কী সন্দেহ করবেন? Leakage: split-এর দুই পাশে duplicate বা একই group, পুরো ডেটায় normalise.
- Train নামছে, val একসময় উঠছে: কী? Overfitting; val-এর সর্বনিম্ন বিন্দুতে checkpoint, regularization বা বেশি ডেটা।
১০ · AI-এর যুগে — AI-কে দিয়ে ডিবাগ করানো, সাবধানে
Training-এর log বা কোড AI assistant-কে দেখিয়ে "কেন শিখছে না?" জিজ্ঞেস করা এখন সাধারণ অভ্যাস, আর অনেক সময় কাজেও লাগে। কিন্তু AI-এর ডিবাগ পরামর্শেরও পরিচিত দুর্বলতা আছে, আর সেগুলো এই পর্বের বাগগুলোর সঙ্গেই মেলে।
| AI প্রায়ই যা করে | ফল | কীভাবে যাচাই করবেন |
|---|---|---|
| সরাসরি hyperparameter বদলাতে বলে ("LR কমান, layer বাড়ান") | আসল কোড বা ডেটার বাগ ঢাকা পড়ে | আগে শুরুর loss আর এক batch overfit নিজে চালান |
| Shape বা dim গুলিয়ে ফেলে, (N, 1) আর (N,) আলাদা করে না | broadcasting-এর চুপচাপ ভুল loss | একটি ছোট batch-এ সব shape ছাপান, assert রাখুন |
| পুরোনো বা বানানো API বলে | error, বা ভুল আচরণ | নিজের torch version-এর docs মিলিয়ে দেখুন |
| পুরো ডেটায় normalise বা split-এর আগে duplicate রাখে | অবিশ্বাস্য ভালো val | split-এর পরে শুধু train-এর গড়, duplicate গোনা |
| একটি run দেখে "এখন ঠিক হয়েছে" বলে | ভাগ্যকে সমাধান ভাবা | কয়েকটি seed, আর সরল baseline-এর সঙ্গে তুলনা |
AI-কে সমাধানের বদলে একটি ডিবাগ পরিকল্পনা চাইতে বলুন:
AI-এর ডিবাগ পরামর্শ হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১“শুরুর loss দেখুন, এক batch overfit করুন” — এই পরামর্শ কোথা থেকে এল, আর কেন এটি অভিজ্ঞদের অভ্যাস হয়ে গেছে?
এই কৌশলগুলো কোনো একজনের আবিষ্কার নয়; neural network-এর চর্চাকারীরা বহু বছর ধরে মুখে মুখে এগুলো চালিয়েছেন। লিখিত রূপে বহুল পরিচিত হয় Andrej Karpathy-র ২০১৯ সালের ব্লগ লেখা “A Recipe for Training Neural Networks” থেকে। সেখানে তিনি বলেন neural network training একটি "leaky abstraction": library-র সুন্দর API থাকলেও ভেতরের ভুল চুপচাপ থাকে, আর ভুল কনফিগারেশনেও network প্রায়ই কিছুটা শেখে, তাই ব্যর্থতা ধরা পড়ে না। তাঁর সমাধান ছিল ধীর, সতর্ক, ধাপে ধাপে এগোনো: ডেটা নিজের চোখে দেখা, সরল baseline, শুরুর loss যাচাই, ছোট batch overfit, তারপর ধীরে ধীরে জটিলতা যোগ।
এর পেছনের নীতি বিজ্ঞানের পুরোনো নীতি: পরীক্ষার আগে উত্তর জানা থাকলে তবেই পরীক্ষা কিছু বলে। ln C-এর মতো একটি সংখ্যা যাচাই করতে কয়েক সেকেন্ড লাগে, অথচ এক দিনের training নষ্ট হওয়া থেকে বাঁচাতে পারে। সফটওয়্যার প্রকৌশলে একই ধারণা unit test আর smoke test; deep learning-এ "ঠিক উত্তর" অনিশ্চিত বলে এমন অল্প কয়েকটি জানা উত্তরই মূল্যবান।
সীমাও আছে। অংশ ৪-এ দেখেছেন, এলোমেলো label আর ভুল ReLU gradient দুটোই এক batch পরীক্ষা পাস করে। বড় ভাষা model-এর মতো বিশাল training-এ এক batch overfit করাও কখনো কখনো ধীর, আর সেখানে loss spike-এর মতো সমস্যা (যা কোডের নয়, সংখ্যাগত স্থিরতার) অনেক দেরিতে আসে। তাই এই পরীক্ষাগুলো প্রয়োজনীয় ন্যূনতম, পূর্ণ তালিকা নয়।
মূল উপলব্ধি: জানা উত্তরের সস্তা পরীক্ষা আগে, দামি পরীক্ষা পরে; এই ক্রম ভুলকে তার উৎসের কাছেই ধরে, যদিও কোনো একক পরীক্ষা সব ভুল ধরে না।
প্র ০২Neural network এলোমেলো label-ও মুখস্থ করতে পারে — এই তথ্য ডিবাগ আর generalization নিয়ে আমাদের চিন্তা কীভাবে বদলায়?
২০১৭ সালে ICLR-এ Chiyuan Zhang, Samy Bengio, Moritz Hardt, Benjamin Recht আর Oriol Vinyals-এর প্রবন্ধ “Understanding deep learning requires rethinking generalization” একটি সরল পরীক্ষা করে: CIFAR-10-এর label সম্পূর্ণ এলোমেলো করে প্রচলিত CNN train করা। ফল ছিল চমকপ্রদ: network train error শূন্যে নামাতে পারে, শুধু সময় একটু বেশি লাগে। অর্থাৎ এই model-গুলোর ধারণক্ষমতা এত বেশি যে অর্থহীন label-ও মুখস্থ করতে পারে। অথচ সত্যিকারের label-এ একই model ভালো generalize করে।
তত্ত্বের দিকে এর মানে, শুধু model-এর ক্ষমতা মেপে (যেমন parameter-এর সংখ্যা বা প্রচলিত জটিলতার মাপ) ব্যাখ্যা করা যায় না কেন deep network নতুন ডেটায় ভালো করে। এর পরে implicit regularization, optimization-এর পথ আর double descent নিয়ে অনেক গবেষণা হয় (এই কোর্সের পর্ব ০৬)। বিষয়টি এখনো পুরোপুরি মীমাংসিত নয়।
ডিবাগের দিকে শিক্ষাটি আরও সরাসরি। Train loss নামছে মানেই ডেটা ঠিক, এমন নয়। অংশ ২-এ এলোমেলো label-এ train loss ১.১ থেকে ০.৯৭-এ নামল, আর এক batch-এ প্রায় শূন্যে। Network ডেটার মধ্যে কোনো নিয়ম না পেলেও মুখস্থ করে loss নামাবে। তাই train loss একা কখনো প্রমাণ নয়; validation, সরল baseline-এর সঙ্গে তুলনা, আর ডেটা নিজের চোখে দেখা দরকার। একটি দরকারি উল্টো পরীক্ষাও এখান থেকে আসে: ইচ্ছা করে label এলোমেলো করে চালান; তখনও val ভালো হলে নিশ্চিত leakage আছে।
মূল উপলব্ধি: বড় network প্রায় যেকোনো কিছু মুখস্থ করতে পারে, তাই train loss নামা শেখার প্রমাণ নয়; শেখার প্রমাণ শুধু সৎ, আলাদা ডেটায়।
প্র ০৩Leakage-এ ফল খারাপ নয়, ভালো দেখায় — বিজ্ঞান আর শিল্পে এই সমস্যা কতটা গুরুতর, আর কীভাবে আত্মরক্ষা করবেন?
Data mining-এ leakage নিয়ে একটি পরিচিত আলোচনা Kaufman ও সহকর্মীদের ২০১২ সালের প্রবন্ধ “Leakage in data mining: Formulation, detection, and avoidance”; সেখানে প্রতিযোগিতায় এমন উদাহরণ আছে যেখানে ডেটার কোনো ফাঁক থেকে মডেল অবাস্তব ভালো ফল পেয়েছিল। ২০২৩ সালে Sayash Kapoor আর Arvind Narayanan Patterns জার্নালে ML-ভিত্তিক বিজ্ঞানে leakage-এর একটি পর্যালোচনা প্রকাশ করেন; রিপোর্ট অনুযায়ী তাঁরা অনেকগুলো গবেষণাক্ষেত্রের কয়েকশো প্রবন্ধে leakage-এর সমস্যা পান, যার কিছু ক্ষেত্রে সংশোধনের পর ML-এর সুবিধা অনেকটা কমে যায়।
Benchmark-ও নিরাপদ নয়। Barz আর Denzler ২০২০ সালে দেখান CIFAR ডেটাসেটের test ছবির একটি অংশের কাছাকাছি-কপি train-এ আছে, আর তাঁরা ciFAIR নামে পরিষ্কার test set প্রস্তাব করেন। বড় ভাষা model-এর যুগে প্রশ্নটি আরও কঠিন: ইন্টারনেট থেকে নেওয়া training ডেটায় benchmark-এর প্রশ্ন ঢুকে গেছে কি না (contamination) নিশ্চিত করা প্রায়ই সম্ভব নয়।
সমস্যা মানুষের মনস্তত্ত্বেও। খারাপ ফল পেলে আমরা বাগ খুঁজি, ভালো ফল পেলে খুশি হয়ে থামি। আত্মরক্ষার উপায়: আগে থেকে একটি সরল baseline আর প্রত্যাশিত পরিসর লিখে রাখা; অস্বাভাবিক ভালো ফলকে বাগ ধরে নিয়ে খোঁজা; split-এর আগে duplicate আর group পরীক্ষা; সময়ভিত্তিক ডেটায় সময় ধরে split; label এলোমেলো করে চালানো; আর সবশেষে একদম নতুন, পরে সংগ্রহ করা ডেটায় একবার পরীক্ষা। অংশ ৮-এ শেষ পরীক্ষাটিই মিথ্যা ০.৮৯৩ ধরিয়ে দিয়েছে।
মূল উপলব্ধি: অবিশ্বাস্য ভালো ফল সবচেয়ে আগে সন্দেহ করার মতো ফল; leakage ধরতে আগে থেকে baseline আর নতুন ডেটার পরীক্ষা পরিকল্পনা করে রাখুন।
প্র ০৪NaN জিনিসটা কেন আছে, কেন এটি পুরো network-এ ছড়িয়ে পড়ে, আর বড় training-এ মানুষ কীভাবে সামলায়?
NaN ("Not a Number") এসেছে IEEE 754 floating-point মানদণ্ড থেকে, যা ১৯৮৫ সালে প্রকাশিত হয় আর William Kahan-এর মতো গবেষকেরা যার নকশায় বড় ভূমিকা রাখেন। নকশাকারীদের লক্ষ্য ছিল, ০/০ বা inf − inf-এর মতো অর্থহীন হিসাবে প্রোগ্রাম যেন হঠাৎ বন্ধ না হয়, বরং একটি বিশেষ মান বয়ে নিয়ে চলে, যা পরে পরীক্ষা করা যায়। তাই NaN-এর সঙ্গে যেকোনো গাণিতিক কাজ NaN দেয়, আর NaN নিজের সমানও নয়, ফলে x == x মিথ্যা হলেই NaN চেনা যায়।
Neural network-এ এই "চুপচাপ বয়ে চলা" নকশা দুই দিকে কাটে। একটি NaN gradient একটি weight-এ ঢুকলে পরের forward-এ সেই layer-এর সব output NaN, তারপর সব gradient NaN: এক ধাপেই পুরো model নষ্ট। আর যেহেতু error আসে না, অনেক সময় অনেক পরে loss-এর ছবিতে ধরা পড়ে। অংশ ৭-এর পরীক্ষায় দেখেছেন, NaN-এর আগে কয়েক ধাপ ধরে weight আর gradient বিস্ফোরিত হচ্ছিল; সংকেত ছিল, শুধু কেউ দেখছিল না।
বড় training-এ সাধারণ অভ্যাস কয়েকটি: প্রতি ধাপে loss আর gradient norm লগ, gradient clipping (পর্ব ১৭), mixed precision-এ loss scaling আর অস্থির কাজ float32-এ রাখা (পর্ব ১২), নিয়মিত checkpoint, আর loss হঠাৎ লাফালে আগের checkpoint থেকে অন্য ডেটার ক্রমে আবার শুরু করা। বড় ভাষা model-এর কয়েকটি প্রকাশিত training-এর বিবরণে এমন loss spike আর আগের checkpoint-এ ফেরার কথা রিপোর্ট করা হয়েছে। Anomaly মোড সেখানে সারাক্ষণ চালানো যায় না, কারণ এটি ধীর; সমস্যার ধাপটি আলাদা করে শুধু সেখানে চালানো হয়।
মূল উপলব্ধি: NaN হঠাৎ জন্মায় না, তার আগে সংখ্যা বিস্ফোরিত হয়; তাই প্রতি ধাপের সস্তা পর্যবেক্ষণ আর isfinite পাহারা দামি anomaly মোডের চেয়ে বেশি কাজে দেয়।
প্র ০৫Learning curve দেখে "overfitting হচ্ছে, থামুন" বলা কি সবসময় ঠিক? Curve কখন ভুল পথে নিয়ে যেতে পারে?
প্রচলিত পাঠ্যবইয়ের ছবিতে val loss একটি U-আকার: নামে, তারপর ওঠে, আর সর্বনিম্ন বিন্দুতে থামা উচিত। বেশিরভাগ ছোট আর মাঝারি task-এ এটি ভালো নিয়ম। কিন্তু deep learning-এর কিছু পর্যবেক্ষণ একে জটিল করেছে। Nakkiran ও সহকর্মীরা (“Deep Double Descent”, ২০১৯) রিপোর্ট করেন যে কিছু পরিস্থিতিতে, বিশেষত label noise থাকলে, test error epoch ধরে একবার বেড়ে আবার কমতে পারে (epoch-wise double descent). Power ও সহকর্মীরা (২০২২) ছোট algorithmic ডেটাসেটে "grokking" বর্ণনা করেন: train-এ নিখুঁত হওয়ার অনেক পরে হঠাৎ validation-এ উন্নতি। দুটিই নির্দিষ্ট পরিস্থিতির ফল, সার্বজনীন নিয়ম নয়, কিন্তু দেখায় যে খুব আগে থামলে ভালো model হারানো সম্ভব।
Plateau-ও একই রকম বিভ্রান্তিকর। Dauphin ও সহকর্মীদের ২০১৪ সালের কাজসহ বেশ কিছু গবেষণা যুক্তি দেয় যে উচ্চ মাত্রার loss-এর ভূদৃশ্যে saddle point আর সমতল অঞ্চল সাধারণ, আর optimizer সেখানে অনেক ধাপ আটকে থেকে পরে বেরিয়ে আসতে পারে, যেমন অংশ ৯-এর curve E.
আরেকটি ফাঁদ noise. ২০০টি উদাহরণের validation-এ accuracy-র এক ঘর বদল মানে দুজন মানুষ; এক epoch-এর লাফকে প্রবণতা ভাবা ভুল। Curve মসৃণ করে দেখা, কয়েকটি seed চালানো আর বড় val set এখানে সাহায্য করে।
তাই বাস্তব অভ্যাস: patience যথেষ্ট রাখুন, নিয়মিত checkpoint নিন, যাতে পরে সেরাটি বাছা যায়, আর curve-কে প্রমাণ নয়, প্রশ্ন তোলার উৎস হিসেবে দেখুন। বাজেট কম হলে নির্দিষ্ট ধাপের schedule আর checkpoint অনেক সময় অধৈর্য early stopping-এর চেয়ে নিরাপদ।
মূল উপলব্ধি: Learning curve রোগনির্ণয়ের ইঙ্গিত দেয়, রায় নয়; noise, plateau আর বিরল দ্বিতীয় নামার সম্ভাবনা মাথায় রেখে checkpoint রেখে সিদ্ধান্ত নিন।
অনুশীলন
প্রথমে খাতায় করুন, তারপর উত্তর মেলান।
-
পূর্ণ numerical: (ক) ১০ শ্রেণির একটি classifier-এর শুরুর loss কত আশা করবেন? প্রথম batch-এ দেখলেন ৯.৮; সম্ভাব্য দুটি কারণ লিখুন। (খ) দুই শ্রেণির BCE-তে শুরুতে model প্রতিটি উদাহরণে p = ০.৫ দিলে loss কত? ডেটায় ৯০% শ্রেণি ০, ১০% শ্রেণি ১; model শুধু এই অনুপাত শিখলে (সবাইকে p = ০.১) loss কত? (গ) Regression-এ target-এর variance ৪. হাতে লেখা MSE-তে output (N, 1) আর target (N,). নিখুঁত অনুমানে loss কত দেখাবে, আর সবাইকে গড় বললে কত?
(ক) −ln(১/১০) = ln ১০ ≈ ২.৩০২৬. ৯.৮ মানে logits শুরু থেকেই বড়: weight-এর init খুব বড়, বা ইনপুট normalise করা নেই (অংশ ৩-এ normalise ছাড়া ৩ শ্রেণিতে ১৮.৬৭)। (খ) p = ০.৫-এ loss = −ln ০.৫ = ln ২ ≈ ০.৬৯৩১. অনুপাত শিখলে loss = −(০.৯ ln ০.৯ + ০.১ ln ০.১) = ০.৯ × ০.১০৫৪ + ০.১ × ২.৩০২৬ ≈ ০.০৯৪৮ + ০.২৩০৩ = ০.৩২৫১. তাই অসমান ডেটায় দ্রুত ০.৩২৫-এ নেমে সেখানে আটকে থাকা মানে model শুধু অনুপাত শিখেছে, কিছুই আলাদা করতে পারছে না। (গ) ভুল loss = (১/N) Σ(pi − ȳ)² + Var(y). নিখুঁত অনুমানে প্রথম অংশও Var(y), মোট ৮; গড় বললে প্রথম অংশ ০, মোট ৪. ভুল loss গড় বলাকে পুরস্কার দেয়।
-
সমালোচনা ও নকশা: একজন সহকর্মী বাজারঘরের রিভিউ থেকে ক্রেতা ফেরত দেবেন কি না অনুমানের model বানিয়েছেন। Log বলছে: শুরুর loss ০.৬৯, train accuracy ০.৯৯, val accuracy ০.৯৭. কিন্তু এক মাস পরের নতুন রিভিউতে accuracy ০.৭১. তিনি বলছেন "model বড় করলে ঠিক হবে"। আপনি কী কী পরীক্ষা করবেন, কোন ক্রমে?
শুরুর loss ln ২-এর সমান, তাই init ঠিক; train ০.৯৯ মানে model শিখতে পারে। সমস্যা val আর নতুন ডেটার ফাঁকে: val অবিশ্বাস্য ভালো, তাই প্রথম সন্দেহ leakage, model বড় করা নয়। ক্রম: ১, train আর val-এ হুবহু বা প্রায়-একই রিভিউ গুনুন (একই ক্রেতা একই লেখা কয়েকবার দিতে পারেন)। ২, split সারি ধরে না ক্রেতা ধরে, আর সময় ধরে কি না দেখুন; ভবিষ্যৎ অনুমানে আগের মাস train, পরের মাস val. ৩, কোনো feature ফেরতের পরে তৈরি তথ্য বহন করে কি না (যেমন "refund" শব্দ বা ফেরতের পরে লেখা রিভিউ)। ৪, vocabulary বা normalise পুরো ডেটা থেকে বানানো কি না। ৫, label এলোমেলো করে চালান; তখনও val ভালো হলে leakage নিশ্চিত। ৬, এরপরও ফাঁক থাকলে distribution shift (নতুন পণ্য, নতুন শব্দ) দেখুন। Model বড় করলে বরং মুখস্থ আরও বাড়বে।
-
আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
import numpy as np pred = np.array([[1.0], [2.0], [3.0]]) y = np.array([1.0, 2.0, 3.0]) d = (pred - y) ** 2 print(d.shape, round(d.mean(), 4), round(2 * y.var(), 4))(3, 3) 1.3333 1.3333(৩, ১) − (৩,) broadcasting-এ (৩, ৩): d[i, j] = (i − j)²-এর মতো ছক, অর্থাৎ ০, ১, ৪ / ১, ০, ১ / ৪, ১, ০. যোগফল ১২, গড় ১২/৯ ≈ ১.৩৩৩৩. y-এর variance ২/৩, দ্বিগুণ ১.৩৩৩৩: নিখুঁত অনুমানেও ভুল loss ঠিক ২ × Var(y), সূত্রের সঙ্গে মিল।
-
কোড লিখুন: একটি
nan_guard(model, loss, step)function লিখুন, যা backward-এর পরে আরopt.step()-এর আগে ডাকা হবে, loss অসীম বা NaN হলে থামাবে, আর কোনো parameter-এর gradient-এ অসীম মান থাকলে তার নাম আর কয়টি ঘর খারাপ তা জানাবে।def nan_guard(model, loss, step): if not torch.isfinite(loss): raise FloatingPointError(f"step {step}: loss = {loss.item()}") for name, p in model.named_parameters(): if p.grad is not None and not torch.isfinite(p.grad).all(): bad = (~torch.isfinite(p.grad)).sum().item() raise FloatingPointError(f"step {step}: {name}.grad-এ {bad}টি অসীম/NaN")opt.step()-এর আগে ডাকলে খারাপ gradient weight-এ ঢোকার আগেই থামে, তাই শেষ ভালো weight অক্ষত থাকে। অংশ ৭-এর LR ১০০ গুণের run-এ বসিয়ে সত্যিকারের run-এ শেষ লাইন এলোFloatingPointError: step 7: loss = nan, অর্থাৎisfiniteপরীক্ষার মতো একই ধাপে। প্রতি ধাপে সব parameter দেখা বড় model-এ একটু খরচের; চাইলে প্রতি কয়েক ধাপে বা শুধু gradient norm অসীম হলে নাম খুঁজুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমনতুন একটি classifier-এর training ডিবাগের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Model-এ ঢোকার আগের tensor-এ label গোনা আর শ্রেণিভিত্তিক feature-এর গড় দেখা
- এলোমেলো weight-এ শুরুর loss মেপে ln C-এর সঙ্গে মেলানো
- Dropout বন্ধ রেখে একটি ছোট batch overfit করানো
- পুরো ডেটায় ছোট run: loss, gradient norm, dead ReLU আর isfinite লগ
- Learning curve-এ train/val ফাঁক দেখা আর duplicate খোঁজা
- Model বড় করা, regularization আর hyperparameter tuning
ঠিকঠাক শুরু করা ৫ শ্রেণির একটি classifier-এর cross-entropy loss training শুরুর আগে প্রায় কত হওয়া উচিত?
- প্রায় ০
- প্রায় ১.৬০৯
- প্রায় ০.২
- প্রায় ১৮
একটি MLP-কে ৩২টি উদাহরণের এক batch-এ বারবার train করে loss প্রায় ০-তে নামল। পরে দেখা গেল ডেটা pipeline-এ label আর ইনপুট আলাদা ক্রমে এলোমেলো হয়েছিল। এ থেকে কী বোঝা যায়?
- এক batch পাস করলে ডেটাও নিশ্চিতভাবে ঠিক
- এমন হওয়া অসম্ভব, নিশ্চয় মাপায় ভুল
- এক batch overfit model আর loss-এর কোড যাচাই করে, ডেটা বা label-এর ভুল ধরে না
- Learning rate খুব বড় ছিল
PyTorch-এ একটি classifier train করার সময় loss −০.৮-এর মতো ঋণাত্মক সংখ্যা দেখাচ্ছে। সবচেয়ে সম্ভাব্য কারণ কী?
- Softmax-এর probability সরাসরি
NLLLoss-এ দেওয়া হয়েছে - Model খুব ভালো শিখেছে
- Learning rate খুব বড়
- Logits সরাসরি
CrossEntropyLoss-এ দেওয়া হয়েছে
একটি regression model-এর output আকার (N, 1), target আকার (N,), আর loss হাতে লেখা ((out - y) ** 2).mean(). Training-এর পরে model কী শেখার দিকে যায়?
- কোনো কিছুই না, কারণ shape না মেলায় error আসে
- সঠিক অনুমান, শুধু loss-এর মান দ্বিগুণ দেখায়
- Target-এর উল্টো চিহ্ন
- সবার জন্য প্রায় একই সংখ্যা, target-এর গড়
একটি customer ডেটাসেটে val accuracy ০.৮৯, train ১.০০, কিন্তু একদম নতুন ক্রেতায় ০.৭১. Split এলোমেলোভাবে সারি ধরে করা। প্রথম কোন পরীক্ষা সবচেয়ে যুক্তিসংগত?
- Model আরও বড় করা
- Val-এর কতগুলো সারি হুবহু train-এও আছে তা গোনা
- Learning rate ১০ গুণ বাড়ানো
- শুরুর loss আবার মাপা
SGD-তে প্রতি ধাপে gradient norm ০.৫, ৫, ৬১, ৮০০০ আর সবচেয়ে বড় weight ০.৪, ০.৯, ৯, ১৩২. Loss এখনো সসীম। কী ঘটছে আর কী করবেন?
- স্বাভাবিক শুরু, কয়েক epoch অপেক্ষা
- Vanishing gradient; learning rate বাড়ান
- Gradient বিস্ফোরিত হচ্ছে; শিগগির NaN আসবে, learning rate কমানো দরকার
- Data leakage
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ক্রম: ডেটা আর label → শুরুর loss → এক batch overfit → ছোট পূর্ণ run → learning curve আর leakage → তারপর বড় করা। একটি ধাপ ব্যর্থ হলে সেখানেই থামুন।
- শুরুর loss ≈ ln C: ৩ শ্রেণিতে ১.০৯৯, ১০ শ্রেণিতে ২.৩০৩. অনেক বেশি হলে init বা ইনপুটের মাপ; ঋণাত্মক হলে loss-এর ভুল জোড়।
- এক batch overfit: কোড যাচাইয়ের প্রয়োজনীয় পরীক্ষা, যথেষ্ট নয়: এলোমেলো label-ও মুখস্থ হয়।
- NaN-এর আগে বিস্ফোরণ: প্রতি ধাপে loss, gradient norm আর isfinite লগ; প্রথম খারাপ ধাপে set_detect_anomaly. Dead ReLU-এর ভাগও মাপুন।
- চুপচাপ আর "খুব ভালো" বাগ: loss-এর আগে shape assert; split-এর আগে duplicate খোঁজা; অবিশ্বাস্য ভালো val মানে আগে leakage সন্দেহ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৯ · Deep learning experiment — reproducibility, hyperparameter search আর trackingপরবর্তী পর্বSeed, deterministic run, config, TensorBoard আর Optuna দিয়ে ন্যায্য তুলনা করা।
- ডিপ লার্নিং, NLP আর Generative AI · কোর্স হোম৯১টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ১৯ (Deep learning experiment) এখান থেকে খুলবেন।
- পর্ব ১০ · Training loopআগের পর্বপাঁচটি কাজের ক্রম, eval আর no_grad, seed আর checkpoint.
- পর্ব ০৭ · PyTorch tensorআগের পর্বBroadcasting-এর নিয়ম আর shape error পড়ার চেকলিস্ট।
- AI-এর ভিত্তি · পর্ব ৫৬ · Train, validation আর testআগের ভিত্তিSplit আর data leakage-এর ফাঁদ।
- AI-এর ভিত্তি · পর্ব ৬১ · Overfitting, bias আর varianceআগের ভিত্তিLearning curve আর regularization-এর ভিত্তি।
- মেশিন লার্নিং · পর্ব ৪ · পুনরুৎপাদনযোগ্য experimentপাশের ট্র্যাকSeed, config আর tracking: একটি বাগ বারবার একইভাবে দেখানোর অভ্যাস।