পাঠ ৩৯ · ৪০-এর মধ্যে · মডিউল ৫
Home / AI Courses / ডিপ লার্নিং / প্রজেক্ট: MNIST classifier

প্রজেক্ট — MNIST classifier

End-to-end CNN classifier in PyTorch
১৫ মিনিট পড়া মাঝারি · Intermediate PyTorch full pipeline

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

  • PyTorch DataLoader — efficient batched data loading
  • CNN architecture — Conv → Pool → FC structure
  • Training loop — forward, loss, backward, optimizer step
  • Validation/test evaluation — accuracy, confusion matrix
  • Saving/loading model — production-ready
  • Common issues — overfitting, slow training, GPU underuse

১ · Project overview

MNIST — DL-এর "Hello World"। LeCun-এর ১৯৯৮ paper-এর dataset। ৬০,০০০ training + ১০,০০০ test image, ২৮×২৮ grayscale, ১০ class (digits 0-9)।

আমরা একটি ছোট CNN train করব — target ৯৯%+ accuracy। এই lesson আপনার পুরো কোর্সের knowledge integrate করবে।

কেন MNIST

ছোট, fast train, well-understood। নতুন architecture/idea test করার জন্য standard। আজও research paper-এ baseline। Production deep learning-এর entry point।

২ · Setup ও DataLoader

Python · Setup & Data
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# Reproducibility
torch.manual_seed(42)

# Device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")

# Transforms — normalize MNIST mean/std
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,)),
])

# Datasets — auto-download
train_set = datasets.MNIST(root='./data', train=True,
                           download=True, transform=transform)
test_set  = datasets.MNIST(root='./data', train=False,
                           download=True, transform=transform)

print(f"Train: {len(train_set)}, Test: {len(test_set)}")
print(f"Image shape: {train_set[0][0].shape}")  # (1, 28, 28)
print(f"Classes: {train_set.classes}")

# DataLoaders
BATCH = 128
train_loader = DataLoader(train_set, batch_size=BATCH, shuffle=True,
                          num_workers=2, pin_memory=True)
test_loader  = DataLoader(test_set, batch_size=BATCH, shuffle=False,
                          num_workers=2, pin_memory=True)

    
pin_memory=True — GPU transfer faster। num_workers=2 — parallel data loading।

৩ · CNN model

Python · CNN architecture
class MNIST_CNN(nn.Module):
    def __init__(self, n_classes=10):
        super().__init__()
        # Conv block 1: 1→32, 28→14
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
        self.bn1   = nn.BatchNorm2d(32)
        # Conv block 2: 32→64, 14→7
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.bn2   = nn.BatchNorm2d(64)
        # FC head
        self.fc1 = nn.Linear(64 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, n_classes)
        self.dropout = nn.Dropout(0.3)

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.max_pool2d(x, 2)        # 28→14
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.max_pool2d(x, 2)        # 14→7
        x = x.view(x.size(0), -1)      # flatten
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        return self.fc2(x)             # logits

model = MNIST_CNN().to(device)
print(model)
print(f"Params: {sum(p.numel() for p in model.parameters())/1e3:.1f}K")

    

~৪২২K parameter — small but effective। Conv → BN → ReLU → Pool — classic CNN block।

MNIST CNN — input → conv → conv → FC → logits 28×28×1 → 14×14×32 → 7×7×64 → 128 → 10 Input 28×28×1 grayscale Conv 3×3 + BN + ReLU + Pool 2×2 14×14×32 Conv 3×3 + BN + ReLU + Pool 2×2 7×7×64 Flatten 3136 FC + ReLU + Dropout 128 FC out + Softmax 10 classes Loss = CrossEntropy(logits, label) total params: ~422K · 99%+ test accuracy
Simple CNN — দু'টি conv block ও দু'টি FC layer। ৪২২K parameter দিয়ে ৯৯%+ accuracy অর্জন সম্ভব।

৪ · Training loop

Python · Train + Eval
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.5)

def train_epoch(model, loader, opt, crit):
    model.train()
    total, correct, loss_sum = 0, 0, 0.0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        opt.zero_grad()
        out = model(x)
        loss = crit(out, y)
        loss.backward()
        opt.step()

        loss_sum += loss.item() * x.size(0)
        correct  += (out.argmax(1) == y).sum().item()
        total    += x.size(0)
    return loss_sum / total, correct / total

@torch.no_grad()
def evaluate(model, loader, crit):
    model.eval()
    total, correct, loss_sum = 0, 0, 0.0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        out = model(x)
        loss = crit(out, y)
        loss_sum += loss.item() * x.size(0)
        correct  += (out.argmax(1) == y).sum().item()
        total    += x.size(0)
    return loss_sum / total, correct / total

EPOCHS = 10
for epoch in range(1, EPOCHS + 1):
    tr_loss, tr_acc = train_epoch(model, train_loader, optimizer, criterion)
    te_loss, te_acc = evaluate(model, test_loader, criterion)
    scheduler.step()
    print(f"Epoch {epoch:2d} | "
          f"train loss {tr_loss:.4f} acc {tr_acc:.4f} | "
          f"test loss {te_loss:.4f} acc {te_acc:.4f}")

    

১০ epoch — Colab T4 GPU-তে ~৩-৪ মিনিট। Test accuracy ~৯৯.২%।

৫ · Confusion matrix ও per-class accuracy

Python · Confusion matrix
import numpy as np

@torch.no_grad()
def confusion_matrix(model, loader, n_classes=10):
    model.eval()
    cm = np.zeros((n_classes, n_classes), dtype=int)
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        pred = model(x).argmax(1)
        for t, p in zip(y.cpu().numpy(), pred.cpu().numpy()):
            cm[t, p] += 1
    return cm

cm = confusion_matrix(model, test_loader)
print("Confusion matrix:")
print(cm)
# Per-class accuracy
for i in range(10):
    acc = cm[i, i] / cm[i].sum()
    print(f"Class {i}: {acc*100:.2f}%")

    

Confusion matrix-এ দেখা যায় — ৪ ও ৯ মাঝে confusion typical (similar shape)। Per-class accuracy ৯৮-৯৯.৭% range।

৬ · Save ও load model

Python · Persistence
# Save state_dict (recommended)
torch.save(model.state_dict(), 'mnist_cnn.pt')
print("Saved!")

# Load — somewhere else
new_model = MNIST_CNN().to(device)
new_model.load_state_dict(torch.load('mnist_cnn.pt'))
new_model.eval()

# Single image inference
img = test_set[0][0].unsqueeze(0).to(device)   # (1, 1, 28, 28)
with torch.no_grad():
    logits = new_model(img)
    pred = logits.argmax(1).item()
    prob = F.softmax(logits, dim=1).max().item()
print(f"Predicted: {pred}, confidence: {prob:.3f}")
print(f"True label: {test_set[0][1]}")

    

৭ · Improvements — ৯৯.৫%+ পেতে

  • Data augmentation: RandomRotation, RandomAffine — generalization বাড়ে।
  • Larger model: ResNet-style residual block।
  • Mixed precision: AMP — ২x faster training।
  • Learning rate finder: Optimal LR auto-detect।
  • Cosine annealing: Better than StepLR usually।
  • Test-time augmentation: Multiple augmented prediction average।
  • Ensemble: ৩-৫ model train, average prediction।

৮ · Common pitfalls

  • Forgot model.train()/model.eval() — BatchNorm ও Dropout buggy behavior।
  • zero_grad() মিস — gradient accumulate, exploding loss।
  • Mixed device — "Expected all tensors on same device"।
  • Normalize ভুলা — image scale issue, training slow।
  • Overfitting — train high, test low — dropout/augment বাড়ান।
  • Batch size too small — GPU underutilized, gradient noisy।

৯ · Production deployment ideas

  • ONNX export — universal runtime।
  • TorchScript — JIT compile, no Python dependency।
  • Quantization INT8 — 4x size, mobile-friendly।
  • Web demo: Gradio / Streamlit — interactive UI।
  • API service: FastAPI + Docker — production endpoint।
  • Mobile: PyTorch Mobile / TFLite — phone inference।
এই lesson আপনার DL skill-এর "graduation moment"। MNIST simple, কিন্তু এই pipeline-ই production model-এর foundation। L40-এ পুরো কোর্সের overview ও পরবর্তী direction।

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

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

প্র ০১ MNIST ৯৯%+ সহজ — কিন্তু এই accuracy "real world handwriting"-এ ফেল করে। কেন? Real digit dataset-এ migration করতে কী করব?

MNIST-এর ৯৯% misleading — production deployment-এ surprisingly poor। Important lesson।

MNIST limitations:

  • Centered, normalized।
  • Single digit per image।
  • Clean background।
  • Fixed size (২৮×২৮)।
  • US-style handwriting bias।

Real world challenges:

  • Skewed angle, rotation।
  • Multiple digit per image।
  • Variable size, position।
  • Noisy background, lighting।
  • Different style (Bangla numeral!)।

Distribution shift:

  • Training distribution ≠ deployment।
  • Out-of-distribution failure।
  • Confidence high — accuracy low।
  • Calibration broken।

Strategies for real-world:

(১) More diverse data:

  • EMNIST — extended MNIST (handwritten letters)।
  • SVHN — Street View House Numbers।
  • Handwritten Bangla — ISI Kolkata, BUET।
  • Real-world capture data।

(২) Data augmentation:

  • RandomAffine — rotation, translation।
  • ElasticDeformation — handwriting variation।
  • RandomErasing — robustness।
  • Color jitter (color images)।

(৩) Pre-processing:

  • Adaptive thresholding।
  • Bounding box detection।
  • Centering, normalization।
  • Multi-digit segmentation।

(৪) Architecture upgrade:

  • ResNet, EfficientNet।
  • Attention mechanisms।
  • Transformer-based।
  • Ensemble multiple model।

(৫) Domain adaptation:

  • Source: MNIST, target: real।
  • Adversarial training।
  • Feature alignment।
  • Active learning unlabeled data।

Bangla numeral case:

  • "০, ১, ২, ৩..." — visually different।
  • Conjunct shapes complex।
  • BanglaLekha dataset (Hossain et al.)।
  • Specific training required।

Production pipeline:

# Real-world digit recognition
# 1. Detection — find digit regions (YOLOv5)
# 2. Segmentation — separate digits
# 3. Classification — your CNN
# 4. Post-processing — sequence join

Confidence calibration:

  • Temperature scaling (Guo et al.)।
  • Reject low-confidence prediction।
  • Out-of-distribution detection।
  • Production safety net।

Real datasets to consider:

  • MNIST-M (different background)।
  • USPS — postal digits।
  • QMNIST — extended।
  • Custom collection।

Practical Bangladesh use case:

  • Bank check digit recognition।
  • Bangla numeral OCR (NID, education forms)।
  • Postal address recognition।
  • BanglaLekha + augmentation key।

Evaluation rigor:

  • Held-out real test set।
  • Multiple writer।
  • Different conditions।
  • Confusion matrix analysis।

মূল উপলব্ধি: MNIST academic baseline — production not directly applicable। Distribution shift critical। Real data + augmentation + robust architecture combination needed। Bangla numeral specific dataset। Production ML — beyond accuracy number।

প্র ০২ Confusion matrix-এ ৪ ও ৯ confused — visualize করে কী পাবেন? Wrong prediction-এ AI কেন "সাহসী" থাকে?

Misclassification analysis — model debugging-এর critical skill।

Visualization technique:

import matplotlib.pyplot as plt
# Find misclassified
wrong = []
for x, y in test_loader:
    x, y = x.to(device), y.to(device)
    pred = model(x).argmax(1)
    mask = pred != y
    if mask.any():
        wrong.append((x[mask].cpu(), y[mask].cpu(),
                      pred[mask].cpu()))

# Plot grid
fig, axes = plt.subplots(2, 5, figsize=(10, 4))
for i, ax in enumerate(axes.flat):
    img, true, pred = wrong[0][0][i], wrong[0][1][i], wrong[0][2][i]
    ax.imshow(img.squeeze(), cmap='gray')
    ax.set_title(f'T:{true} P:{pred}')

Common confusion pairs:

  • ৪ ↔ ৯: similar curve top।
  • ৩ ↔ ৮: closed loops।
  • ১ ↔ ৭: vertical stroke।
  • ০ ↔ ৬: oval shape।

Why model confidently wrong:

  • Softmax output peaked even for OOD।
  • Features dominate similar pattern।
  • No "I don't know" output।
  • Calibration issue।

Examples:

  • Sloppy 4 — top open, 9-like।
  • European 7 — extra crossbar, 4-like।
  • Squashed 8 — looks like 3।
  • Edge cases inherently ambiguous।

Confidence analysis:

# Wrong predictions confidence
wrong_confs = []
for x, y in test_loader:
    x, y = x.to(device), y.to(device)
    out = model(x)
    pred = out.argmax(1)
    probs = F.softmax(out, dim=1).max(1)[0]
    mask = pred != y
    wrong_confs.extend(probs[mask].tolist())

import numpy as np
print(f"Mean wrong confidence: {np.mean(wrong_confs):.3f}")
print(f"Max wrong confidence: {np.max(wrong_confs):.3f}")
# Often >0.95 — overconfident

Calibration techniques:

  • Temperature scaling — divide logits by T।
  • Platt scaling — sigmoid fit।
  • Isotonic regression।
  • Deep ensembles — uncertainty estimate।

Out-of-distribution detection:

  • Maximum softmax probability।
  • Energy-based score।
  • Mahalanobis distance feature space।
  • Reject low confidence।

Saliency map analysis:

  • Gradient w.r.t. input।
  • Highlight important pixel।
  • Misclassification — wrong region focus।
  • Debug feature learning।

Grad-CAM:

  • Class activation map।
  • What region influenced decision।
  • Visual debugging।
  • Production explanation।

Improvement strategies:

  • Hard example mining।
  • Focal loss — hard sample focus।
  • Curriculum learning — easy→hard।
  • Data augmentation targeted।

Human comparison:

  • Show wrong examples to humans।
  • Many ambiguous — humans also struggle।
  • Inter-annotator agreement।
  • Upper bound accuracy।

Production safeguards:

  • Reject threshold — confidence < 0.9।
  • Human-in-loop for uncertain।
  • Continuous monitoring।
  • Drift detection।

মূল উপলব্ধি: Misclassification visualization — model understanding key। Confused pairs systematic, often visually similar। Confidence often miscalibrated — high even wrong। Calibration + OOD detection production essential। Saliency map debugging tool।

প্র ০৩ Train accuracy ৯৯.৯% কিন্তু test ৯৭% — overfitting-এর diagnostic ও fix step-by-step?

Overfitting — DL-এর most common issue। Diagnostic + fix systematic।

Diagnostic:

(১) Loss curve analysis:

  • Training loss decrease।
  • Validation loss increase or plateau।
  • Gap widening — overfit।

(২) Accuracy gap:

  • Train vs validation gap >2-3% concerning।
  • $>5\%$ overfit clear।

(৩) Capacity check:

  • Parameter / training sample ratio।
  • >1 — likely overfit risk।

Fix hierarchy:

(১) More data — best fix:

  • More training data ideal।
  • Often unavailable।

(২) Data augmentation:

train_transform = transforms.Compose([
    transforms.RandomRotation(10),
    transforms.RandomAffine(0, translate=(0.1, 0.1)),
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,)),
])
  • Effectively increase data।
  • Free, easy।
  • Domain-appropriate augmentation।

(৩) Dropout:

  • Already 0.3 in code।
  • Increase to 0.5 if persist।
  • Multiple layers।

(৪) Weight decay:

optimizer = optim.Adam(model.parameters(),
                       lr=1e-3, weight_decay=1e-4)
  • L2 regularization।
  • Standard 1e-4 to 1e-3।

(৫) Early stopping:

  • Validation loss monitor।
  • No improvement 3 epoch — stop।
  • Best checkpoint use।

(৬) Reduce model size:

  • Fewer parameters।
  • Smaller layer।
  • Less overfit capacity।

(৭) Batch normalization:

  • Already in code।
  • Slight regularization effect।
  • Stable training।

(৮) Learning rate schedule:

  • Cosine annealing।
  • Warmup gradual।
  • Better convergence।

Implementation order:

  1. Add augmentation first।
  2. Increase dropout if still overfit।
  3. Add weight decay।
  4. Early stopping।
  5. Architecture tweak।
  6. More data if possible।

Validation set strategy:

  • Train/val/test split।
  • Tune on val, evaluate test।
  • Cross-validation for small data।

Modern techniques:

  • Mixup — sample interpolation।
  • CutMix — patch swap।
  • Label smoothing — soft targets।
  • Stochastic depth।

MNIST-specific:

  • RandomRotation(10) major help।
  • RandomAffine translation 10%।
  • Train acc dip slightly, test rise।
  • ৯৯.৪%+ achievable।

Bias-variance trade-off:

  • High variance = overfit।
  • Regularization reduce variance।
  • Slight bias increase OK।
  • Total error decrease goal।

মূল উপলব্ধি: Overfitting diagnostic — loss/accuracy gap। Fix order: augmentation, dropout, weight decay, early stopping। MNIST RandomRotation impactful। Modern techniques (Mixup, label smoothing) extra boost। Systematic approach essential।

প্র ০৪ Bangladesh-এর একটি bank check digit OCR system বানাতে চাচ্ছে। MNIST baseline থেকে production system কিভাবে?

Practical Bangladesh ML deployment — bank check OCR। Production-grade engineering।

Use case requirements:

  • Cheque amount recognition (digit + Bangla)।
  • Account number reading।
  • High accuracy — money matters।
  • Real-time processing।
  • Audit trail।

Pipeline architecture:

Stage 1 — Image processing:

  • Cheque scan/photo।
  • Geometric correction (perspective)।
  • Skew correction।
  • Background removal।

Stage 2 — Field detection:

  • YOLOv8 — amount field, signature, date।
  • Custom train Bangladesh check format।
  • Templates per bank।

Stage 3 — Digit segmentation:

  • Connected components।
  • Touching digit separation।
  • Vertical projection profile।

Stage 4 — Classification:

  • Per-digit CNN।
  • Latin + Bangla numeral।
  • Confidence per digit।

Stage 5 — Post-processing:

  • Sequence reconstruction।
  • Sanity check (amount > 0)।
  • Format validation।
  • Cross-reference written amount।

Data collection:

  • Real cheque sample collection।
  • Diverse banks (Sonali, DBBL, City)।
  • Privacy/legal compliance।
  • Synthetic augmentation।

Bangla numeral specific:

  • Use BanglaLekha-Isolated dataset।
  • BUET handwritten Bangla numeral।
  • Mixed Latin-Bangla detection।
  • Conjunct/joint character handling।

Robustness:

  • Stamp overlay handling।
  • Strikethrough recognition।
  • Various pen colors।
  • Stain/fold tolerance।

Confidence-based routing:

# Critical decision logic
def route_decision(digit_confs, amount):
    min_conf = min(digit_confs)
    if min_conf > 0.99 and len(digit_confs) >= 3:
        return "auto_process"
    elif min_conf > 0.95:
        return "human_verify"
    else:
        return "human_review"

Cross-validation:

  • Numeric amount vs written amount।
  • Discrepancy → reject।
  • Multiple model ensemble।
  • Voting consensus।

Architecture upgrade from MNIST:

  • EfficientNet-B0 for digit।
  • Detection — YOLO/DETR।
  • OCR — TrOCR style transformer।
  • Ensemble multiple।

Training strategy:

  • Synthetic data first।
  • Real data fine-tune।
  • Active learning ongoing।
  • Continuous improvement।

Deployment infrastructure:

  • On-premise (data sensitivity)।
  • Docker + Kubernetes।
  • GPU server bank-internal।
  • Audit log every decision।

Monitoring:

  • Per-day accuracy tracking।
  • Confidence distribution।
  • Reject rate trend।
  • Human override rate।

Compliance:

  • Bangladesh Bank guidelines।
  • Data Protection Act।
  • Customer data security।
  • Audit trail mandatory।

Error handling:

  • OOD detection — unusual cheque।
  • Reject confidently।
  • Human escalation।
  • Customer protection priority।

Human-in-loop:

  • Verification UI।
  • Quick review workflow।
  • Disagreement learning loop।
  • Continuous data collection।

Cost analysis:

  • Initial development: ৬-১২ মাস।
  • Hardware: $50K-200K।
  • Annual maintenance: $50K।
  • ROI: significant via automation।

Bangladesh banking landscape:

  • BACPS (Bangladesh Automated Cheque Processing System)।
  • BB initiative driving digitization।
  • Local bank tech adoption growing।
  • Practical AI opportunity।

মূল উপলব্ধি: Bank check OCR — multi-stage pipeline। Detection → segmentation → classification → validation। MNIST CNN entry point only। Real production — robust architecture, confidence routing, human-in-loop। Bangladesh banking — mature ML opportunity। Compliance + accuracy critical।

অনুশীলন

  1. Run end-to-end: Colab-এ পুরো code চালান, ১০ epoch — final test accuracy কত পান?

    Typical: ~৯৯.১-৯৯.৩%। Variations:

    • ৯৮.৫% — কিছু issue (data normalization, batch size)।
    • ৯৯.০% — baseline OK।
    • ৯৯.৩%+ — excellent।
    • ৯৯.৫%+ — augmentation/longer training সাথে possible।
  2. Augmentation add: RandomRotation ও RandomAffine include করে retrain — accuracy difference?
    train_transform = transforms.Compose([
        transforms.RandomRotation(10),
        transforms.RandomAffine(0, translate=(0.1, 0.1)),
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,)),
    ])
    train_set = datasets.MNIST(root='./data', train=True,
                                download=True, transform=train_transform)

    Test accuracy ০.১-০.৩% improve সাধারণত। Train accuracy কিছু কম, but test better — generalization improvement।

  3. চিন্তা: এই CNN-এর কোন layer এ output features সবচেয়ে "abstract"? কেন?

    FC1 (১২৮-D) — সবচেয়ে abstract। Hierarchy:

    • Conv1 — edge, simple texture।
    • Conv2 — corner, curve, digit-part।
    • Flatten — combined spatial features।
    • FC1 — global digit representation।
    • FC2 — class score।

    FC1-এর activation visualize করলে — same digit-এর different style cluster করে। This is the "learned representation" useful for transfer learning, retrieval, similarity search।

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন — Google-এর ফ্রি অনলাইন Python পরিবেশ, শুধু Gmail অ্যাকাউন্ট লাগে।
পূর্ববর্তী পাঠ
পাঠ ৩৮ · GPU ও CUDA