Chapter 5 of 28
The standard pipeline from pixels to predictions
আপনি এখন প্রতিটি উপাদান — কনভোলিউশন ফিল্টার, ওয়েট শেয়ারিং, পুলিং — আলাদা আলাদাভাবে চেনেন। কিন্তু শুধুমাত্র উপকরণ বা উপাদান থাকলেই তো আর রান্না হয়ে যায় না। একটি CNN কিন্তু "কতগুলো এলোমেলো কনভোলিউশন লেয়ারের স্তুপ" নয়; এটি হলো সুনির্দিষ্ট ও বারবার ব্যবহারযোগ্য একটি পাইপলাইন (pipeline), যা দীর্ঘ কয়েক দশকের গবেষণায় তৈরি হয়েছে এবং উপাদানগুলোকে এমন এক নিয়মে সাজিয়েছে যা সফলভাবে কাজ করে। এই পাইপলাইনের নকশাটি একবার আপনার মাথায় গেঁথে নিতে পারলে, আপনি LeNet, AlexNet, ResNet সহ আজ পর্যন্ত তৈরি হওয়া প্রায় প্রতিটি ইমেজ ক্লাসিফায়ারের ভেতরে এটিকে সাথে সাথে চিনে ফেলতে পারবেন — ভেতরের খুঁটিনাটি হয়তো বদলাবে, কিন্তু মূল কঙ্কাল বা স্কেলেটনটি (skeleton) একই থাকবে।
ভাবুন তো, একজন রেডিওলজিস্ট কীভাবে একটি এক্স-রে রিপোর্ট পড়েন? তারা কিন্তু পুরো ছবিটির দিকে একপলক তাকিয়েই সাথে সাথে রোগ নির্ণয় বা ডায়াগনসিস করে ফেলেন না। তারা প্রথমে খুব সাধারণ বা লো-লেভেল কাঠামো — হাড়ের কিনারা বা এজ (edge), ডেনসিটি গ্রেডিয়েন্ট (density gradients) — পর্যবেক্ষণ করেন। এরপর তারা আস্তে আস্তে টেক্সচার এবং লোকাল আকৃতিগুলো খেয়াল করেন — এই নির্দিষ্ট প্যাচটি কি স্বাভাবিকের চেয়ে বেশি ঘন নাকি বেশি কালো? লোকাল বা স্থানীয় স্তরে এমন একটি ধারণা গুছিয়ে নেওয়ার পরেই তারা পুরো ছবিটি নিয়ে চিন্তা করেন এবং চূড়ান্ত সিদ্ধান্তে পৌঁছান — হাড় ভেঙেছে নাকি ভাঙেনি। একটি CNN-এর আর্কিটেকচার ঠিক এই প্রক্রিয়াটিকে অনুকরণ করেই সাজানো হয়েছে: প্রথমে সাধারণ লোকাল প্যাটার্ন চেনা, পরে সেগুলোকে মিলিয়ে আরও জটিল ও বিমূর্ত রূপ তৈরি করা, এবং সবশেষে সিদ্ধান্ত নেওয়ার চূড়ান্ত ধাপ।
একটি CNN আর্কিটেকচার হলো লেয়ার বা স্তরগুলোর একটি নির্দিষ্ট বিন্যাস — যেখানে সাধারণত ফিচার এক্সট্র্যাকশনের জন্য পর্যায়ক্রমে Convolution, Activation এবং Pooling লেয়ার থাকে — এবং এর পরে ক্লাসিফিকেশনের জন্য Flatten ও Fully Connected লেয়ার থাকে, যা শেষমেশ একটি Output লেয়ারে গিয়ে শেষ হয়।
সামগ্রিকভাবে দেখলে, প্রতিটি ক্লাসিক্যাল CNN ঠিক একই আকার বা নিয়ম অনুসরণ করে: প্রথমে ফিচার এক্সট্র্যাক্ট বা বের করে নেওয়া, এবং পরে সেই ফিচারের ওপর ভিত্তি করে ক্লাসিফাই বা শ্রেণীবিভাগ করা।

খাতায় দ্রুত স্কেচ করার জন্য, এই একই পাইপলাইনকে এভাবে সংক্ষেপে প্রকাশ করা যায়:
ইনপুট ইমেজ (Input Image)
|
v
[ Convolution ] -> [ Activation (ReLU) ] -> [ Pooling ] }
^_____________________________________________________| N বার পুনরাবৃত্তি
|
v
[ Flatten ] -> [ Fully Connected Layer(s) ] -> [ Output (Softmax) ]| ধাপ (Stage) | ভূমিকা (Role) |
|---|---|
| Input | raw ইমেজের টেন্সর, যার আকার H × W × C। |
| Convolution | ইনপুটের ওপর লার্নেবল ফিল্টারগুলোকে স্লাইড করে লোকাল প্যাটার্ন (কিনারা, টেক্সচার) সনাক্ত করে; এটি ফিচার ম্যাপ তৈরি করে। |
| Activation | একটি নন-লিনিয়ার ফাংশন (সাধারণত ReLU) উপাদান-ভিত্তিক বা element-wise প্রয়োগ করে, যাতে নেটওয়ার্কটি জটিল ও নন-লিনিয়ার প্যাটার্ন শিখতে পারে। |
| Pooling | ফিচার ম্যাপের ডাউন-স্যাম্পল করে (যেমন Max Pooling), যা স্থানিক আকার ও কম্পিউটেশনাল খরচ কমায় এবং একটি হালকা ট্রান্সলেশন ইনভ্যারিয়েন্স দেয়। |
| (Repeat) | Convolution + Activation + Pooling ব্লকগুলোকে একটার ওপর আরেকটা সাজানো বা স্ট্যাক করা হয়; প্রথম দিকের ব্লকগুলো সাধারণ ফিচার শেখে, গভীর ব্লকগুলো জটিল ও বিমূর্ত ফিচার শেখে। |
| Flatten | চূড়ান্ত 3D ফিচার ম্যাপগুলোকে একটি 1D ভেক্টরে রূপান্তর বা ফ্ল্যাট করে, যাতে সেগুলোকে একটি fully connected লেয়ারে পাঠানো যায়। |
| Fully Connected (FC) | ডেন্স লেয়ারসমূহ যা এক্সট্র্যাক্ট করা সমস্ত ফিচারকে একত্রিত করে সামগ্রিক ছবি নিয়ে চিন্তা করে এবং ক্লাস স্কোর (logits) তৈরি করে। |
| Output | চূড়ান্ত লেয়ার (মাল্টি-ক্লাসের জন্য সাধারণত Softmax, অথবা বাইনারির জন্য Sigmoid) যা লজিটগুলোকে প্রোবাবিলিটি বা সম্ভাবনায় রূপান্তর করে। |
এই চ্যাপ্টারের সবচেয়ে গুরুত্বপূর্ণ সহজাত ধারণাটি হলো: গভীরতা বা depth ছবিকে বিমূর্ত বা abstract করে তোলে। একদম শুরুর দিকের কনভোলিউশনাল লেয়ারগুলো, যা raw পিক্সেলের সবচেয়ে কাছাকাছি থাকে, তারা শুধুমাত্র খুব সহজ সরল জিনিস শেখে — যেমন কিনারা বা এজ (edges) এবং কোণ (corners)। আরও কয়েক লেয়ার গভীরে গেলে, নেটওয়ার্কটি সেই কিনারাগুলোকে একত্রিত করে বিভিন্ন টেক্সচার ও ছোট আকৃতি তৈরি করতে শেখে। আরও গভীরে গেলে, টেক্সচারগুলো মিলে ছবির চেনা অংশ তৈরি করে — যেমন একটি চোখ, বা গাড়ির চাকা। একদম শেষ দিকের কনভোলিউশনাল লেয়ারে পৌঁছাতে পৌঁছাতে, নেটওয়ার্কটি কার্যত আস্ত অবজেক্ট বা বস্তু চিনতে শুরু করে।

"কিনারা → টেক্সচার → অংশ → অবজেক্ট" — এই নিচ থেকে ওপরে ওঠার হায়ারার্কিটির কারণেই মূলত CNN-এ একটিমাত্র লেয়ারের ওপর ভরসা না করে অনেকগুলো লেয়ার সাজানো হয়। প্রতিটি অতিরিক্ত লেয়ার রিসেপ্টিভ ফিল্ডকে আরও চওড়া করে এবং নেটওয়ার্কের রিপ্রেজেন্টেশনাল ক্ষমতা বাড়ায়।
একটি বাস্তব ইনপুটের ওপর এই পাইপলাইনটি কীভাবে কাজ করে তা দেখলে ছবিটি সহজ হবে। CIFAR-10 ডেটাসেটের সাইজের রঙিন ছবির জন্য একটি সাধারণ CNN দেখতে এমন হতে পারে:
| লেয়ার (Layer) | আউটপুট আকার (Output Shape) | প্যারামিটার সংখ্যা (প্রায়) |
|---|---|---|
| Input | 32 × 32 × 3 | — |
| Conv (32 filters, 3×3) + ReLU | 32 × 32 × 32 | 896 |
| MaxPool (2×2) | 16 × 16 × 32 | 0 |
| Conv (64 filters, 3×3) + ReLU | 16 × 16 × 64 | 18,496 |
| MaxPool (2×2) | 8 × 8 × 64 | 0 |
| Flatten | 4096 | 0 |
| Dense(128) + ReLU | 128 | 524,416 |
| Dense(10) + Softmax | 10 | 1,290 |
এখানের প্যাটার্নটি খেয়াল করুন: প্রতি পুলিং ধাপে স্থানিক আকার (32 → 16 → 8) ছোট হচ্ছে, যেখানে প্রতিটি কনভোলিউশন ধাপে গভীরতা বা চ্যানেলের সংখ্যা (3 → 32 → 64) বাড়ছে — নেটওয়ার্কটি যত গভীরে যায়, স্থানিক রেজোলিউশনের বিনিময়ে তত সমৃদ্ধ ও বিমূর্ত ফিচার রিপ্রেজেন্ট করতে শেখে। স্থানিক মাত্রা যখন যথেষ্ট ছোট হয়ে যায়, তখন Flatten everything সবকিছুকে একটি একক ভেক্টরে রূপান্তর করে যাতে ডেন্স লেয়ারগুলো সেটির ওপর সিদ্ধান্ত নিতে পারে।
ওপরের টেবিলের মতো একটি লেয়ার কনফিগারেশন দেওয়া থাকলে, ফিল্টার সাইজ, স্ট্রাইড এবং প্যাডিং হিসাব করে নিজে হাতে আউটপুট সাইজ এবং প্যারামিটার কাউন্ট বের করার জন্য প্রস্তুত থাকুন। যে ফর্মুলাগুলো ব্যবহার করে এটি করা হয়, সেগুলোই আমাদের পরবর্তী চ্যাপ্টারের মূল বিষয়, যা আপনাকে খুব নিখুঁতভাবে আয়ত্ত করতে হবে।
এখনই কোনো মডেল ট্রেন না করলেও, এই পাইপলাইনটি কোডে কীভাবে কাজ করে তা দেখে নেওয়া কাজের হবে — এখানে আমরা ওপরের টেবিলটিকে অনুসরণ করে প্রতিটি ধাপের আউটপুট সাইজ হিসাব করে দেখব।
import numpy as np
def conv_output_size(n, f, p, s):
"""কনভোলিউশন/পুলিং অপারেশনের পর একটি স্থানিক মাত্রার আউটপুট সাইজ।"""
return (n - f + 2 * p) // s + 1
# ওপরের ছোট CNN-টির আকারগুলো ট্র্যাক করা
h = w = 32
h = conv_output_size(h, f=3, p=1, s=1) # 'same' padding conv -> stays 32
w = conv_output_size(w, f=3, p=1, s=1)
print(f"Conv1-এর পর: {h}x{w}x32")
h = conv_output_size(h, f=2, p=0, s=2) # 2x2 max pool, stride 2 -> halves
w = conv_output_size(w, f=2, p=0, s=2)
print(f"Pool1-এর পর: {h}x{w}x32")
h = conv_output_size(h, f=3, p=1, s=1)
w = conv_output_size(w, f=3, p=1, s=1)
print(f"Conv2-এর পর: {h}x{w}x64")
h = conv_output_size(h, f=2, p=0, s=2)
w = conv_output_size(w, f=2, p=0, s=2)
print(f"Pool2-এর পর: {h}x{w}x64 -> flatten to {h*w*64}")এই ছোট হেল্পার ফাংশনটি শুরুতেই মাথায় গেঁথে নেওয়া খুব ভালো — এটি ঠিক সেই ফর্মুলা যা পরবর্তী চ্যাপ্টারে গাণিতিকভাবে প্রতিপাদন করা হবে, এবং নিজে হাতে এভাবে সাইজ হিসাব করতে পারা হোয়াইটবোর্ড ইন্টারভিউতে খুবই কমন একটি কাজ।
Max Pooling-এর মতো পুলিং অপারেশনগুলো একটি নির্দিষ্ট নিয়ম প্রয়োগ করে (যেমন সর্বোচ্চ মান নেওয়া) — এখানে কোনো কিছু শেখার বা আপডেট করার বিষয় নেই। তাই প্রতি কনভোলিউশন ও ডেন্স লেয়ারে প্যারামিটার থাকলেও পুলিং লেয়ারের প্যারামিটার সংখ্যা সবসময় ০ হয়।
বাস্তবে এর উল্টোটিই ঘটে: গভীরতার সাথে সাথে স্থানিক আকার (উচ্চতা এবং প্রস্থ) ছোট হয় ঠিকই, কিন্তু ফিল্টারের সংখ্যা — এবং এর ফলে চ্যানেলের সংখ্যা — বৃদ্ধি পায়, যাতে প্রতিটি স্থানিক অবস্থানে আরও সমৃদ্ধ ও বৈচিত্র্যময় ফিচার রিপ্রেজেন্ট করা যায়।
দশকের পুরনো LeNet-5 থেকে শুরু করে আধুনিক ResNet বা EfficientNet — যেকোনো ইমেজ ক্লাসিফায়ারের সোর্স কোড খুলে দেখলেই আপনি নির্দিষ্ট কিছু পরিবর্তনের নিচে ঠিক এই একই মূল কঙ্কাল বা স্কেলেটনটি খুঁজে পাবেন: বারবার ব্যবহৃত conv-activation-pool ব্লকের মাধ্যমে ফিচার এক্সট্র্যাকশন, ফ্ল্যাটেন, এবং শেষে ক্লাসিফিকেশন। এই পাইপলাইনটি বুঝতে পারলেই আপনি যেকোনো অপরিচিত আর্কিটেকচার ডায়াগ্রাম দেখে সাথে সাথেই তার প্রতিটা ধাপের কাজ ধরে ফেলতে পারবেন। পরবর্তী চ্যাপ্টারের গণিত আপনাকে এই পাইপলাইনের ওপর কেবল অনুমানের ওপর ভিত্তি করে নয়, একদম নিখুঁত ও যথাযথ গাণিতিক হিসাবের মাধ্যমে কাজ করার ক্ষমতা এনে দেবে।