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

Transfer learning — pretrained model, fine-tuning আর feature extraction

Transfer Learning — Pretrained CNNs, Feature Extraction, Fine-Tuning, BatchNorm When Frozen, Backbone Learning Rates and the Domain Gap

Taught in Bangla, this lesson explains why early CNN features transfer and compares three plans: feature extraction, fine-tuning the last block, and full fine-tuning, with trainable parameter counts. It covers torchvision weight enums and their transforms(), freezing with requires_grad while keeping frozen BatchNorm in eval mode, and smaller backbone learning rates through parameter groups. A real desktop run compares six setups on 2,000 CIFAR-10 images, then shows domain gap checks and timm and Hugging Face Hub.

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

  • Pretrained CNN-এর feature কেন অন্য কাজে চলে, আর কখন চলে না
  • Feature extraction, শেষ block fine-tune আর পুরো fine-tune-এর parameter হিসাব আর বাছাই
  • torchvision-এর weights enum, transforms(), meta আর weight-এর licence পড়া
  • PyTorch-এ সঠিকভাবে freeze করা: requires_grad আর BatchNorm-এর eval mode
  • Parameter group দিয়ে backbone-এ ছোট learning rate, আর domain gap মাপা

AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ transfer learning-এর তিন পথ (যেমন আছে, freeze করে নতুন head, fine-tune), Hugging Face Hub, model card আর licence দেখেছেন, বাংলা রিভিউয়ের ভাষা model দিয়ে। এখানে সেগুলো আবার বলব না। এই পর্বের প্রশ্ন ছবির CNN-এ: শেখা feature কেন অন্য কাজে চলে, কোন layer কখন freeze করবেন, BatchNorm-কে নিয়ে কোন ফাঁদ আছে, backbone-এ learning rate কেন ছোট, আর কখন transfer কাজ করে না। পাতার তিনটি ব্লক NumPy আর scikit-learn-এ চলে (প্রথমবার scikit-learn নামতে প্রায় ১০ সেকেন্ড লাগে)। PyTorch-এর সব আউটপুট ডেস্কটপের সত্যিকারের run থেকে, torch 2.15.0, torchvision 0.29.2 আর Python 3.14 দিয়ে (লেখার সময়ে)।

১ · Feature কেন transfer হয় — স্তরে স্তরে শেখা

পুরান ঢাকার একজন অভিজ্ঞ দর্জি বিশ বছর পাঞ্জাবি বানিয়েছেন। এখন তাঁকে ব্লেজার বানাতে বলা হলো। মাপ নেওয়া, কাপড় কাটা, সোজা সেলাই, বোতামঘর: এই হাতের কাজ বদলায় না। বদলায় শুধু শেষের নকশা। তাঁকে নতুন করে সুই ধরা শেখাতে হয় না; শেষের দু-একটি ধাপ শেখালেই চলে। কিন্তু তাঁকে যদি গয়না বানাতে বলেন, হাতের অনেক দক্ষতাই কাজে আসবে না। দূরত্ব যত বেশি, পুরোনো শেখা তত কম চলে।

ছবির CNN-এও শেখা জিনিস স্তরে স্তরে সাজানো থাকে, যাকে feature hierarchy বলে। প্রথম দিকের layer ছোট এলাকা দেখে: কিনারা (edge), রঙের দাগ, সরল নকশা। মাঝের layer সেগুলো জোড়া দিয়ে texture আর অংশ (চাকা, চোখ, পাতার শিরা) চেনে। শেষের layer পুরো ছবির বড় এলাকা দেখে, আর যে কাজে train হয়েছে তার শ্রেণির কাছাকাছি ধারণা ধরে। Zeiler আর Fergus (২০১৪) ভেতরের activation উল্টো দিকে ছবিতে ফিরিয়ে এই ধাপগুলো দেখিয়েছিলেন। Yosinski ও সহকর্মীরা (“How transferable are features in deep neural networks?”, ২০১৪) মেপে দেখান, প্রথম দিকের feature প্রায় সব ছবির কাজে চলে, আর গভীরে গেলে feature মূল কাজের দিকে বেশি নির্দিষ্ট হয়, তাই দূরের কাজে কম চলে।

এখানে যে pretrained model ব্যবহার করব সেটি torchvision-এর resnet18, যা ১০০০ শ্রেণির ImageNet-এ train করা। এর গঠন পর্ব ১৭-এর residual connection দিয়ে; ResNet block-এর বিস্তারিত পর্ব ২২ (ResNet আর আধুনিক CNN)-এ। এই পর্বে শুধু দরকার model-টি কয়েকটি ধাপে ভাগ করা: stem, layer1 থেকে layer4, তারপর global average pool আর শেষে classifier head fc. Head বাদে বাকি অংশকে বলে backbone.

resnet18-এর ধাপ, কী শেখে, আর তিন পরিকল্পনায় কোনটি শেখে

resnet18 stages for a 224×224 input: shape, parameters and what is trained in plans A, B, C

resnet18 stages and which parts are trained in feature extraction, last-block fine-tuning and full fine-tuning সাধারণ নির্দিষ্ট ইনপুট ছবি3 × 224 × 224 stem · 64 × 56 × 569,536 · কিনারা, রং layer1 · 64 × 56 × 56147,968 · সরল নকশা layer2 · 128 × 28 × 28525,568 · texture layer3 · 256 × 14 × 142,099,712 · অংশ layer4 · 512 × 7 × 78,393,728 · বস্তুর মতো ধারণা pool → fc · 512 → 105,130 · নতুন head কখগ শেখে (requires_grad = True) freeze ক = feature extraction · খ = layer4 + fc · গ = পুরো fine-tune
ছবি ২৩.১ — parameter-এর হিসাব ১০ শ্রেণির head ধরে (অংশ ৩-এর ব্লকে হাতে গোনা)। লক্ষ করুন, layer4 একাই প্রায় ৭৫% parameter: “শুধু শেষ block fine-tune” মোটেই ছোট কাজ নয়।

তিনটি পরিকল্পনার নাম মনে রাখুন। ক, feature extraction: পুরো backbone স্থির, শুধু নতুন head শেখে। খ, শেষ block-এর fine-tuning: শেষের এক বা একাধিক ধাপ আর head শেখে, প্রথম দিকের সাধারণ feature স্থির। গ, পুরো fine-tuning: সব weight শেখে, তবে শুরু হয় pretrained মান থেকে, এলোমেলো মান থেকে নয়।

২ · ছোট পরীক্ষা — স্থির feature + logistic head

Feature extraction-এর মূল দাবি: ভালো feature থাকলে তার ওপর একটি সরল linear classifier-ই যথেষ্ট, আর অল্প label-এই শেখে। এমন linear classifier-কে গবেষণায় linear probe বলা হয়। দাবিটি একটি কৃত্রিম জগতে যাচাই করি। প্রতিটি “ছবি” ৬০টি সংখ্যা (pixel), যা ৬টি লুকানো গুণ (আকার, রং, ভঙ্গির মতো) থেকে একটি অ-রৈখিক নিয়মে তৈরি। শ্রেণি ঠিক হয় লুকানো গুণের কোন দিকে মান সবচেয়ে বড়, তা দিয়ে।

  • উৎস কাজ: ১২ শ্রেণি, ২,০০০টি label দেওয়া উদাহরণ (ImageNet-এর ছোট প্রতিরূপ)। এতে ৬০ → ৩২ ReLU → ১২-এর একটি ছোট network শেখে।
  • লক্ষ্য কাজ: বাজারঘরের নতুন ৩ শ্রেণি, উৎসের সঙ্গে সম্পর্কিত কিন্তু এক নয়। label আছে মাত্র ২০ থেকে ২,০০০টি।
  • তিন রকম feature, সবার ওপর একই logistic regression: কাঁচা pixel; একটি এলোমেলো, কখনো-না-শেখা projection-এর পরে ReLU (random features); আর উৎস কাজে শেখা hidden layer, freeze করা। শেষ কলামে একই শেখা feature, কিন্তু লক্ষ্যের ছবি অন্য “জগৎ” থেকে, যেখানে pixel তৈরির নিয়মই আলাদা।
import numpy as np, pandas as pd
from sklearn.linear_model import LogisticRegression

rng = np.random.default_rng(42)
A = rng.normal(0, 1, (6, 60)); B = rng.normal(0, 1, (60, 60)) / 8
def world(n, A):                               # লুকানো 6টি গুণ z → 60টি "pixel" x
    z = rng.normal(0, 1, (n, 6))
    return np.tanh(z @ A) @ B + 0.1 * rng.normal(0, 1, (n, 60)), z
label = lambda z, W: np.abs(z @ W).argmax(1)   # শ্রেণি: কোন দিকে |প্রক্ষেপ| সবচেয়ে বড়
Ws = rng.normal(0, 1, (6, 12))                 # উৎস কাজ: 12 শ্রেণি
Wt = Ws[:, :3] + 0.3 * rng.normal(0, 1, (6, 3))  # লক্ষ্য কাজ: 3 শ্রেণি, সম্পর্কিত

Xs, zs = world(2000, A); ys = label(zs, Ws)    # উৎসে 2000টি label দেওয়া উদাহরণ
W1 = rng.normal(0, 0.2, (60, 32)); W2 = rng.normal(0, 0.2, (32, 12))
for _ in range(400):                           # 60 → 32 ReLU → 12, full-batch GD
    H = np.maximum(Xs @ W1, 0); Z = H @ W2; Z -= Z.max(1, keepdims=True)
    P = np.exp(Z); P /= P.sum(1, keepdims=True); P[np.arange(2000), ys] -= 1
    dH = (P @ W2.T) * (H > 0)
    W2 -= H.T @ P / 2000; W1 -= Xs.T @ dH / 2000
R = rng.normal(0, 0.2, (60, 32))               # random projection: কখনো শেখেনি

feats = {"raw x": lambda X: X, "random ReLU": lambda X: np.maximum(X @ R, 0),
         "learned (source)": lambda X: np.maximum(X @ W1, 0)}
A2 = rng.normal(0, 1, (6, 60))                 # অন্য "জগৎ": pixel তৈরির নিয়মই আলাদা
rows = {}
for n in (20, 60, 200, 2000):                  # লক্ষ্য কাজে কতটি label আছে
    acc = []
    for Aw in (A, A2):
        Xtr, ztr = world(n, Aw); Xte, zte = world(2000, Aw)
        acc += [LogisticRegression(max_iter=3000).fit(f(Xtr), label(ztr, Wt))
                .score(f(Xte), label(zte, Wt)) for f in feats.values()]
    rows[n] = acc[:3] + acc[5:]                # শেষটি: শেখা feature, কিন্তু অন্য জগৎ
pd.set_option("display.width", 80)
cols = list(feats) + ["learned, gap"]
print(pd.DataFrame(rows, index=cols).T.rename_axis("n").round(3))

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

      raw x  random ReLU  learned (source)  learned, gap
n                                                       
20    0.378        0.462             0.558         0.446
60    0.382        0.566             0.736         0.524
200   0.378        0.632             0.796         0.574
2000  0.448        0.694             0.852         0.638

সারির n হলো লক্ষ্য কাজে label দেওয়া উদাহরণের সংখ্যা, আর তিন শ্রেণিতে আন্দাজে পাওয়া যায় প্রায় ০.৩৩. কাঁচা pixel-এ logistic regression ২,০০০টি label পেয়েও ০.৪৪৮-এ আটকে, কারণ শ্রেণির নিয়ম pixel-এর রৈখিক ফাংশন নয়। Random ReLU feature কিছুটা সাহায্য করে (২,০০০-এ ০.৬৯৪)। উৎস কাজে শেখা feature মাত্র ৬০টি label-এ ০.৭৩৬, যা random feature-এর ২,০০০টি label-এর ফলের (০.৬৯৪) চেয়েও বেশি: ভালো feature এখানে প্রায় ৩০ গুণ label বাঁচিয়েছে। কিন্তু শেষ কলামটি দেখুন। একই শেখা feature অন্য জগতের ছবিতে ২,০০০টি label পেয়েও ০.৬৩৮, random feature-এর চেয়েও কম। Feature শিখেছিল উৎস জগতের pixel কোন নিয়মে তৈরি হয়; নিয়ম বদলালে সেই জ্ঞান আর কাজে আসে না।

মূল ধারণা

Transfer হয় feature, আর তার মূল্য সবচেয়ে বেশি যখন লক্ষ্যের label কম। Feature ভালো কি না, তা নির্ভর করে দুটি জিনিসের ওপর: উৎস কাজ আর লক্ষ্য কাজ কতটা সম্পর্কিত, আর দুই জগতের ছবি একই নিয়মে তৈরি কি না।

৩ · তিন পরিকল্পনা — কতটি parameter শেখে, আর কোনটি কখন

Freeze করা মানে সেই parameter-এর জন্য gradient হিসাব না করা আর optimizer-এ না দেওয়া। তাই পরিকল্পনা বাছাইয়ের প্রথম হিসাব: কতটি সংখ্যা শিখবে, আর optimizer-এর state-এর জন্য কত memory লাগবে। নিচের ব্লক resnet18-এর প্রতিটি ধাপের parameter হাতে গোনে। নিয়মগুলো: k × k convolution-এ Cout · Cin · k² weight (ResNet-এ conv-এর bias নেই, কারণ পরে BatchNorm বসে); BatchNorm-এ ২C (γ আর β); BasicBlock-এ দুটি 3 × 3 conv; channel বদলালে shortcut-এ একটি 1 × 1 conv, যার নাম projection. Convolution-এর parameter গোনার পুরো যুক্তি পর্ব ২০ (Convolution)-এ।

import pandas as pd

def conv(cin, cout, k): return cout * cin * k * k     # bias নেই (পরে BatchNorm)
def bn(c): return 2 * c                                # γ আর β (running stats parameter নয়)
def block(cin, cout):                                  # ResNet-এর BasicBlock
    n = conv(cin, cout, 3) + bn(cout) + conv(cout, cout, 3) + bn(cout)
    if cin != cout:
        n += conv(cin, cout, 1) + bn(cout)             # projection shortcut
    return n

parts = {"stem (conv1+bn1)": conv(3, 64, 7) + bn(64)}
cin = 64
for i, c in enumerate([64, 128, 256, 512], start=1):  # layer1 … layer4, প্রতিটিতে 2টি block
    parts[f"layer{i}"] = block(cin, c) + block(c, c); cin = c
parts["fc (512→10)"] = 512 * 10 + 10
total = sum(parts.values())
print(pd.Series(parts).map("{:,}".format).to_string())
print(f"মোট: {total:,}  (1000 শ্রেণির fc হলে {total - parts['fc (512→10)'] + 512 * 1000 + 1000:,})\n")

plans = {"feature extraction": ["fc (512→10)"],
         "fine-tune layer4+fc": ["layer4", "fc (512→10)"],
         "full fine-tune": list(parts)}
rows = []
for name, keys in plans.items():
    k = sum(parts[x] for x in keys)
    rows.append([name, f"{k:,}", f"{100 * k / total:.2f}%",
                 f"{k * 4 * 3 / 2**20:.1f} MiB"])          # grad + Adam-এর m, v (float32)
print(pd.DataFrame(rows, columns=["plan", "trainable", "share", "grad+Adam state"])
      .to_string(index=False))

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

stem (conv1+bn1)        9,536
layer1                147,968
layer2                525,568
layer3              2,099,712
layer4              8,393,728
fc (512→10)             5,130
মোট: 11,181,642  (1000 শ্রেণির fc হলে 11,689,512)

               plan  trainable   share grad+Adam state
 feature extraction      5,130   0.05%         0.1 MiB
fine-tune layer4+fc  8,398,858  75.11%        96.1 MiB
     full fine-tune 11,181,642 100.00%       128.0 MiB

প্রতিটি ধাপের সংখ্যা ছবি ২৩.১-এর সঙ্গে মেলে, আর ১০০০ শ্রেণির রূপ ১,১৬,৮৯,৫১২, যা অংশ ৪-এ torchvision-এর নিজের meta-র সঙ্গে হুবহু মিলবে। layer4 এত বড় কেন? সেখানে channel ৫১২, আর একটি 3 × 3 conv-এই ৫১২ × ৫১২ × ৯ = ২৩,৫৯,২৯৬ weight; layer4-এ এমন তিনটি conv আর একটি ২৫৬ → ৫১২-এর conv আছে। Channel দ্বিগুণ হলে conv-এর parameter চার গুণ হয়, তাই parameter-এর বেশিরভাগ শেষের দিকে। শেষ কলাম: শেখার যোগ্য প্রতিটি সংখ্যার জন্য float32-এ gradient আর AdamW-এর m ও v, মোট ১২ byte. Feature extraction-এ মাত্র ০.১ MiB, পুরো fine-tune-এ ১২৮ MiB. এর বাইরে আছে activation-এর memory, যা সাধারণত আরও বড়; frozen অংশের ভেতর দিয়ে backward না চললে সেটিও অনেক বাঁচে।

সংখ্যাগুলো হাতে নিয়ে এখন সিদ্ধান্ত। বাজারঘর পণ্যের ছবি থেকে শ্রেণি (শাড়ি, জুতা, মসলা, মোবাইল…) চিনতে চায়, resnet18 দিয়ে শুরু। ডেটার আকার আর domain-এর দূরত্বভেদে কী freeze করবেন, ধাপে ধাপে দেখুন। ডেটার সীমাগুলো অভিজ্ঞতার মোটামুটি নির্দেশ, কোনো নির্দিষ্ট নিয়ম নয়।

resnet18, ১০ শ্রেণি · trainable সংখ্যা উপরের ব্লক থেকে
পরিস্থিতি
freeze
শেখে
trainable
learning rate
মূল ঝুঁকি
  1. অল্প ডেটা, কাছের domain. মোট ৫০০টি ছবিতে ১.১ কোটি weight বদলালে model ছবিগুলো মুখস্থ করবে। Backbone পুরো freeze, শুধু ৫,১৩০টি সংখ্যার head শেখে। এটি আসলে pretrained feature-এর ওপর একটি logistic regression, ঠিক অংশ ২-এর পরীক্ষার মতো।
  2. মাঝারি ডেটা. প্রায় ১০,০০০টি ছবিতে শেষ ধাপটি নিজের কাজে মানিয়ে নেওয়ার সামর্থ্য আছে। layer4 আর fc শেখে, প্রথম দিকের সাধারণ feature স্থির। কিন্তু layer4 একাই ৮৪ লাখ weight, তাই backbone-এ ছোট learning rate আর validation curve-এ চোখ রাখা জরুরি।
  3. বড় ডেটা. লাখের ঘরে ছবি থাকলে পুরো network fine-tune করা সাধারণত সবচেয়ে ভালো ফল দেয়। Pretrained মান তখন একটি ভালো শুরুর বিন্দু; ছোট learning rate-এ সেখান থেকে বেশি দূরে না সরেই নতুন কাজ শেখে।
  4. দূরের domain. রসিদের স্ক্যান বা এক্স-রের মতো ছবিতে layer4-এর “বস্তু” feature কম কাজে লাগে, কিন্তু কিনারা আর texture এখনো চলে। তাই শুধু stem আর layer1 স্থির রেখে বাকিটা শেখানো একটি যুক্তিসংগত শুরু (layer1 পর্যন্ত মোট ১,৫৭,৫০৪ বাদ দিলে ১,১০,২৪,১৩৮)। কোন পথ ভালো, তা মেপে ঠিক করতে হবে (অংশ ৮)।

৪ · torchvision-এর weight — enum, transforms() আর meta

torchvision ০.১৩ থেকে pretrained weight একটি weights enum দিয়ে চাওয়া হয়, যেমন ResNet18_Weights.IMAGENET1K_V1. পুরোনো pretrained=True আর ব্যবহার করবেন না। Enum-এর সুবিধা তিনটি: ঠিক কোন weight এল তা কোডে লেখা থাকে; weight-এর সঙ্গে তার নিজের preprocessing (transforms()) বাঁধা থাকে; আর meta-তে শ্রেণির নাম, training recipe আর মাপা accuracy থাকে। DEFAULT নামটি “এই version-এ সেরা weight”-এর ডাকনাম, যা version বদলালে বদলাতে পারে বলে documentation সতর্ক করে; তাই পরীক্ষা পুনরুৎপাদনযোগ্য রাখতে নির্দিষ্ট নাম লিখুন।

import torch
from torchvision.models import resnet18, ResNet18_Weights

w = ResNet18_Weights.IMAGENET1K_V1
m = w.meta
print("num_params:", f"{m['num_params']:,}", "| min_size:", m["min_size"])
print("categories:", len(m["categories"]), m["categories"][:5], "...")
print("recipe:", m["recipe"]); print("metrics:", m["_metrics"])
print("transforms:", w.transforms())
model = resnet18(weights=w)                  # weight নামে TORCH_HOME-এর cache-এ
model.fc = torch.nn.Linear(512, 10)          # ১০০০ শ্রেণির head বদলে ১০ শ্রেণি

সত্যিকারের run (প্রথমবার প্রায় ৪৭ MB-এর resnet18-f37072fd.pth ফাইল PyTorch-এর সার্ভার থেকে নামে):

~/bazarghar-dl$ uv run python meta.py
torch 2.15.0 | torchvision 0.29.2 | python 3.14.4
weights: ResNet18_Weights.IMAGENET1K_V1 | DEFAULT is ResNet18_Weights.IMAGENET1K_V1
meta keys: ['_docs', '_file_size', '_metrics', '_ops', 'categories', 'min_size', 'num_params', 'recipe']
num_params: 11,689,512 | min_size: (1, 1)
categories: 1000 ['tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead'] ...
recipe: https://github.com/pytorch/vision/tree/main/references/classification#resnet
metrics: {'ImageNet-1K': {'acc@1': 69.758, 'acc@5': 89.078}}
docs: These weights reproduce closely the results of the paper using a simple training recipe.
transforms: ImageClassification(
    crop_size=[224]
    resize_size=[256]
    mean=[0.485, 0.456, 0.406]
    std=[0.229, 0.224, 0.225]
    interpolation=InterpolationMode.BILINEAR
)
total with 10-class head: 11,181,642
fc only: 5,130 | layer4+fc: 8,398,858
BatchNorm running_mean/var numbers: 9,600

কয়েকটি জিনিস লক্ষ করুন। num_params ১,১৬,৮৯,৫১২, ঠিক অংশ ৩-এর হাতের হিসাবের ১০০০ শ্রেণির রূপ; head বদলানোর পরে ১,১১,৮১,৬৪২ আর ৫,১৩০ আর ৮৩,৯৮,৮৫৮-ও হুবহু মেলে। resnet18-এর এই একটিই weight, তাই এখানে DEFAULT আর IMAGENET1K_V1 একই; resnet50-এর মতো model-এ DEFAULT নতুন V2 weight-কে বোঝায়। Metrics-এ ImageNet-এর validation-এ top-1 accuracy ৬৯.৭৬%: এটি weight-এর নিজের কাজে মাপ, আপনার কাজে কত পাবেন তার কোনো প্রতিশ্রুতি নয়। Recipe-এর link-এ কোন optimizer, কত epoch আর কোন augmentation-এ train হয়েছিল তা লেখা।

transforms()-এর অর্থ: ছবির ছোট দিক ২৫৬-এ resize (bilinear), মাঝখান থেকে ২২৪ × ২২৪ কাটা, ০–১-এ আনা, তারপর ImageNet-এর প্রতিটি channel-এর mean বাদ দিয়ে std দিয়ে ভাগ। Pretrained network ঠিক এই মাপের সংখ্যায় শিখেছে; নিজের ডেটার mean/std দিলে বা normalize ভুলে গেলে প্রথম layer অচেনা মাপের সংখ্যা পায়। CIFAR-10-এর ৩২ × ৩২ ছবি এতে ২৫৬-এ বড় হয়ে ২২৪-এ কাটা পড়ে। ছোট ছবি এভাবে বড় করা খরচসাপেক্ষ (হিসাব প্রায় ৪৯ গুণ), কিন্তু pretrained feature সাধারণত সেই আকারেই ভালো চলে।

Licence আর ডেটার শর্ত: meta-তে licence-এর কোনো ঘর নেই। torchvision-এর documentation সতর্ক করে যে pretrained model-এর নিজস্ব licence বা শর্ত থাকতে পারে, যা train-এর ডেটাসেট থেকে আসে, আর নিজের ব্যবহারে অনুমতি আছে কি না তা ঠিক করার দায় ব্যবহারকারীর। torchvision-এর কোড BSD-3-Clause, কিন্তু এই weight ImageNet-এ train করা, যার ডাউনলোডের শর্তে অবাণিজ্যিক গবেষণার কথা আছে। বাজারঘরের মতো বাণিজ্যিক কাজে ব্যবহারের আগে শর্তগুলো পড়ুন আর প্রয়োজনে আইনি পরামর্শ নিন (ভাবনার প্রশ্ন ৪)। ফাইলটি pickle-ভিত্তিক .pth; অচেনা উৎসের এমন ফাইল কেন ঝুঁকিপূর্ণ আর safetensors কী, তা AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ।

৫ · PyTorch-এ freeze — requires_grad আর BatchNorm-এর eval mode

পর্ব ০৮-এ দেখেছেন, p.requires_grad_(False) একটি parameter-কে freeze করে: তার gradient হিসাব হয় না, তাই optimizer তাকে বদলায় না, আর backward-এ সময় ও memory বাঁচে। কিন্তু resnet18-এ এমন কিছু আছে যা gradient দিয়ে বদলায় না: BatchNorm-এর running statistics. এগুলো buffer, আর train mode-এ প্রতিটি forward-এই নতুন batch-এর গড়ের দিকে সরে (পর্ব ১৪)। তাই সত্যিকারের freeze-এর দুটি অংশ:

def build(mode):
    m = resnet18(weights=None if mode == "scratch" else W)
    m.fc = torch.nn.Linear(512, 10)                # নতুন head, ১০ শ্রেণি
    head = list(m.fc.parameters())
    body = [p for n, p in m.named_parameters() if not n.startswith("fc.")]
    if mode == "feature-extract":
        for p in body: p.requires_grad_(False)     # ১: gradient বন্ধ
        groups = [{"params": head, "lr": 1e-3}]
    elif mode == "finetune-lowlr":
        groups = [{"params": body, "lr": 1e-4}, {"params": head, "lr": 1e-3}]
    else:                                          # scratch, finetune-samelr
        groups = [{"params": body + head, "lr": 1e-3}]
    return m.to(dev), torch.optim.AdamW(groups, weight_decay=1e-4)

def set_mode(m, mode):                             # প্রতি epoch-এর শুরুতে
    m.train()
    if mode == "feature-extract":
        m.eval(); m.fc.train()                     # ২: statistics বন্ধ (BatchNorm eval-এ)

খেয়াল করুন, head-কে আগে বদলে তারপর বাকিগুলো freeze; উল্টো ক্রমে loop চালিয়ে পরে head বসালেও চলে (নতুন layer-এর requires_grad নিজে থেকে True), কিন্তু loop-এর আগে head বসিয়ে সব parameter-এ loop চালালে head-ও freeze হয়ে যায়। দ্বিতীয় অংশটি বেশি ভুল হয়: training loop প্রতি epoch-এ model.train() ডাকে (পর্ব ১০), আর সেটি পুরো model-কে train mode-এ নেয়, frozen অংশসহ। তাই train()-এর পরে প্রতিবার frozen অংশকে আবার eval mode-এ ফেরাতে হয়। কাজটি ভুলে গেলে কী হয়, অংশ ৭-এর একই run-এর দুটি সারি (তিন seed-এর গড়, সত্যিকারের run):

feature-extract  trainable      5,130 | test acc  78.3 ± 0.5% (seeds 78.8, 78.2, 77.8) | train   3.9 s | conv1 rel. change 0.0000 | bn1 running_mean shift 0.0000
frozen-bn-train  trainable      5,130 | test acc  67.7 ± 0.5% (seeds 68.2, 68.0, 67.0) | train   4.7 s | conv1 rel. change 0.0000 | bn1 running_mean shift 0.0750

দুটিতেই ঠিক ৫,১৩০টি parameter শেখে, আর conv1-এর weight একটুও বদলায়নি (০.০০০০)। তবু accuracy ৭৮.৩% থেকে ৬৭.৭%-এ, প্রায় ১০.৬ শতাংশ-বিন্দু কম। পার্থক্যটি শেষ কলামে: train mode-এ প্রথম BatchNorm-এর running_mean গড়ে ০.০৭৫ সরেছে। কারণ দুটি। Training-এর সময় প্রতিটি batch নিজের গড় দিয়ে normalize হয়, তাই head শেখে কিছুটা ভিন্ন feature-এ; আর eval-এ running statistics এখন ImageNet-এর নয়, CIFAR-10-এর ৫ epoch-এর চলমান গড়, যা training-এর batch-এর পরিসংখ্যানের সঙ্গেও পুরো মেলে না। ফলে head যে feature-এ শিখেছে আর test-এ যে feature পায়, দুটি আলাদা। ছোট batch-এ এই সমস্যা আরও বাড়ে।

নিয়ম

Frozen অংশে দুটি কাজ: requires_grad_(False) (gradient বন্ধ) আর .eval() (statistics বন্ধ), প্রতিটি model.train()-এর পরে। আর একবার trainable সংখ্যা গুনে ছাপিয়ে নিন: sum(p.numel() for p in m.parameters() if p.requires_grad).

Feature extraction-এর একটি বাড়তি সুবিধা: backbone যেহেতু বদলায় না, feature একবার হিসাব করে রেখে দিলেই হয়, প্রতি epoch-এ আবার পুরো network চালাতে হয় না। একই ২,০০০টি ছবিতে সত্যিকারের run:

~/bazarghar-dl$ uv run python fe_cache.py
features once: 1.6 s (shape (2000, 512)) | head 5 epochs: 0.39 s | test acc 78.9%

Feature একবার বের করতে ১.৬ সেকেন্ড, তারপর ৫ epoch-এ head শেখাতে ০.৩৯ সেকেন্ড, আর accuracy প্রায় একই (seed ০-এ ৭৮.৮% বনাম এখানে ৭৮.৯%; পার্থক্য batch-এর ক্রম আর bfloat16-এর গোলকরণের)। Hyperparameter খুঁজতে head বারবার train করলে এই কৌশল সময় অনেক বাঁচায়। শর্ত: augmentation থাকলে প্রতিটি epoch-এ ছবি বদলায়, তখন feature আগে থেকে রাখা যায় না।

৬ · Backbone-এ ছোট learning rate — parameter group

Fine-tuning-এ একটি অসমতা আছে। Head নতুন, এলোমেলো; তাকে অনেক শিখতে হবে, তাই বড় ধাপ দরকার। Backbone ইতিমধ্যে কাজের feature রাখে; তাকে অল্প মানিয়ে নিতে হবে, বড় ধাপে নষ্ট হতে পারে। PyTorch-এ এর উপায় optimizer-এর parameter group (পর্ব ১৫-এ weight decay-এর জন্য দেখেছেন): উপরের build()-এর finetune-lowlr-এ backbone ১০−৪, head ১০−৩. স্তরভেদে আরও সূক্ষ্মভাবে, নিচের layer-এ আরও ছোট learning rate দেওয়াকে discriminative fine-tuning বলে (অনুশীলন ৪)। কেন এত পার্থক্য, সংখ্যা দিয়ে দেখুন।

২,০০০টি ছবি, batch ৬৪ → প্রতি epoch-এ ৩২ ধাপ × ৫ epoch = ১৬০ ধাপ
ধাপ
head (fc)
conv1 (stem)
layer4 (শেষ conv)
test acc
  1. শুরুর মাপ. নতুন head এলোমেলো (PyTorch-এর default-এ ±০.০৪৪২-এর মধ্যে, গড় │w│ প্রায় ০.০২২)। Backbone pretrained: stem-এর conv1-এর গড় │w│ ০.০৭৬২, আর layer4-এর শেষ conv-এর মাত্র ০.০১০১ (সত্যিকারের run-এ মাপা)।
  2. একটি ধাপ. Adam বা AdamW-এ প্রতিটি ধাপে প্রতিটি weight মোটামুটি η পরিমাণ পর্যন্ত সরে, gradient-এর মাপ যা-ই হোক (পর্ব ১৫)। η = ১০−৩-এ layer4-এর একটি সাধারণ weight একটি ধাপেই তার মাপের প্রায় ১০% পর্যন্ত সরতে পারে।
  3. ১৬০ ধাপ. সব ধাপ একই দিকে গেলে সর্বোচ্চ ১৬০ × η: ১০−৩-এ ০.১৬, যা conv1-এর গড় weight-এর দ্বিগুণ আর layer4-এর ১৬ গুণ। ১০−৪-এ সীমাটি ১০ গুণ ছোট। বাস্তবে দিক বদলায়, তাই সরণ কম, কিন্তু ঝুঁকিটা স্পষ্ট।
  4. মাপা বদল. ৫ epoch-এর পরে conv1-এর আপেক্ষিক বদল ‖Δw‖/‖w‖: একই lr-এ ০.০৩৯০ (প্রায় ৩.৯%), ছোট lr-এ ০.০০৪৭, প্রায় ৮ গুণ কম। অর্থাৎ ছোট lr-এ backbone pretrained মানের অনেক কাছে থাকে।
  5. ফল. একই lr-এ test accuracy ৬৯.৯ ± ৩.৩% (seed-ভেদে ৬৫.২ থেকে ৭২.৩), যা feature extraction-এর ৭৮.৩%-এর চেয়েও কম। ছোট backbone lr-এ ৮২.৭ ± ০.২%. বড় ধাপ শুধু খারাপ নয়, অস্থিরও।

সংখ্যাগুলো এক local run-এর, একটি নির্দিষ্ট সেটআপের (AdamW, ৫ epoch, কোনো warmup বা schedule নেই)। Warmup আর decay (পর্ব ১৫) থাকলে একই lr-এর ক্ষতি কিছুটা কমতে পারে, কারণ শুরুর ধাপে এলোমেলো head-এর বড় gradient backbone-এ পৌঁছায় ছোট η নিয়ে। ১০ গুণ অনুপাতটি একটি প্রচলিত শুরু, প্রাকৃতিক নিয়ম নয়; নিজের কাজে validation-এ যাচাই করুন।

৭ · সত্যিকারের তুলনা — CIFAR-10-এর ২,০০০টি ছবিতে ছয় সেটআপ

এখন সব একসঙ্গে। বাজারঘরের পণ্যের ছবির বদলে এখানে সৎভাবে CIFAR-10 (১০ শ্রেণির ৩২ × ৩২ রঙিন ছবি: বিমান, গাড়ি, পাখি, বিড়াল…), Hugging Face-এর uoft-cs/cifar10 অনুলিপি থেকে। Train-এর অংশ থেকে প্রতি শ্রেণিতে ২০০টি (মোট ২,০০০), আর test-এর অংশ থেকে প্রতি শ্রেণিতে ২০০টি (মোট ২,০০০), seed-সহ এলোমেলো বাছাই। সব সেটআপে একই ব্যবস্থা: weights.transforms() (ছবি ২২৪-এ বড় করা), কোনো augmentation নেই, AdamW (weight decay ১০−৪), batch ৬৪, ৫ epoch, GPU-তে bfloat16 autocast (পর্ব ১২), তিনটি seed. সত্যিকারের run:

~/bazarghar-dl$ uv run python compare.py scratch,feature-extract,frozen-bn-train,finetune-samelr,finetune-lowlr 0,1,2
torch 2.15.0 | torchvision 0.29.2 | python 3.14.4 | GPU-তে
train (2000, 3, 32, 32) | test (2000, 3, 32, 32) | after transforms (2, 3, 224, 224)
scratch          trainable 11,181,642 | test acc  34.0 ± 1.6% (seeds 32.4, 33.6, 36.1) | train  12.0 s | conv1 rel. change 0.3341 | bn1 running_mean shift 0.0723
feature-extract  trainable      5,130 | test acc  78.3 ± 0.5% (seeds 78.8, 78.2, 77.8) | train   3.9 s | conv1 rel. change 0.0000 | bn1 running_mean shift 0.0000
frozen-bn-train  trainable      5,130 | test acc  67.7 ± 0.5% (seeds 68.2, 68.0, 67.0) | train   4.7 s | conv1 rel. change 0.0000 | bn1 running_mean shift 0.0750
finetune-samelr  trainable 11,181,642 | test acc  69.9 ± 3.3% (seeds 65.2, 72.3, 72.3) | train  12.5 s | conv1 rel. change 0.0390 | bn1 running_mean shift 0.0892
finetune-lowlr   trainable 11,181,642 | test acc  82.7 ± 0.2% (seeds 82.4, 83.0, 82.8) | train  11.9 s | conv1 rel. change 0.0047 | bn1 running_mean shift 0.0749
~/bazarghar-dl$ uv run python compare.py finetune-layer4 0,1,2
finetune-layer4  trainable  8,398,858 | test acc  85.2 ± 0.3% (seeds 84.8, 85.2, 85.6) | train   5.4 s | conv1 rel. change 0.0000 | bn1 running_mean shift 0.0000

ছয় সেটআপের test accuracy আর training সময়

CIFAR-10, 2,000 train images, 5 epochs, mean ± std of 3 seeds, one local run on a GPU

Test accuracy and training time of six transfer-learning setups on a CIFAR-10 subset 020406080 test accuracy (%) শূন্য থেকেscratch · 12.0 s Feature extractionfc only · 3.9 s Freeze, BN trainভুল mode · 4.7 s পুরো, একই lr1e-3 সবখানে · 12.5 s layer4 + fcbackbone 1e-4 · 5.4 s পুরো, ছোট lrbackbone 1e-4 · 11.9 s 34.0 78.3 67.7 69.9 ± 3.3 85.2 82.7
ছবি ২৩.২ — কালো দাগ seed-ভেদের ±১ standard deviation. কমলা দুটি বার একই কাজের ভুল সেটআপ; সবুজ দুটি ছোট backbone learning rate-এর fine-tune.

ফল থেকে পাঁচটি শিক্ষা পাওয়া যায়, তবে মনে রাখবেন এগুলো এক local run, একটি ছোট সেটআপের:

  • Pretraining-এর মূল্য: ২,০০০টি ছবিতে শূন্য থেকে ৫ epoch-এ মাত্র ৩৪.০%; প্রায় এক-তৃতীয়াংশ সময়ে frozen feature-এ ৭৮.৩%. অল্প ডেটায় এটিই transfer learning-এর মূল লাভ। শূন্য থেকে model-কে আরও অনেক epoch আর augmentation দিলে ব্যবধান কমত, তবে এই সেটআপে নয়।
  • সময়: feature extraction-এ backward শুধু head পর্যন্ত, তাই ৩.৯ সেকেন্ড বনাম পুরো network-এর প্রায় ১২ সেকেন্ড; feature cache করলে আরও কম (অংশ ৫)।
  • শেষ block-এর fine-tune সেরা: layer4 + fc ৮৫.২%, পুরো fine-tune-এর (৮২.৭%) চেয়েও বেশি, আর অর্ধেকের কম সময়ে। ২,০০০টি ছবিতে stem আর প্রথম layer-গুলো বদলানোর লাভ নেই, ঝুঁকি আছে। ডেটা বাড়লে এই ক্রম বদলাতে পারে।
  • Learning rate: একই বড় lr-এর পুরো fine-tune (৬৯.৯ ± ৩.৩%) feature extraction-এর চেয়েও খারাপ আর seed-ভেদে অস্থির।
  • BatchNorm-এর mode: একটি লাইনের ভুল (frozen অংশ train mode-এ) ১০ শতাংশ-বিন্দুর বেশি খরচ করে।

এই তুলনার সীমা: ২,০০০টি ছবি, ৫ epoch, প্রতিটি সেটআপে একটি মাত্র lr আর কোনো schedule বা augmentation নেই, তিনটি seed. CIFAR-10-এর শ্রেণিগুলো ImageNet-এর শ্রেণির খুব কাছের (বিমান, গাড়ি, পাখি, কুকুর), তাই এখানে transfer সহজ; দূরের domain-এ ফল অনেক আলাদা হতে পারে (অংশ ৮)। ৩২ × ৩২ ছবিকে ২২৪-এ বড় করায় হিসাব প্রায় ৪৯ গুণ বেড়েছে; আসল পণ্যের ছবি সাধারণত বড়ই থাকে। Test set শুধু শেষ মাপে ব্যবহার হয়েছে; বাস্তবে পরিকল্পনা বাছাই হবে আলাদা validation set-এ (পর্ব ১৯)।

৮ · Domain gap — কখন transfer কাজ করে না

অংশ ২-এর শেষ কলামে দেখেছেন, একই শেখা feature অন্য “জগতে” প্রায় কাজে আসে না। বাস্তবে এর নাম domain shift বা domain gap: pretrained model যে ধরনের ছবিতে শিখেছে (ইন্টারনেটের রঙিন ছবি, মাঝখানে বস্তু) আর আপনার ছবি (রসিদের সাদা-কালো স্ক্যান, মাইক্রোস্কোপের স্লাইড, স্যাটেলাইটের ওপর থেকে তোলা ছবি) আলাদা। Gap বড় হলে pretrained শুরু কখনো কখনো এলোমেলো শুরুর চেয়েও খারাপ ফল দেয়, যাকে negative transfer বলে। মেশিন লার্নিং কোর্সের পর্ব ৫৯-এ shift ধরার একটি সরল উপায় দেখেছেন: domain classifier. দুই ডেটাকে আলাদা করতে একটি classifier শেখান; সে যদি প্রায় ৫০%-এর বেশি পারে না, দুই ডেটা প্রায় একই রকম। এখানে একই পরীক্ষা কাঁচা ডেটায় নয়, frozen feature-এ, কারণ head শুধু feature-টুকুই দেখে।

from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt

f = feats["learned (source)"]                  # আগের ব্লকের শেখা, freeze করা feature
Xsrc, _ = world(1000, A)                       # উৎস জগতের নতুন 1000টি ছবি
worlds = {"same world": A, "mild shift": A + 0.4 * rng.normal(0, 1, A.shape),
          "other world": A2}
for name, Aw in worlds.items():
    Xt, zt = world(1000, Aw)
    Hd = np.vstack([f(Xsrc), f(Xt)]); d = np.r_[np.zeros(1000), np.ones(1000)]
    dom = cross_val_score(LogisticRegression(max_iter=3000), Hd, d, cv=5).mean()
    Xtr, ztr = world(200, Aw); Xte, zte = world(2000, Aw)
    probe = LogisticRegression(max_iter=3000).fit(f(Xtr), label(ztr, Wt))
    acc = probe.score(f(Xte), label(zte, Wt))
    print(f"{name:12s} domain classifier acc {dom:.3f} | 200-label probe acc {acc:.3f}")

df = pd.DataFrame(rows, index=cols).T
fig, ax = plt.subplots(figsize=(6.4, 3.4))
for c in cols:
    ax.plot(df.index, df[c], marker="o", label=c)
ax.set_xscale("log"); ax.set_xlabel("labelled target examples (log)")
ax.set_ylabel("test accuracy"); ax.set_title("Frozen features + logistic head")
ax.legend(fontsize=8); plt.show()

আউটপুট (পাতার Python-এ চালানো; আগের ব্লকের namespace ব্যবহার করে, তাই সেটি আগে নিজে থেকেই চলে):

same world   domain classifier acc 0.496 | 200-label probe acc 0.795
mild shift   domain classifier acc 0.681 | 200-label probe acc 0.766
other world  domain classifier acc 0.964 | 200-label probe acc 0.566

Same world-এ domain classifier ০.৪৯৬, প্রায় ৫০%: frozen feature দেখে কোন ছবি কোন জগতের তা বলা যায় না, আর ২০০টি label-এর probe ০.৭৯৫. Mild shift-এ (pixel তৈরির নিয়ম অল্প বদলানো) classifier ০.৬৮১ আর probe সামান্য কম, ০.৭৬৬. Other world-এ classifier ০.৯৬৪, প্রায় প্রতিটি ছবি আলাদা করা যায়, আর probe নেমে ০.৫৬৬. Domain classifier-এর accuracy যত ওপরে, frozen feature-এর ওপর ভরসা তত কম রাখুন। ছবিতে অংশ ২-এর পুরো টেবিল: label বাড়লে সবাই ওঠে, কিন্তু শেখা feature-এর (same world) রেখা সবসময় সবার ওপরে, আর gap-এর রেখা random-এর নিচে নেমে যায়।

বাস্তব কাজে এই পরীক্ষাটি একটি ছোট নিয়মিত ধাপ হিসেবে চালান। তিন জগতের সংখ্যা দিয়ে পুরো চেকটি ধাপে ধাপে:

তিন লক্ষ্য জগৎ · frozen শেখা feature · উপরের ব্লকের সংখ্যা
ধাপ
same world
mild shift
other world
  1. Preprocessing. সবার আগে নিশ্চিত হোন gap আপনার নিজের তৈরি নয়: আকার, interpolation, mean/std আর channel-এর ক্রম weight-এর transforms()-এর সঙ্গে মেলে। এখানে তিন জগতেই একই নিয়ম, তাই এই ধাপ পরিষ্কার।
  2. Domain classifier. উৎস আর লক্ষ্যের feature আলাদা করার accuracy. Same world-এ ০.৪৯৬, মানে feature-এর জগতে দুই ডেটা আলাদা করা যায় না। Mild shift-এ ০.৬৮১, কিছুটা আলাদা। Other world-এ ০.৯৬৪: প্রায় প্রতিটি ছবি দেখেই বলা যায় সে কোন জগতের।
  3. Linear probe বনাম baseline. ২০০টি label-এ শেখা feature-এর probe: ০.৭৯৫, ০.৭৬৬, ০.৫৬৬. একই সংখ্যক label-এ কখনো-না-শেখা random feature পেয়েছিল ০.৬৩২ (অংশ ২)। Other world-এ শেখা feature random-এর চেয়েও খারাপ: এটিই negative transfer-এর লক্ষণ।
  4. সিদ্ধান্ত. Gap ছোট হলে feature extraction যথেষ্ট আর নিরাপদ। মাঝারি হলে শেষ block fine-tune করে দেখুন। বড় হলে গভীরে fine-tune, ওই domain-এর ছবিতে pretrained কোনো model, বা শূন্য থেকে train, তিনটিই validation-এ মেপে বাছুন। Domain classifier শুধু সতর্কসংকেত; শেষ কথা বলে লক্ষ্য কাজের validation.

৯ · torchvision-এর বাইরে — timm, Hugging Face Hub আর model card

torchvision-এ কয়েক ডজন ছবির model আছে, প্রতিটির এক বা একাধিক weight enum. আরও অনেক model আর weight পাওয়া যায় দুটি জায়গায়:

  • timm (PyTorch Image Models, Ross Wightman-এর শুরু করা, এখন Hugging Face-এর অধীনে): শত শত ছবির model আর weight এক API-তে, যেমন timm.create_model("resnet18", pretrained=True, num_classes=10). প্রতিটি weight-এর preprocessing-এর তথ্য model-এর সঙ্গে থাকে, আর weight-গুলো Hugging Face Hub-এ card-সহ রাখা।
  • Hugging Face Hub: transformers-এর ছবির model (ViT, ConvNeXt ইত্যাদি) আর অন্যদের আপলোড করা fine-tuned weight. Revision pin করা, safetensors আর card পড়ার নিয়ম AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ।

যেখান থেকেই নিন, ব্যবহারের আগে চারটি প্রশ্নের উত্তর লিখে রাখুন: (১) weight কোন ডেটায়, কোন recipe-তে train; (২) preprocessing ঠিক কী (আকার, interpolation, mean/std, channel-এর ক্রম); (৩) licence আর ডেটাসেটের শর্ত আপনার ব্যবহার অনুমোদন করে কি না; (৪) model card-এ লেখা সীমা আর পক্ষপাত আপনার কাজে প্রাসঙ্গিক কি না। ভুল preprocessing সবচেয়ে সাধারণ নীরব ভুল: কোড চলে, accuracy শুধু কয়েক শতাংশ-বিন্দু কম।

আরও গভীরে: Yosinski ও সহকর্মীরা (NeurIPS ২০১৪) layer কেটে কেটে দেখান, feature-এর transfer-যোগ্যতা গভীরতার সঙ্গে কমে, আর মাঝের layer-এ “co-adaptation” ভাঙলেও সমস্যা হয়। Kornblith ও সহকর্মীরা (CVPR ২০১৯) ১৬টি architecture আর ১২টি ডেটাসেটে ImageNet accuracy আর transfer-এর সম্পর্ক মাপেন। Kumar ও সহকর্মীরা (ICLR ২০২২) দেখান পুরো fine-tuning distribution-এর বাইরের ডেটায় linear probe-এর চেয়ে খারাপ হতে পারে, আর LP-FT প্রস্তাব করেন। ভাষার জগতে একই প্রশ্নের উত্তর এসেছে parameter-efficient পথে (adapter, LoRA), যা পর্ব ৫৫-এ আসবে।

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

  • Feature extraction আর fine-tuning-এর পার্থক্য? প্রথমটিতে backbone স্থির, শুধু head শেখে; দ্বিতীয়টিতে backbone-এর কিছু বা সব weight-ও শেখে, সাধারণত ছোট learning rate-এ।
  • Backbone freeze করার পরেও কী বদলাতে পারে? BatchNorm-এর running statistics, যদি backbone train mode-এ থাকে; তাই eval mode.
  • ৫১২ → ১০-এর head-এ কতটি parameter? ৫১২ × ১০ + ১০ = ৫,১৩০.
  • কখন transfer ব্যর্থ হতে পারে? Domain অনেক দূরে, preprocessing না মিললে, বা লক্ষ্য কাজ উৎসের feature দিয়ে প্রকাশযোগ্য না হলে; তখন negative transfer.
  • অল্প ডেটা, কাছের domain, প্রথম পছন্দ? Feature extraction (linear probe), তারপর প্রয়োজনে শেষ block fine-tune.

১০ · AI-এর যুগে — AI-এর লেখা fine-tuning কোড যাচাই

“resnet18 দিয়ে আমার ছবিতে transfer learning-এর কোড দাও” বললে AI assistant কয়েক সেকেন্ডে কোড দেয়, আর তা সাধারণত চলেও। কিন্তু এই পর্বের ভুলগুলো নীরব: accuracy একটু কম, বা seed-ভেদে অস্থির, আর কেউ বুঝতে পারে না কেন।

AI প্রায়ই যা করেফলকীভাবে ধরবেন
পুরোনো pretrained=True লেখে, বা অস্তিত্বহীন weight-এর নাম দেয়পুরোনো API বা error; কোন weight এল অস্পষ্টনিজের version-এর docs; নির্দিষ্ট enum, যেমন IMAGENET1K_V1
নিজে mean/std বা আকার লেখে, বা নিজের ডেটার mean/std নেয়Preprocessing না মেলায় নীরবে কম accuracyweights.transforms() ছাপিয়ে মিলিয়ে দেখুন
Freeze করে কিন্তু model.train()-এ BatchNorm-এর কথা ভুলে যায়Statistics সরে; ছোট batch-এ বড় ক্ষতিএক epoch-এর আগে-পরে running_mean তুলনা
Head বদলানোর আগে freeze loop-এর ক্রম গুলিয়ে ফেলে, বা সবখানে একই বড় lrHead শেখে না, বা pretrained feature নষ্টTrainable parameter গুনে ছাপান; param_groups-এর lr দেখুন
এক seed-এ “fine-tuning সবচেয়ে ভালো” বলে, test-এ বারবার দেখে বাছেভাগ্যকে উন্নতি ভাবা; test leakকয়েকটি seed, scratch baseline, validation-এ বাছাই

AI-কে কোডের সঙ্গে যাচাইও চাইতে বলুন:

আমি torchvision-এর resnet18 (ResNet18_Weights.IMAGENET1K_V1) দিয়ে ১০ শ্রেণির পণ্যের ছবি classify করব, প্রতি শ্রেণিতে প্রায় ২০০টি ছবি। ১. প্রথমে feature extraction-এর কোড দাও: backbone freeze, নতুন fc, আর backbone eval mode-এ থাকবে প্রতিটি model.train()-এর পরেও। ২. Preprocessing-এর জন্য শুধু weights.transforms() ব্যবহার করো, নিজে mean/std লিখো না। ৩. কতটি parameter শেখার যোগ্য তা ছাপাও, আর optimizer-এ শুধু সেগুলোই দাও। ৪. তারপর fine-tuning-এর কোড দাও: দুটি parameter group, backbone 1e-4 আর head 1e-3, আর প্রতিটি group-এর lr ছাপাও। ৫. একটি scratch baseline আর তিনটি seed-এ তুলনার কাঠামো দাও; test set শুধু শেষে একবার। ৬. আমার torchvision version-এ কোনো API নিয়ে নিশ্চিত না হলে স্পষ্ট বলো; অনুমান করে argument বানিও না।

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

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

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

প্র ০১ImageNet-এ pretraining কি সবসময় দরকার? “শূন্য থেকে” train করার পক্ষে কোনো প্রমাণ আছে কি?

২০১৩–২০১৪ সালে কয়েকটি কাজ দেখায়, ImageNet-এ শেখা CNN-এর ভেতরের activation অন্য কাজে সরাসরি feature হিসেবে খুব ভালো চলে: Donahue ও সহকর্মীদের DeCAF, আর Razavian ও সহকর্মীদের “CNN Features off-the-shelf”, যেখানে একটি স্থির network-এর feature আর একটি linear SVM অনেক ছবির কাজে তখনকার হাতে বানানো feature-এর পদ্ধতিকে ছাড়িয়ে যায়। Detection-এ R-CNN (Girshick ও সহকর্মী, ২০১৪) ImageNet-এ pretrain করে তারপর fine-tune করার রীতিকে প্রায় মানক করে তোলে। এরপর কয়েক বছর ধরে ছবির প্রায় সব কাজের শুরু ছিল ImageNet-এর weight.

২০১৯ সালে He, Girshick আর Dollár-এর “Rethinking ImageNet Pre-training” প্রশ্নটি নতুন করে তোলে। তাঁদের COCO detection-এর পরীক্ষায়, যথেষ্ট দীর্ঘ training আর উপযুক্ত normalization দিলে, শূন্য থেকে train করা model pretrained-এর কাছাকাছি ফল পায়। কিন্তু একই কাজে pretraining দ্রুত পৌঁছায়, আর ডেটা অনেক কমালে শূন্য থেকে শুরু পিছিয়ে পড়ে। অর্থাৎ pretraining মূলত গতি আর অল্প ডেটার সমস্যার সমাধান; প্রচুর ডেটা আর compute থাকলে তার সুবিধা কমে আসে।

একই বছরে Kornblith, Shlens আর Le (“Do Better ImageNet Models Transfer Better?”) দেখান, fine-tuning-এ ImageNet-এ ভালো model সাধারণত অন্য কাজেও ভালো transfer করে, কিন্তু সম্পর্কটি সব ডেটাসেটে সমান নয়; কিছু সূক্ষ্ম শ্রেণির (যেমন গাড়ির মডেল বা বিমানের ধরন) ছোট ডেটাসেটে fine-tuning-এর লাভ শুরুর চেয়ে কম। এই পর্বের CIFAR-10 run-ও একই কথা বলে: ২,০০০টি ছবিতে শূন্য থেকে শুরু অনেক পিছিয়ে। কিন্তু সেটি একটি ছোট, এক local run-এর ফল, সর্বজনীন নিয়ম নয়।

মূল উপলব্ধি: Pretraining প্রায় কখনো ক্ষতি করে না আর অল্প ডেটায় বড় সুবিধা দেয়, কিন্তু এটি জাদু নয়; ডেটা যত বেশি আর domain যত দূরে, শূন্য থেকে শুরুর সঙ্গে ব্যবধান তত কম, তাই নিজের ডেটায় দুটোই মেপে দেখুন।

প্র ০২Fine-tuning-এ আগের শেখা “ভুলে যাওয়া” কী, আর ছোট learning rate, ধাপে ধাপে unfreeze বা linear probe আগে করা কীভাবে সাহায্য করে?

McCloskey আর Cohen (১৯৮৯) neural network-এ একটি সমস্যা লক্ষ করেন: নতুন কাজ শেখাতে গেলে network পুরোনো কাজ দ্রুত ভুলে যায়, যার নাম পরে হয় catastrophic forgetting. Fine-tuning-এ ঠিক এটিই ঝুঁকি: বড় learning rate-এ নতুন ছোট ডেটায় কয়েক ধাপেই pretrained weight অনেক সরে যায়, আর সাধারণ feature-গুলো নতুন ডেটার দিকে বেঁকে যায়।

সমাধানের কয়েকটি পরিচিত ধারা আছে। ভাষার জগতে Howard আর Ruder-এর ULMFiT (২০১৮) তিনটি কৌশল জনপ্রিয় করে: স্তরভেদে আলাদা learning rate (discriminative fine-tuning, নিচের স্তরে ছোট), ধাপে ধাপে unfreeze (প্রথমে শুধু শেষ স্তর, তারপর একে একে নিচের স্তর), আর বিশেষ একটি learning-rate schedule. ছবির জগতে backbone-এ head-এর চেয়ে ছোট learning rate দেওয়া বহু প্রচলিত অভ্যাস। Kumar ও সহকর্মীরা (২০২২) আরেকটি সূক্ষ্ম কারণ দেখান: নতুন head এলোমেলো থাকলে শুরুর বড় gradient backbone-এর feature বিকৃত করে, যা বিশেষ করে distribution-এর বাইরের ডেটায় ক্ষতি করে। তাঁদের প্রস্তাব LP-FT: আগে linear probe দিয়ে head শেখানো, তারপর পুরো fine-tune.

এই পর্বের stepper-এ এর একটি সংখ্যাগত রূপ দেখেছেন: Adam-এ প্রতিটি ধাপে weight প্রায় η পরিমাণ সরে, আর stem-এর weight-এর গড় মাপ ছোট, তাই একই η-তে আপেক্ষিক বদল বিশাল। মনে রাখবেন, ভুলে যাওয়া সবসময় খারাপ নয়: লক্ষ্য কাজে ভালো করাই উদ্দেশ্য হলে পুরোনো ১০০০ শ্রেণি ভুলে গেলে ক্ষতি নেই। ক্ষতি হয় যখন যা ভুলছে তা লক্ষ্য কাজের জন্যও দরকারি সাধারণ জ্ঞান। Continual learning নামের একটি পুরো গবেষণা-ক্ষেত্র পুরোনো কাজও ধরে রাখার উপায় খোঁজে।

মূল উপলব্ধি: Pretrained weight একটি মূল্যবান শুরুর বিন্দু, আর fine-tuning-এর কাজ তার কাছাকাছি থেকে নতুন কাজ শেখা; ছোট backbone learning rate, আগে head শেখানো আর ধাপে ধাপে unfreeze, সবগুলোই সেই দূরত্ব নিয়ন্ত্রণের উপায়।

প্র ০৩Backbone freeze করলে BatchNorm-কে নিয়ে বিতর্ক কী? Running statistics স্থির রাখবেন, না নতুন ডেটায় আবার হিসাব করবেন?

BatchNorm-এর দুটি ভিন্ন জিনিস আছে: শেখা γ আর β (parameter, gradient পায়) আর running_mean ও running_var (buffer, gradient পায় না, train mode-এ forward-এর সময়ই বদলায়; বিস্তারিত পর্ব ১৪-এ)। requires_grad_(False) শুধু প্রথমটি স্থির করে। তাই backbone “freeze” করেও model.train() রাখলে running statistics নীরবে নতুন ডেটার দিকে সরে, আর প্রতিটি batch নিজের গড় দিয়ে normalize হয়। এই পর্বের run-এ ঠিক এই পার্থক্য মাপা হয়েছে।

Detection-এর জগতে এর একটি দৃঢ় অভ্যাস তৈরি হয়েছে। সেখানে ছবি বড়, প্রতি GPU-তে batch-এ মাত্র কয়েকটি ছবি ধরে, আর এত ছোট batch-এর গড় খুব কোলাহলপূর্ণ। তাই Detectron-ধাঁচের framework আর torchvision-এর detection model অনেক সময় backbone-এর BatchNorm-কে “frozen” রূপে বদলে দেয় (torchvision-এ FrozenBatchNorm2d), যেখানে statistics আর affine দুটিই স্থির। Wu আর He-এর Group Normalization (২০১৮) এই ছোট-batch সমস্যার আরেকটি উত্তর।

অন্যদিকে, domain বদলালে পুরোনো statistics ভুলও হতে পারে। Li ও সহকর্মীদের AdaBN (২০১৬ সালের দিকে) প্রস্তাব করে, বাকি সব weight স্থির রেখে শুধু লক্ষ্য domain-এর ডেটায় BatchNorm-এর statistics আবার হিসাব করলে domain-এর ফারাক কিছুটা কমে। তাই কোনো একক সঠিক উত্তর নেই: ছোট batch, কাছের domain আর feature extraction-এ statistics স্থির রাখা নিরাপদ; যথেষ্ট বড় batch আর পুরো fine-tune-এ train mode স্বাভাবিক; আর দূরের domain-এ statistics নতুন করে মাপা একটি সস্তা পরীক্ষা হতে পারে, যা validation-এ যাচাই করতে হয়।

মূল উপলব্ধি: “Freeze” দুই রকম: gradient থামানো আর statistics থামানো। কোনটি চান তা স্পষ্ট করে ঠিক করুন, আর model.train() ডাকার পরে আবার কোন অংশ eval-এ ফিরবে, তা কোডে লিখে রাখুন।

প্র ০৪Pretrained weight-এর licence আর উৎস নিয়ে কেন সতর্ক থাকতে হয়, যদিও library-র কোড নিজে open source?

torchvision-এর কোড BSD-3-Clause licence-এ, কিন্তু weight একটি আলাদা জিনিস: সেটি কোনো ডেটাসেটে train করার ফল। torchvision-এর নিজের documentation সতর্ক করে যে pretrained model-এর নিজস্ব licence বা শর্ত থাকতে পারে, যা train-এর ডেটাসেট থেকে আসে, আর নিজের ব্যবহারে অনুমতি আছে কি না তা ঠিক করার দায় ব্যবহারকারীর। ImageNet-এর ছবিগুলো ইন্টারনেট থেকে সংগ্রহ করা, আর ডেটাসেট ডাউনলোডের শর্তে গবেষণা আর অবাণিজ্যিক ব্যবহারের কথা আছে। এই শর্ত থেকে train করা weight-এর বাণিজ্যিক ব্যবহারে কী দাঁড়ায়, তা নিয়ে আইনি স্পষ্টতা নেই বলে বহু আলোচনায় বলা হয়; এখানে কোনো আইনি পরামর্শ দেওয়া হচ্ছে না।

সমস্যাটি শুধু আইনের নয়। ImageNet আর অন্য বড় ছবির ডেটাসেটে মানুষের ছবির শ্রেণি, আপত্তিকর label আর পক্ষপাত নিয়ে প্রকাশিত সমালোচনা আছে (যেমন Yang ও সহকর্মীদের ২০২০ সালের কাজ, যার পরে ImageNet-এর person উপশ্রেণির একটি অংশ সরানো হয়)। Pretrained feature সেই ডেটার পক্ষপাতও সঙ্গে আনে। Mitchell ও সহকর্মীদের model card (২০১৯) আর Gebru ও সহকর্মীদের datasheet-এর ধারণা এ কারণেই: weight-এর সঙ্গে কোন ডেটা, কোন উদ্দেশ্য, কোন সীমা, তা লিখিত থাকা।

ব্যবহারিক দিক: torchvision-এর weight-এর meta-তে training recipe-এর link আর মাপা accuracy থাকে, কিন্তু licence-এর আলাদা ঘর নেই। Hugging Face Hub-এ প্রতিটি repository-র card-এ licence-এর ঘর থাকে, timm-এর model-গুলোও সেখানে card-সহ রাখা। বাজারঘরের মতো একটি ব্যবসায় model ব্যবহার করার আগে weight-এর উৎস, licence, ডেটাসেটের শর্ত আর card-এর সীমাবদ্ধতা লিখে রাখা, আর প্রয়োজনে আইনি পরামর্শ নেওয়া উচিত। AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ licence-এর ধরনগুলো বিস্তারিত আছে।

মূল উপলব্ধি: কোড, weight আর ডেটার licence তিনটি আলাদা প্রশ্ন; “library open source” মানে weight যেকোনো কাজে ব্যবহারের অনুমতি নয়।

প্র ০৫Supervised ImageNet pretraining-এর পরে কী এসেছে — self-supervised আর বড় “foundation” model transfer learning-কে কীভাবে বদলেছে?

ImageNet-এর label বানাতে বিপুল মানুষের শ্রম লেগেছিল, আর label-এর সংখ্যা সীমিত। তাই প্রশ্ন ওঠে: label ছাড়া ছবি থেকেই কি ভালো feature শেখা যায়? ২০২০ সালের দিকে contrastive পদ্ধতি, যেমন Chen ও সহকর্মীদের SimCLR আর He ও সহকর্মীদের MoCo, একই ছবির দুটি ভিন্ন রূপান্তরকে কাছে আর ভিন্ন ছবিকে দূরে রাখতে শেখায়। এদের মূল্যায়নের প্রধান মাপকাঠিই ছিল linear probe: feature freeze করে একটি linear classifier, ঠিক এই পর্বের অংশ ২-এর মতো। পরে He ও সহকর্মীদের MAE (২০২১) ছবির বড় অংশ ঢেকে বাকিটা থেকে পূরণ করতে শেখায়, যা ভাষায় BERT-এর লুকানো শব্দ অনুমানের সঙ্গে মেলে।

আরেকটি ধারা ছবি আর লেখা একসঙ্গে শেখায়। Radford ও সহকর্মীদের CLIP (২০২১) ইন্টারনেটের বিপুল ছবি-ক্যাপশন জোড়া থেকে শেখে, আর কোনো fine-tune ছাড়াই শুধু শ্রেণির নাম লিখে classification করতে পারে (zero-shot). Oquab ও সহকর্মীদের DINOv2 (২০২৩) self-supervised পথে এমন feature দেয়, যা অনেক কাজে freeze অবস্থাতেই ভালো চলে বলে রিপোর্ট আছে। এই model-গুলোর একটি সাধারণ প্রভাব: feature extraction আবার শক্তিশালী বিকল্প হয়েছে, কারণ feature নিজেই অনেক সাধারণ।

তবে নতুন প্রশ্নও এসেছে: এত বড় ডেটায় train করা model-এর ডেটার উৎস আর licence আরও অস্পষ্ট, পক্ষপাত যাচাই কঠিন, আর benchmark-এর ছবি pretraining ডেটায় আগেই থাকতে পারে (contamination). Vision Transformer আসবে পর্ব ৩৮-এ, CLIP আর multimodal model পর্ব ৮২-তে, আর বড় ভাষা model-এ parameter-efficient fine-tuning (LoRA) পর্ব ৫৫-তে; transfer learning-এর মূল প্রশ্নগুলো (কী freeze, কত learning rate, domain কতটা দূরে) সেখানেও একই থাকে।

মূল উপলব্ধি: Pretraining-এর উৎস বদলেছে (label থেকে label-হীন, ছবি থেকে ছবি-লেখা), কিন্তু transfer-এর যুক্তি বদলায়নি; linear probe আজও feature-এর মান মাপার সবচেয়ে সরল আর জনপ্রিয় পরীক্ষা।

অনুশীলন

প্রথমে খাতায় করুন, তারপর উত্তর মেলান।

  1. পূর্ণ numerical: (ক) resnet18-এর layer3-এর প্রথম BasicBlock-এ channel ১২৮ থেকে ২৫৬ হয় (দুটি 3 × 3 conv, দুটি BatchNorm, আর 1 × 1 projection shortcut ও তার BatchNorm; conv-এ bias নেই)। এই block-এ কতটি parameter? (খ) ৫ শ্রেণির একটি কাজে layer3, layer4 আর নতুন fc fine-tune করবেন (layer3 = ২০,৯৯,৭১২, layer4 = ৮৩,৯৩,৭২৮)। কতটি parameter শিখবে, আর float32-এ gradient + AdamW-এর m ও v রাখতে কত MiB লাগবে?

    (ক) প্রথম conv: ২৫৬ × ১২৮ × ৯ = ২,৯৪,৯১২; BatchNorm: ২ × ২৫৬ = ৫১২; দ্বিতীয় conv: ২৫৬ × ২৫৬ × ৯ = ৫,৮৯,৮২৪; BatchNorm: ৫১২; projection: ২৫৬ × ১২৮ = ৩২,৭৬৮; তার BatchNorm: ৫১২. মোট ৯,১৯,০৪০. (যাচাই: দ্বিতীয় block ৫,৮৯,৮২৪ × ২ + ১,০২৪ = ১১,৮০,৬৭২; দুটি মিলে ২০,৯৯,৭১২, যা layer3-এর মোটের সঙ্গে মেলে।) (খ) fc: ৫১২ × ৫ + ৫ = ২,৫৬৫. মোট ২০,৯৯,৭১২ + ৮৩,৯৩,৭২৮ + ২,৫৬৫ = ১,০৪,৯৬,০০৫. প্রতিটির জন্য তিনটি float32 (gradient, m, v) = ১২ byte: ১২,৫৯,৫২,০৬০ byte ÷ ১০,৪৮,৫৭৬ ≈ ১২০.১ MiB. Weight নিজে আর activation এর বাইরে।

  2. সমালোচনা ও নকশা: একটি দল বাজারঘরের ৮০০টি পণ্যের ছবিতে (৮ শ্রেণি) এই কোড চালিয়ে বলছে “pretrained model কাজ করে না, accuracy ৪০%”. কী কী ভুল, আর আপনার নকশা কী?
    model = resnet18(weights="DEFAULT")
    for p in model.parameters():
        p.requires_grad = False
    model.fc = nn.Linear(512, 8)
    opt = torch.optim.Adam(model.parameters(), lr=1e-2)
    tf = v2.Compose([v2.Resize(64), v2.ToDtype(torch.float32, scale=True)])
    model.train()   # পুরো সময়, validation-এও
    # validation: train-এর ৮০০টি ছবি থেকেই এলোমেলো ১০০টি

    ১, preprocessing: ImageNet-এর mean/std দিয়ে normalize নেই, আর আকার মাত্র ৬৪; pretrained network যে মাপে শিখেছে তার সঙ্গে মেলে না। weights.transforms() ব্যবহার করুন। ২, BatchNorm: frozen backbone train mode-এ, তাই running statistics বাজারঘরের ছোট batch-এর দিকে সরে; validation-ও train mode-এ, তাই ফল batch-ভেদে বদলায়। Backbone-কে eval-এ রাখুন, আর মূল্যায়নে model.eval(). ৩, validation: train-এর ছবি থেকেই নেওয়া, তাই সংখ্যাটি নির্ভরযোগ্য নয়; আলাদা validation আর test রাখুন। ৪, “DEFAULT”: version-এর সঙ্গে বদলাতে পারে; নির্দিষ্ট enum লিখুন। ৫, lr ১০−২: head-এর জন্য বড়, ছোট পরিসরে খুঁজুন; optimizer-কে শুধু শেখার যোগ্য parameter দেওয়া পরিষ্কার অভ্যাস। নকশা: ৮০০টি ছবিতে প্রথমে feature extraction (feature একবার হিসাব করে রেখে), তারপর দরকার হলে layer4 + fc ছোট backbone lr-এ; একটি scratch baseline আর কয়েকটি seed; শেষে আলাদা test set. “Pretrained কাজ করে না” বলার আগে pipeline-এর এই ভুলগুলো সরান।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড তিন লাইনে কী ছাপবে।
    import torch
    from torchvision.models import resnet18
    m = resnet18(weights=None)
    for p in m.parameters():
        p.requires_grad_(False)
    m.fc = torch.nn.Linear(512, 5)
    print(sum(p.numel() for p in m.parameters() if p.requires_grad))
    before = m.bn1.running_mean.clone()
    m.train(); m(torch.randn(4, 3, 64, 64))
    print(torch.equal(before, m.bn1.running_mean))
    m.eval(); before = m.bn1.running_mean.clone(); m(torch.randn(4, 3, 64, 64))
    print(torch.equal(before, m.bn1.running_mean))
    2565
    False
    True

    (সত্যিকারের run, torch 2.15.0, torchvision 0.29.2.) প্রথম লাইন: loop-এর পরে নতুন fc বসানো হয়েছে, তাই শুধু তার ৫১২ × ৫ + ৫ = ২,৫৬৫টি parameter শেখার যোগ্য। দ্বিতীয় লাইন False: সব parameter freeze হলেও train mode-এ forward-এর সময় BatchNorm-এর running_mean বদলেছে, কারণ এটি buffer, gradient দিয়ে নয়, forward-এই আপডেট হয়। তৃতীয় লাইন True: eval mode-এ running statistics শুধু পড়া হয়, বদলায় না।

  4. নকশা ও কোড: resnet18-এর জন্য স্তরভেদে learning rate (layer-wise decay) লিখুন: fc ১০−৩, layer4 তার ৩ ভাগের ১, layer3 আরও ৩ ভাগের ১, এভাবে stem পর্যন্ত। প্রতিটি group-এর learning rate আর parameter সংখ্যা ছাপান, আর কোনো parameter যেন বাদ বা দুবার না পড়ে, তা যাচাই করুন।
    stages = [["fc"], ["layer4"], ["layer3"], ["layer2"], ["layer1"], ["conv1", "bn1"]]
    groups, seen = [], set()
    for depth, names in enumerate(stages):
        ps = [p for n, p in model.named_parameters() if n.split(".")[0] in names]
        seen |= {id(p) for p in ps}
        groups.append({"params": ps, "lr": 1e-3 / 3 ** depth})
    assert len(seen) == sum(1 for _ in model.parameters())   # সবাই ঠিক একবার
    opt = torch.optim.AdamW(groups, weight_decay=1e-4)
    for g in opt.param_groups:
        print(f"lr {g['lr']:.2e}  params {sum(p.numel() for p in g['params']):,}")

    stem পায় ১০−৩/৩৫ ≈ ৪.১ × ১০−৬. এটি ULMFiT-ধাঁচের discriminative fine-tuning; ৩ গুণকটি একটি প্রচলিত শুরুর মান, যা validation-এ যাচাই করতে হয়। Bias আর BatchNorm-কে weight decay থেকে বাদ দিতে চাইলে প্রতিটি স্তরে আরও দুটি group লাগবে (পর্ব ১৫)। Scheduler থাকলে সে প্রতিটি group-এর lr একই অনুপাতে বদলায়, তাই অনুপাত বজায় থাকে।

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

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

ক্রমএকটি নতুন ছবির কাজে transfer learning-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Weight-এর licence, model card আর preprocessing (transforms) পড়া
  2. Backbone freeze করে নতুন head বসানো, backbone eval mode-এ
  3. Linear probe-এর accuracy একটি scratch baseline-এর পাশে মাপা
  4. দরকার হলে শেষের block unfreeze করে ছোট backbone learning rate-এ fine-tune
  5. কয়েকটি seed-এ validation দেখে পরিকল্পনা বাছাই
  6. আলাদা test set-এ একবার চূড়ান্ত মাপ

resnet18 (মোট ১,১১,৮১,৬৪২ parameter, ১০ শ্রেণির head ৫১২ → ১০) দিয়ে feature extraction করলে কতটি parameter শেখে?

  • ৫,১২০
  • ৮৩,৯৮,৮৫৮
  • ৫,১৩০
  • ১,১১,৮১,৬৪২

PyTorch-এ backbone-এর সব parameter-এ requires_grad_(False) করা হলো, কিন্তু training-এ পুরো model model.train()-এ। BatchNorm-এর কী হয়?

  • কিছুই বদলায় না, কারণ সব parameter freeze
  • γ আর β স্থির থাকে, কিন্তু running statistics বদলায় আর batch-এর গড় দিয়ে normalize হয়
  • RuntimeError আসে, কারণ frozen layer train mode-এ চলতে পারে না
  • γ আর β শেখে, statistics স্থির থাকে

Fine-tuning-এ backbone-কে head-এর চেয়ে ছোট learning rate (যেমন ১০−৪ বনাম ১০−৩) দেওয়ার মূল কারণ কোনটি?

  • Backbone-এর gradient সবসময় অনেক বড়, তাই তা কমাতে হয়
  • ছোট learning rate GPU memory বাঁচায়
  • ছোট learning rate-এ training দ্রুত শেষ হয়
  • Pretrained weight ইতিমধ্যে কাজের feature রাখে, তাই অল্প সরানো উচিত; এলোমেলো head-কে বড় ধাপে শিখতে হয়

একটি frozen backbone-এর feature-এ উৎস ডেটা আর লক্ষ্য ডেটা আলাদা করতে একটি logistic regression (domain classifier) train করা হলো। Cross-validation accuracy ০.৯৬. এর সবচেয়ে যুক্তিসংগত ব্যাখ্যা কী?

  • Feature-এর জগতে দুই ডেটা প্রায় পুরো আলাদা, তাই domain gap বড়
  • Domain gap নেই, কারণ accuracy উঁচু মানে model ভালো
  • লক্ষ্য কাজে accuracy ৯৬% হবে
  • Backbone এখন লক্ষ্য domain-এ মানিয়ে গেছে

torchvision-এ ResNet18_Weights.IMAGENET1K_V1 ব্যবহার করে নিজের ছবি classify করার সময় preprocessing-এর সবচেয়ে নিরাপদ উৎস কোনটি?

  • নিজের ডেটাসেটের mean আর std হিসাব করে normalize
  • কোনো preprocessing ছাড়া ০–২৫৫-এর pixel সরাসরি
  • ResNet18_Weights.IMAGENET1K_V1.transforms()
  • AI assistant-এর দেওয়া যেকোনো mean/std

বাজারঘরের প্রতি শ্রেণিতে মাত্র ৩০টি পণ্যের সাধারণ ছবি আছে, ImageNet-এর ছবির মতোই। resnet18 দিয়ে প্রথম পছন্দ কোনটি?

  • শূন্য থেকে পুরো network train
  • Feature extraction: backbone freeze, শুধু নতুন head
  • পুরো network fine-tune, সবখানে একই বড় learning rate
  • শুধু stem freeze, বাকি সব শেখানো

শব্দকোষ

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

মনে রাখুন

  1. Feature transfer হয়: প্রথম দিকের layer সাধারণ (কিনারা, texture), শেষের layer নির্দিষ্ট। লক্ষ্যের label যত কম, pretrained feature-এর মূল্য তত বেশি।
  2. তিন পরিকল্পনা: feature extraction (শুধু head), শেষ block fine-tune, পুরো fine-tune; ডেটা যত বেশি আর domain যত দূরে, তত বেশি layer শেখান। resnet18-এ layer4 একাই প্রায় ৭৫% parameter.
  3. Freeze-এর দুই দিক: requires_grad_(False) gradient থামায়; BatchNorm-এর statistics থামাতে backbone-কে eval()-এ রাখুন, প্রতিটি model.train()-এর পরে।
  4. ছোট backbone learning rate: parameter group দিয়ে backbone ১০−৪, head ১০−৩-এর মতো; pretrained weight-কে কম সরিয়ে নতুন কাজ শেখানো।
  5. যাচাই: weight-এর নিজের transforms(), licence আর model card; scratch baseline, কয়েকটি seed, আর domain gap-এর পরীক্ষা (domain classifier, linear probe).

পরের পর্বে: এই পর্বের run-এ কোনো augmentation ছিল না, ইচ্ছা করেই। পর্ব ২৪ (Data augmentation — ছবির রূপান্তর, Mixup আর CutMix) দেখাবে ছবি ঘুরিয়ে, কেটে, রং বদলে আর দুটি ছবি মিশিয়ে কীভাবে model-কে প্রয়োজনীয় invariance শেখানো যায়, আর কোন রূপান্তর label নষ্ট করে।

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

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