Chapter 7 of 28
Tracing an image through every layer, step by step
আলাদা আলাদাভাবে প্রতিটি ফর্মুলা — কনভোলিউশন, পুলিং, সফটম্যাক্স — জানা এক জিনিস; আর একটি বাস্তব ছবির ওপর সেগুলোকে ক্রমানুসারে একের পর এক জুড়ে কাজ করতে দেখা সম্পূর্ণ ভিন্ন কথা। এই চ্যাপ্টারটি আগের চ্যাপ্টারে আলোচনা করা সমীকরণগুলোকে একত্রিত করে একটিমাত্র অবিচ্ছিন্ন পাইপলাইনে রূপান্তর করবে: যাকে বলা হয় ফরওয়ার্ড প্রোপাগেশন (forward propagation)। এটি হলো মূলত একটি raw ইমেজের টেন্সরকে নির্দিষ্ট ও সুশৃঙ্খল ধাপগুলোর মাধ্যমে ধাপে ধাপে ক্লাস প্রোবাবিলিটি ডিস্ট্রিবিউশনে বা আনুমানিক সম্ভাবনায় রূপান্তর করার প্রক্রিয়া।
কল্পনা করুন, একটি ট্রেইন করা CNN-এর কাছে নিজে হাতে লেখা কোনো সংখ্যার ছবি দিয়ে জিজ্ঞেস করলেন, "এটি কোন সংখ্যা?" নেটওয়ার্কের ভেতরে কোনো অলৌকিক কিছু ঘটে না — ইমেজের টেন্সরটিকে ওয়েট দিয়ে গুণ করা হয়, বায়াস যোগ করা হয়, থ্রেশহোল্ড করা হয় এবং নির্দিষ্ট ধাপের মাধ্যমে ছোট করা হয়। এর প্রতিটি ধাপই আসলে আপনার আগে থেকে চেনা এক একটি গাণিতিক সমীকরণ, যা শেষমেশ ১০টি সংখ্যায় এসে শেষ হয় যাদের যোগফল ঠিক ১ — যা নির্দেশ করে কোন সংখ্যাটি হওয়ার সম্ভাবনা কতটুকু। ফরওয়ার্ড প্রোপাগেশন হলো সেই সম্পূর্ণ যাত্রাটির প্রথম থেকে শেষ পর্যন্ত নিখুঁত বিবরণ।
কোনো জটিল চিহ্নে বা নোটে যাওয়ার আগে, ছবির শেপ বা আকারের পরিবর্তনের গল্পটি মাথায় রাখুন: ইনপুটটি শুরু হয় বেশ চওড়া ও অগভীর আকারে (বড় উচ্চতা ও প্রস্থ, কিন্তু কম চ্যানেল — ১ বা ৩), এবং শেষমেশ এটি চিপে গিয়ে বেশ সংকীর্ণ ও গভীর আকারে রূপ নেয় (ছোট উচ্চতা ও প্রস্থ, কিন্তু অনেক বেশি চ্যানেল)। এরপর এটি একটি ফ্ল্যাট ভেক্টরে রূপান্তরিত হয় এবং ডেন্স লেয়ার পেরিয়ে ক্লাস স্কোর বা লজিটে পরিণত হয়। প্রতিটি কনভোলিউশন ও পুলিং ধাপ স্থানিক আকারের বিনিময়ে একটু বেশি গভীরতা বা বিমূর্ততা কিনে নেয়; Flatten হলো সেই বিশেষ মুহূর্ত যখন 'স্পেশাল টেন্সর' থেকে 'সাধারণ ভেক্টরে' রূপান্তর ঘটে; আর একদম শেষে থাকা fully connected লেয়ার ও সফটম্যাক্স কিন্তু স্থানিক বা স্পেশাল কোনো কাজই করে না — ওই পর্যায়ে এসে এটি মূলত কনভোলিউশনাল স্ট্যাক থেকে এক্সট্র্যাক্ট করা ফিচারগুলোর ওপর সাধারণ MLP-র মতো সিদ্ধান্ত গ্রহণ করে মাত্র।
একটা কারখানার প্রোডাকশন লাইনের কথা ভাবুন — কাঁচামাল (raw material) এক প্রান্ত দিয়ে ঢোকে, একের পর এক স্টেশন পার হয় (কাটা, রং করা, প্যাক করা), আর অন্য প্রান্ত দিয়ে বেরিয়ে আসে সম্পূর্ণ তৈরি একটা পণ্য। Forward propagation ঠিক এমনই একটা লাইন — raw ছবি (raw material) এক প্রান্ত দিয়ে ঢোকে, প্রতিটা layer একটা করে স্টেশন (convolution, activation, pooling), আর শেষ প্রান্তে বেরিয়ে আসে class probability — একটা সুনির্দিষ্ট সিদ্ধান্ত। প্রতিটা স্টেশনের কাজ আলাদা, কিন্তু কোনো স্টেশনেই কোনো জাদু নেই — শুধু আগে শেখা ফর্মুলার সরল, পুনরাবৃত্ত প্রয়োগ।

ছবিটি নেটওয়ার্কের ভেতরে আকারের একটি টেন্সর হিসেবে প্রবেশ করে — যা উচ্চতা, প্রস্থ এবং কালার চ্যানেল নিয়ে গঠিত। এর ওপর এখনও কোনো কাজ করা হয়নি; এটি সরাসরি raw পিক্সেল ডেটা।
যেখানে: হলো পূর্ববর্তী লেয়ারের আউটপুট (যা এই লেয়ারের ইনপুট), হলো লেয়ারের -তম ফিল্টার (যা সব ইনপুট চ্যানেল জুড়ে বিস্তৃত), হলো ওই ফিল্টারের বায়াস, এবং হলো -তম আউটপুট ফিচার ম্যাপের পজিশনের প্রি-অ্যাক্টিভেশন মান।
সহজ কথা: এটি আগের চ্যাপ্টারে শেখা 2D কনভোলিউশন ফর্মুলাই, যেখানে শুধু একটি কালার চ্যানেলের বদলে সবগুলো ইনপুট চ্যানেল জুড়ে ডাবল সামেশনের (double summation) হিসাব স্পষ্ট করে দেখানো হয়েছে। এই লেয়ারের সংখ্যক ফিল্টারের প্রতিটি নিজস্ব একটি করে সম্পূর্ণ ফিচার ম্যাপ তৈরি করে, যার ফলে লেয়ারের মোট আউটপুট দাঁড়ায় সংখ্যক স্তুপীকৃত ফিচার ম্যাপ।
সহজ কথা: ধাপ ২-এ ক্যালকুলেট করা প্রতিটি মান স্বতন্ত্রভাবে ReLU-র মধ্য দিয়ে প্রবাহিত হয় — পজিটিভ মানগুলো কোনো পরিবর্তন ছাড়াই পার হয়ে যায়, আর নেগেটিভ মানগুলো কেটে শূন্য করে দেওয়া হয়। এটিই নেটওয়ার্কটিকে নন-লিনিয়ার রাখে; এটি না থাকলে যতগুলো লেয়ারই যোগ করুন না কেন, কনভোলিউশনগুলো গাণিতিকভাবে ভেঙে একটিমাত্র বড় লিনিয়ার অপারেশনে পরিণত হতো।
সহজ কথা: প্রতিটি অ্যাক্টিভেশন ম্যাপ আলাদাভাবে ডাউন-স্যাম্পল করা হয়, যেখানে প্রতি ছোট উইন্ডোর ভেতরের শুধুমাত্র সবচেয়ে বড় বা শক্তিশালী মানটি রাখা হয়। এটিই সেই ধাপ যা বাস্তবে লেয়ারে লেয়ারে স্থানিক আকারকে ছোট করে আনে।
আর্কিটেকচারের নকশা অনুযায়ী ধাপ ২ থেকে ৪ পর্যন্ত পর্যায়ক্রমে পুনরাবৃত্তি করা হয়। প্রতিটি ব্লকের পুলিং করা আউটপুট পরবর্তী ব্লকের ইনপুট হিসেবে কাজ করে। এভাবে ক্রমান্বয়ে ব্লকের পর ব্লক পেরোনোর সময় স্থানিক আকার (উচ্চতা ও প্রস্থ) ছোট হতে থাকে এবং চ্যানেলের গভীরতা (চ্যানেল সংখ্যা) বৃদ্ধি পেতে থাকে — যা আমরা আগের চ্যাপ্টারের আকারের টেবিলে দেখেছি।
সর্বশেষ ব্লকটি আকারের একটি 3D টেন্সর তৈরি করে। Flatten কোনো অতিরিক্ত হিসাব বা গাণিতিক কাজ ছাড়াই এটিকে শুধুমাত্র রিসেপ (reshape) করে আকারের একটি 1D ভেক্টরে রূপান্তর করে — ঠিক এই মুহূর্ত থেকেই নেটওয়ার্কটি স্থানিক বা স্পেশাল চিন্তা করা বন্ধ করে দিয়ে একটি লম্বা ফিচারের তালিকার কথা ভাবতে শুরু করে, যা ডেন্স লেয়ারের ইনপুটের জন্য একদম প্রস্তুত।
যেখানে: হলো ধাপ ৬ থেকে পাওয়া ফ্ল্যাটেন করা ফিচার ভেক্টর, এবং হলো ডেন্স লেয়ারের ওয়েট ও বায়াস, এবং হলো একটি নন-লিনিয়ার অ্যাক্টিভেশন (সাধারণত আবার ReLU)।
সহজ কথা: এটি মূলত কোর্সের শুরুতে শেখা MLP-র নিউরন সমীকরণ, যা এখানে raw পিক্সেলের ওপর প্রয়োগ না করে কনভোলিউশনাল স্ট্যাক থেকে এক্সট্র্যাক্ট করা বা বের করে নেওয়া চমৎকার ফিচারগুলোর ওপর প্রয়োগ করা হচ্ছে। "কোন প্যাটার্নটি ছবির কোন জায়গায় আছে" তা চেনার কঠিন কাজটি আগেই শেষ হয়ে গেছে — এই ধাপটি শুধু সেই প্যাটার্নগুলোকে একত্রিত করে একটি সামগ্রিক সিদ্ধান্ত নেয়।
সহজ কথা: সর্বশেষ ডেন্স লেয়ারটি প্রতি ক্লাসের জন্য একটি করে raw স্কোর বা লজিট (logit) তৈরি করে, মোট সংখ্যক; সফটম্যাক্স সেগুলোকে একটি বাস্তবসম্মত প্রোবাবিলিটি ডিস্ট্রিবিউশনে রূপান্তর করে। যার ফলে প্রাপ্ত মানগুলোর মধ্যে যেটি সবচেয়ে বড়, সেটিই নেটওয়ার্কের প্রেডিক্ট করা ক্লাস বা শ্রেণী হিসেবে গণ্য হয়।
পরীক্ষকের সামনে নিজের ভাষায় ব্যাখ্যা করার জন্য প্রস্তুত থাকুন যে কেন প্রতিটি ধাপে টেন্সরের আকার বা শেপ পরিবর্তিত হয়: এটি কনভোলিউশন ও পুলিংয়ের কারণে স্থানিক দিক থেকে ছোট হয়, গভীর লেয়ারে বেশি ফিল্টার যোগ করার ফলে চ্যানেলের সংখ্যা বাড়ে, এবং শেষমেশ Flatten ধাপে এটি একটি 1D ভেক্টরে পরিণত হয়, যা আউটপুটে এসে দৈর্ঘ্যের একটি প্রোবাবিলিটি ভেক্টর হিসেবে শেষ হয়। কোনো একটি অপরিচিত আর্কিটেকচার দেখে তার এই ডাইমেনশন পরিবর্তনের গল্পটি লেয়ার বাই লেয়ার বলতে পারাটা অন্যতম কমন ইন্টারভিউ প্রশ্ন।
"C-A-P, Flatten, Fully, Finish" — Convolution, Activation, Pooling (এটি আর্কিটেকচারের চাহিদা অনুযায়ী বারবার ঘটবে), তারপর Flatten, Fully Connected, এবং সফটম্যাক্স দিয়ে Finish।
NumPy ব্যবহার করে ওপরের ৮টি ধাপ হুবহু অনুসরণ করে একটি কনভোলিউশন ব্লক, ফ্ল্যাটেন, একটি ডেন্স লেয়ার এবং সফটম্যাক্স সহ একটি বাস্তব ফরওয়ার্ড পাস নিচে কোডে দেখানো হলো:
import numpy as np
def relu(z):
return np.maximum(0, z)
def softmax(z):
exp_z = np.exp(z - np.max(z)) # গাণিতিক স্থিতিশীলতার জন্য সর্বোচ্চ মান বিয়োগ করা হয়েছে
return exp_z / np.sum(exp_z)
def conv2d(x, kernel, bias, stride=1, padding=0):
"""x: (H, W, C_in), kernel: (F_h, F_w, C_in), bias: scalar -> (H', W') ফিচার ম্যাপ।"""
if padding > 0:
x = np.pad(x, ((padding, padding), (padding, padding), (0, 0)))
h, w, _ = x.shape
fh, fw, c_in = kernel.shape
out_h = (h - fh) // stride + 1
out_w = (w - fw) // stride + 1
out = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
r, c = i * stride, j * stride
patch = x[r:r + fh, c:c + fw, :]
out[i, j] = np.sum(patch * kernel) + bias
return out
def max_pool(x, size=2, stride=2):
h, w = x.shape
out_h = (h - size) // stride + 1
out_w = (w - size) // stride + 1
out = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
r, c = i * stride, j * stride
out[i, j] = np.max(x[r:r + size, c:c + size])
return out
# --- ধাপ ১: ইনপুট ---
image = np.random.rand(8, 8, 1) # H=8, W=8, C=1 (গ্রে-স্কেল)
# --- ধাপ ২-৪: একটি Conv -> ReLU -> Pool ব্লক, ২টি ফিল্টার সহ ---
filters = [np.random.randn(3, 3, 1) * 0.1 for _ in range(2)]
biases = [0.0, 0.0]
feature_maps = []
for k, b in zip(filters, biases):
z = conv2d(image, k, b, stride=1, padding=0) # ধাপ ২
a = relu(z) # ধাপ ৩
p = max_pool(a, size=2, stride=2) # ধাপ ৪
feature_maps.append(p)
stacked = np.stack(feature_maps, axis=-1) # (h', w', F)
# --- ধাপ ৬: ফ্ল্যাটেন ---
v = stacked.flatten()
# --- ধাপ ৭: Fully Connected ---
W_fc = np.random.randn(10, v.shape[0]) * 0.1
b_fc = np.zeros(10)
z_fc = W_fc @ v + b_fc
a_fc = relu(z_fc)
# --- ধাপ ৮: আউটপুট (Softmax) ---
y_hat = softmax(a_fc)
print("প্রেডিক্টেড প্রোবাবিলিটি বা সম্ভাবনা:", np.round(y_hat, 3))
print("প্রেডিক্টেড ক্লাস বা শ্রেণী:", np.argmax(y_hat))এই কোডের প্রতিটি ফাংশন কল ওপরের এক একটি ধাপের সাথে সরাসরি মিলে যায়: conv2d হলো ধাপ ২, relu হলো ধাপ ৩, max_pool হলো ধাপ ৪, .flatten() হলো ধাপ ৬, W_fc @ v + b_fc হলো ধাপ ৭, and softmax হলো ধাপ ৮। এই র্যান্ডম ওয়েটগুলোর জায়গায় যদি ট্রেইন করা ওয়েট বসিয়ে দেওয়া যায় এবং ব্লকের সংখ্যা বাড়িয়ে নেওয়া যায়, তবে এটিই আসলে যেকোনো বাস্তব CNN-এর ইনফ্যারেন্স পাথ (inference path) হিসেবে কাজ করে।
Flatten কোনো ম্যাজিক বা জটিল কম্পিউটেশন নয় — এটি কেবল একটি রিসেপ। এখানে কোনো মান পরিবর্তিত বা অপসারিত হয় না। এই ধাপে যা হারিয়ে যায় তা হলো পিক্সেলগুলোর মধ্যকার পারস্পরিক 2D স্থানিক বিন্যাস বা অ্যারেঞ্জমেন্ট, ভেতরের ডেটা বা মানগুলো নয়।
চ্যানেল ও কার্নেল পজিশন নিয়ে ট্রিপল সামেশন লেখার উত্তেজনায় শেষের বায়াসটি বাদ দিয়ে ফেলা খুব কমন একটি ভুল — বায়াস কিন্তু অবহেলার কোনো বিষয় নয়, এটি লেয়ারের একটি বাস্তব ও অত্যন্ত গুরুত্বপূর্ণ লার্নেবল প্যারামিটার।
এই ৮টি ধাপের পুঙ্খানুপুঙ্খ বিবরণই হলো মূলত সেই মূল ভিত্তি যা বাস্তব প্রোডাকশন সিস্টেমে কোনো ছবি ইনপুট দেওয়ার সাথে সাথে যান্ত্রিকভাবে সচল হয়ে ওঠে — তা ফেস রিকগনিশন দিয়ে ফোন আনলক করার কাজই হোক বা কোনো মেডিকেল স্ক্যান থেকে রোগ সনাক্ত করার কাজই হোক। ট্রেনিংয়ের মাধ্যমে ওয়েটগুলো আগে থেকেই শিখে নেওয়া থাকে (যা আমরা এই কোর্সের পরবর্তী অংশে জানব), তবে ফরওয়ার্ড পাসের এই গতিপথটি সবসময় একই থাকে: একদিকে ইনপুট টেন্সর ঢোকে, প্রতিটা সুনির্দিষ্ট ধাপ একে একে পার হয়, আর অন্যদিকে বেরিয়ে আসে class probabilities।