Chapter 14 of 28
Wiring every piece into one end-to-end workflow
আগের চ্যাপ্টারগুলোর প্রতিটিতে আমরা একটি CNN-এর আলাদা আলাদা অংশকে এককভাবে পরীক্ষা করেছি — একটি কনভোলিউশন লেয়ার, একটি অ্যাক্টিভেশন ফাংশন, একটি লস এবং একটি অপ্টিমাইজার। তবে এর কোনো একটি অংশও একা একা মডেলকে ট্রেইন করতে পারে না। এরা তখনই একটি কার্যকরী সিস্টেমে পরিণত হয় যখন এদের একটি পুনরাবৃত্তিমূলক লুপে (repeating loop) জুড়ে দেওয়া হয়: ডেটা ফিড করা, প্রেডিকশন বা অনুমান করা, ভুলের পরিমাপ বা লস হিসাব করা, ভুলটিকে ব্যাকপ্রোপাগেশনের মাধ্যমে পেছনের দিকে প্রবাহিত করা, ওজনকে সামান্য পরিবর্তন করা এবং এর পুনরাবৃত্তি করা — সম্পূর্ণ ডেটাসেট জুড়ে লক্ষ লক্ষ বার, অনেকগুলো ইপক (epochs) ধরে।
এই চ্যাপ্টারটি মূলত সেই জুড়ে দেওয়ার কাজ বা ওয়ারিং (wiring) নিয়ে। এখানে নতুন কোনো গণিতের চেয়ে সঠিক ইঞ্জিনিয়ারিংয়ের ওপর বেশি জোর দেওয়া হয়েছে: কোন ডেটা কখন কোথায় যাবে, কোন সিকোয়েন্সে যাবে এবং কেন এই ক্রম সামান্য পরিবর্তন করলেও তা পুরো পাইপলাইনের পরিশ্রম ও ফলাফলকে নিঃশব্দে নষ্ট করে দেবে।
তিনটি সম্পূর্ণ আলাদা প্র্যাকটিস কোশ্চেনের সেট ব্যবহার করে একজন ছাত্রকে চূড়ান্ত পরীক্ষার জন্য প্রস্তুত করার কথা ভাবুন। প্রথম সেটটি দেখে ছাত্রটি সরাসরি পড়াশোনা করে — সে সঠিক উত্তর দেখে, ভুলগুলো নিয়ে কাজ করে এবং বিষয়গুলো শেখে। দ্বিতীয় সেটটি শুধুমাত্র ছাত্রটি কেমন পড়াশোনা করছে তা মাঝপথে যাচাই করার জন্য ব্যবহৃত হয়, যাতে সিদ্ধান্ত নেওয়া যায় কখন পড়াশোনার কৌশল পরিবর্তন করতে হবে অথবা ইতিমধ্যে আয়ত্তে আসা কোনো বিষয়ের পড়াশোনা বন্ধ করতে হবে — কিন্তু ছাত্রটিকে এই সেটের প্রশ্নগুলো মুখস্থ করার সুযোগ দেওয়া হয় না। আর তৃতীয় সেটটি চূড়ান্ত পরীক্ষার দিন পর্যন্ত একদম তালাবদ্ধ করে রাখা হয় এবং শেষমেশ মাত্র একবার ব্যবহার করা হয় ছাত্রটির একটি সৎ ও চূড়ান্ত গ্রেড দেওয়ার জন্য। যদি তৃতীয় সেটের কোনো প্রশ্ন প্রথম দুটি সেটের মধ্যে লিক বা প্রকাশ হয়ে যায়, তবে চূড়ান্ত গ্রেডটি সম্পূর্ণ অর্থহীন হয়ে পড়বে — যা প্রকৃত বোঝার বদলে মুখস্থ করার কারণে অনেক বেশি দেখাবে। একটি CNN-এর training set, validation set এবং test set play বা এই তিনটি ভূমিকা পালন করে, এবং এদের একে অপরের থেকে আলাদা রাখার নিয়মটি কোনো ঐচ্ছিক বিষয় নয়।
raw ছবি (Raw Images)
যেভাবে ডেটাসেটটি সংগ্রহ করা হয়েছিল।
Preprocessing
একটি নির্দিষ্ট আকারে রিসাইজ করা, পিক্সেল মান নরমালাইজ করা।
Data Augmentation
ট্রেনিং ইমেজগুলোকে র্যান্ডমলি ফ্লিপ, রোটেট, ক্রপ বা কালার-জিটার করা।
Train/Validation/Test Split
সাধারণত ৭০/১৫/১৫ অথবা ৮০/১০/১০ অনুপাতে বিভক্ত করা, যা কঠোরভাবে আলাদা রাখা হয়।
CNN Model
চ্যাপ্টার ৯-এর উপাদানগুলো দিয়ে তৈরি নেটওয়ার্ক আর্কিটেকচার।
Forward Pass
ইনপুট ব্যাচ নেটওয়ার্কের ভেতর দিয়ে প্রবাহিত হয়ে প্রেডিকশন তৈরি করে।
Compute Loss
আসল লেবেলের সাথে প্রেডিকশনের তুলনা করে লস হিসাব করা (চ্যাপ্টার ১১)।
Backpropagation
প্রতিটি ওজনের সাপেক্ষে লসের গ্রেডিয়েন্ট ক্যালকুলেট করা (চ্যাপ্টার ৮)।
Weight Update
অপ্টিমাইজার গ্রেডিয়েন্ট ব্যবহার করে ওজনের মান আপডেট করে (চ্যাপ্টার ১২)।
Evaluation
ভ্যালিডেশন সেটের ওপর পারফরম্যান্স মাপা; এবং পরবর্তী ইপকের ফরওয়ার্ড পাস থেকে আবার শুরু করা।
| ধাপ (Stage) | কী ঘটে (What Happens) |
|---|---|
| Preprocessing | সব ছবিকে একটি নির্দিষ্ট সাইজে রিসাইজ করা, পিক্সেল মান নরমালাইজ করা (০-২৫৫ থেকে ০-১ সীমার মধ্যে নিয়ে আসা অথবা ডেটাসেটের গড়/স্ট্যান্ডার্ড ডেভিয়েশন দিয়ে স্ট্যান্ডার্ডাইজ করা)। |
| Data Augmentation | ডেটাসেটকে কৃত্রিমভাবে বড় করার জন্য র্যান্ডম ট্রান্সফরমেশন (ফ্লিপ, রোটেট, ক্রপ, কালার জিটার, জুম) প্রয়োগ করা, যা জেনারেলাইজেশন বাড়াতে ও ওভারফিটিং কমাতে সাহায্য করে। |
| Train/Val/Test Split | সাধারণত ৭০/১৫/১৫ অথবা ৮০/১০/১০ অনুপাতে ভাগ করা হয়। Training set ওজন ট্রেইন করে; validation set হাইপারপ্যারামিটার টিউন করে ও ওভারফিটিং পর্যবেক্ষণ করে; test set চূড়ান্ত, নিরপেক্ষ পারফরম্যান্স অনুমান দেয়। |
| Forward Pass | ইনপুটের ব্যাচটি নেটওয়ার্কের ভেতর দিয়ে প্রবাহিত হয়ে প্রেডিকশন ভেক্টর তৈরি করে। |
| Loss Computation | বেছে নেওয়া লস ফাংশন ব্যবহার করে প্রেডিকশনের সাথে আসল লেবেলের তুলনা করা হয়। |
| Backpropagation | ওজনের সাপেক্ষে লসের পরিবর্তনের হার বা গ্রেডিয়েন্ট ক্যালকুলেট করা হয়। |
| Weight Update | অপ্টিমাইজার গ্রেডিয়েন্ট ব্যবহার করে ওজনের মান সামান্য সংশোধন করে। |
| Evaluation | প্রতিটি ইপকের শেষে ভ্যালিডেশন সেটের পারফরম্যান্স মেপে অগ্রগতি মূল্যায়ন করা হয়, যাতে ওভারফিটিং আগেভাগেই শনাক্ত করা যায়। |
Train — মডেল সরাসরি এখান থেকেই শেখে। Validation — হাইপারপ্যারামিটার টিউন করতে এবং সেরা মডেল চেকপয়েন্ট বাছাই করতে ব্যবহৃত হয়; মডেল সরাসরি এখান থেকে শেখে না, তবে এর সিদ্ধান্ত ভ্যালিডেশন পারফরম্যান্স দ্বারা প্রভাবিত হয়। Test — শুধুমাত্র একবার, একদম শেষে ব্যবহার করা হয়, চূড়ান্ত ও সৎ পারফরম্যান্স রিপোর্ট করার জন্য।
ধরুন, একটি ট্রেনিং রান ৩২ ব্যাচ সাইজ ব্যবহার করছে, এবং ট্রেনিং সেট থেকে একটি নির্দিষ্ট ব্যাচ এসে পৌঁছাল। Preprocessing ইতিমধ্যেই প্রতিটি ছবিকে আকারে রিসাইজ করেছে এবং পিক্সেলের মানকে সীমার মধ্যে স্কেল করেছে; আর augmentation ৩২টি ছবির মধ্যে ১৪টি ছবিকে র্যান্ডমলি অনুভূমিকভাবে বা horizontally ফ্লিপ করেছে এবং বাকিগুলো অপরিবর্তিত রেখেছে।
ফরওয়ার্ড পাসে ৩২টি ছবিই CNN-এর মধ্য দিয়ে যায় (কনভোলিউশন, অ্যাক্টিভেশন, পুলিং, ফ্ল্যাটেন, ডেন্স — চ্যাপ্টার ৯ ও ১০-এর সবকিছু) এবং ৩২টি প্রেডিক্টেড ক্লাস-প্রোবাবিলিটি ভেক্টর তৈরি করে। লস ফাংশন (চ্যাপ্টার ১১ — ধরুন Categorical Cross-Entropy, যেহেতু এটি মাল্টি-ক্লাস) সেই ৩২টি প্রেডিকশনের সাথে ৩২টি ওয়ান-হট লেবেলের তুলনা করে এবং ব্যাচের গড় হিসেবে একটি স্কেলার লস ভ্যালু তৈরি করে।
এরপর ব্যাকপ্রোপাগেশন (চ্যাপ্টার ৮) সেই লস মানের সাপেক্ষে নেটওয়ার্কের প্রতিটি ওজনের গ্রেডিয়েন্ট ক্যালকুলেট করে — যা ডেন্স লেয়ার, ফ্ল্যাটেন, পুলিং, অ্যাক্টিভেশন ও কনভোলিউশন লেয়ার পেরিয়ে একদম উল্টো ক্রমানুসারে পেছনের দিকে ধাবিত হয়। সবশেষে অপ্টিমাইজার (চ্যাপ্টার ১২ — ধরুন Adam) সেই গ্রেডিয়েন্টগুলো ব্যবহার করে প্রতিটি ওজনকে সামান্য আপডেট করে। এটিই হলো একটি ট্রেনিং স্টেপ (training step)। ট্রেনিং সেটের সবগুলো ব্যাচ এভাবে রান করার পর একটি ইপক সম্পন্ন হয়; ভ্যালিডেশন সেটের পারফরম্যান্স ভালো হওয়া বন্ধ না হওয়া পর্যন্ত ইপকের পুনরাবৃত্তি করা হয় এবং এভাবে ট্রেনিং শেষ হয় — যার পর সম্পূর্ণ আলাদা করে রাখা টেস্ট সেটের ওপর চূড়ান্ত মূল্যায়ন বা evaluation করা হয়।
def train_pipeline(model, optimizer, loss_fn, train_loader, val_loader, num_epochs):
"""
High-level training loop wiring together every stage of the pipeline:
forward pass -> loss -> backprop -> optimizer update -> per-epoch evaluation.
"""
for epoch in range(num_epochs):
model.set_mode("train")
for images, labels in train_loader:
images = augment(images) # data augmentation (শুধুমাত্র train সেটের জন্য)
predictions = model.forward(images) # forward pass
loss = loss_fn(predictions, labels) # compute loss
gradients = backpropagate(loss, model) # backward pass
optimizer.update(model.weights, gradients) # weight update
model.set_mode("eval") # কোনো augmentation ও weight update হবে না
val_loss, val_acc = evaluate(model, val_loader)
print(f"ইপক {epoch}: val_loss={val_loss:.4f}, val_acc={val_acc:.4f}")
return model
def final_test_evaluation(model, test_loader):
"""সব ট্রেনিং ও হাইপারপ্যারামিটার টিউনিং শেষ হওয়ার পর হুবহু একবার এটি রান করা হয়।"""
model.set_mode("eval")
test_loss, test_acc = evaluate(model, test_loader)
print(f"চূড়ান্ত test_loss={test_loss:.4f}, test_acc={test_acc:.4f}")
return test_loss, test_accকোডটির কয়েকটি বিশেষ দিক লক্ষ্য করুন:
model.set_mode("eval") শুধু অগমেন্টেশন এড়ানোর চেয়েও বেশি কাজ করে — Dropout এবং Batch Normalization-এর মতো লেয়ারগুলো ট্রেনিং ও ইভালুয়েশনের সময় ভিন্নভাবে আচরণ করে, এবং মোড পরিবর্তন করতে ভুলে যাওয়া খারাপ ভ্যালিডেশন সংখ্যার একটি সাধারণ কিন্তু নিঃশব্দ কারণ।final_test_evaluation একটি সম্পূর্ণ আলাদা ফাংশন, যাতে কোড দেখেই খুব স্পষ্টভাবে বোঝা যায় যে এটি অন্যান্য সব সিদ্ধান্ত চূড়ান্ত হওয়ার পর একদম শেষে হুবহু একবার কল করার জন্য তৈরি হয়েছে।ভ্যালিডেশন বা টেস্ট সেটে এমনভাবে data augmentation প্রয়োগ করা যা মূল্যায়নকে বদলে দেয়, অথবা — এর চেয়েও খারাপ — কোনো টেস্ট ডেটাকে ট্রেনিং বা ভ্যালিডেশনে লিক হতে দেওয়া, রিপোর্ট করা অ্যাকুরেসিকে অতিরিক্ত আশাবাদী ও অবিশ্বস্ত করে তোলে। একটি মডেল হয়তো চমৎকার পারফরম্যান্স দেখাতে পারে, কিন্তু ডিপ্লয় করার পর ভয়াবহভাবে ব্যর্থ হতে পারে — কারণ তার "চূড়ান্ত" মূল্যায়নটি আসলে কখনো সত্যিকার অর্থে ট্রেনিং থেকে স্বাধীন ছিলই না।

টেস্ট সেট ব্যবহার করে যেকোনো সিদ্ধান্ত নেওয়া — যেমন দুটি ট্রেইন করা মডেলের মধ্যে বাছাই করা — এবং তারপর সেই একই টেস্ট সেটের অ্যাকুরেসিকে "চূড়ান্ত" সংখ্যা হিসেবে রিপোর্ট করা ডেটা লিকেজের একটি সূক্ষ্ম রূপ। টেস্ট সেট শুধুমাত্র তখনই বাস্তব-জগতের পারফরম্যান্সের একটি নিরপেক্ষ অনুমান দেয়, যখন এটি একদম শেষ মূল্যায়নের আগে কোনো সিদ্ধান্তকেই প্রভাবিত করে না।
Dropout এবং Batch Normalization-এর মতো লেয়ারগুলো ট্রেনিং ও ইভালুয়েশনের সময় ভিন্নভাবে আচরণ করে। মডেল এখনো "ট্রেনিং মোডে" থাকা অবস্থায় ভ্যালিডেশন বা টেস্ট ইভালুয়েশন চালালে তা নয়েজি, অসামঞ্জস্যপূর্ণ ও বিভ্রান্তিকর মেট্রিক্স তৈরি করে — এই বাগটি সহজেই ঢুকে যায় এবং সহজেই চোখ এড়িয়ে যায়।
প্রতিটি ডিপ লার্নিং ফ্রেমওয়ার্কের হাই-লেভেল ট্রেনিং API (যেমন PyTorch-এর ট্রেনিং লুপ, Keras-এর model.fit) এই পুরো পাইপলাইনটিকে ভেতরে ভেতরে স্বয়ংক্রিয় করে দেয়। প্রতিটি ধাপ আসলে কী করছে তা জানাই আপনাকে এমন একটি ট্রেনিং রান ডিবাগ করতে সাহায্য করে যা নিঃশব্দে ভুল আচরণ করছে — যেমন সন্দেহজনকভাবে নিখুঁত ভ্যালিডেশন অ্যাকুরেসি, প্রোডাকশন পারফরম্যান্সের সাথে না মেলা একটি টেস্ট স্কোর, অথবা এমন একটি মডেল যা চমৎকারভাবে ট্রেইন হয় কিন্তু খারাপভাবে জেনারেলাইজ করে — পুরো পাইপলাইনটিকে একটি অস্বচ্ছ ব্ল্যাক বক্স হিসেবে না দেখে।