Chapter 12

শূন্য থেকে ANN বানানো

Building ANN from Scratch
🎬 কেন শূন্য থেকে?
PyTorch বা TensorFlow ব্যবহার করার আগে অন্তত একবার নিজের হাতে পুরো নেটওয়ার্ক বানালে — যে জাদু হয় ভেতরে — সেটা চিরদিন মনে থাকবে। এই অধ্যায়ে আমরা শুধু NumPy দিয়ে digit classifier বানাব।

পরিকল্পনা

Input (784)  →  Hidden (128, ReLU)  →  Output (10, Softmax)
        Loss: Categorical Cross-Entropy
        Optimizer: Mini-batch SGD

সম্পূর্ণ কোড

import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

# ---- ডেটা ----
data = load_digits()
X = data.data / 16.0                # normalize
y = data.target

# one-hot encode
Y = np.zeros((y.size, 10))
Y[np.arange(y.size), y] = 1

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)

# ---- Helpers ----
def relu(z): return np.maximum(0, z)
def relu_deriv(z): return (z > 0).astype(float)

def softmax(z):
    z = z - z.max(axis=1, keepdims=True)
    e = np.exp(z)
    return e / e.sum(axis=1, keepdims=True)

def cross_entropy(y_true, y_pred, eps=1e-12):
    return -np.mean(np.sum(y_true * np.log(y_pred + eps), axis=1))

# ---- Initialize ----
np.random.seed(42)
W1 = np.random.randn(64, 128) * np.sqrt(2/64)   # He init
b1 = np.zeros((1, 128))
W2 = np.random.randn(128, 10) * np.sqrt(2/128)
b2 = np.zeros((1, 10))

lr = 0.1
batch_size = 32
epochs = 50

# ---- Train ----
for epoch in range(epochs):
    # shuffle
    idx = np.random.permutation(len(X_train))
    X_train, Y_train = X_train[idx], Y_train[idx]

    losses = []
    for i in range(0, len(X_train), batch_size):
        xb = X_train[i:i+batch_size]
        yb = Y_train[i:i+batch_size]

        # Forward
        z1 = xb @ W1 + b1
        a1 = relu(z1)
        z2 = a1 @ W2 + b2
        a2 = softmax(z2)

        loss = cross_entropy(yb, a2)
        losses.append(loss)

        # Backward
        dz2 = (a2 - yb) / len(xb)              # softmax+CE shortcut
        dW2 = a1.T @ dz2
        db2 = dz2.sum(axis=0, keepdims=True)

        dz1 = (dz2 @ W2.T) * relu_deriv(z1)
        dW1 = xb.T @ dz1
        db1 = dz1.sum(axis=0, keepdims=True)

        # Update
        W2 -= lr * dW2; b2 -= lr * db2
        W1 -= lr * dW1; b1 -= lr * db1

    # Eval
    a1_t = relu(X_test @ W1 + b1)
    pred = softmax(a1_t @ W2 + b2).argmax(axis=1)
    acc = (pred == Y_test.argmax(axis=1)).mean()
    print(f"Epoch {epoch+1:2d} | loss {np.mean(losses):.4f} | acc {acc:.3f}")
🔑 He Initialization
ReLU-এর জন্য সবচেয়ে ভালো initialization — std = √(2/fan_in)। এটা vanishing/exploding gradient কমায়।

Softmax + Cross-Entropy এর Magic Shortcut

Softmax আর Cross-Entropy আলাদাভাবে derivative করা জটিল। কিন্তু একসাথে — অসাধারণ সরল হয়ে যায়:

dz_output = a_output - y_true     # এটাই পুরো derivative!

কী শিখলেন?

  • কোনো framework ছাড়াই end-to-end একটা নিউরাল নেট তৈরি করা যায়।
  • Forward, backward, update — সব হাতে।
  • Sklearn digits-এ ~95%+ accuracy পাওয়া যায়।

সাধারণ ভুল ধারণা

⚠️ এড়িয়ে চলবেন
  • Initialization 0 দেওয়া — সব নিউরন একই শিখবে।
  • Softmax-এ exp overflow — তাই max বাদ দিয়ে exp করুন।
  • Cross-entropy-তে log(0) — epsilon দিয়ে clip।

অনুশীলন

১. Hidden layer-এর size 128 → 256 বা 64 — accuracy কেমন বদলায়?

২. আরো একটি hidden layer যোগ করুন (64-128-64-10)।

৩. ReLU-এর বদলে Sigmoid দিয়ে train করুন — অনেক ধীর কেন?

মিনি প্রজেক্ট

🚀 আজকের চ্যালেঞ্জ
উপরের কোডে momentum যোগ করুন (v = β·v + grad; w -= lr·v)। convergence speed তুলনা করুন।

ইন্টারভিউ প্রশ্ন

  1. He vs Xavier initialization — পার্থক্য।
  2. Softmax+CE-এর derivative কেন এত সরল?
  3. Mini-batch ব্যবহার কেন?

সারসংক্ষেপ

✨ এই অধ্যায়ে যা শিখলাম
  • শূন্য থেকে NumPy দিয়ে একটি digit classifier।
  • Forward, backward, init, training loop — সব হাতে।
  • পরের অধ্যায়ে — TensorFlow-এ একই কাজ ১০ লাইনে।