সরাসরি পর্বে যান
পর্ব ২১ · ৯১-এর মধ্যে · মডিউল ৪

CNN-এর স্থাপত্য — LeNet থেকে AlexNet, VGG আর Inception

CNN Architectures — LeNet-5, AlexNet, VGG and GoogLeNet/Inception, Counted and Trained

Taught in Bangla, this lesson reads CNN architectures as design decisions. It counts LeNet-5's parameters, explains AlexNet's ReLU, dropout, augmentation and GPU training, shows why stacked 3×3 filters in VGG need fewer weights than larger ones, and measures how 1×1 bottlenecks cut an Inception block's multiply-adds. Real desktop runs count torchvision models without weights and train an MLP, a LeNet-style and a small VGG-style network on Fashion-MNIST over three seeds, recording accuracy and epoch time.

এই পর্বে যা শিখবেন

  • LeNet-5, AlexNet, VGG আর GoogLeNet-এর মূল ধারণা, আর প্রতিটি কোন সমস্যার উত্তর ছিল
  • যেকোনো CNN-এর স্তর ধরে output shape, parameter আর MAC হাতে আর NumPy-তে হিসাব করা
  • 3×3 স্তূপের যুক্তি: receptive field, parameter আর non-linearity
  • 1×1 bottleneck দিয়ে Inception block-এর খরচ কমানোর হিসাব
  • PyTorch-এ LeNet-ধাঁচ আর VGG-ধাঁচের CNN বানিয়ে MLP baseline-এর সঙ্গে সৎ তুলনা, আর ImageNet-এর মতো benchmark-এর ভূমিকা ও সীমা

পর্ব ২০ (Convolution — filter, stride, padding আর receptive field)-এ একটি convolution স্তরের ভেতরের হিসাব দেখেছেন। এই পর্বে প্রশ্ন বদলাচ্ছে: এমন স্তর কীভাবে সাজালে একটি ভালো network হয়? ইতিহাসের চারটি বিখ্যাত নকশা ধরে ধরে দেখব, প্রতিটির shape আর খরচ গুনব, তারপর ছোট দুটি সংস্করণ ডেস্কটপে সত্যিকারের ডেটায় শেখাব। পাতার তিনটি ব্লক ব্রাউজারের Python আর NumPy-তে চলে; প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে। PyTorch-এর সব সংখ্যা ডেস্কটপের সত্যিকারের run থেকে, CPU-তে।

১ · স্থাপত্য কী, আর ImageNet কেন মঞ্চ হয়ে উঠল

একই ইট, রড আর সিমেন্ট দিয়ে একতলা টিনের ঘরও হয়, দশতলা ভবনও হয়। পার্থক্য নকশায়: কোথায় কলাম, কোন তলায় কত ঘর, সিঁড়ি কোথায়। CNN-এও উপকরণ একই: convolution, pooling, activation আর fully connected স্তর। স্থাপত্য (architecture) হলো এগুলো কোন ক্রমে, কত মাপে আর কত সংখ্যায় সাজানো হবে, সেই নকশা।

একটি CNN-এর নকশায় প্রায় সবসময় কয়েকটি একই প্রশ্ন আসে:

  • Filter-এর মাপ: 11×11, 5×5 না 3×3? বড় filter বড় এলাকা দেখে, কিন্তু দামি।
  • গভীরতা আর প্রস্থ: কতগুলো স্তর, আর প্রতিটিতে কত channel?
  • Downsampling-এর সূচি: কোথায় ছবির উচ্চতা-প্রস্থ অর্ধেক হবে, আর তখন channel কতটা বাড়বে?
  • মাথা (head): শেষে বড় fully connected স্তর, নাকি global average pooling?

এই প্রশ্নগুলোর উত্তর খোঁজার মঞ্চ ছিল ImageNet. Fei-Fei Li-র নেতৃত্বে Deng ও সহকর্মীরা ২০০৯ সালে এটি প্রকাশ করেন; ২০১০ থেকে ২০১৭ পর্যন্ত ImageNet Large Scale Visual Recognition Challenge (ILSVRC) হতো, যার classification অংশে প্রায় ১২ লাখ train ছবি আর ১০০০টি শ্রেণি। প্রধান মাপ ছিল top-5 error: model-এর পাঁচটি সেরা অনুমানের কোনোটি ঠিক না হলে ভুল। সবাই একই train আর test ভাগে লড়েছে বলে এটি একটি benchmark dataset: নতুন নকশা সত্যিই ভালো কি না, সংখ্যায় তুলনা করা যেত। এই পর্বের চারটি নকশার তিনটি এই প্রতিযোগিতা থেকেই খ্যাতি পেয়েছে।

নকশাসালমূল ধারণাযে সমস্যার উত্তর
LeNet-5১৯৯৮conv আর subsampling পালাক্রমে, শেষে fully connectedহাতে লেখা অঙ্ক, ছোট ছবি
AlexNet২০১২বড় আর গভীর, ReLU, dropout, augmentation, GPUলাখো রঙিন ছবিতে শেখা
VGG২০১৪শুধু 3×3 স্তূপ, pooling-এর পরে channel দ্বিগুণসরল নিয়মে গভীরতা বাড়ানো
GoogLeNet (Inception)২০১৪বহু শাখা, 1×1 bottleneck, global average poolingকম খরচে গভীর আর চওড়া

২ · LeNet-5 — ছাঁচটি যেখান থেকে এল

Yann LeCun, Léon Bottou, Yoshua Bengio আর Patrick Haffner-এর ১৯৯৮ সালের প্রবন্ধ “Gradient-Based Learning Applied to Document Recognition”-এ LeNet-5 বর্ণিত। এর আগে ১৯৮৯ সালেই LeCun ও সহকর্মীরা backpropagation দিয়ে ডাকের zip code-এর অঙ্ক চেনার convolutional network দেখিয়েছিলেন। প্রবন্ধে বলা হয়, এমন network-ভিত্তিক একটি ব্যবস্থা ব্যাংকের চেক পড়ার কাজে বাণিজ্যিকভাবে ব্যবহার হয়েছিল; সংখ্যাগুলো উৎসভেদে আলাদা, তাই এখানে পরিমাণ বলছি না।

ছাঁচটি সহজ: [convolution → subsampling] দুবার, তারপর কয়েকটি fully connected স্তর. প্রতিটি convolution-এর output shape আসে পর্ব ২০-এর সূত্র থেকে:

Hout = ⌊(H + 2P − K) / S⌋ + 1

এখানে H হলো input-এর উচ্চতা (বা প্রস্থ), K kernel-এর মাপ, P প্রতি পাশে padding, S stride, আর ⌊ ⌋ মানে নিচের পূর্ণসংখ্যায় নামানো। আর একটি convolution স্তরের parameter = Cout × (Cin × K × K + 1), যেখানে +1 প্রতিটি output channel-এর bias. খরচ গোনা হয় MAC-এ (একটি গুণ আর যোগ): Hout × Wout × Cout × Cin × K × K. এক MAC প্রায় দুটি FLOP.

shape = channel × উচ্চতা × প্রস্থ · parameter = আধুনিক সরল রূপে
স্তর
হিসাব
output shape
parameter
মোট এ পর্যন্ত
  1. Input. মূল প্রবন্ধে ৩২×৩২ ছবি, মাঝখানে ২৮×২৮-এর মধ্যে অঙ্ক। এখনো কোনো parameter নেই।
  2. C1. ৬টি 5×5 filter, padding নেই: (৩২ − ৫) + ১ = ২৮. প্রতিটি filter-এ ২৫টি weight আর ১টি bias, মোট ১৫৬.
  3. S2. 2×2 জানালায় subsampling, উচ্চতা-প্রস্থ অর্ধেক: ১৪×১৪. মূল প্রবন্ধে এখানে প্রতি channel-এ একটি শেখা গুণক আর bias ছিল (মোট ১২টি); আধুনিক রূপে max pooling, parameter নেই।
  4. C3. ১৬টি 5×5 filter, প্রতিটি ৬টি channel দেখে: ১৬ × (১৫০ + ১) = ২,৪১৬. মূল প্রবন্ধে প্রতিটি filter সব channel দেখত না, একটি সংযোগ-তালিকা মেনে; তাতে ছিল ১,৫১৬.
  5. S4. আবার অর্ধেক: ১৬×৫×৫ = ৪০০টি সংখ্যা। এখানেই ছবির “স্থানিক” অংশ শেষ।
  6. C5. 5×5 filter একটি 5×5 map-এ পড়লে output 1×1, তাই এটি কার্যত ৪০০ → ১২০ fully connected: ৪৮,১২০ parameter, এক লাফে মোটের প্রায় ৭৮%.
  7. F6 আর output. ১২০ → ৮৪ → ১০. মোট ৬১,৭০৬, যার ৯৫.৮% fully connected অংশে। মূল প্রবন্ধ “প্রায় ৬০,০০০” বলে; তফাত আসে C3-এর সংযোগ-তালিকা আর RBF output স্তর থেকে।

নিচের ব্লক এই হিসাব যেকোনো স্তরের তালিকায় চালায়: LeNet-5-এর আধুনিক রূপ (২৮×২৮ input, তাই প্রথম conv-এ padding 2, যাতে shape মূলের মতোই থাকে) আর একটি ছোট VGG-ধাঁচের network, যা অংশ ৭-এ সত্যিই শেখাব।

def walk(name, c, h, layers):            # c = channel, h = উচ্চতা = প্রস্থ
    print(f"{name}: input {c}x{h}x{h}")
    print(f"{'layer':14s}{'output':>10s}{'params':>9s}{'MACs':>11s}")
    tot_p = tot_m = fc_p = 0
    for kind, a, k, pad in layers:
        if kind == "conv":               # a = output channel, k = kernel, stride 1
            h = h + 2 * pad - k + 1      # (H + 2P − K)/S + 1
            p, m = a * (c * k * k + 1), h * h * a * c * k * k
            c, label = a, f"conv{k}x{k}-{a}"
        elif kind == "pool":             # 2x2 max pool, stride 2: parameter নেই
            h, p, m, label = h // 2, 0, 0, "maxpool2"
        else:                            # fc: সব মান flatten করে a-টি output
            n_in = c * h * h
            p, m, c, h, label = a * (n_in + 1), n_in * a, a, 1, f"fc-{a}"
            fc_p += p
        tot_p, tot_m = tot_p + p, tot_m + m
        shape = f"{c}x{h}x{h}" if h > 1 else f"{c}"
        print(f"{label:14s}{shape:>10s}{p:>9,d}{m:>11,d}")
    print(f"{'total':14s}{'':>10s}{tot_p:>9,d}{tot_m:>11,d}"
          f"   (fc: {fc_p / tot_p:.1%} of params)\n")

P = ("pool", 0, 0, 0)
walk("LeNet-5 (modern)", 1, 28, [("conv", 6, 5, 2), P, ("conv", 16, 5, 0), P,
     ("fc", 120, 0, 0), ("fc", 84, 0, 0), ("fc", 10, 0, 0)])
walk("small VGG-style", 1, 28, [("conv", 32, 3, 1), ("conv", 32, 3, 1), P,
     ("conv", 64, 3, 1), ("conv", 64, 3, 1), P, ("fc", 128, 0, 0), ("fc", 10, 0, 0)])

আউটপুট (পাতার Python-এ চালানো):

LeNet-5 (modern): input 1x28x28
layer             output   params       MACs
conv5x5-6        6x28x28      156    117,600
maxpool2         6x14x14        0          0
conv5x5-16      16x10x10    2,416    240,000
maxpool2          16x5x5        0          0
fc-120               120   48,120     48,000
fc-84                 84   10,164     10,080
fc-10                 10      850        840
total                      61,706    416,520   (fc: 95.8% of params)

small VGG-style: input 1x28x28
layer             output   params       MACs
conv3x3-32      32x28x28      320    225,792
conv3x3-32      32x28x28    9,248  7,225,344
maxpool2        32x14x14        0          0
conv3x3-64      64x14x14   18,496  3,612,672
conv3x3-64      64x14x14   36,928  7,225,344
maxpool2          64x7x7        0          0
fc-128               128  401,536    401,408
fc-10                 10    1,290      1,280
total                     467,818 18,691,840   (fc: 86.1% of params)

একটি নকশার সবচেয়ে গুরুত্বপূর্ণ শিক্ষা এই দুই কলামের অমিলে। Parameter থাকে fully connected স্তরে, খরচ থাকে convolution-এ। LeNet-এ fc-120 একাই ৪৮ হাজার parameter, কিন্তু মাত্র ৪৮ হাজার MAC; দ্বিতীয় conv-এর parameter মাত্র ২,৪১৬, অথচ MAC ২ লাখ ৪০ হাজার, কারণ একই weight ১০০টি অবস্থানে আবার ব্যবহার হয়। ছোট VGG-এ মোট MAC প্রায় ১.৮৭ কোটি, LeNet-এর প্রায় ৪৫ গুণ, যদিও parameter মাত্র সাড়ে সাত গুণ। খরচের বড় অংশ দুটি স্তরে (৭২ লাখ করে): পূর্ণ ২৮×২৮ মাপে 32 → 32 আর ১৪×১৪ মাপে 64 → 64. দেখুন, pooling-এ অবস্থান চার ভাগের এক ভাগ হলো আর channel দ্বিগুণ হলো, তাই প্রতি স্তরের খরচ একই রইল: VGG-এর ইচ্ছাকৃত নিয়ম।

ডেস্কটপে একই LeNet torchinfo দিয়ে মিলিয়ে দেখা হয়েছে (সত্যিকারের run, torch 2.15.0, torchinfo 1.8.0; লেখার সময়ের সংস্করণ):

~/bazarghar-dl$ uv run python shapes.py
Layer (type (var_name))                  Output Shape              Param #
Sequential (Sequential)                  [1, 10]                   --
├─Conv2d (0)                             [1, 6, 28, 28]            156
├─MaxPool2d (2)                          [1, 6, 14, 14]            --
├─Conv2d (3)                             [1, 16, 10, 10]           2,416
├─MaxPool2d (5)                          [1, 16, 5, 5]             --
├─Flatten (6)                            [1, 400]                  --
├─Linear (7)                             [1, 120]                  48,120
├─Linear (9)                             [1, 84]                   10,164
├─Linear (11)                            [1, 10]                   850
…  (ReLU-এর সারি আর ঘের-রেখা বাদ)
Total params: 61,706

হাতের হিসাব আর library-র হিসাব হুবহু মিলেছে। নতুন কোনো network লেখার পর প্রথম কাজ এটিই: একটি নকল input চালিয়ে প্রতিটি স্তরের shape দেখা। ভুল in_features-এর সবচেয়ে পরিচিত error, “mat1 and mat2 shapes cannot be multiplied”, এভাবেই আগে ধরা পড়ে (পর্ব ০৯)।

৩ · AlexNet — একই ছাঁচ, বড় মাপ, আর চারটি কৌশল

২০১২ সালে Alex Krizhevsky, Ilya Sutskever আর Geoffrey Hinton-এর “ImageNet Classification with Deep Convolutional Neural Networks” (NeurIPS ২০১২) ILSVRC-2012-এ top-5 test error ১৫.৩% পায়, যেখানে দ্বিতীয় সেরা দলের ২৬.২%. প্রবন্ধ অনুযায়ী network-এ প্রায় ৬ কোটি parameter আর সাড়ে ছয় লাখ neuron; পাঁচটি convolution আর তিনটি fully connected স্তর; দুটি GPU-তে পাঁচ-ছয় দিন train. এই ফলকে প্রায়ই deep learning-এর পুনরুত্থানের মোড় বলা হয়, যদিও একটি ঘটনা একা ইতিহাস বদলায় না: বড় লেবেলযুক্ত ডেটা, সস্তা GPU আর আগের দুই দশকের কাজ একসঙ্গে পেকেছিল।

নকশায় AlexNet আসলে LeNet-এর বড় ভাই: conv আর pool, শেষে fully connected. নতুনত্ব চারটি কৌশলে, যার প্রতিটি এই কোর্সের আগের কোনো পর্বের বিষয়:

ReLU

tanh-এর বদলে ReLU. প্রবন্ধে একটি ছোট পরীক্ষায় দেখানো হয়, CIFAR-10-এ নির্দিষ্ট training error-এ পৌঁছাতে ReLU network tanh-এর চেয়ে প্রায় ছয় গুণ কম epoch নেয়। কারণ saturation নেই (পর্ব ১৭)।

Dropout

প্রথম দুটি fully connected স্তরে p = 0.5 dropout. প্রায় সব parameter ওই স্তরে, তাই overfitting-এর ভয়ও সেখানে (পর্ব ১৬)।

Augmentation

২৫৬×২৫৬ ছবি থেকে এলোমেলো ২২৪×২২৪ অংশ কাটা, আড়াআড়ি উল্টানো, আর রঙের তীব্রতা সামান্য বদলানো। Data augmentation-এর পুরো আলোচনা পর্ব ২৪ (Data augmentation)-এ।

GPU

দুটি GPU-তে network ভাগ করে train; কিছু স্তর শুধু নিজের GPU-র অর্ধেক channel দেখত। GPU-তে হিসাবের মূল ধারণা পর্ব ১২-এ।

আরও দুটি ছোট জিনিস ছিল, যা পরে প্রায় বাদ পড়েছে: local response normalization (পাশের channel-গুলোর সঙ্গে তুলনা করে মান ছোট করা; পরে পর্ব ১৪-এর BatchNorm এর জায়গা নেয়) আর overlapping pooling (3×3 জানালা, stride 2).

প্রথম স্তরের হিসাব, পরীক্ষার প্রিয় প্রশ্ন। ৯৬টি 11×11 filter, stride 4, input ৩ channel. Parameter = ৯৬ × (৩ × ১২১ + ১) = ৩৪,৯৪৪. Output: (২২৭ − ১১)/৪ + ১ = ৫৫, অর্থাৎ ৯৬×৫৫×৫৫. প্রবন্ধে input ২২৪ লেখা, কিন্তু ২২৪ দিলে (২২৪ − ১১)/৪ + ১ = ৫৪.২৫ আসে, পূর্ণসংখ্যা নয়; তাই বহু বাস্তবায়ন ২২৭ ধরে, বা padding যোগ করে। পুরোনো প্রবন্ধের সংখ্যা নিজে মিলিয়ে নেওয়ার এটি ভালো উদাহরণ।

৪ · VGG — শুধু 3×3, কিন্তু গভীর

২০১৪ সালে অক্সফোর্ডের Visual Geometry Group-এর Karen Simonyan আর Andrew Zisserman-এর “Very Deep Convolutional Networks for Large-Scale Image Recognition” (ICLR ২০১৫) একটি সরল প্রশ্ন করে: সব filter 3×3 রেখে শুধু গভীরতা বাড়ালে কী হয়? তাঁদের দল ILSVRC-2014-এর localisation অংশে প্রথম আর classification অংশে দ্বিতীয় হয়। VGG-এর নিয়ম দুটি: (১) সব convolution 3×3, stride 1, padding 1, তাই shape বদলায় না; (২) প্রতিটি 2×2 max pool-এর পরে channel দ্বিগুণ (64 → 128 → 256 → 512), যতক্ষণ না ৫১২-এ পৌঁছায়। VGG-16-এ ১৩টি conv আর ৩টি fully connected স্তর।

রিকশাভ্যানে একটা বড় আলমারি একবারে তোলা কঠিন; তিনটি ছোট তাকে ভাগ করে তুললে সহজ, আর মাঝে মাঝে বাঁধন দেওয়া যায়। VGG-ও একটি বড় filter-এর কাজ কয়েকটি ছোট filter-এ ভাগ করে, আর প্রতিটির মাঝে একটি ReLU বসায়।

যুক্তিটি। একটি 3×3 conv-এর ওপর আরেকটি 3×3 বসালে দ্বিতীয় স্তরের প্রতিটি ঘর প্রথম স্তরের ৩×৩ ঘর দেখে, আর তাদের প্রত্যেকে input-এর ৩×৩ দেখে; মিলিয়ে input-এর ৫×৫ জানালা। অর্থাৎ receptive field একটি 5×5-এর সমান। n-টি stride-1 3×3 স্তরের receptive field (2n + 1) × (2n + 1). C channel থেকে C channel-এ (bias বাদ):

দুটি 3×3: 2 · 9C² = 18C² বনাম একটি 5×5: 25C² · তিনটি 3×3: 27C² বনাম একটি 7×7: 49C²

একটি K×K conv-এর weight = C·C·K·K (bias বাদ), 56×56 feature map-এ MAC = weight × 3136
ধাপ
receptive field
3×3 স্তূপ
একটি বড় filter
অনুপাত
  1. একটি 3×3. প্রতিটি output ঘর input-এর ৩×৩ দেখে; ৬৪ থেকে ৬৪ channel-এ ৯ × ৬৪² = ৩৬,৮৬৪টি weight.
  2. দুটি 3×3. Receptive field ৩ + ২ = ৫. Weight ৭৩,৭২৮, একটি 5×5-এর ১,০২,৪০০-এর ৭২%. অর্থাৎ ২৮% সাশ্রয়।
  3. ভেতরের ভার. একটি উজ্জ্বল pixel দুটি “সব-১” 3×3 পেরোলে ৫×৫ জুড়ে ছড়ায়, কিন্তু সমানভাবে নয়: প্রান্তে ১, কেন্দ্রে ৯. স্তূপ মাঝখানকে বেশি গুরুত্ব দেয়; ব্লকে নিজে দেখবেন।
  4. তিনটি 3×3. Receptive field ৭. Weight ১,১০,৫৯২, একটি 7×7-এর ২,০০,৭০৪-এর ৫৫%. VGG প্রবন্ধ উল্টো দিক থেকে বলে: 7×7-এ ৮১% বেশি weight.
  5. খরচ. ৫৬×৫৬ map-এ MAC একই অনুপাতে: ২৩১.২ বনাম ৩২১.১ মিলিয়ন, আর ৩৪৬.৮ বনাম ৬২৯.৪ মিলিয়ন। সাথে বাড়তি লাভ: তিনটি ReLU, তাই একটির চেয়ে জটিল function.
  6. দাম. বেশি স্তর মানে training-এ বেশি activation মেমরিতে রাখা আর ক্রমে চলা বেশি kernel, তাই latency বাড়তে পারে; আর গভীরতা বাড়লে শেখানো কঠিন হয়, যার উত্তর পর্ব ২২ (ResNet আর আধুনিক CNN)-এ।
import numpy as np
import matplotlib.pyplot as plt
from numpy.lib.stride_tricks import sliding_window_view

C, H = 64, 56                    # in = out = 64 channel, feature map 56x56
print("RF    one KxK: weights  MMACs | stack: weights  MMACs  ReLUs")
rf, single, stack = [], [], []
for n in range(1, 5):
    k = 2 * n + 1                # n-টি 3x3 (stride 1) মিলে (2n+1)x(2n+1) দেখে
    p1, p3 = C * C * k * k, n * C * C * 9      # bias বাদ
    print(f"{k}x{k} {p1:>14,d} {p1 * H * H / 1e6:6.1f} | {n} x 3x3"
          f"{p3:>8,d} {p3 * H * H / 1e6:6.1f}  {n}")
    rf.append(k); single.append(p1); stack.append(p3)

def conv3x3_ones(x):             # 'same' padding, সব weight 1
    return sliding_window_view(np.pad(x, 1), (3, 3)).sum(axis=(2, 3))

img = np.zeros((9, 9)); img[4, 4] = 1   # একটি মাত্র উজ্জ্বল pixel (impulse)
out = conv3x3_ones(conv3x3_ones(img))   # দুটি 3x3 পরপর
print("\nafter two 3x3 convs, which outputs see the impulse?")
print(out[2:7, 2:7].astype(int))
print("nonzero:", np.count_nonzero(out), "= a 5x5 window")

fig, ax = plt.subplots(figsize=(6, 3.4))
ax.plot(rf, np.array(single) / 1e3, "o-", label="one KxK conv")
ax.plot(rf, np.array(stack) / 1e3, "s-", label="stack of 3x3 convs")
ax.set_xticks(rf, [f"{k}x{k}" for k in rf])
ax.set_xlabel("receptive field"); ax.set_ylabel("weights (thousands)")
ax.set_title("Same receptive field, C = 64 in and out"); ax.legend()
plt.show()

আউটপুট (পাতার Python-এ চালানো; সঙ্গে একটি রেখাচিত্র):

RF    one KxK: weights  MMACs | stack: weights  MMACs  ReLUs
3x3         36,864  115.6 | 1 x 3x3  36,864  115.6  1
5x5        102,400  321.1 | 2 x 3x3  73,728  231.2  2
7x7        200,704  629.4 | 3 x 3x3 110,592  346.8  3
9x9        331,776 1040.4 | 4 x 3x3 147,456  462.4  4

after two 3x3 convs, which outputs see the impulse?
[[1 2 3 2 1]
 [2 4 6 4 2]
 [3 6 9 6 3]
 [2 4 6 4 2]
 [1 2 3 2 1]]
nonzero: 25 = a 5x5 window

প্রথম সারিতে দুই পদ্ধতি সমান, কারণ একটি 3×3 নিজেই 3×3. তারপর ফারাক বাড়ে: 9×9 receptive field-এ একটি বড় filter-এর weight ৩,৩১,৭৭৬, চারটি 3×3-এর মাত্র ১,৪৭,৪৫৬. রেখাচিত্রে একক filter-এর রেখা বর্গাকারে ওঠে (K², তাই প্রায় (2n + 1)²), স্তূপের রেখা সরলরেখায় (9n). Impulse-এর ৫×৫ ছক দেখায় দুটি 3×3 সত্যিই ২৫টি ঘর দেখে, কিন্তু ভার 1-2-3-2-1-এর গুণফল: কেন্দ্রের pixel ৯টি পথে পৌঁছায়, কোণের pixel একটি পথে। শেখা weight দিয়েও কাগজের receptive field-এর ভেতরে কেন্দ্র সাধারণত বেশি প্রভাব রাখে; গবেষণায় একে effective receptive field বলা হয়।

VGG-এর দাম। নিয়মটি এত সরল যে VGG বহু বছর feature বের করার জনপ্রিয় backbone ছিল। কিন্তু ২২৪×২২৪ পূর্ণ মাপে ৬৪ channel-এর দুটি 3×3 স্তর খুব দামি, আর শেষের তিনটি বড় fully connected স্তর (৭×৭×৫১২ = ২৫,০৮৮ → ৪০৯৬ → ৪০৯৬ → ১০০০) parameter-এর বেশির ভাগ নেয়। অংশ ৬-এর সত্যিকারের run-এ VGG-16-এর সংখ্যা দেখবেন।

৫ · GoogLeNet আর Inception — পাশাপাশি শাখা, 1×1-এর সরু গলা

একই ২০১৪ সালে Christian Szegedy ও সহকর্মীদের “Going Deeper with Convolutions” (CVPR ২০১৫) ভিন্ন পথ নেয়। প্রশ্ন: কোন স্তরে কোন মাপের filter লাগবে আগে থেকে জানি না; তাহলে সব মাপ পাশাপাশি চালাই না কেন? তাঁদের GoogLeNet ILSVRC-2014 classification-এ প্রথম হয়, top-5 error ৬.৬৭%; প্রবন্ধ অনুযায়ী parameter-যুক্ত স্তর ২২টি, আর parameter AlexNet-এর প্রায় ১২ ভাগের এক ভাগ। নামটি LeNet-এর প্রতি শ্রদ্ধা। (VGG দ্বিতীয় হয়েছিল, কাছাকাছি error নিয়ে।)

মূল একক Inception module: একই input-এ চারটি শাখা, আর তাদের output channel-এর দিকে জোড়া (concatenate). সমস্যা হলো 5×5 শাখা ১৯২ channel-এর ওপর সরাসরি চালালে খুব দামি। উত্তর 1×1 convolution: প্রতিটি pixel-এ শুধু channel-গুলোর weighted যোগফল, ছবির মাপ অপরিবর্তিত, কিন্তু channel ১৯২ থেকে ১৬-এ নামানো যায়। দামি শাখার আগে এই সরু গলাকে বলে bottleneck.

Inception (3a) module — চারটি শাখা, তিনটিতে 1×1 bottleneck

Input 192×28×28 → four branches → concatenated 256×28×28

Inception (3a) module with 1x1 bottlenecks input 192 × 28 × 28 1×1 conv64 1×1 conv96 1×1 conv16 3×3 maxpool 3×3 conv128 5×5 conv32 1×1 conv32 concat: 64+128+32+32 = 256 × 28 × 28 green = 1×1 bottleneck / projection
ছবি ২১.১ — সবুজ 1×1 বাক্সগুলো channel কমায় (১৯২ → ৯৬, ১৯২ → ১৬) বা pool-এর পরে ছোট করে (১৯২ → ৩২)। সব শাখার output ২৮×২৮, তাই channel-এর দিকে জোড়া যায়।
MAC = 28 · 28 · C_in · C_out · K · K (bias বাদ) · 28 · 28 = 784
ধাপ
weight
MAC
মাঝের shape
  1. সরাসরি. প্রতিটি output ঘরে ১৯২ × ২৫ = ৪,৮০০টি গুণ, ৩২টি channel আর ৭৮৪টি অবস্থানে: প্রায় ১২০.৪ মিলিয়ন MAC, একটি শাখার জন্যই।
  2. আগে 1×1. ১৯২ channel থেকে ১৬-এ নামানো: প্রতি ঘরে মাত্র ১৯২টি গুণ, মোট ২.৪ মিলিয়ন MAC.
  3. তারপর 5×5. এখন input মাত্র ১৬ channel: ১৬ × ২৫ = ৪০০টি গুণ প্রতি ঘরে, মোট ১০.০ মিলিয়ন MAC.
  4. মিলিয়ে. ১২.৪ মিলিয়ন MAC, সরাসরির প্রায় ৯.৭ ভাগের এক ভাগ। Output একই মাপের (৩২×২৮×২৮); মাঝখানে বাড়তি একটি ReLU-ও পাওয়া গেল।
  5. পুরো block. চারটি শাখায় একই কৌশলে ৩০৩.৫ থেকে ১২৮.০ মিলিয়ন MAC. আর pool শাখার পরে 1×1 না থাকলে output ৪১৬ channel হতো, যা পরের block-কে আরও দামি করত।
import numpy as np
import matplotlib.pyplot as plt

H, C_in = 28, 192                 # GoogLeNet-এর inception (3a)-র input: 192x28x28

def conv(c_in, c_out, k):         # (weights, MACs); stride 1, 'same' padding, bias বাদ
    return c_in * c_out * k * k, H * H * c_in * c_out * k * k

naive = {"1x1, 64": [conv(C_in, 64, 1)],
         "3x3, 128": [conv(C_in, 128, 3)],
         "5x5, 32": [conv(C_in, 32, 5)],
         "pool (192 out)": []}    # pool-এর পথ সব 192 channel সামনে পাঠায়
bottle = {"1x1, 64": [conv(C_in, 64, 1)],
          "1x1 96 -> 3x3 128": [conv(C_in, 96, 1), conv(96, 128, 3)],
          "1x1 16 -> 5x5 32": [conv(C_in, 16, 1), conv(16, 32, 5)],
          "pool -> 1x1 32": [conv(C_in, 32, 1)]}

totals = {}
for name, block, c_out in [("naive", naive, 64 + 128 + 32 + 192),
                           ("with 1x1 bottlenecks", bottle, 64 + 128 + 32 + 32)]:
    print(f"{name}: output {c_out}x{H}x{H}")
    for branch, layers in block.items():
        p, m = np.sum(layers, axis=0, dtype=np.int64) if layers else (0, 0)
        print(f"  {branch:20s} weights {p:>8,d}   MACs {m / 1e6:6.1f} M")
    W, M = np.sum([l for ls in block.values() for l in ls], axis=0, dtype=np.int64)
    totals[name] = M / 1e6
    print(f"  {'total':20s} weights {W:>8,d}   MACs {M / 1e6:6.1f} M\n")

fig, ax = plt.subplots(figsize=(6, 2.6))
ax.barh(list(totals), list(totals.values()), color=["#94a3b8", "#16a34a"])
ax.set_xlabel("multiply-adds per image (millions)")
ax.set_title("Inception (3a): cost of one block"); ax.invert_yaxis()
plt.show()

আউটপুট (পাতার Python-এ চালানো; সঙ্গে একটি bar chart):

naive: output 416x28x28
  1x1, 64              weights   12,288   MACs    9.6 M
  3x3, 128             weights  221,184   MACs  173.4 M
  5x5, 32              weights  153,600   MACs  120.4 M
  pool (192 out)       weights        0   MACs    0.0 M
  total                weights  387,072   MACs  303.5 M

with 1x1 bottlenecks: output 256x28x28
  1x1, 64              weights   12,288   MACs    9.6 M
  1x1 96 -> 3x3 128    weights  129,024   MACs  101.2 M
  1x1 16 -> 5x5 32     weights   15,872   MACs   12.4 M
  pool -> 1x1 32       weights    6,144   MACs    4.8 M
  total                weights  163,328   MACs  128.0 M

সরল সংস্করণে সবচেয়ে দামি শাখা 3×3 (১৭৩.৪ মিলিয়ন), তারপর 5×5 (১২০.৪). Bottleneck দেওয়ায় 3×3 শাখা ১০১.২-এ আর 5×5 শাখা ১২.৪-এ নামল; 5×5-এ লাভ বেশি, কারণ সেখানে channel ১৯২ থেকে ১৬, বারো ভাগের এক ভাগ। Weight ৩,৮৭,০৭২ থেকে ১,৬৩,৩২৮. প্রশ্ন থাকে: ১৬টি channel-এ চেপে ধরায় কি তথ্য হারায়? হারাতে পারে; লেখকদের যুক্তি ছিল, পাশের pixel-গুলোর feature খুব সম্পর্কিত, তাই কম মাত্রায় ভালোভাবে ধরা যায়। এটি ImageNet-এ কাজ করেছে বলে দেখানো হয়েছে, সব ডেটায় নিশ্চিত নিয়ম নয়।

GoogLeNet-এর আরও দুটি নকশা পরে খুব প্রভাব ফেলেছে। (১) Global average pooling: শেষে ৭×৭×১০২৪ map-এর প্রতিটি channel-এর গড় নিয়ে ১০২৪ মাপের vector, তারপর একটি মাত্র ১০২৪ → ১০০০ স্তর। VGG-এর ২৫,০৮৮ → ৪০৯৬ স্তরের প্রায় ১০ কোটি weight সেখানে লাগে না। ধারণাটি Lin, Chen আর Yan-এর ২০১৩ সালের “Network in Network” প্রবন্ধ থেকে, যেখান থেকে 1×1 convolution-এর ব্যবহারও এসেছে। (২) Auxiliary classifier: মাঝের দুটি স্তর থেকে ছোট দুটি বাড়তি classifier, যাদের loss ০.৩ গুণ করে মূল loss-এ যোগ হতো, যাতে গভীর network-এর মাঝের স্তরে gradient পৌঁছায়। পরে Inception v2/v3 (২০১৫) 5×5-কে দুটি 3×3-এ ভাঙে (VGG-এর যুক্তি) আর BatchNorm যোগ করে; residual connection আসার পর auxiliary classifier সাধারণত আর লাগে না।

৬ · সত্যিকারের হিসাব — torchvision-এর মডেল, weight ছাড়াই

বড় network-এর সংখ্যা হাতে গোনা ক্লান্তিকর, আর প্রবন্ধের সংখ্যায় ছোট অমিল থাকে। torchvision-এ alexnet, vgg11, vgg16 আর googlenet আছে; weights=None দিলে কোনো ফাইল নামে না, শুধু এলোমেলো init-এর কাঠামো তৈরি হয়, যা গোনার জন্য যথেষ্ট। খরচ মাপা হয়েছে PyTorch-এর নিজস্ব torch.utils.flop_counter.FlopCounterMode দিয়ে, একটি ছবির forward pass-এ; এটি FLOP গোনে, অর্থাৎ MAC-এর প্রায় দ্বিগুণ।

from torch.utils.flop_counter import FlopCounterMode
from torchvision import models

def count(model, shape):
    model.eval()
    params = sum(p.numel() for p in model.parameters())
    fc = sum(p.numel() for m in model.modules() if isinstance(m, torch.nn.Linear)
             for p in m.parameters())                 # fully connected-এর ভাগ
    with torch.no_grad(), FlopCounterMode(display=False) as fcm:
        model(torch.zeros(shape))
    return params, fc, fcm.get_total_flops()

net = models.googlenet(weights=None, aux_logits=False, init_weights=True)

সত্যিকারের run (ডেস্কটপ, CPU-তে; লেখার সময়ের সংস্করণ):

~/bazarghar-dl$ uv run python zoo.py
Python 3.14.4 | torch 2.15.0+cpu | torchvision 0.29.2+cpu
model                    input        params in Linear    MFLOPs
mlp (784-256-10)       1x28x28       203,530    100.0%       0.4
lenet-style            1x28x28        61,706     95.8%       0.8
small vgg-style        1x28x28       467,818     86.1%      37.4
alexnet              3x224x224    61,100,840     96.0%   1,428.4
vgg11                3x224x224   132,863,336     93.1%  15,218.2
vgg16                3x224x224   138,357,544     89.4%  30,940.5
googlenet            3x224x224     6,624,904     15.5%   2,996.8

পাঁচটি পর্যবেক্ষণ। (১) প্রথম তিন সারি পাতার ব্লকের সঙ্গে মেলে: LeNet ০.৮ MFLOP = ২ × ৪,১৬,৫২০ MAC, ছোট VGG ৩৭.৪ = ২ × ১.৮৭ কোটি। (২) AlexNet আর VGG-তে parameter-এর ৯০%-এর বেশি fully connected স্তরে; GoogLeNet-এ মাত্র ১৫.৫%, global average pooling-এর ফল। (৩) GoogLeNet-এর parameter VGG-16-এর প্রায় ২১ ভাগের এক ভাগ, FLOP প্রায় ১০ ভাগের এক ভাগ। (৪) VGG-11 থেকে VGG-16-এ parameter মাত্র ৪% বাড়ল, কিন্তু FLOP দ্বিগুণ: বাড়তি স্তরগুলো সব convolution, যা parameter-এ সস্তা কিন্তু হিসাবে দামি। (৫) torchvision-এর alexnet মূল প্রবন্ধের হুবহু নয়: এর documentation বলে বাস্তবায়নটি Krizhevsky-র ২০১৪ সালের “One weird trick…” প্রবন্ধের এক-GPU রূপের ওপর ভিত্তি করে, প্রথম স্তরে ৯৬-এর বদলে ৬৪টি filter. তাই ৬.১১ কোটি, মূলের “প্রায় ৬ কোটি”-র কাছাকাছি কিন্তু আলাদা। ব্যবহারের আগে নিজের version-এর docs দেখার এটি আরেকটি কারণ।

FLOP কম মানে দ্রুত, এমন নয়। GoogLeNet-এর FLOP VGG-16-এর দশ ভাগের এক ভাগ হলেও বাস্তবে গতির পার্থক্য এত বড় না-ও হতে পারে: অনেক ছোট শাখা মানে অনেক ছোট kernel, যা GPU-কে পুরো ব্যস্ত রাখে না, আর মেমরি থেকে ডেটা আনা-নেওয়ার খরচ FLOP-এ ধরা পড়ে না (পর্ব ১২)। নিজের যন্ত্রে latency মাপা আর তুলনা পর্ব ২২ (ResNet আর আধুনিক CNN)-এ।

৭ · নিজে বানানো — LeNet-ধাঁচ, VGG-ধাঁচ আর MLP, Fashion-MNIST-এ

এবার নকশাগুলো শেখাই। বাজারঘরের পোশাক বিভাগের ছবির জায়গায় একটি সৎ বিকল্প: Fashion-MNIST (Zalando Research, ২০১৭; MIT লাইসেন্স), ১০টি শ্রেণির ২৮×২৮ ধূসর ছবি, যেমন টি-শার্ট, ট্রাউজার, স্যান্ডেল আর ব্যাগ। এগুলো আসল বাজারঘরের ছবি নয়, ছোট আর একরঙা; তবু শ্রেণির মিল-অমিলের ধরন কাছাকাছি। ফাইলগুলো প্রকল্পের GitHub থেকে নামিয়ে torchvision যে MD5 আশা করে তার সঙ্গে মিলিয়ে download=False দিয়ে পড়া হয়েছে (পর্ব ১১)।

তিনটি model: MLP baseline (৭৮৪ → ২৫৬ → ১০, ছবিকে সমতল vector ধরে), LeNet-ধাঁচ (অংশ ২-এর আধুনিক রূপ) আর ছোট VGG-ধাঁচ (দুটি VGG block, প্রতিটিতে দুটি 3×3 conv আর একটি max pool, তারপর fc-128, dropout 0.5, fc-10). একই training: পুরো ৬০,০০০ train ছবি, ৫ epoch, batch ১২৮, AdamW lr 1e-3, একই seed (পর্ব ১০-এর loop).

def vgg_block(c_in, c_out):        # দুটি 3x3 conv, তারপর 2x2 max pool
    return nn.Sequential(
        nn.Conv2d(c_in, c_out, 3, padding=1), nn.ReLU(),
        nn.Conv2d(c_out, c_out, 3, padding=1), nn.ReLU(),
        nn.MaxPool2d(2))

def small_vgg():
    return nn.Sequential(
        vgg_block(1, 32),           # 32x14x14
        vgg_block(32, 64),          # 64x7x7
        nn.Flatten(),
        nn.Linear(64 * 7 * 7, 128), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(128, 10))

সত্যিকারের run (ডেস্কটপ, CPU-তে, ৪টি thread; seed 0):

~/bazarghar-dl$ uv run python train_cnns.py 0
torch 2.15.0+cpu | torchvision 0.29.2+cpu | CPU, 4 threads
train (60000, 1, 28, 28), test (10000, 1, 28, 28), 5 epochs, batch 128, AdamW lr 1e-3
mlp              seed 0 | params 203,530 |   0.6 s/epoch | train 0.9064 | test by epoch: 0.8511 0.8565 0.8651 0.8763 0.8774
lenet-style      seed 0 | params  61,706 |   3.2 s/epoch | train 0.9083 | test by epoch: 0.8267 0.8616 0.8716 0.8859 0.8883
small vgg-style  seed 0 | params 467,818 |  45.7 s/epoch | train 0.9388 | test by epoch: 0.8693 0.8930 0.9047 0.9140 0.9184

একটি seed-এর ফলে ভরসা না করে আরও দুটি seed চালানো হয়েছে:

~/bazarghar-dl$ uv run python train_cnns.py 1 2
…  (প্রথম দুই লাইন আগের মতোই)
mlp              seed 1 | params 203,530 |   0.7 s/epoch | train 0.9070 | test by epoch: 0.8550 0.8653 0.8652 0.8721 0.8761
mlp              seed 2 | params 203,530 |   0.8 s/epoch | train 0.9061 | test by epoch: 0.8478 0.8688 0.8674 0.8780 0.8749
lenet-style      seed 1 | params  61,706 |   4.4 s/epoch | train 0.9011 | test by epoch: 0.8358 0.8651 0.8708 0.8622 0.8874
lenet-style      seed 2 | params  61,706 |   4.5 s/epoch | train 0.8918 | test by epoch: 0.8382 0.8610 0.8726 0.8866 0.8767
small vgg-style  seed 1 | params 467,818 |  55.8 s/epoch | train 0.9408 | test by epoch: 0.8660 0.8928 0.9044 0.9145 0.9158
small vgg-style  seed 2 | params 467,818 |  60.9 s/epoch | train 0.9355 | test by epoch: 0.8738 0.8962 0.9032 0.9146 0.9101
modelparameterশেষ test accuracy (seed 0, 1, 2)গড়সময়/epoch
MLP২,০৩,৫৩০০.৮৭৭৪, ০.৮৭৬১, ০.৮৭৪৯০.৮৭৬০.৬–০.৮ সে.
LeNet-ধাঁচ৬১,৭০৬০.৮৮৮৩, ০.৮৮৭৪, ০.৮৭৬৭০.৮৮৪৩.২–৪.৫ সে.
ছোট VGG-ধাঁচ৪,৬৭,৮১৮০.৯১৮৪, ০.৯১৫৮, ০.৯১০১০.৯১৫৪৬–৬১ সে.

যা বলা যায়: তিন seed-এই ছোট VGG-ধাঁচ সবার ওপরে, MLP-এর চেয়ে প্রায় ৪ শতাংশ-বিন্দু বেশি; ভুলের হার প্রায় ১২.৪% থেকে ৮.৫%, অর্থাৎ প্রায় এক-তৃতীয়াংশ কম। LeNet-ধাঁচ MLP-এর তিন ভাগের এক ভাগেরও কম parameter-এ গড়ে সামান্য ভালো, কিন্তু ব্যবধান seed-এর ওঠানামার কাছাকাছি (seed 2-এ চতুর্থ epoch-এর ০.৮৮৬৬ থেকে শেষে ০.৮৭৬৭-এ নেমেছে)। VGG-ধাঁচের train accuracy (প্রায় ০.৯৪) test-এর চেয়ে একটু বেশি, তবে পাঁচ epoch-এ বড় overfitting নেই। দাম: প্রতি epoch-এ VGG-ধাঁচ LeNet-এর ১৩ থেকে ১৪ গুণ সময় নিয়েছে, অংশ ২-এর ব্লকের ৪৫ গুণ MAC-এর চেয়ে কম, কারণ ছোট network-এ হিসাব ছাড়াও অন্য খরচ থাকে। সময়গুলো একটি ভাগাভাগি করা CPU-তে মাপা, তাই run থেকে run-এ ওঠানামা করেছে। যা বলা যায় না: পাঁচ epoch, কোনো hyperparameter খোঁজা ছাড়া, এক ডেটাসেটের এই ফল সাধারণ নিয়ম নয়; এটি একটি স্থানীয় পরীক্ষা। BatchNorm, augmentation আর বেশি epoch যোগ করলে সংখ্যা বদলাবে (পর্ব ২২, ২৪)।

৮ · নকশার শিক্ষা — গভীরতা, প্রস্থ, downsampling আর benchmark-এর সীমা

চারটি নকশা থেকে কয়েকটি নিয়ম আজও প্রায় সব CNN-এ দেখা যায়:

ছোট filter, বেশি স্তর

3×3 স্তূপ একই receptive field কম weight-এ দেয়, সঙ্গে বেশি non-linearity. 11×11 বা 7×7 এখন সাধারণত শুধু প্রথম স্তরে (stem) দেখা যায়, যেখানে channel মাত্র ৩.

অর্ধেক মাপ, দ্বিগুণ channel

প্রতিবার উচ্চতা-প্রস্থ অর্ধেক হলে অবস্থান চার ভাগের এক ভাগ, আর channel দ্বিগুণ হলে প্রতি অবস্থানে খরচ চার গুণ; প্রতি স্তরের খরচ মোটামুটি সমান থাকে, আর গভীরে বিমূর্ত feature-এর জন্য বেশি channel মেলে।

বড় মাথা নয়

Parameter-এর বেশির ভাগ fully connected-এ, অথচ সেখানে কাজ কম। Global average pooling-এ মাথা হালকা, আর input-এর মাপও বদলানো যায়।

1×1 দিয়ে channel নিয়ন্ত্রণ

দামি স্তরের আগে channel কমানো, পরে বাড়ানো। পর্ব ২২-এর ResNet-এর bottleneck block আর MobileNet-এর হিসাব এই ধারণার ওপর দাঁড়িয়ে।

গভীরতা বনাম প্রস্থ. পর্ব ০৬-এ দেখেছেন, তত্ত্বে একটি চওড়া স্তরই অনেক কিছু পারে, কিন্তু গভীরতা কম unit-এ জটিল function দেয়। CNN-এ গভীরতার আরেকটি কারণ receptive field: প্রতিটি 3×3 স্তর আর প্রতিটি pooling জানালা বড় করে, তাই গভীর স্তর বড় বস্তু দেখতে পায়। তবে VGG-১৯-এর পরে শুধু স্তর যোগ করলে ফল আর ভালো হচ্ছিল না; এর কারণ আর সমাধান পর্ব ২২-এর বিষয়।

Downsampling-এর সূচি. খুব আগে ছোট করলে ছোট বস্তুর তথ্য হারায়, খুব দেরিতে করলে খরচ বাড়ে। অংশ ২-এর ব্লকে দেখেছেন, ছোট VGG-এর খরচের প্রায় ৩৯% পূর্ণ ২৮×২৮ মাপের একটি স্তরে। বাস্তবে সাধারণত ৪ থেকে ৫ বার অর্ধেক করা হয় (২২৪ → ৭), আর ছবির বস্তু কত বড়, তা দেখে সূচি ঠিক হয়।

Benchmark-এর ভূমিকা আর সীমা. ImageNet না থাকলে এই অগ্রগতি মাপা যেত না; একই test set-এ বছর বছর লড়াই নকশার উন্নতি স্পষ্ট করেছে। কিন্তু সমস্যাও আছে। বছরের পর বছর একই test set দেখে সিদ্ধান্ত নিলে নকশাগুলো ধীরে ধীরে সেই ডেটার সঙ্গে মানিয়ে যেতে পারে। Recht ও সহকর্মীরা ২০১৯ সালে একই পদ্ধতিতে নতুন একটি ImageNet test set (ImageNetV2) বানিয়ে দেখান, প্রায় সব model-এর accuracy উল্লেখযোগ্যভাবে কমে, যদিও model-গুলোর ক্রম মোটামুটি একই থাকে; লেখকদের মতে পতনের বড় অংশ নতুন ডেটার সামান্য ভিন্ন বণ্টন থেকে, বারবার একই test set দেখা থেকে নয়। আবার কিছু গবেষণা বড় test set-গুলোতে কয়েক শতাংশ ভুল লেবেল পেয়েছে। শিক্ষা: benchmark-এর সংখ্যা একটি নির্দিষ্ট ডেটার মাপ, বাস্তব জগতের প্রতিশ্রুতি নয়; বাজারঘরের নিজের ছবিতে নিজের validation সেট লাগবে (মেশিন লার্নিং কোর্সের পর্ব ৫৯)।

আরও গভীরে: ২০১২ আর ২০১৪-এর মাঝে Zeiler আর Fergus (২০১৩) deconvolution দিয়ে স্তরের feature দেখে AlexNet-এর প্রথম স্তর ঠিক করেন (ZFNet); feature দেখার পদ্ধতি পর্ব ২৬ (CNN কী দেখে)-এ। Inception v3 (Szegedy ও সহকর্মী, ২০১৬) n×n-কে 1×n আর n×1-এ ভাঙার (asymmetric factorisation) কথা বলে, আর label smoothing-ও সেখান থেকে জনপ্রিয় হয় (পর্ব ১৩)। আজ নকশা খোঁজার কাজ অনেকটা স্বয়ংক্রিয় (neural architecture search), আর ২০২০-এর পরে Vision Transformer CNN-এর একক আধিপত্যকে চ্যালেঞ্জ করেছে (পর্ব ৩৮); কিন্তু “shape আর খরচ আগে গুনুন” নিয়ম সব জায়গায় খাটে।

পরীক্ষা ও ইন্টারভিউতে:

  • ২২৭×২২৭×৩ input-এ ৯৬টি 11×11 filter, stride 4, padding 0: output আর parameter? (২২৭ − ১১)/৪ + ১ = ৫৫, অর্থাৎ ৯৬×৫৫×৫৫; parameter ৯৬ × (৩৬৩ + ১) = ৩৪,৯৪৪.
  • তিনটি 3×3 বনাম একটি 7×7, C channel? Receptive field একই, weight 27C² বনাম 49C², সঙ্গে তিনটি ReLU.
  • 1×1 convolution কী করে? প্রতিটি pixel-এ channel-গুলোর weighted যোগফল; মাপ না বদলে channel কমায় বা বাড়ায়, আর পরের ReLU-সহ একটি বাড়তি non-linearity যোগ করে।
  • ৫×৫ conv, ১৯২ → ৩২ channel, ২৮×২৮ map, আগে 1×1 দিয়ে ১৬-এ নামালে MAC? সরাসরি ১২০.৪ M; bottleneck-সহ ২.৪ + ১০.০ = ১২.৪ M.
  • VGG-এর parameter কোথায়? প্রায় ৯০% fully connected স্তরে; global average pooling সেটা কমায়।

৯ · AI-এর যুগে — architecture-এর কোডে AI-কে যাচাই

“একটি VGG-ধাঁচের CNN লিখে দাও” বললে AI assistant কয়েক সেকেন্ডে কোড দেবে। প্রায়ই চলবেও। কিন্তু এই ধরনের কোডে কয়েকটি ভুল খুব পরিচিত:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Flatten-এর পরে ভুল in_features (যেমন ২২৪-এর হিসাব ২৮×২৮ ছবিতে)pool-এর সংখ্যা বা padding বদলালে shape বদলায়একটি নকল input চালিয়ে প্রতিটি স্তরের shape ছাপুন, বা torchinfo
পুরোনো API: pretrained=Truetorchvision-এ এখন weights= enum; পুরোনো argument সরানো হয়েছেনিজের version-এর docs
“GoogLeNet-এ ৫ মিলিয়ন parameter”, “AlexNet-এ ৮টি GPU”-র মতো সংখ্যামনে থাকা সংখ্যা ভুল বা অন্য সংস্করণের হতে পারেনিজে numel() গুনুন; প্রবন্ধ খুলে দেখুন
GoogLeNet train-এ aux output বাদ দিয়ে tuple-কে tensor ধরাtrain mode-এ aux_logits=True হলে output একটি namedtupleoutput-এর type আর shape ছাপুন
CNN বনাম MLP-এর এক run দেখে বড় দাবিseed আর সময় না দেখলে তুলনা অসম্পূর্ণকয়েকটি seed, accuracy-র সঙ্গে সময় আর parameter

AI-কে কোডের সঙ্গে হিসাবও চাইতে বলুন, যাতে আপনি মিলিয়ে দেখতে পারেন:

আমি PyTorch-এ (version নিচে দিলাম) 64×64 মাপের রঙিন পণ্যের ছবির জন্য একটি ছোট VGG-ধাঁচের CNN চাই, ৮টি শ্রেণি। ১. কোড দেওয়ার আগে একটি টেবিলে প্রতিটি স্তরের output shape, parameter সংখ্যা আর MAC লিখে দাও, সূত্রসহ। ২. Downsampling কোথায় আর কেন, channel কীভাবে বাড়ছে, তা এক লাইনে বলো। ৩. শেষে fully connected না global average pooling, দুটির parameter তুলনা করো। ৪. torchvision বা PyTorch-এর কোনো function ব্যবহার করলে আমার version-এ তার নাম আর argument ঠিক আছে কি না বলো; না জানলে স্পষ্ট বলো। ৫. একটি নকল input দিয়ে shape যাচাইয়ের কোড আর torchinfo summary-র লাইন দাও। ৬. Accuracy-র কোনো সংখ্যা অনুমান করো না।

AI-এর দেওয়া architecture হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১LeNet ১৯৯৮ সালেই কাজ করছিল; তাহলে CNN-এর বড় সাফল্য আসতে ২০১২ পর্যন্ত কেন লাগল?

LeNet-এর সাফল্য ছিল একটি সীমিত জগতে: ছোট, একরঙা, মাঝখানে বসানো অঙ্কের ছবি। বড় রঙিন ছবিতে হাজার শ্রেণি চেনার কাজে তিনটি জিনিসের অভাব ছিল। প্রথমত ডেটা: বড় network শেখাতে লাখো লেবেলযুক্ত ছবি লাগে, আর ImageNet-এর মতো ডেটাসেট ২০০৯ সালের আগে ছিল না। দ্বিতীয়ত হিসাবের ক্ষমতা: AlexNet-এর মতো network-এর এক ছবির forward pass-এ এক বিলিয়নের বেশি FLOP (অংশ ৬-এর run-এ torchvision-এর রূপে ১.৪ বিলিয়ন), আর লাখো ছবিতে বহুবার; সাধারণ CPU-তে তা কয়েক মাসের কাজ। ২০০০-এর দশকের শেষে GPU-তে সাধারণ হিসাব চালানোর পরিবেশ (যেমন CUDA, ২০০৭) এটিকে দিনে নামিয়ে আনে। তৃতীয়ত কৌশল: sigmoid আর tanh-এ গভীর network শেখানো কঠিন ছিল, আর বড় network সহজে overfit করত; ReLU আর dropout ঠিক এই দুই সমস্যার উত্তর।

এর মাঝের সময়ে computer vision-এ প্রভাবশালী ছিল হাতে বানানো feature, যেমন SIFT আর HOG, তার ওপর SVM-এর মতো classifier. এগুলো তত্ত্বে পরিষ্কার, কম ডেটায় কাজ করে, আর তখনকার benchmark-এ ভালো ফল দিত। ফলে অনেকে neural network-কে পুরোনো, অস্থির পদ্ধতি মনে করতেন। LeCun, Hinton, Bengio-র মতো কয়েকটি দল এই সময়ে কাজ চালিয়ে যান; তাঁদের অনেক ধারণা (convolution, pretraining, ReLU) ২০১২-এর আগেই প্রকাশিত।

অর্থাৎ AlexNet নতুন তত্ত্ব আনেনি; পুরোনো ছাঁচকে নতুন মাপে চালানোর পরিবেশ তৈরি হয়েছিল। ইতিহাসকে “একটি প্রবন্ধ সব বদলে দিল” বলে সরল করা সহজ, কিন্তু সঠিক ছবি হলো ডেটা, যন্ত্র আর কৌশলের মিলন।

মূল উপলব্ধি: একটি নকশার সাফল্য নির্ভর করে তার চারপাশের ডেটা, যন্ত্র আর শেখানোর কৌশলের ওপর; ভালো ধারণাও ঠিক সময়ের অপেক্ষায় থাকতে পারে।

প্র ০২VGG আর GoogLeNet একই বছরে প্রায় একই error-এ পৌঁছায়, কিন্তু দর্শন একেবারে আলাদা। দুটির মধ্যে কোন trade-off, আর বাস্তবে কে টিকে গেল?

VGG-এর দর্শন সরলতা: একটি নিয়ম (3×3, pooling-এর পরে channel দ্বিগুণ) বারবার। কোড লেখা, বোঝা আর বদলানো সহজ; প্রতিটি স্তরের shape মাথায় হিসাব করা যায়। এর দাম খরচে: অংশ ৬-এর run-এ VGG-16-এর প্রায় ১৩.৮ কোটি parameter আর এক ছবিতে প্রায় ৩১ বিলিয়ন FLOP. GoogLeNet-এর দর্শন দক্ষতা: হাতে সাজানো বহু শাখা আর 1×1 bottleneck দিয়ে একই মানের ফল প্রায় ৬৬ লাখ parameter আর ৩ বিলিয়ন FLOP-এ। কিন্তু নকশাটি জটিল: প্রতিটি module-এ শাখার channel সংখ্যা আলাদা আলাদা বাছা, যা নতুন সমস্যায় নিজে বদলানো কঠিন।

বাস্তবে দুটিই ভিন্ন জায়গায় টিকেছে। VGG-এর feature বহু বছর ধরে style transfer আর perceptual loss-এর মতো কাজে জনপ্রিয় ছিল, কারণ এর স্তরগুলো সহজ আর “পরিষ্কার” feature দেয় বলে অনেকে পেয়েছেন। Inception পরিবার v2, v3, v4 আর Inception-ResNet-এ এগোয়, আর এর ধারণা (1×1 bottleneck, factorisation, global average pooling) পরের প্রায় সব নকশায় ঢুকে যায়। কিন্তু ২০১৫ সালে ResNet একটি তৃতীয় পথ দেখায়: VGG-এর মতো সরল নিয়মিত block, তার সঙ্গে Inception-এর bottleneck আর একটি shortcut. সরলতা আর দক্ষতা দুটিই মেলে বলে ResNet অনেক দিন ডিফল্ট backbone হয়ে থাকে।

আজকের ভাষায়, প্রকৌশলীরা প্রায়ই দক্ষতার চেয়ে নিয়মিততাকে বেশি দাম দেন, কারণ নিয়মিত নকশা scale করা, hardware-এ optimize করা আর স্বয়ংক্রিয়ভাবে খোঁজা সহজ। তবে মোবাইলের মতো সীমিত যন্ত্রে দক্ষতা আবার প্রধান হয়ে ওঠে।

মূল উপলব্ধি: ভালো নকশা শুধু benchmark-এর সংখ্যা নয়; বোঝা, বদলানো আর চালানোর খরচও তার অংশ, আর সময়ের সঙ্গে দুই দর্শনের সেরা অংশ মিলে যায়।

প্র ০৩Fashion-MNIST-এর run-এ একটি MLP-ও প্রায় ৮৮% পেল। তাহলে CNN-এর আসল সুবিধা কোথায়, আর এই ছোট পরীক্ষা থেকে কী বলা যায় না?

MLP-এর ৮৮% দেখায়, Fashion-MNIST-এর অনেকটা ধরা যায় শুধু pixel-এর মোটা ধরন দিয়ে: ট্রাউজার লম্বা, ব্যাগ চওড়া, স্যান্ডেল ফাঁকা। ছবিগুলো মাঝখানে বসানো আর একই মাপের, তাই একটি pixel-এর অবস্থান প্রায় সবসময় একই অংশ বোঝায়। এমন অবস্থায় CNN-এর inductive bias, অর্থাৎ “একই feature ছবির যেকোনো জায়গায় আসতে পারে আর কাছের pixel সম্পর্কিত”, পুরো সুবিধা দেখানোর সুযোগ কম পায়। তবু তিন seed-এই ছোট VGG MLP-এর চেয়ে কয়েক শতাংশ-বিন্দু ভালো, আর ভুলের হার প্রায় এক-তৃতীয়াংশ কম।

দুটি পার্থক্য ছোট ডেটায় দেখা যায় না, বড় ডেটায় স্পষ্ট হয়। প্রথমত parameter-এর দক্ষতা: ২২৪×২২৪×৩ ছবিতে প্রথম স্তরেই ২৫৬ unit-এর MLP-এর প্রায় ৩.৮ কোটি weight লাগে, যেখানে ৬৪টি 3×3 filter-এ মাত্র ১,৭২৮. দ্বিতীয়ত অবস্থান বদলের প্রতি আচরণ: ছবি কয়েক pixel সরালে MLP-এর কাছে তা সম্পূর্ণ নতুন vector, কিন্তু convolution-এর feature map-ও একই পরিমাণ সরে (translation equivariance), আর pooling কিছুটা অবিচলতা দেয়। পর্ব ২০-এ এই যুক্তি বিস্তারিত।

যা বলা যায় না: এক ডেটাসেট, পাঁচ epoch, কোনো hyperparameter খোঁজা ছাড়া, তিন seed-এর ফল থেকে “VGG সবসময় LeNet-এর চেয়ে ভালো” বা “CNN ছবিতে x% ভালো” বলা যায় না। সময়ও দেখতে হবে: এখানে VGG-ধাঁচ CPU-তে LeNet-এর ১৩ থেকে ১৪ গুণ সময় নিয়েছে; বাজারঘরের মতো একটি দোকানের সার্ভারে প্রতি ছবির খরচ সিদ্ধান্তের অংশ। আর LeNet-ধাঁচ মাত্র ৬২ হাজার parameter-এ MLP-এর ২ লাখের চেয়ে গড়ে সামান্য ভালো, যা দক্ষতার যুক্তিকে সমর্থন করে, যদিও ব্যবধান seed-এর ওঠানামার কাছাকাছি।

মূল উপলব্ধি: সহজ ডেটায় সরল baseline অনেক দূর যায়; architecture-এর আসল মূল্য দেখতে হয় কঠিন ডেটা, কয়েকটি seed আর খরচসহ।

প্র ০৪ImageNet-এর মতো benchmark গবেষণাকে এগিয়ে নিয়েছে, আবার সমালোচনাও আছে। এর ভালো আর খারাপ দিক কী?

ভালো দিক স্পষ্ট। একই ডেটা, একই test, একই মাপ থাকায় বিভিন্ন দলের পদ্ধতি সরাসরি তুলনা করা যেত, আর ভালো ধারণা দ্রুত ছড়াত। ILSVRC-র বছরওয়ারি ফল দেখায় কীভাবে AlexNet, তারপর ZFNet, VGG, GoogLeNet আর ResNet ধাপে ধাপে error কমিয়েছে। ImageNet-এ শেখা network-এর feature অন্য কাজেও কাজে লাগে, যা transfer learning-এর ভিত্তি (পর্ব ২৩)।

সমালোচনা কয়েক রকম। প্রথমত test set-এ অতিরিক্ত মানিয়ে যাওয়া: হাজারো গবেষণা একই test set দেখে সিদ্ধান্ত নিলে, কেউ সরাসরি প্রতারণা না করলেও, সংখ্যাগুলো আশাবাদী হতে পারে। Recht ও সহকর্মীদের ২০১৯ সালের ImageNetV2 গবেষণায় নতুন test set-এ accuracy উল্লেখযোগ্যভাবে কমেছে, যদিও model-গুলোর ক্রম প্রায় একই থেকেছে; লেখকেরা এর বড় অংশ নতুন ডেটার সামান্য ভিন্ন বণ্টনকে দায়ী করেন, শুধু overfitting-কে নয়। দ্বিতীয়ত লেবেলের মান: কিছু গবেষণা test set-এ কয়েক শতাংশ ভুল বা দ্ব্যর্থক লেবেল পেয়েছে, তাই শীর্ষ model-গুলোর ছোট ব্যবধান অর্থহীন হতে পারে। তৃতীয়ত প্রতিনিধিত্ব: ছবিগুলো মূলত ইন্টারনেট থেকে নেওয়া, ভৌগোলিক আর সাংস্কৃতিকভাবে অসম; “person” শ্রেণির কিছু লেবেল নিয়ে আপত্তির পর ImageNet-এর রক্ষণাবেক্ষণকারীরা কিছু শ্রেণি সরিয়েছেন বলে জানা যায়। বাংলাদেশের বাজারের পণ্য, লেখা বা আলোর অবস্থা এমন ডেটায় কম থাকতে পারে।

তাই benchmark একটি মানচিত্র, ভূখণ্ড নয়। নিজের কাজে নিজের ডেটা থেকে নেওয়া validation আর test সেট, আর বাস্তবে চালানোর পরে নজরদারি লাগে।

মূল উপলব্ধি: Benchmark অগ্রগতি মাপার শক্তিশালী হাতিয়ার, কিন্তু তার সংখ্যা সেই ডেটার সীমায় বাঁধা; নিজের সমস্যায় নিজে মাপা বাদ দেওয়া যায় না।

প্র ০৫Parameter কম কিন্তু FLOP বেশি, বা FLOP কম কিন্তু গতি কম — একটি model “ছোট” না “বড়”, তা কোন মাপে বলবেন?

“ছোট model” বললে অন্তত চারটি আলাদা জিনিস বোঝাতে পারে। Parameter-এর সংখ্যা ঠিক করে model ফাইলের আকার আর মেমরিতে weight-এর জায়গা: float32-এ প্রতি parameter ৪ বাইট, তাই VGG-16-এর ১৩.৮ কোটি parameter প্রায় ৫৫০ MB. মোবাইল অ্যাপে ডাউনলোডের আকারের জন্য এটা মুখ্য। FLOP বা MAC ঠিক করে একটি ছবিতে কত হিসাব; ব্যাটারি আর সার্ভারের খরচের মোটা আন্দাজ। Activation মেমরি ঠিক করে training-এ batch কত বড় নেওয়া যায়: VGG-এর প্রথম স্তরগুলোর ৬৪×২২৪×২২৪ map প্রতিটি প্রায় ৩২ লাখ সংখ্যা। Latency হলো আসল যন্ত্রে একটি ছবির উত্তর পেতে কত সময়, যা FLOP ছাড়াও মেমরি থেকে ডেটা আনা-নেওয়া, kernel-এর সংখ্যা আর hardware-এর ওপর নির্ভর করে।

এই পর্বের উদাহরণেই চারটি আলাদা দিকে যায়। LeNet-ধাঁচে parameter বেশির ভাগ fc স্তরে, কিন্তু FLOP convolution-এ। VGG-11 থেকে VGG-16-এ parameter প্রায় একই, FLOP দ্বিগুণ। GoogLeNet-এর FLOP VGG-এর দশ ভাগের এক ভাগ, কিন্তু অনেক ছোট শাখা GPU-তে পুরো দক্ষতায় চলে না, তাই গতির পার্থক্য সাধারণত কম। পরে MobileNet-এর depthwise separable convolution FLOP অনেক কমায়, তবু কিছু GPU-তে memory-bound বলে আশানুরূপ দ্রুত চলে না বলে অনেকে পেয়েছেন (পর্ব ২২)।

তাই ব্যবহারের জায়গা দেখে মাপ বাছতে হয়: ফোনে মডেলের আকার আর latency, সার্ভারে প্রতি সেকেন্ডে কত ছবি (throughput) আর খরচ, training-এ মেমরি। আর সবচেয়ে বিশ্বাসযোগ্য মাপ হলো লক্ষ্যের যন্ত্রে আসল batch মাপে সত্যিকারের সময় মাপা।

মূল উপলব্ধি: Model-এর আকারের একটি সংখ্যা নেই; parameter, FLOP, activation মেমরি আর latency আলাদা প্রশ্নের উত্তর দেয়, আর শেষ বিচার হয় লক্ষ্যের যন্ত্রে মেপে।

অনুশীলন

প্রথমে খাতায় করুন, তারপর উত্তর মেলান। অংশ ২ আর ৫-এর ব্লক বদলে নিজের উত্তর যাচাই করতে পারেন।

  1. পূর্ণ numerical: (ক) ৬৪×৬৪×৩ ছবিতে ৩২টি 5×5 filter, stride 2, padding 2: output shape আর parameter? (খ) ২৫৬ থেকে ২৫৬ channel-এ তিনটি 3×3 স্তর আর একটি 7×7 স্তরের weight (bias বাদ) কত, আর অনুপাত? (গ) ১৪×১৪ map-এ ৪৮০ channel থেকে ৪৮টি 5×5 filter সরাসরি, আর আগে 1×1 দিয়ে ১৬-এ নামিয়ে: MAC কত?

    (ক) ⌊(৬৪ + ৪ − ৫)/২⌋ + ১ = ⌊৩১.৫⌋ + ১ = ৩২, তাই ৩২×৩২×৩২; parameter ৩২ × (৩ × ২৫ + ১) = ২,৪৩২. (খ) তিনটি 3×3: ২৭ × ২৫৬² = ১৭,৬৯,৪৭২; 7×7: ৪৯ × ২৫৬² = ৩২,১১,২৬৪; অনুপাত ২৭/৪৯ ≈ ০.৫৫. (গ) অবস্থান ১৪ × ১৪ = ১৯৬. সরাসরি: ১৯৬ × ৪৮০ × ৪৮ × ২৫ = ১১,২৮,৯৬,০০০ (প্রায় ১১২.৯ M). Bottleneck: 1×1 অংশ ১৯৬ × ৪৮০ × ১৬ = ১৫,০৫,২৮০; 5×5 অংশ ১৯৬ × ১৬ × ৪৮ × ২৫ = ৩৭,৬৩,২০০; মোট ৫২,৬৮,৪৮০ (প্রায় ৫.৩ M), প্রায় ২১ গুণ কম।

  2. নকশা ও সমালোচনা: বাজারঘরের একজন সহকর্মী ১২৮×১২৮ রঙিন পণ্যের ছবির জন্য এই নকশা দিলেন: একটি 3×3 conv (৩২ channel, padding 1), কোনো pooling নেই, তারপর flatten করে fc-৫১২, তারপর fc-২০ (২০টি শ্রেণি)। মডেলটি দ্রুত overfit করছে। কী সমস্যা, আর আপনি কী বদলাতেন?

    Flatten-এর আগে shape ৩২×১২৮×১২৮ = ৫,২৪,২৮৮; fc-৫১২-এ প্রায় ২৬.৮ কোটি weight (৫,২৪,২৮৮ × ৫১২)। প্রায় সব parameter এক স্তরে, আর একটি মাত্র conv-এর receptive field ৩×৩, তাই পণ্যের আকার ধরার মতো বড় feature-ই নেই; model মূলত pixel মুখস্থ করছে। বদল: (১) VGG-ধাঁচে ৪–৫টি block, প্রতিটিতে এক বা দুটি 3×3 conv আর 2×2 max pool, channel ৩২ → ৬৪ → ১২৮ → ২৫৬; ১২৮ থেকে ৮×৮-এ নামবে। (২) শেষে global average pooling, তারপর ২৫৬ → ২০, যাতে মাথায় মাত্র ৫,১৪০ parameter. (৩) BatchNorm (পর্ব ১৪), augmentation (পর্ব ২৪) আর ছোট ডেটায় pretrained model (পর্ব ২৩) বিবেচনা। (৪) নকল input দিয়ে shape আর parameter গুনে, তারপর আগের নকশার সঙ্গে কয়েকটি seed-এ তুলনা।

  3. আউটপুট অনুমান করুন: নিচের কোড কী ছাপবে?
    import torch
    from torch import nn
    x = torch.zeros(8, 3, 64, 64)
    net = nn.Sequential(nn.Conv2d(3, 16, 3, padding=1), nn.MaxPool2d(2),
                        nn.Conv2d(16, 32, 3), nn.MaxPool2d(2), nn.Conv2d(32, 8, 1))
    print(net(x).shape)
    print(sum(p.numel() for p in net.parameters()))
    torch.Size([8, 8, 15, 15])
    5352

    Shape: ৬৪ (padding 1-এ অপরিবর্তিত) → pool ৩২ → padding ছাড়া 3×3: ৩০ → pool ১৫ → 1×1: ১৫; channel শেষে ৮, batch ৮. Parameter: ১৬ × (৩ × ৯ + ১) = ৪৪৮; ৩২ × (১৬ × ৯ + ১) = ৪,৬৪০; ৮ × (৩২ + ১) = ২৬৪; মোট ৫,৩৫২. ডেস্কটপে torch 2.15.0-এ চালিয়ে মেলানো।

  4. কোড লিখুন: NumPy বা সাধারণ Python-এ একটি function branch_cost(H, W, c_in, layers) লিখুন, যেখানে layers হলো (c_out, k)-এর তালিকা (stride 1, same padding). এটি একটি শাখার মোট weight আর MAC ফেরত দেবে। তারপর অনুশীলন ১(গ)-এর দুটি সংস্করণে চালিয়ে অনুপাত ছাপুন।
    def branch_cost(H, W, c_in, layers):
        weights = macs = 0
        for c_out, k in layers:
            w = c_in * c_out * k * k          # bias বাদ
            weights += w
            macs += H * W * w                 # same padding: H, W অপরিবর্তিত
            c_in = c_out                      # পরের স্তরের input
        return weights, macs
    
    direct = branch_cost(14, 14, 480, [(48, 5)])
    bottle = branch_cost(14, 14, 480, [(16, 1), (48, 5)])
    print(direct, bottle, round(direct[1] / bottle[1], 1))
    # (576000, 112896000) (26880, 5268480) 21.4

    মূল চাল হলো c_in = c_out: প্রতিটি স্তরের output পরের স্তরের input. প্রতিটি weight প্রতিটি অবস্থানে একবার ব্যবহার হয়, তাই MAC = H × W × weight. শেষের মন্তব্যে ডেস্কটপের Python-এ চালানো ফল।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমLeNet-5-এর আধুনিক রূপে ২৮×২৮ ছবি যে ক্রমে স্তর পেরোয়, সেভাবে সাজান (▲▼ চেপে সরান)

  1. 5×5 conv, ৬টি filter (output ৬×২৮×২৮)
  2. 2×2 max pool (৬×১৪×১৪)
  3. 5×5 conv, ১৬টি filter (১৬×১০×১০)
  4. 2×2 max pool (১৬×৫×৫)
  5. Flatten করে ৪০০ → ১২০ fully connected
  6. ১২০ → ৮৪ → ১০ fully connected

C channel থেকে C channel-এ তিনটি 3×3 convolution (stride 1) আর একটি 7×7 convolution তুলনা করলে কোনটি ঠিক (bias বাদ)?

  • তিনটি 3×3-এর receptive field 3×3, তাই তুলনা চলে না
  • receptive field একই, কিন্তু তিনটি 3×3-এ weight বেশি
  • receptive field একই 7×7, weight 27C² বনাম 49C², সঙ্গে বেশি non-linearity
  • weight কম, কিন্তু MAC বেশি আর receptive field ছোট

২২৭×২২৭×৩ input-এ ৯৬টি 11×11 filter, stride 4, padding 0 থাকলে output shape আর parameter সংখ্যা কত?

  • ৯৬×২১৭×২১৭; ১১,৬১৬
  • ৯৬×৫৫×৫৫; ৩৪,৯৪৪
  • ৯৬×৫৫×৫৫; ১১,৭১২
  • ৯৬×৫৬×৫৬; ৩৪,৯৪৪

২৮×২৮ map-এ ১৯২ channel থেকে ৩২টি 5×5 filter সরাসরি চালালে প্রায় ১২০.৪ মিলিয়ন MAC লাগে। আগে একটি 1×1 convolution দিয়ে ১৬ channel-এ নামালে মোট MAC প্রায় কত?

  • প্রায় ১২২.৮ মিলিয়ন, কারণ একটি স্তর বাড়ল
  • প্রায় ১০.০ মিলিয়ন
  • প্রায় ৬০ মিলিয়ন
  • প্রায় ১২.৪ মিলিয়ন

AlexNet আর VGG-16-এর মতো CNN-এ parameter-এর বেশির ভাগ কোথায় থাকে, আর GoogLeNet কোন কৌশলে তা কমায়?

  • শেষের fully connected স্তরে; GoogLeNet global average pooling দিয়ে বড় fc স্তর বাদ দেয়
  • প্রথম convolution স্তরে; GoogLeNet বড় stride নেয়
  • Pooling স্তরে; GoogLeNet pooling বাদ দেয়
  • Bias-এ; GoogLeNet bias বাদ দেয়

AlexNet (২০১২)-এর সাফল্যের পেছনের কৌশল হিসেবে কোনটি সাধারণত উল্লেখ করা হয় না?

  • ReLU activation
  • Fully connected স্তরে dropout
  • প্রতিটি block-এ residual connection
  • Data augmentation আর GPU-তে training

একটি VGG-ধাঁচের network-এ প্রতিটি 2×2 max pool-এর পরে channel দ্বিগুণ করা হয়। এর প্রধান কারণ কী?

  • মোট parameter কমানো
  • প্রতি স্তরের হিসাবের খরচ মোটামুটি সমান রাখা আর গভীরে বেশি feature রাখা
  • Receptive field দ্বিগুণ করা
  • Max pool-এর output মাপ ঠিক রাখা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. গুনুন: Hout = ⌊(H + 2P − K)/S⌋ + 1; conv-এর parameter Cout(CinK² + 1); MAC = HoutWoutCoutCinK². নতুন network-এ আগে নকল input দিয়ে shape দেখুন।
  2. Parameter বনাম খরচ: parameter জমে fully connected-এ, MAC জমে convolution-এ; LeNet, AlexNet, VGG-তে ৯০%-এর বেশি parameter মাথায়।
  3. VGG: n-টি 3×3-এর receptive field (2n + 1); দুটি 3×3 = 18C² বনাম 25C², তিনটি = 27C² বনাম 49C², সঙ্গে বেশি ReLU.
  4. Inception: বহু শাখা পাশাপাশি; 1×1 bottleneck channel কমিয়ে খরচ কমায় (৩০৩.৫ → ১২৮.০ M MAC); global average pooling মাথা হালকা করে।
  5. সৎ তুলনা: baseline, কয়েকটি seed, accuracy-র সঙ্গে সময় আর parameter; benchmark-এর সংখ্যা নিজের ডেটার প্রতিশ্রুতি নয়।

পরের পর্বে: পর্ব ২২ (ResNet আর আধুনিক CNN — skip connection, EfficientNet আর ConvNeXt): VGG-এর পরে শুধু স্তর বাড়ালে কেন ফল খারাপ হয়, residual block কীভাবে তা সারায়, আর MobileNet, EfficientNet ও ConvNeXt-এর নকশা।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান