পাঠ ১৯ · ৩৫-এর মধ্যে · মডিউল ৫
Home / AI Courses / Math for AI & ML / কম্পিউটেশনাল গ্রাফ

কম্পিউটেশনাল গ্রাফ ও ফরওয়ার্ড পাস

Computational graphs & the forward pass
১৪ মিনিট পড়া উন্নত · Advanced NumPy কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কম্পিউটেশনাল গ্রাফ কী, এবং নিউরাল নেটওয়ার্ককে কেন এভাবে মডেল করা হয়
  • ফরওয়ার্ড পাস মানে কী — টপোলজিক্যাল অর্ডার এবং ইন্টারমিডিয়েট মান ক্যাশ করার গুরুত্ব
  • এই মডিউলের বাকি অংশে বারবার ব্যবহৃত হবে এমন একটি concrete ক্ষুদ্র নেটওয়ার্কের পূর্ণ পরিচিতি
  • "নোড", "লোকাল গ্রেডিয়েন্ট" পরিভাষার প্রাথমিক সংজ্ঞা, যা পরবর্তী পাঠের ভিত্তি

১ · কম্পিউটেশনাল গ্রাফ কী

পাঠ ১৩-১৮-তে আমরা ডেরিভেটিভ, গ্রেডিয়েন্ট ও অ্যাক্টিভেশন ফাংশন আলাদা আলাদাভাবে দেখেছি। এখন আমরা এগুলো একসাথে জোড়া লাগাব একটি পূর্ণ নিউরাল নেটওয়ার্কের গণনায়। এই গণনাকে সংগঠিত করার সবচেয়ে স্বচ্ছ উপায় হলো একটি কম্পিউটেশনাল গ্রাফComputational Graphএকটি জটিল গণনাকে ছোট ছোট প্রাথমিক অপারেশনের (যোগ, গুণ, স্কোয়ার, সিগময়েড ইত্যাদি) একটি ডিরেক্টেড অ্যাসাইক্লিক গ্রাফ (DAG) হিসেবে প্রকাশ করা। — যেখানে প্রতিটি নোড একটি একক প্রাথমিক অপারেশন (যোগ, গুণ, বিয়োগ, স্কোয়ার, সিগময়েড...), আর প্রতিটি এজ (তীর) একটি নোডের আউটপুট আরেকটি নোডের ইনপুট হিসেবে কোথায় যাচ্ছে তা দেখায়।

"ডিরেক্টেড" মানে প্রতিটি তীরের একটি নির্দিষ্ট দিক আছে (তথ্য কোন দিকে বইছে), আর "অ্যাসাইক্লিক" মানে গ্রাফে কোনো চক্র (loop) নেই — কোনো নোডের গণনা নিজের উপর নির্ভর করতে পারে না। এই কারণেই সবসময় একটি সুনির্দিষ্ট ক্রম আছে যেখানে প্রতিটি নোড গণনা করা যায়।

একটি খুব ছোট উদাহরণ দিয়ে শুরু করি — একটি একক-ইনপুট নিউরনের স্কোয়ার্ড-এরর লস $L=(wx+b-y)^2$। একে প্রাথমিক অপারেশনে ভেঙে লিখলে:

$$ u = wx, \qquad z = u + b, \qquad e = z - y, \qquad L = e^2 $$

এখানে চারটি নোড — গুণ ($u=wx$), যোগ ($z=u+b$), বিয়োগ ($e=z-y$), স্কোয়ার ($L=e^2$) — একটি চেইনে যুক্ত। এই ছোট গ্রাফটিই পাঠ ২০-তে চেইন রুল প্রয়োগের প্রথম উদাহরণ হিসেবে ফিরে আসবে। কিন্তু এই মডিউলের মূল কাজের জন্য আমাদের একটু বড় — অথচ এখনও হাতে-কলমে সম্পূর্ণ ট্র্যাক করা যায় এমন — একটি নেটওয়ার্ক দরকার।

২ · আমাদের ক্ষুদ্র নেটওয়ার্ক — পুরো মডিউল ৫ জুড়ে ব্যবহৃত হবে

নিচের নেটওয়ার্কটি এই পাঠ, পাঠ ২০ ও পাঠ ২১ — তিনটিতেই অবিকল একই সংখ্যা সহ ব্যবহৃত হবে, যাতে ফরওয়ার্ড পাস, ব্যাকওয়ার্ড ডেরিভেশন ও চূড়ান্ত সংখ্যাগত যাচাই — সব একে অপরের সাথে নিখুঁতভাবে মেলে।

নেটওয়ার্কের গঠন: ২টি ইনপুট ($x_1, x_2$) → ১টি হিডেন নিউরন (ওজন $w_1, w_2$, বায়াস $b_1$, সিগময়েড অ্যাক্টিভেশন) → ১টি আউটপুট নিউরন (ওজন $w_3$, বায়াস $b_2$, লিনিয়ার — কোনো অ্যাক্টিভেশন ছাড়া) → স্কোয়ার্ড এরর লস।

$$ z_1 = w_1 x_1 + w_2 x_2 + b_1 \qquad h = \sigma(z_1) \qquad \hat{y} = w_3 h + b_2 \qquad L = (\hat{y}-y)^2 $$

এখানে $\sigma(x) = \frac{1}{1+e^{-x}}$ পাঠ ১৮-এর সিগময়েড ফাংশন, এবং $\hat{y}$ হলো মডেলের পূর্বাভাস। আমরা নিচের পরিষ্কার সংখ্যাগুলো বেছে নিচ্ছি:

এই মডিউলের ফিক্সড নেটওয়ার্ক

$x_1=1.0,\; x_2=2.0,\; y=0.0$ (প্রকৃত লক্ষ্য)  ·  $w_1=0.5,\; w_2=-0.5,\; b_1=0.5$ (হিডেন লেয়ার)  ·  $w_3=2.0,\; b_2=0.0$ (আউটপুট লেয়ার)। এই সংখ্যাগুলো পাঠ ২০ ও ২১-এ ঠিক এভাবেই পুনরায় ব্যবহার হবে।

লক্ষ করুন — $w_1 x_1 + w_2 x_2 + b_1 = 0.5(1.0) + (-0.5)(2.0) + 0.5 = 0$ হয়, ফলে $z_1=0$, এবং $\sigma(0)=0.5$ ঠিক। এই সংখ্যাগুলো ইচ্ছাকৃতভাবেই এমন বেছে নেওয়া হয়েছে যাতে হাতে হিসাব সহজ থাকে এবং কোনো decimal-এর জঞ্জাল তৈরি না হয় — এটি একটি বাস্তব প্রশিক্ষণ পরিস্থিতির সরলীকৃত সংস্করণ, কিন্তু গাণিতিকভাবে সম্পূর্ণ সৎ।

৩ · ফরওয়ার্ড পাস — টপোলজিক্যাল অর্ডারে গণনা

ফরওয়ার্ড পাসForward Passকম্পিউটেশনাল গ্রাফের প্রতিটি নোড ইনপুট থেকে আউটপুটের দিকে, টপোলজিক্যাল অর্ডারে, একবার করে গণনা করা এবং প্রতিটি ইন্টারমিডিয়েট মান সংরক্ষণ করা। মানে হলো — একটি নোড গণনা করার আগে তার সব ইনপুট ইতিমধ্যে গণনা করা থাকতে হবে। আমাদের গ্রাফে এই ক্রম একেবারে স্পষ্ট: প্রথমে $z_1$ (এটির ইনপুট শুধু $x_1,x_2,w_1,w_2,b_1$ — সবই আগে থেকেই জানা), তারপর $h$ (যা শুধু $z_1$-এর উপর নির্ভর করে), তারপর $\hat{y}$ (যা $h,w_3,b_2$-এর উপর নির্ভর করে), সবশেষে $L$।

আমাদের সংখ্যা দিয়ে ধাপে ধাপে হাতে হিসাব করি:

$z_1 = (0.5)(1.0) + (-0.5)(2.0) + 0.5 = 0.5 - 1.0 + 0.5 = 0$  →  $h = \sigma(0) = \dfrac{1}{1+e^0} = \dfrac{1}{2} = 0.5$  →  $\hat{y} = (2.0)(0.5) + 0.0 = 1.0$  →  $L = (1.0 - 0.0)^2 = 1.0$

তাই এই নির্দিষ্ট ইনপুটে আমাদের নেটওয়ার্কের লস $L=1.0$। এই মানটিই পাঠ ২১-এর NumPy কোডের আউটপুটের সাথে অবিকল মিলবে — এটাই আমাদের প্রথম যাচাই-বিন্দু।

x₁ x₂ w₁ w₂ b₁ z₁ = w₁x₁+w₂x₂+b₁ hidden pre-activation h = σ(z₁) sigmoid w₃ b₂ ŷ = w₃h+b₂ output (linear) y L = (ŷ−y)² squared-error loss
ধূসর তীর = প্যারামিটার/ইনপুট প্রবেশ করছে; সবুজ তীর = ফরওয়ার্ড পাসের মূল প্রবাহ। প্রতিটি নোড আগেরটির উপর নির্ভরশীল — এটাই টপোলজিক্যাল অর্ডার।

৪ · গ্রাফ, নোড ও "লোকাল গ্রেডিয়েন্ট" — পরিভাষা

পাঠ ২০-তে যাওয়ার আগে তিনটি পরিভাষা স্পষ্ট করে নেওয়া দরকার। প্রতিটি নোডের একটি লোকাল গ্রেডিয়েন্টLocal Gradientএকটি নোডের আউটপুট তার নিজের ইনপুটের সাপেক্ষে যে হারে বদলায় — বাকি গ্রাফ সম্পর্কে কিছু না জেনেই গণনাযোগ্য। আছে — নোডের আউটপুট তার নিজের ইনপুটের সাপেক্ষে কতটা বদলায়, বাকি পুরো গ্রাফ সম্পর্কে কিছু না জেনেই এটি গণনা করা যায়। যেমন, $z_1=w_1x_1+w_2x_2+b_1$ নোডের জন্য:

$$ \frac{\partial z_1}{\partial w_1} = x_1, \qquad \frac{\partial z_1}{\partial w_2} = x_2, \qquad \frac{\partial z_1}{\partial b_1} = 1 $$

এই তিনটি লোকাল গ্রেডিয়েন্ট শুধু $z_1$ নোডের সংজ্ঞা থেকেই বের করা যায় — $h$ বা $L$ কী তা না জেনেও। পাঠ ২০-তে আমরা দেখব, $L$-এর সাপেক্ষে কোনো প্যারামিটারের (যেমন $w_1$) পূর্ণ গ্রেডিয়েন্ট বের করতে হলে $L$ থেকে $w_1$ পর্যন্ত গ্রাফের পথ ধরে সব লোকাল গ্রেডিয়েন্ট একসাথে গুণ করতে হয় — এটাই মাল্টিভেরিয়েবল চেইন রুল।

একটি রান্নার রেসিপির কথা ভাবুন — প্রতিটি ধাপ (কাটা, ভাজা, মেশানো) আগের ধাপের ফলাফলের উপর নির্ভরশীল, এবং প্রতিটি ধাপ নিজে থেকেই স্বাধীনভাবে বোঝা যায় ("পেঁয়াজ কাটলে কী হয়" জানতে পুরো রেসিপি জানা লাগে না)। কম্পিউটেশনাল গ্রাফও তেমনই — প্রতিটি নোড একটি ছোট, স্বয়ংসম্পূর্ণ ধাপ, আর পুরো রেসিপি (গ্রাফ) সেগুলোর সঠিক ক্রম মাত্র।

নিচের কোডে আমাদের নেটওয়ার্কের ফরওয়ার্ড পাস প্রতিটি ইন্টারমিডিয়েট মান ক্যাশ করে গণনা করা হলো:

Python · NumPy
import numpy as np

# ইনপুট ও প্রকৃত লক্ষ্য
x1, x2 = 1.0, 2.0
y = 0.0

# হিডেন লেয়ার প্যারামিটার
w1, w2, b1 = 0.5, -0.5, 0.5
# আউটপুট লেয়ার প্যারামিটার
w3, b2 = 2.0, 0.0

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

# ---- ফরওয়ার্ড পাস: টপোলজিক্যাল অর্ডারে, প্রতিটি মান ক্যাশ করে ----
cache = {}
cache["z1"] = w1 * x1 + w2 * x2 + b1
cache["h"]  = sigmoid(cache["z1"])
cache["yhat"] = w3 * cache["h"] + b2
cache["L"]  = (cache["yhat"] - y) ** 2

for name, val in cache.items():
    print(f"{name} = {val}")

    
cache ডিকশনারিতে $z_1, h, \hat{y}, L$ — সব ইন্টারমিডিয়েট মান জমা রাখা হচ্ছে। এটি শুধু কোডিং স্টাইল নয় — পাঠ ২০-২১-এ দেখব, ব্যাকওয়ার্ড পাসের জন্য এই ঠিক এই মানগুলোই (বিশেষত $h$) আবার লাগবে। ক্যাশ না করলে ব্যাকওয়ার্ড পাসে সেগুলো আবার নতুন করে গণনা করতে হতো — অপচয়।
সবচেয়ে সাধারণ ভুল — নোডগুলো ভুল ক্রমে গণনা করা, যেমন $h$ গণনার আগেই $\hat{y}$ গণনার চেষ্টা করা। যেহেতু $\hat{y}$ সরাসরি $h$-এর উপর নির্ভরশীল, টপোলজিক্যাল অর্ডার ভাঙলে ভুল (বা stale) মান ব্যবহার হয়ে যাবে। বড় নেটওয়ার্কে (বহু লেয়ার, branch) এই ক্রম নির্ণয় করাই একটি আলাদা অ্যালগরিদমিক সমস্যা — যদিও আমাদের সরল চেইনে এটি একেবারে স্পষ্ট।
মূল কথা · Key takeaway

একটি কম্পিউটেশনাল গ্রাফ যেকোনো জটিল নেটওয়ার্ককে ছোট, সহজে-বোঝা-যায় এমন প্রাথমিক অপারেশনের সমষ্টিতে ভেঙে দেয়। ফরওয়ার্ড পাস = টপোলজিক্যাল অর্ডারে গণনা + ক্যাশিং। এই কাঠামোটাই পাঠ ২০-তে ব্যাকপ্রপাগেশনের ভিত্তি — কারণ ব্যাকওয়ার্ড পাস আসলে এই একই গ্রাফের উপর দিয়ে উল্টো দিকে হাঁটা, প্রতিটি নোডের লোকাল গ্রেডিয়েন্ট ব্যবহার করে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কম্পিউটেশনাল গ্রাফে কেন কোনো চক্র (cycle) থাকতে পারে না?

কারণ একটি নোড গণনা করতে হলে তার সব ইনপুট আগে থেকেই জানা থাকতে হবে। যদি $A$ নোড $B$-এর উপর নির্ভর করে, আর $B$ আবার $A$-এর উপর নির্ভর করে, তাহলে কোনটি আগে গণনা করবেন তা নির্ধারণ করা অসম্ভব — এটি একটি স্ব-বিরোধী সংজ্ঞা তৈরি করে। "অ্যাসাইক্লিক" শর্তটি নিশ্চিত করে সবসময় অন্তত একটি বৈধ টপোলজিক্যাল অর্ডার (গণনার ক্রম) বিদ্যমান থাকে।

প্র ০২ ইন্টারমিডিয়েট মান ক্যাশ না করলে ব্যাকওয়ার্ড পাসে ঠিক কী সমস্যা হবে?

পাঠ ২০-তে দেখব, $\partial L/\partial w_1$ বের করতে $h$-এর মান ($\sigma(z_1)(1-\sigma(z_1))$-এর অংশ হিসেবে) সরাসরি লাগে। ক্যাশ না থাকলে ব্যাকওয়ার্ড পাসের সময় $z_1$ থেকে $h$ আবার নতুন করে গণনা করতে হবে — একটি বড় নেটওয়ার্কে যেখানে লক্ষ লক্ষ নোড আছে, এই পুনরাবৃত্তি প্রশিক্ষণকে বহুগুণ ধীর করে দেবে। এই কারণেই PyTorch/ TensorFlow-এর মতো ফ্রেমওয়ার্ক ফরওয়ার্ড পাসের সময় স্বয়ংক্রিয়ভাবে প্রতিটি ইন্টারমিডিয়েট টেনসর মেমোরিতে রাখে।

প্র ০৩ আমাদের নেটওয়ার্কে আউটপুট নিউরনকে লিনিয়ার (কোনো অ্যাক্টিভেশন ছাড়া) রাখা হয়েছে কেন — সিগময়েড রাখলে কী বদলাত?

লিনিয়ার আউটপুট রাখার ফলে $\hat{y}=z_2$ সরাসরি, তাই ব্যাকওয়ার্ড পাসের একটি ধাপ (আউটপুট অ্যাক্টিভেশনের ডেরিভেটিভ) স্বয়ংক্রিয়ভাবে $1$ হয়ে যায় — হাতে হিসাব সরল থাকে। যদি আউটপুটেও সিগময়েড থাকত, তাহলে $\hat{y}=\sigma(z_2)$ হতো এবং $\partial \hat{y}/\partial z_2 = \sigma(z_2)(1-\sigma(z_2))$ নামে আরেকটি পদ চেইনে যুক্ত হতো। ধারণাগতভাবে কিছুই বদলাত না — শুধু একটি বাড়তি লোকাল গ্রেডিয়েন্ট গুণ হতো।

অনুশীলন

  1. নিজে গণনা করুন: যদি $x_1=2.0$ (বাকি সব মান অপরিবর্তিত) হতো, তাহলে $z_1, h, \hat{y}, L$-এর নতুন মান হাতে হিসাব করুন।

    $z_1 = 0.5(2.0) + (-0.5)(2.0) + 0.5 = 1.0 - 1.0 + 0.5 = 0.5$। $h=\sigma(0.5)\approx 0.6225$। $\hat{y}=2.0(0.6225)+0.0\approx 1.245$। $L=(1.245-0)^2\approx 1.550$।

  2. কোড চালান: উপরের code cell-এ x1 = 2.0 করে Run চাপুন, এবং উপরের অনুশীলন ১-এর হাতে-হিসাব করা মানের সাথে মিলিয়ে দেখুন।

    কোডের আউটপুট z1 = 0.5, h ≈ 0.6224593, yhat ≈ 1.2449187, L ≈ 1.5498232 দেখানো উচিত — যা উপরের হাতে-হিসাবের সাথে (রাউন্ডিং বাদে) মেলে।

  3. শনাক্ত করুন: $z_1$ নোডের লোকাল গ্রেডিয়েন্ট $\partial z_1/\partial w_2$ কত, এবং কেন এটি গণনা করতে $h$ বা $L$ সম্পর্কে কিছু জানার দরকার নেই?

    $z_1 = w_1x_1+w_2x_2+b_1$ হওয়ায় $\partial z_1/\partial w_2 = x_2 = 2.0$। এটি শুধু $z_1$ নোডের নিজস্ব সংজ্ঞার উপর নির্ভরশীল — গ্রাফে এর পরে কী ঘটছে (অর্থাৎ $h$ বা $L$ কীভাবে সংজ্ঞায়িত) তার সাথে এই লোকাল গ্রেডিয়েন্টের কোনো সম্পর্ক নেই, এবং এটাই "লোকাল" শব্দের অর্থ।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

পূর্ববর্তী পাঠ
অ্যাক্টিভেশন ফাংশন ও তাদের ডেরিভেটিভ