Chapter 8 of 28
How gradients flow backward through convolution and pooling
এখন পর্যন্ত আমরা CNN-এর যে সমস্ত চ্যাপ্টার নিয়ে আলোচনা করেছি তার প্রতিটিই ছিল ফরওয়ার্ড পাস (forward pass) নিয়ে — কীভাবে একটি ছবি ফিচার ম্যাপে পরিণত হয়, কীভাবে ফিচার ম্যাপ ফ্ল্যাট ভেক্টরে পরিণত হয়, এবং সেই ভেক্টর কীভাবে প্রেডিকশনে পরিণত হয়। কিন্তু যে নেটওয়ার্কটি কেবল সামনের দিকেই তাকাতে পারে, সে কখনো নিজে নিজে কোনো কিছু শিখতে পারে না। তার নিজের ভুলগুলো পেছনের দিকে ফিরে দেখা এবং তার কোটি কোটি ওয়েটের মধ্যে সুনির্দিষ্টভাবে ঠিক কোনটির দোষ ছিল তা খুঁজে বের করার একটি মেকানিজম বা উপায় প্রয়োজন।
পেছনের দিকে ফিরে তাকানোর সেই প্রক্রিয়াই হলো ব্যাকপ্রোপাগেশন (backpropagation)। একটি সাধারণ ফিডফরওয়ার্ড নেটওয়ার্কে চেইন রুল (chain rule) ডেন্স লেয়ারের মধ্য দিয়ে পেছনের দিকে ধাবিত হয় এবং এটাই তার মূল কাজ। কিন্তু একটি CNN-এ, চেইন রুলকে এমন সব অপারেশনের মধ্য দিয়ে পেছনের দিকে যেতে হয় যা সাধারণ ম্যাট্রিক্স গুণের সাথে মোটেও মেলে না — যেমন max pooling, যা উদ্দেশ্যপ্রণোদিতভাবে তথ্যকে ফেলে দেয়, এবং convolution, যা প্রতিটি স্থানিক অবস্থানে একই ছোট কার্নেলকে বারবার ব্যবহার করে। ঠিক এই দুটি অপারেশনের মধ্য দিয়ে কীভাবে গ্রেডিয়েন্টকে সঠিকভাবে প্রবাহিত করা যায়, তা নিয়ে আমাদের এই চ্যাপ্টারের মূল আলোচনা।
কল্পনা করুন একটি বড় রান্নাঘরের কথা যেখানে একটি তরকারি একটু বেশি নোনতা হয়ে টেবিল থেকে ফেরত এসেছে। হেড শেফ নিজে কিন্তু তরকারিতে লবণ দেননি — তিন ধাপ আগের এক লাইনের বাবুর্চি লবণ দিয়েছিলেন, একজন সু-শেফের এক সপ্তাহ আগে লিখে রাখা রেসিপি কার্ড দেখে। এই সমস্যাটি স্থায়ীভাবে সমাধান করার জন্য হেড শেফকে ওই তরকারিটি যে যে হাত ঘুরে তৈরি হয়েছে তার প্রতিটিধাপ পেছনের দিকে ট্রেস বা অনুসরণ করতে হবে: "প্লেটিং বা সাজানো ঠিক ছিল, সস ঠিক ছিল, কিন্তু সল্টিং স্টেশনে লবণ বেশি হয়ে গেছে — আর ওই রেসিপি কার্ডটিই তো ওই স্টেশনের মধ্য দিয়ে যাওয়া প্রতিটা তরকারির জন্য ব্যবহৃত হয়!"
ব্যাকপ্রোপাগেশন হলো ঠিক এই ঘটনার একটি গাণিতিক রূপ। "বেশি নোনতা" বা ভুল সিগন্যালটিই হলো আমাদের লস (loss)। প্রতিটি স্টেশন — ডেন্স লেয়ার, পুলিং লেয়ার, অ্যাক্টিভেশন, কনভোলিউশন — উল্টো ক্রমানুসারে ভিজিট করা হয় এবং প্রতিটিতে আমরা জিজ্ঞেস করি: আউটপুট যেহেতু এতটা ভুল ছিল, তবে এই স্টেশনের অবদান ঠিক কতটা ভুল ছিল এবং এর কী করা উচিত ছিল? কনভোলিউশন কার্নেল হলো সেই রেসিপি কার্ড — যা সব জায়গায় বারবার ব্যবহৃত হয়েছে, তাই এর সংশোধনের সময়ও যেখানে যেখানে এটি ব্যবহৃত হয়েছিল তার সবগুলোর অবদান হিসেবে রাখতে হবে।
কোনো ডেরিভেটিভ বা ব্যবকলন লেখার আগে তিনটি প্রধান সত্য মাথায় গেঁথে নিন:
যেখানে: হলো লেয়ারের প্রি-অ্যাক্টিভেশন আউটপুট, এবং হলো বর্তমান ব্যাচের স্কেলার লস।
সহজ কথা: হলো এমন একটি একক মান যা প্রতিটি লেয়ার তার আগের লেয়ারকে পাঠিয়ে দেয়। এটি এই প্রশ্নের উত্তর দেয়: "যদি এই লেয়ারের raw আউটপুট সামান্য পরিবর্তিত হয়, তবে চূড়ান্ত লস ঠিক কতটা পরিবর্তিত হবে?" কোনো লেয়ারের কাছে একবার নিচের লেয়ার থেকে আসা পৌঁছালে, সে তার নিজের ওয়েট গ্রেডিয়েন্ট এবং তার আগের লেয়ারে পাঠানোর জন্য প্রয়োজনীয় উভয়ই হিসাব করতে পারে — এটিই ব্যাকপ্রোপাগেশনের পুরো রিকার্সিভ বা পুনরাবৃত্তিমূলক কাঠামো।
নেটওয়ার্কটি যখন সফটম্যাক্সের (Softmax) পর ক্রস-এন্ট্রপি লস (Cross-Entropy loss) দিয়ে শেষ হয় — যা ক্লাসিফিকেশনের জন্য ডিফল্ট জোড়া — তখন আউটপুট লজিটের সাপেক্ষে গ্রেডিয়েন্ট হিসাবের রূপটি অত্যন্ত সহজ ও চমৎকার হয়।
যেখানে: হলো সফটম্যাক্স থেকে পাওয়া প্রেডিক্টেড প্রোবাবিলিটি, এবং হলো আসল ওয়ান-হট লেবেল।
সহজ কথা: গ্রেডিয়েন্টটি হলো স্রেফ "আমরা কতটুকু আত্মবিশ্বাসী ছিলাম মাইনাস আমাদের কতটুকু হওয়া উচিত ছিল।" নেটওয়ার্কটি যদি সঠিক ক্লাসের ব্যাপারে ৯০% নিশ্চিত থাকে, তবে গ্রেডিয়েন্ট হবে খুবই ক্ষুদ্র — এখানে আর তেমন কোনো সংশোধনের প্রয়োজন নেই। আর যদি এটি আত্মবিশ্বাসের সাথে ভুল সিদ্ধান্ত দেয়, তবে লজিটকে নিচের দিকে টেনে নামানোর জন্য গ্রেডিয়েন্ট হচ্ছে অনেক বড়। ঠিক এই চমৎকার কাটাকাটি বা সরল রূপটির কারণেই সফটম্যাক্স এবং ক্রস-এন্ট্রপিকে কখনো আলাদা ব্যবহার করা হয় না — অন্য যেকোনো লস ফাংশন ব্যবহার করলে এই গাণিতিক সরলতা নষ্ট হয়ে যায়।
একবার কোনো ডেন্স লেয়ারে (যা হিসাব করে) এসে পৌঁছালে, একই চেইন রুল থেকে তিনটি গ্রেডিয়েন্ট বের হয়ে আসে:
যেখানে: হলো লেয়ারের ইনপুট ভেক্টর, হলো পরবর্তী লেয়ার থেকে নেমে আসা এরর সিগন্যাল, এবং হলো এই লেয়ারের তার আগের লেয়ারকে পাঠানো এরর সিগন্যাল।
সহজ কথা: সমীকরণটি বলে "ওয়েটের সংশোধন বা আপডেট মূলত এর ইনপুট কতটা সক্রিয় ছিল এবং আউটপুট কতটা ভুল ছিল তার গুণের সমানুপাতিক" — যা পারসেপ্ট্রন নিয়মের মতোই একই প্যাটার্ন, শুধু ম্যাট্রিক্স রূপে। আর হলো চমৎকার অংশ: এটি এরর বা ত্রুটিকে পুনরায় "ইনপুট স্পেসে" ফিরিয়ে নিয়ে যায় যাতে আগের লেয়ার সেটিকে নিজের ইনকামিং হিসেবে ব্যবহার করতে পারে।
পুলিং লেয়ারের কোনো ওয়েট বা ওজন থাকে না, তাই এখানে আপডেট করার মতো কিছু নেই — তবে এর নিচের কনভোলিউশন লেয়ারে গ্রেডিয়েন্ট পৌঁছানোর জন্য একে সঠিক নিয়মে পুলিং লেয়ারের ভেতর দিয়ে পার হতে হবে।
যেখানে: হলো প্রি-পুলিং অ্যাক্টিভেশন ম্যাপ, হলো পুলিংয়ের আউটপুট, এবং হলো ফরওয়ার্ড পাসের সময় পুলিং উইন্ডোর ভেতরের সর্বোচ্চ বা পজিশন।
সহজ কথা: ফরওয়ার্ড পাসের সময় উইন্ডোর ভেতরের যে নোড বা উপাদানটি সর্বোচ্চ হয়ে জিতেছিল, শুধুমাত্র সেই পিক্সেলটিই আউটপুট পরিবর্তনের জন্য দায়ী ছিল — অন্য নোডগুলোর মান যাই হোক না কেন একটুও নড়ত না, তাই তাদের গ্রেডিয়েন্ট হুবহু শূন্য। ঠিক এই কারণেই প্রতিটি ফ্রেমওয়ার্ককে ফরওয়ার্ড পাসের উইনিং ইনডেক্সটি (যাকে "সুইচ বা switch" বলা হয়) মনে রাখতে হয়; তা না হলে ব্যাকওয়ার্ড পাস কোনোভাবেই বুঝতে পারবে না যে গ্রেডিয়েন্টকে ঠিক কোন পিক্সেলের দিকে পাঠাতে হবে।
সহজ কথা: যেহেতু উইন্ডোর ভেতরের প্রতিটি নোডই গড়ের ক্ষেত্রে সমান অবদান রেখেছিল, তাই ভুলের জন্য সবাই সমান দোষ পাবে — ইনকামিং গ্রেডিয়েন্টকে স্রেফ উইন্ডোর নোড সংখ্যা দিয়ে ভাগ করে সবগুলোর মধ্যে সমানভাবে বন্টন করা হয়।
Max pooling এবং average pooling-এর কোনো লার্নেবল প্যারামিটার নেই। একটি পুলিং লেয়ারের ভেতর দিয়ে ব্যাকপ্রোপাগেট করার সময় কোনো ওয়েট গ্রেডিয়েন্ট তৈরি হয় না — এটি শুধুমাত্র ইনকামিং গ্রেডিয়েন্টকে সঠিক ইনপুট পজিশনে পাঠিয়ে দেয় যাতে তা নিচে থাকা কনভোলিউশন লেয়ারে পৌঁছাতে পারে।
এই ধাপটিই মূলত CNN-এর ব্যাকপ্রোপাগেশনকে সাধারণ MLP থেকে সম্পূর্ণ আলাদা করে তোলে। আমাদের ফরওয়ার্ড কনভোলিউশন মনে করুন: । ইনকামিং গ্রেডিয়েন্ট দেওয়া থাকলে তিনটি মান প্রয়োজন:
সহজ কথা: এই সমীকরণটি নিজেই একটি কনভোলিউশন — যেখানে raw ইনপুট -এর ওপর ইনকামিং এরর ম্যাপ -কে স্লাইড করানো হচ্ছে। কার্নেলটি ছবির ওপরে যেখানে যেখানে বসেছিল তার প্রতিটি অবস্থানই এর মোট গ্রেডিয়েন্টে অবদান রেখেছিল, এবং এই সমীকরণটি সেই সব অবদানকে একত্রিত করে কার্নেলের প্রতিটি ওয়েটের জন্য একটি চূড়ান্ত সংশোধন তৈরি করে।
সহজ কথা: এটি মূলত কার্নেল -কে ১৮০° কোণে ঘুরিয়ে বা রোটেট করে এরর ম্যাপ -র ওপর একটি full কনভোলিউশন চালানোর সমীকরণ। কার্নেলকে ঘোরানোর কারণ হলো ফরওয়ার্ড পাসের সময় একটি নির্দিষ্ট ইনপুট পিক্সেল একাধিক আউটপুট পজিশনকে প্রভাবিত করেছিল, এবং পিছনের দিকে আসার সময় প্রতিটি পজিশন থেকে ভুলের অংশটি ঠিকঠাক ওই পিক্সেলের কাছেই ফেরত পাঠাতে হবে। এটিই সেই গ্রেডিয়েন্ট যা আরও পেছনে প্রবাহিত হয়ে যে লেয়ার থেকে এসেছে সেখানে চলে যায়।
সহজ কথা: যেহেতু ফিচার ম্যাপের প্রতিটি স্থানিক পজিশনে একই বায়াস যোগ করা হয়েছিল, তাই এর মোট গ্রেডিয়েন্ট হলো এরর ম্যাপের প্রতিটি পজিশনের ভ্যালুগুলোর সাধারণ যোগফল।
প্রতিটি গ্রেডিয়েন্ট ক্যালকুলেট করার পর, আসল ওজনের সংশোধন (plain-SGD পদ্ধতিতে) আর কিছুই নয়, গ্রেডিয়েন্টের বিপরীত দিকে সামান্য নেমে যাওয়া:
যেখানে: হলো লার্নিং রেট। আমরা পরে Momentum, RMSProp, Adam-এর মতো উন্নত আপডেট নিয়মগুলো দেখব — তবে তাদের প্রতিটির কাজ করার জন্যও ওপরের গ্রেডিয়েন্টগুলো একদম অপরিহার্য।

"Loss → FC → Flatten → Pool → ReLU → Conv → Update।" একটি CNN-এর ব্যাকপ্রোপাগেশন হলো ফরওয়ার্ড পাসকে উল্টো দিক থেকে পড়া এবং প্রতিটি ধাপে চেইন রুল প্রয়োগ করা। যদি আপনি ধাপগুলোর নাম উল্টো দিক থেকে বলতে পারেন, তবে পুরো ডেরাইভেশনটি যেকোনো সময় নিজে নিজেই বের করে নিতে পারবেন।
ধরুন, ফরওয়ার্ড পাসের সময় একটি max-pooling উইন্ডোর ভেতরের মানগুলো ছিল । এখানে সর্বোচ্চ মান ৫, যা অবস্থান করছে পজিশনে (দ্বিতীয় সারি, প্রথম কলাম)। তাই সামনে চলে যায় এবং পুলিং লেয়ার মনে রাখে যে পজিশনটি জিতেছিল।
এখন ধরুন, ব্যাকপ্রোপাগেশনের সময় এই পুলিং লেয়ারের কাছে ইনকামিং গ্রেডিয়েন্ট হিসেবে এসে পৌঁছাল। Max-pooling-এর ব্যাকওয়ার্ড নিয়ম অনুযায়ী:
এর সাথে যদি average pooling-এর তুলনা করি: তবে এই একই গ্রেডিয়েন্ট ৪টি নোডের মধ্যে সমানভাবে ভাগ হয়ে প্রতিটিতে করে বসত, তাদের ফরওয়ার্ড পাসের মান যাই হোক না কেন। এই পার্থক্যটি — "সব গ্রেডিয়েন্ট একজন পায়" বনাম "সবাই সমানভাবে ভাগ করে নেয়" — পুলিংয়ের ব্যাকওয়ার্ড পাসের অত্যন্ত বহুল জিজ্ঞাসিত একটি প্রশ্ন।
import numpy as np
def maxpool_backward(dP, A, pool_size=2, stride=2):
"""
dP: pooled আউটপুটের সাপেক্ষে গ্রেডিয়েন্ট, shape (H_out, W_out)
A: মূল প্রি-পুলিং অ্যাক্টিভেশন ম্যাপ, shape (H, W)
dA ফেরত দেয়: A-র সাপেক্ষে গ্রেডিয়েন্ট, A-র সমান shape
"""
H, W = A.shape
dA = np.zeros_like(A)
for i in range(dP.shape[0]):
for j in range(dP.shape[1]):
r0, c0 = i * stride, j * stride
window = A[r0:r0 + pool_size, c0:c0 + pool_size]
# উইন্ডোর ভেতরে আরগম্যাক্স পজিশন খুঁজে বের করা
local_r, local_c = np.unravel_index(np.argmax(window), window.shape)
dA[r0 + local_r, c0 + local_c] += dP[i, j]
return dA
def conv_backward(dZ, X, K):
"""
dZ: conv আউটপুটের সাপেক্ষে গ্রেডিয়েন্ট, shape (H_out, W_out)
X: conv লেয়ারের ইনপুট, shape (H, W)
K: কার্নেল, shape (Fh, Fw)
dK, db, dX ফেরত দেয়
"""
Fh, Fw = K.shape
dK = np.zeros_like(K)
for m in range(Fh):
for n in range(Fw):
region = X[m:m + dZ.shape[0], n:n + dZ.shape[1]]
dK[m, n] = np.sum(dZ * region)
db = np.sum(dZ)
# ইনপুটের সাপেক্ষে গ্রেডিয়েন্ট: ১৮০° ঘোরানো কার্নেলের সাথে dZ-এর full convolution
K_flipped = np.flip(K)
dX = np.zeros_like(X)
padded_dZ = np.pad(dZ, ((Fh - 1, Fh - 1), (Fw - 1, Fw - 1)))
for i in range(dX.shape[0]):
for j in range(dX.shape[1]):
region = padded_dZ[i:i + Fh, j:j + Fw]
dX[i, j] = np.sum(region * K_flipped)
return dK, db, dXকয়েকটি জিনিস খেয়াল রাখা দরকার:
maxpool_backward কোডের সুবিধার জন্য উইনিং পজিশন রান-টাইমে হিসাব করছে, তবে বাস্তব বা প্রোডাকশন ফ্রেমওয়ার্কগুলোতে ফরওয়ার্ড পাসের সময় জিতেছিল এমন নোড ইনডেক্স সরাসরি ক্যাশে রেখে দেওয়া হয় — রান-টাইমে আবার হিসাব করতে গেলে টাই (tie) বা টাই হওয়ার ক্ষেত্রে গড়মিল হতে পারে এবং কম্পিউটেশনাল সময় নষ্ট হয়।conv_backward ওপরে আলোচনা করা সমীকরণগুলোকেই হুবহু রান করে: dK হলো ইনপুটের সাথে এরর ম্যাপের ক্রস-কোরিলেশন, এবং dX হলো ঘোরানো বা ফ্লিপ করা কার্নেলের সাথে এরর ম্যাপের ফুল কনভোলিউশন।K -= lr * dK অপ্টিমাইজারের কাজ, যা আমরা চ্যাপ্টার ১২-তে শিখব।Max Pooling লেয়ার যদি ফরওয়ার্ড পাসের সময় কোন পিক্সেলটি সর্বোচ্চ ছিল তা মনে না রাখে, তবে ব্যাকওয়ার্ড পাসের সময় গ্রেডিয়েন্টকে সঠিক দিকে পাঠানোর আর কোনো পথ থাকে না। এটি রান-টাইমে পুনরায় হিসাব করা অত্যন্ত ঝুঁকিপূর্ণ ও ধীরগতির।
ফরওয়ার্ড পাস এবং ওজনের গ্রেডিয়েন্ট হিসাব করার ধাপ দুটি মূলত ম্যাট্রিক্সের স্লাইডিং ও গুণের সমষ্টি — যা গাণিতিকভাবে ক্রস-কোরিলেশন, উল্টানো কনভোলিউশন নয়। শুধুমাত্র ইনপুটের গ্রেডিয়েন্ট হিসাব করার সময় কার্নেলকে ১৮০° ফ্লিপ করে ফুল কনভোলিউশন করতে হয়। পরীক্ষায় এই দুটিকে গুলিয়ে ফেলা খুব কমন একটি ভুল।
যেকোনো বাস্তব বা প্রোডাকশন মেশিন লার্নিং সিস্টেমের দিকে ভালো করে তাকান — কোনো ব্যাংকের ফ্রড সনাক্তকরণ পাইপলাইন, হাসপাতালের ঝুঁকি পরিমাপের ড্যাশবোর্ড, কোনো রেকমেন্ডেশন ইঞ্জিন, অথবা আপনি এই মুহূর্তে যে ল্যাঙ্গুয়েজ মডেলটি ব্যবহার করছেন তার ভেতরে — সবখানেই খুব জাঁকজমকহীন কিন্তু অত্যন্ত গুরুত্বপূর্ণ কাজ করে চলেছে একটি CNN (অথবা এর কাছাকাছি কোনো উন্নত রূপ)। wrapping বা autograd-এর মাধ্যমে ফ্রেমওয়ার্কগুলো স্বয়ংক্রিয়ভাবে এটি করে নেয়। তবে এই ডেরাইভেশনটি বুঝতে পারাই একজন সাধারণ ব্যবহারকারী থেকে একজন দক্ষ ইঞ্জিনিয়ারকে আলাদা করে: গ্রেডিয়েন্ট এক্সপ্লোড বা ভ্যানিশ হওয়া, কোনো পুলিং লেয়ারের কারণে গ্রেডিয়েন্ট ফ্লো আটকে যাওয়া, অথবা কাস্টম লস ফাংশনের সাথে আউটপুট অ্যাক্টিভেশনের অমিল — এই সব ধরনের বাস্তব সমস্যা চিহ্নিত করার জন্য মনে মনে এই চ্যাপ্টারের ব্যাকপ্রোপাগেশন রান করতে পারা অত্যন্ত দরকারি।