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

GAN-এর রূপ — DCGAN, WGAN, StyleGAN আর conditional GAN

GAN Variants: DCGAN, WGAN, StyleGAN and Conditional GAN

Taught in Bangla, this lesson follows DCGAN's design rules with layer shapes and parameters by hand, shows why JS divergence stalls for disjoint distributions while the Wasserstein distance moves, and computes the WGAN critic's Lipschitz constraint with weight clipping and gradient penalty. It covers conditional GAN, StyleGAN's mapping network and truncation, and compares three GANs on Fashion-MNIST at the same budget over three seeds with a classifier-feature FID and class coverage in a real desktop run.

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

  • DCGAN-এর নকশার নিয়ম, আর generator-এর প্রতিটি স্তরের মাপ ও parameter হাতে হিসাব
  • JS divergence কেন আলাদা জায়গার দুটি বণ্টনে আটকে যায়, আর Wasserstein দূরত্ব কেন নড়ে
  • WGAN-এর critic, Lipschitz শর্ত, weight clipping আর gradient penalty-র হিসাব
  • Conditional GAN, StyleGAN-এর mapping network আর truncation trick; image-to-image GAN-এর নাম
  • নিজের classifier দিয়ে coverage আর classifier-feature FID মেপে তিনটি GAN-এর সৎ তুলনা, কয়েকটি seed-সহ

পর্ব ৭৭ (GAN — generator, discriminator আর প্রশিক্ষণের অস্থিরতা) মূল খেলাটি শিখিয়েছে: generator নকল বানায়, discriminator আসল-নকল আলাদা করে, দুজনের minimax খেলায় optimal discriminator-এর বিপরীতে generator আসলে JS divergence কমায়; আর বড় বিপদ mode collapse. এখানে প্রশ্ন: সেই ধারণাকে কীভাবে ছবির জন্য কাজের, বেশি স্থিতিশীল আর নিয়ন্ত্রণযোগ্য করা হলো। পর্ব ৭৪ (Generative modeling-এর মানচিত্র) দেখিয়েছে GAN likelihood দেয় না, তাই মান মাপতে বাইরের “বিচারক” লাগে। Transposed convolution-এর মাপের সূত্র পর্ব ৭৫ (Autoencoder)-এ। Convolution আর stride পর্ব ২০ (Convolution)-এ, BatchNorm পর্ব ১৪ (Weight initialization আর normalization)-এ, আর KL divergence AI-এর ভিত্তি কোর্সের পর্ব ৩৮-এ আছে।

ডেটা Fashion-MNIST (Zalando Research, MIT লাইসেন্স): ২৮ × ২৮ মাপের ধূসর পোশাক, জুতা আর ব্যাগের ছবি, ১০টি শ্রেণি। বাজারঘরের পণ্যের ছবির বদলে এটি ব্যবহার করছি; এগুলো আসল দোকানের ছবি নয়। Desktop-এর run লেখার সময়ে (নভেম্বর ২০২৬) এই version-এ: Python 3.14.4, torch 2.15.0, NumPy 2.5.4. GAN-গুলো GPU-তে, বিচারক classifier CPU-তে (৩টি thread) শেখানো হয়েছে। সময়গুলো এই এক যন্ত্রের একটি run-এর, সাধারণ নিয়ম নয়। এই পর্বে কোনো মানুষের মুখের ছবি বানানো হয়নি, আর হবেও না।

১ · DCGAN — convolution দিয়ে GAN-এর নকশার নিয়ম

২০১৪ সালের মূল GAN-এ দুটি network ছিল সাধারণ MLP, যা পাশের pixel-এর সম্পর্ক কাজে লাগায় না। ছবির স্বাভাবিক যন্ত্র convolution, কিন্তু প্রথম দিকে convolution-যুক্ত GAN শেখানো খুব অস্থির ছিল। Radford, Metz আর Chintala ২০১৫ সালে (DCGAN, “Deep Convolutional GAN”) অনেক পরীক্ষা করে কয়েকটি নিয়ম দেন, যাতে শেখা তুলনামূলক স্থির হয়। পেপার অনুযায়ী নিয়মগুলো মোটামুটি এই:

Pooling নয়, stride

Discriminator-এ max pooling-এর বদলে stride ২-এর convolution, generator-এ stride ২-এর transposed convolution. মাপ ছোট-বড় করাও network নিজে শেখে।

BatchNorm

দুই network-এ BatchNorm, তবে generator-এর শেষ স্তর আর discriminator-এর প্রথম স্তরে নয়। মাঝের মান একটি স্থির পরিসরে থাকে।

Activation

Generator-এ ReLU, শেষ স্তরে tanh (তাই ছবি −১ থেকে ১); discriminator-এ LeakyReLU (ঢাল ০.২), যাতে ঋণাত্মক দিকেও gradient যায়।

কম fully connected

লুকানো fully connected স্তর বাদ; z সরাসরি প্রথম transposed convolution-এ। পেপারে Adam (lr ০.০০০২, β১ = ০.৫) আর ওজন N(০, ০.০২²) থেকে শুরু।

এগুলো তত্ত্ব থেকে প্রমাণিত নয়, পরীক্ষা থেকে পাওয়া অভিজ্ঞতা; তবু ছোট ছবিতে আজও ভালো শুরু। এই পর্বের সব GAN একই generator ব্যবহার করে।

DCGAN generator and discriminator shapes for 28×28 images Generator G (ConvTranspose2d) z 100 256 7 × 7 128 14 × 14 ছবি 1 × 28 × 28 নকল Discriminator D (Conv2d, stride 2) আসল বা নকল ছবি 64 14 × 14 128 7 × 7 score
উপরে generator মাপ বাড়ায় (১ → ৭ → ১৪ → ২৮), নিচে discriminator মাপ কমায় (২৮ → ১৪ → ৭ → ১)। দুটি প্রায় একে অপরের আয়না।

PyTorch-এ generator-টি এরকম (সংক্ষিপ্ত করা; পুরো কোড desktop lab-এ চালানো হয়েছে):

G = nn.Sequential(                                        # input: z, আকার (B, 100, 1, 1)
    nn.ConvTranspose2d(100, 256, 7, 1, 0, bias=False),    # → (B, 256, 7, 7)
    nn.BatchNorm2d(256), nn.ReLU(True),
    nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False),    # → (B, 128, 14, 14)
    nn.BatchNorm2d(128), nn.ReLU(True),
    nn.ConvTranspose2d(128, 1, 4, 2, 1),                  # → (B, 1, 28, 28)
    nn.Tanh())

মাপের সূত্র পর্ব ৭৫ থেকে: Hout = (Hin − ১)·s − ২p + k + output_padding, যেখানে s stride, p padding, k kernel-এর মাপ। এবার স্তরে স্তরে হিসাব করুন।

মাপের খাতা
ধাপ
হিসাব
ফল
  1. z. Standard normal থেকে ১০০টি সংখ্যা, (B, ১০০, ১, ১) আকারে: ১০০টি channel, প্রতিটি ১ × ১। Parameter নেই।
  2. স্তর ১. Kernel ৭, stride ১, padding ০: (১ − ১)·১ − ০ + ৭ = ৭। ওজনের আকার (in, out, k, k) = (১০০, ২৫৬, ৭, ৭), মোট ১২,৫৪,৪০০। Bias নেই, কারণ পরের BatchNorm-এর β সেই কাজ করে।
  3. BatchNorm + ReLU. প্রতিটি channel-এ γ আর β: ২ × ২৫৬ = ৫১২। আকার বদলায় না।
  4. স্তর ২. (৭ − ১)·২ − ২ + ৪ = ১৪। Kernel ৪ আর stride ২-এ output_padding লাগে না; পর্ব ৭৫-এর kernel ৩-এর মতো ১৩ আসে না। ওজন ২৫৬ × ১২৮ × ১৬ = ৫,২৪,২৮৮, সঙ্গে BatchNorm-এর ২৫৬।
  5. স্তর ৩. (১৪ − ১)·২ − ২ + ৪ = ২৮, আসল ছবির মাপ, এক channel. ওজন ১২৮ × ১৬ = ২,০৪৮, সঙ্গে ১টি bias: ২,০৪৯। শেষ স্তরে BatchNorm নেই।
  6. tanh. Output −১ থেকে ১। তাই আসল pixel (০–২৫৫)-কে x/১২৭.৫ − ১ দিয়ে একই পরিসরে আনতে হয়; নইলে discriminator পরিসর দেখেই নকল ধরত।
  7. মোট. ১৭,৮১,৫০৫, PyTorch-এর sum(p.numel())-এর সঙ্গে হুবহু; প্রায় ৭০% প্রথম স্তরে। Discriminator মাত্র ১,৩৮,৬৮৯, প্রায় ১৩ গুণ ছোট, তবু পিছিয়ে থাকে না: আলাদা করা বানানোর চেয়ে সহজ।

একটি সতর্কতা। Transposed convolution-এ kernel-এর মাপ যদি stride দিয়ে বিভাজ্য না হয় (যেমন kernel ৩, stride ২), তাহলে output-এর কিছু pixel বেশি ছাপ পায়, কিছু কম। ফলে ছবিতে দাবার ছকের মতো দাগ (checkerboard artifact) আসতে পারে (Odena ও সহলেখক, Distill, ২০১৬)। Kernel ৪, stride ২ বাছার একটি কারণ এটি; আরেকটি পথ হলো আগে upsample, তারপর সাধারণ convolution.

২ · একই budget-এ তিনটি GAN — পরীক্ষার নকশা আর বিচারক

দুই ব্যাটারের তুলনায় একই পিচ আর একই সংখ্যক বল চাই; GAN-এর তুলনাও তাই। তিনটি model:

  • DCGAN: উপরের generator, BatchNorm-যুক্ত discriminator, BCE loss (generator-এর জন্য non-saturating রূপ, পর্ব ৭৭-এর মতো), Adam lr ০.০০০২, β = (০.৫, ০.৯৯৯)।
  • WGAN-GP: একই generator; critic একই আকারের, কিন্তু BatchNorm নেই (অংশ ৫-এ কারণ)। প্রতিটি generator ধাপের আগে critic-এর ৫টি ধাপ, λ = ১০, Adam lr ০.০০০১, β = (০, ০.৯), পেপারের সুপারিশ মেনে।
  • Conditional DCGAN: DCGAN-এর মতোই, শুধু generator আর discriminator দুজনেই শ্রেণির label পায় (অংশ ৬)।

Budget সবার এক: batch ৬৪, ৩,০০০টি generator update, seed ০, ১ আর ২। WGAN-GP-র critic এতে ১৫,০০০ বার শেখে; “একই budget” মানে একই সংখ্যক generator update, একই সময় নয়।

বিচারক. GAN likelihood দেয় না, তাই মান মাপা হয় বাইরের একটি classifier দিয়ে। পর্ব ৭৫ নমুনার মান যাচাইয়ে একটি Fashion-MNIST CNN শিখিয়েছিল (test accuracy ০.৮৯৬)। এখানে আলাদা একটি classifier শেখানো হয়েছে, কারণ দুটি জিনিস লাগবে: GAN-এর output-এর মতো −১ থেকে ১ পরিসরে শেখা model, আর তার শেষের আগের স্তরের ১২৮টি feature. দুটি conv স্তর, একটি ১২৮-এর লুকানো স্তর, ৪ epoch, CPU-তে। সত্যিকারের run:

$ python train_clf.py
epoch 1 test_acc 0.8696 time 26s
epoch 2 test_acc 0.8916 time 47s
epoch 3 test_acc 0.9007 time 62s
epoch 4 test_acc 0.9038 time 81s
FID train5000 vs test10000: 0.508
FID train5000+noise0.3 vs test: 59.228

এই classifier দিয়ে দুটি মাপ:

  1. Coverage: ৫,০০০টি নমুনার প্রতিটির শ্রেণি classifier দিয়ে বের করে histogram. আসল test সেটে প্রতিটি শ্রেণি ঠিক ১০%. Histogram-এর entropy (bit-এ) সর্বোচ্চ log2 ১০ ≈ ৩.৩২২, যখন সব শ্রেণি সমান। সবচেয়ে কম শ্রেণির ভাগও দেখা হয়: mode collapse হলে কোনো শ্রেণি প্রায় শূন্যে নামে।
  2. Classifier-feature FID: নমুনার ১২৮টি feature-এর গড় μ আর covariance Σ, আর test-এর ১০,০০০টি আসল ছবির একই হিসাব; দুটি Gaussian-এর Fréchet দূরত্ব: ‖μ১ − μ২‖² + Tr(Σ১ + Σ২ − ২(Σ১Σ২)১/২). কম মানে কাছাকাছি। আসল train ছবি বনাম test দিলে ০.৫০৮, আর সেই ছবিতে noise দিলে ৫৯.২২৮: স্কেলটি বোঝার জন্য এই দুটি খুঁটি।

এটি Inception FID নয়। গবেষণাপত্রের FID ImageNet-এ শেখা Inception network-এর ২,০৪৮টি feature ব্যবহার করে; তার সংখ্যার সঙ্গে এখানকার সংখ্যা তুলনা করা যাবে না। এখানকার মান শুধু এই classifier, এই ৫,০০০টি নমুনা আর এই test সেটের মধ্যে তুলনার জন্য। নমুনার সংখ্যা বদলালেও FID বদলায় (ছোট নমুনায় বেশি আসে)। FID-এর পুরো derivation আর সীমা পর্ব ৮১-এর (Generative model-এর মূল্যায়ন আর নিরাপত্তা) বিষয়।

৩ · JS divergence কেন আটকে যায় — Wasserstein দূরত্ব

পর্ব ৭৭-এ দেখা গেছে, optimal discriminator থাকলে মূল GAN-এর generator আসলে আসল বণ্টন P আর নিজের বণ্টন Q-এর মধ্যে JS divergence কমায়। সমস্যা হলো, ছবির মতো উঁচু মাত্রার ডেটায় P আর Q প্রায়ই খুব সরু, নিচু মাত্রার জায়গায় থাকে, আর শুরুতে দুটি প্রায় কখনো একে অপরকে ছোঁয় না। Arjovsky আর Bottou (২০১৭) যুক্তি দেন, এমন অবস্থায় একটি নিখুঁত discriminator প্রায় সবসময় পাওয়া যায়, আর তখন generator-এর gradient প্রায় শূন্য।

একটি উপমা। উঠানের এক কোণে এক স্তূপ বালি (P), আরেক কোণে একটি গর্ত (Q). JS divergence শুধু জিজ্ঞেস করে, “দুটি কি একই জায়গায়?” স্তূপ ১ মিটার দূরে হোক বা ১০০ মিটার, উত্তর একই: “না”। Wasserstein দূরত্ব জিজ্ঞেস করে, “সব বালি গর্তে নিতে কত খাটুনি?”, অর্থাৎ ভর × দূরত্ব; তাই এর আরেক নাম Earth mover's distance. স্তূপ কাছে আনলে খরচ কমে, তাই generator বোঝে কোন দিকে সরতে হবে।

সবচেয়ে ছোট উদাহরণ: P-এর সব ভর ০ বিন্দুতে, generator-এর Qθ-এর সব ভর θ বিন্দুতে; θ generator-এর একমাত্র parameter. WGAN পেপারের উদাহরণও এই ধারণার, দুটি সমান্তরাল রেখা দিয়ে।

দূরত্বের খাতা (ln, ভিত্তি e)
ধাপ
হিসাব
ফল
  1. শুরু. P-এর পুরো ভর ০ বিন্দুতে, Q-এর পুরো ভর θ = ২-তে। W = ভর × দূরত্ব = ১ × ২ = ২।
  2. JS, θ = ২. M = ½P + ½Q-এর দুই বিন্দুতে ভর ০.৫ করে। KL(P‖M) = ১ × ln(১ ÷ ০.৫) = ln ২, KL(Q‖M)-ও তা-ই। JS = ln ২ ≈ ০.৬৯৩, JS-এর সর্বোচ্চ মান।
  3. θ = ১. W ২ থেকে ১-এ নামল। কিন্তু দুই বিন্দু এখনো আলাদা, তাই হিসাব হুবহু আগের মতো: JS আবার ০.৬৯৩।
  4. θ = ০.১. W = ০.১, অথচ JS তবুও ০.৬৯৩; এর কাছে “অনেক দূরে” আর “প্রায় মিলে গেছে” একই কথা।
  5. θ = ০. P = Q, তাই JS = ০ আর W = ০। JS হঠাৎ লাফ দিল; W মসৃণভাবে ০-তে নামল।
  6. Gradient. θ ≠ ০ হলে JS একটি ধ্রুবক, তাই ∂JS/∂θ = ০: generator জানে না কোন দিকে সরবে। W = |θ|, তাই θ > ০ হলে ∂W/∂θ = ১, আর gradient descent θ-কে ০-এর দিকে ঠেলে দেয়।
  7. KL. KL(P‖Qθ)-এ ০ বিন্দুতে Q-এর ভর ০, তাই ln(১/০) = অসীম। দুটি বণ্টন একে অপরকে না ছুঁলে KL, JS কোনোটিই কাজের দূরত্ব নয়; Wasserstein-ই এখানে অর্থবহ।

এবার বিন্দুর বদলে চওড়া দুটি বণ্টন নিন, যাতে আংশিক মিল থাকে। P হলো ০-কে কেন্দ্র করে ১ চওড়া সমান উচ্চতার বণ্টন, Q একই আকারের কিন্তু θ সরানো। নিচের কোড SciPy দিয়ে দুটি দূরত্ব মাপে।

import numpy as np
from scipy.stats import wasserstein_distance
from scipy.spatial.distance import jensenshannon
import matplotlib.pyplot as plt

x = np.linspace(-1, 5, 601)                 # একটি ছোট grid
def bump(c):                                # c-কে কেন্দ্র করে চওড়া 1-এর সমান উচ্চতার বণ্টন
    p = ((x >= c - 0.5) & (x < c + 0.5)).astype(float)
    return p / p.sum()

P = bump(0.0)                               # আসল ডেটা
thetas = np.round(np.arange(0, 3.01, 0.25), 2)
js, w = [], []
for t in thetas:
    Q = bump(t)                             # generator-এর বণ্টন, θ সরে যায়
    js.append(jensenshannon(P, Q) ** 2)     # scipy দেয় JS-এর বর্গমূল, তাই বর্গ
    w.append(wasserstein_distance(x, x, P, Q))
for t, a, b in zip(thetas, js, w):
    if t in (0, 0.5, 0.75, 1, 1.5, 3):
        print(f"theta={t:4.2f}  JS={a:.4f}  W={b:.3f}")
print("log 2 =", round(np.log(2), 4))

fig, ax = plt.subplots(figsize=(5, 3))
ax.plot(thetas, js, "o-", label="JS divergence")
ax.plot(thetas, w, "s-", label="Wasserstein-1")
ax.axhline(np.log(2), ls="--", c="gray", lw=1)
ax.set_xlabel("theta (shift of generator distribution)")
ax.legend(); plt.show()

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

theta=0.00  JS=0.0000  W=0.000
theta=0.50  JS=0.3466  W=0.500
theta=0.75  JS=0.5199  W=0.750
theta=1.00  JS=0.6931  W=1.000
theta=1.50  JS=0.6931  W=1.500
theta=3.00  JS=0.6931  W=3.000
log 2 = 0.6931

কী দেখা গেল: যতক্ষণ দুই বণ্টন আংশিক মেলে (θ < ১), JS বাড়ে: θ = ০.৫-এ ০.৩৪৬৬, ০.৭৫-এ ০.৫১৯৯। θ = ১-এ দুটি আর ছোঁয় না, আর তখন থেকে JS ঠিক ln ২ = ০.৬৯৩১-এ আটকে থাকে; θ = ১.৫ বা ৩, কোনো পার্থক্য নেই। Wasserstein দূরত্ব প্রতিটি ক্ষেত্রে ঠিক θ, অর্থাৎ সরানোর দূরত্বের সমান। লেখচিত্রে JS-এর রেখা ১-এর পরে সমতল (gradient শূন্য), আর W-এর রেখা সোজা উঠছে (gradient সবসময় ১)।

W গণনা করা যায় কীভাবে? সংজ্ঞা অনুযায়ী W(P, Q) হলো P-এর ভর Q-তে নেওয়ার সব পরিকল্পনার মধ্যে সবচেয়ে কম খরচ, যা উঁচু মাত্রায় সরাসরি হিসাব করা অসম্ভব রকম কঠিন। Kantorovich–Rubinstein duality নামের একটি ফল একে অন্যভাবে লেখে:

W(P, Q) = max‖f‖L ≤ ১ [ Ex∼P f(x) − Ex∼Q f(x) ]

এখানে f যেকোনো ফাংশন যার ঢাল কোথাও ১-এর বেশি নয় (1-Lipschitz: |f(a) − f(b)| ≤ |a − b|), আর E মানে গড়। অর্থাৎ এমন f খুঁজুন যা আসল নমুনায় বড় আর নকলে ছোট মান দেয়, কিন্তু খুব খাড়া নয়। এই f-কে একটি neural network দিয়ে শেখানো হয়: সেটিই WGAN-এর critic.

৪ · WGAN — critic, Lipschitz শর্ত আর weight clipping

Arjovsky, Chintala আর Bottou ২০১৭ সালে WGAN প্রস্তাব করেন। পরিবর্তন তিনটি:

  • Discriminator থেকে critic: শেষে sigmoid নেই, output যেকোনো বাস্তব সংখ্যা। এটি “আসল হওয়ার সম্ভাবনা” নয়, একটি score: আসল ছবিতে বেশি, নকলে কম। পাস-ফেল নয়, পরীক্ষার খাতার নম্বরের মতো।
  • Loss: critic সর্বোচ্চ করে D(আসল)-এর গড় − D(নকল)-এর গড়, যা W-এর একটি অনুমান। Generator কমায় −D(G(z))-এর গড়। কোনো log নেই।
  • Lipschitz শর্ত রাখতে weight clipping: প্রতিটি critic ধাপের পরে সব ওজন [−c, c]-এর মধ্যে কেটে দেওয়া; পেপারে c = ০.০১, RMSProp, lr ০.০০০০৫, আর প্রতিটি generator ধাপে critic-এর ৫টি ধাপ। ওজন ছোট থাকলে ফাংশনের ঢালও সীমিত থাকে।

পেপারের দুটি দাবি: critic-কে পুরোপুরি শেখালেও generator-এর gradient মরে না, আর critic-এর loss নমুনার মানের সঙ্গে মোটামুটি নামে। তবে লেখকেরা নিজেরাই বলেছেন, weight clipping “একটি স্পষ্টভাবে খারাপ উপায়”: c বড় হলে শর্ত রাখতে সময় লাগে, ছোট হলে gradient মিলিয়ে যায়, আর ওজন প্রায়ই ঠিক −c আর +c-তে জমে critic খুব সরল ফাংশন শেখে। Desktop-এ মূল WGAN (clipping, c = ০.০১, RMSProp)-ও একই generator আর ৩,০০০টি generator update-এ চালানো হয়েছে, seed ০, GPU-তে ৬০.৩ সেকেন্ড। সত্যিকারের run: FID ২৪১.৪ → ১৯৩.১ → ১৪০.৫ (১k, ২k, ৩k ধাপ), coverage entropy ৩.০৭৫; W-এর অনুমান ২৫০তম ধাপে ০.৫৩, তারপর ০.০৬৫-০.০৭৪-এর মধ্যে প্রায় স্থির। Clipping-এ critic-এর score এত ছোট পরিসরে আটকে থাকে যে অনুমানের মাপও ছোট। FID নামছে, কিন্তু অংশ ৭-এর DCGAN-এর (৩১.৯–৪১.২) চেয়ে অনেক পিছিয়ে।

৫ · WGAN-GP — gradient penalty হাতে আর কোডে

Gulrajani ও সহলেখকেরা (২০১৭, “Improved Training of Wasserstein GANs”) clipping-এর বদলে একটি নরম শর্ত দেন। একটি তথ্য কাজে লাগে: সেরা critic-এর ঢাল, আসল আর নকল নমুনার মাঝের সরলরেখাগুলোতে, প্রায় সব জায়গায় ঠিক ১। তাই ওজন না কেটে, ওই রেখার বিন্দুতে gradient-এর মাপ ১ থেকে সরে গেলে শাস্তি:

LD = E[D(x̃)] − E[D(x)] + λ · E[(‖∇x̂D(x̂)‖২ − ১)²]

x আসল, x̃ নকল, x̂ = εx + (১ − ε)x̃ যেখানে ε প্রতিটি জোড়ায় ০ থেকে ১-এর মধ্যে এলোমেলো, ‖·‖২ হলো vector-এর দৈর্ঘ্য, আর λ শাস্তির ওজন (পেপারে ১০)। শেষ অংশটিই gradient penalty. ছোট সংখ্যায় একবার পুরো হিসাব করুন। ধরুন critic একটি সরল ফাংশন D(x) = ০.৫x², তাই D′(x) = x; ডেটা এক মাত্রার, batch-এ দুটি জোড়া।

Critic-এর খাতা
ধাপ
হিসাব
ফল
  1. ডেটা. দুটি আসল নমুনা, দুটি নকল, আর প্রতিটি জোড়ায় একটি এলোমেলো ε; λ = ১০।
  2. Score. D(x) = ০.৫x²: আসলে ০.৫ × ৪ = ২.০ আর ০.৫ × ১ = ০.৫; নকলে দুটিই ০.৫ × ০.২৫ = ০.১২৫। গড়: ১.২৫ আর ০.১২৫।
  3. W-এর অনুমান. ১.২৫ − ০.১২৫ = ১.১২৫। Critic এটি বাড়াতে চায়, তাই loss-এ যায় ঋণাত্মক রূপে।
  4. মাঝের বিন্দু. x̂ = εx + (১ − ε)x̃: প্রথম জোড়ায় ১.২ + ০.২ = ১.৪, দ্বিতীয়তে ০.২ − ০.৪ = −০.২। দুটিই আসল আর নকলের মাঝের রেখায়।
  5. Gradient. D′(x̂) = x̂, তাই মাপ ১.৪ (খুব খাড়া) আর ০.২ (প্রায় সমতল); দুটিই ১ থেকে দূরে।
  6. Penalty. (১.৪ − ১)² = ০.১৬, (০.২ − ১)² = ০.৬৪; গড় ০.৪০। λ দিয়ে গুণ করে ৪.০। খেয়াল করুন, ঢাল কম হলেও শাস্তি আছে: এটি দুই দিকের শাস্তি, শুধু “১-এর বেশি নয়” নয়।
  7. মোট. LD = ০.১২৫ − ১.২৫ + ৪.০ = ২.৮৭৫। এখানে শাস্তিই বড় অংশ, তাই পরের update-এ critic আগে নিজের ঢাল ঠিক করবে। Generator-এর loss = −গড় D(x̃) = −০.১২৫।

বাস্তব critic একটি neural network, আর x একটি ছবি, তাই gradient হাতে নয়, autograd দিয়ে বের হয়। PyTorch-এ মূল অংশ (desktop-এ চালানো কোড থেকে সংক্ষিপ্ত):

eps = torch.rand(B, 1, 1, 1, device=dev)               # প্রতিটি জোড়ায় একটি ε
xh = (eps * xr + (1 - eps) * xf).requires_grad_(True)
grad = torch.autograd.grad(D(xh).sum(), xh, create_graph=True)[0]
gp = ((grad.flatten(1).norm(dim=1) - 1) ** 2).mean()
lossD = D(xf).mean() - D(xr).mean() + 10.0 * gp

create_graph=True জরুরি: penalty নিজেই একটি gradient-এর ফাংশন, তাই critic-এর ওজন বদলাতে gradient-এর gradient লাগে (double backprop), আর প্রতিটি critic ধাপ ভারী হয়। Critic-এ BatchNorm নয়, কারণ BatchNorm একটি নমুনার output-কে batch-এর অন্যদের ওপর নির্ভরশীল করে, অথচ penalty প্রতিটি নমুনার নিজের gradient-এর ওপর; দরকারে পেপার LayerNorm সুপারিশ করে। নিচে একটি ছোট NumPy critic-এ penalty, gradient হাতে বের করে finite difference দিয়ে যাচাইসহ।

import numpy as np
rng = np.random.default_rng(42)
W = rng.normal(size=(3, 2)); b = rng.normal(size=3); v = rng.normal(size=3)

def critic(x, s=1.0):                      # ছোট critic: D(x) = s · v·tanh(Wx + b)
    return s * np.tanh(x @ W.T + b) @ v

def grad_x(x, s=1.0):                      # হাতে বের করা gradient: Wᵀ (v ⊙ (1 − tanh²))
    h = np.tanh(x @ W.T + b)
    return s * ((1 - h**2) * v) @ W

real = rng.normal([2, 2], 0.3, size=(4, 2))    # আসল ডেটার ৪টি বিন্দু
fake = rng.normal([0, 0], 0.3, size=(4, 2))    # generator-এর ৪টি বিন্দু
eps = rng.uniform(size=(4, 1))
xh = eps * real + (1 - eps) * fake             # দুইয়ের মাঝের সরলরেখায় বিন্দু

g = grad_x(xh)
h = 1e-5                                       # finite difference দিয়ে যাচাই
fd = np.array([[(critic(p + h*e) - critic(p - h*e)) / (2*h) for e in np.eye(2)] for p in xh])
print("max |analytic - numeric| =", f"{np.abs(g - fd).max():.1e}")

lam = 10
for s in (1.0, 4.0):
    norms = np.linalg.norm(grad_x(xh, s), axis=1)
    gp = ((norms - 1) ** 2).mean()
    w_est = critic(real, s).mean() - critic(fake, s).mean()
    loss = -w_est + lam * gp
    print(f"scale {s}: |grad| = {np.round(norms, 2)}")
    print(f"   W_est={w_est:.3f}  GP={gp:.3f}  critic loss={loss:.3f}")

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

max |analytic - numeric| = 6.3e-11
scale 1.0: |grad| = [1.22 0.7  0.63 0.28]
   W_est=1.304  GP=0.198  critic loss=0.674
scale 4.0: |grad| = [4.9  2.78 2.52 1.14]
   W_est=5.216  GP=5.178  critic loss=46.565

কী দেখা গেল: হাতের gradient আর finite difference-এর পার্থক্য ১০−১০-এর ঘরে, তাই সূত্র ঠিক। এবার মূল শিক্ষা। Critic যদি “চালাকি” করে নিজের output ৪ গুণ করে, W-এর অনুমান ১.৩০৪ থেকে ৫.২১৬ হয়, অর্থাৎ শুধু গুণ করেই বড় দেখায়। কিন্তু gradient-এর মাপও ৪ গুণ, penalty ০.১৯৮ থেকে ৫.১৭৮, আর মোট loss ০.৬৭৪ থেকে ৪৬.৫৬৫-এ লাফায়। Penalty ছাড়া critic শুধু নিজেকে বড় করে W-কে অসীমে নিতে পারত; penalty তাকে ঢাল প্রায় ১-এ রাখতে বাধ্য করে, তাই W-এর অনুমান অর্থবহ থাকে।

Lipschitz শর্ত রাখার তৃতীয় জনপ্রিয় উপায় spectral normalization (Miyato ও সহলেখক, ২০১৮): প্রতিটি স্তরের ওজন-matrix-কে তার সবচেয়ে বড় singular value দিয়ে ভাগ করা, যাতে প্রতিটি স্তরের ঢাল ১-এর বেশি না হয়। খরচ কম (power iteration-এর একটি ধাপ), double backprop লাগে না, আর সাধারণ GAN loss-এও কাজ করে; PyTorch-এ torch.nn.utils.parametrizations.spectral_norm. Singular value মেশিন লার্নিং কোর্সের পর্ব ৯-এ। এটি এখানে চালানো হয়নি।

৬ · Conditional GAN — label দুই দিকেই

সাধারণ GAN-কে “একটি জুতা বানাও” বলা যায় না; z থেকে যা আসে তা-ই। বাজারঘরের ক্যাটালগের জন্য চাই নির্দিষ্ট শ্রেণির ছবি। Conditional GAN (Mirza আর Osindero, ২০১৪) এর সহজ সমাধান: শর্ত y (এখানে শ্রেণির label) দুই network-কেই দিন। Generator শেখে G(z, y), আর discriminator বিচার করে D(x, y): “এই ছবি কি আসল, আর এটি কি সত্যিই y শ্রেণির?” দর্জিকে শুধু কাপড় দিলে তিনি যা খুশি বানাবেন; সঙ্গে চিরকুট দিলে নির্দিষ্ট জিনিস, আর মালিক যাচাই করবেন জামাটি চিরকুটের সঙ্গে মেলে কি না।

এখানে উপায়টি সরল: generator-এর input-এ z-এর ১০০টি সংখ্যার পাশে one-hot label-এর ১০টি (মোট ১১০টি channel); discriminator-এ ছবির পাশে ১০টি পূর্ণ ২৮ × ২৮ channel, যার একটিতে সব ১ আর বাকিগুলোতে ০। এতে parameter বাড়ে সামান্য: generator ১৭,৮১,৫০৫ থেকে ১৯,০৬,৯৪৫ (প্রথম স্তরে ১০ × ২৫৬ × ৪৯ = ১,২৫,৪৪০ বেশি), discriminator ১,৩৮,৬৮৯ থেকে ১,৪৮,৯২৯।

Label শুধু generator-কে দিলে discriminator দেখত শুধু ছবিটি বাস্তবসম্মত কি না; জুতার label পেয়ে ব্যাগ আঁকলেও কেউ ধরত না। পরে আরও উন্নত উপায় এসেছে: discriminator-এ বাড়তি classifier মাথা (AC-GAN, Odena ও সহলেখক, ২০১৭) বা label embedding আর feature-এর গুণফল (projection discriminator, Miyato আর Koyama, ২০১৮)।

Conditional model-এর বাড়তি মাপ label accuracy: প্রতিটি শ্রেণির সমান নমুনা চেয়ে দেখা, বিচারক কতগুলোকে চাওয়া শ্রেণিতেই ফেলে। বিচারক নিজেই আসল test-এ ৯০.৪% ঠিক, তাই ১০০% আশা করা যায় না।

৭ · ফলাফল — তিনটি GAN পাশাপাশি, তিনটি seed-এ

সব run GPU-তে, প্রতিটির সর্বোচ্চ GPU মেমরি প্রায় ৩৯০ MB (torch.cuda.max_memory_allocated). প্রতিটি model-এর শেষে ৫,০০০টি নমুনা (seed ১২৩৪-এর z, conditional model-এ প্রতিটি শ্রেণির ৫০০টি করে), আর অংশ ২-এর বিচারক দিয়ে মাপ। সত্যিকারের run, JSON log থেকে সাজানো:

ModelSeedFID (১k → ২k → ৩k ধাপ)Coverage entropy (bit)সবচেয়ে কম শ্রেণিLabel accuracyসময় (s)
DCGAN০৭৯.৪ → ৪০.৫ → ৩১.৯৩.২২২৫.২%—২৩.১
DCGAN১২০০.৭ → ৬১.৫ → ৪১.২৩.২১২৪.৭%—২৩.১
DCGAN২৭৮.৪ → ৪৬.৯ → ৩৬.৬৩.২১৭৪.২%—২২.৫
WGAN-GP০২৯১.৫ → ২৫৭.১ → ২৫১.৪১.৫৩০০.১%—৯৪.২
WGAN-GP১১৪৪.২ → ১১০.৮ → ৯৬.৮৩.০৮৬৩.৮%—৮৭.০
WGAN-GP২৮০.৯ → ৪৪.৭ → ৩৪.২৩.২৫৬৬.১%—৮৬.৪
Conditional DCGAN০৫২.৪ → ১৮.৮ → ১৩.১৩.৩০৭৭.৫%০.৮৬১৩৭.৪
Conditional DCGAN১৬৪.১ → ২১.৭ → ১৫.০৩.২৭৭৬.৮%০.৮৪০৩৬.৭
Conditional DCGAN২৫৭.৮ → ১৬.২ → ১০.৮৩.২৮০৬.৭%০.৮৪৫৩৬.৬

DCGAN: তিন seed-এ FID ৩১.৯ থেকে ৪১.২, গড় ৩৬.৬, এখনো নামছে। Coverage entropy ৩.২১-৩.২২, সর্বোচ্চ ৩.৩২২-এর কাছে: কোনো শ্রেণি হারায়নি। WGAN-GP: একই কোডে seed ২-এ ৩৪.২ (DCGAN-এর সেরার কাছাকাছি), seed ১-এ ৯৬.৮, আর seed ০-এ ২৫১.৪, যেখানে বিচারক ৬৯% নমুনাকে Bag বলেছে। পরিসর সাত গুণের বেশি; গড় (১২৭.৫) দেখে কোনো সিদ্ধান্ত টানা যায় না। সময় DCGAN-এর প্রায় ৩.৯ গুণ, কারণ প্রতিটি generator ধাপে ৫টি critic ধাপ আর double backprop. Conditional DCGAN: FID ১০.৮ থেকে ১৫.০, entropy সবচেয়ে বেশি, label accuracy ০.৮৪-০.৮৬; তবে প্রতিটি শ্রেণির জন্য সমান নমুনা চাওয়া হয়েছে বলে শ্রেণির ভারসাম্য বাইরে থেকে দেওয়া, তাই এই তুলনা পুরোপুরি সমান নয় (ভাবনার প্রশ্ন ৪)। WGAN-GP seed ০ ছাড়া অন্য run-এ Pullover প্রায় ১৬-১৮%; এর একটি অংশ বিচারকের নিজের ভুল (Pullover, Coat আর Shirt সে আসল ছবিতেও গুলিয়ে ফেলে)। একটি ডেটাসেট আর তিনটি seed-এর ফল, সাধারণ নিয়ম নয়।

WGAN-GP seed ০ কি শুধু ধীর, না আটকে গেছে? একই seed-এ budget বাড়িয়ে আবার চালানো হলো। ৩,০০০ নয়, ১০,০০০টি generator update, GPU-তে ২৮৬.৩ সেকেন্ড। সত্যিকারের run, প্রতি ২,০০০ ধাপে FID: ২৬৮.৩ → ২৫৩.০ → ৭৮.৬ → ৩৯.৫ → ২৭.৪, শেষে coverage entropy ৩.২৫৫. প্রায় ৪,০০০ ধাপ পর্যন্ত আটকে থেকে ৪,০০০ আর ৬,০০০-এর মধ্যে হঠাৎ বেরিয়ে এসেছে (ঠিক কখন, তা এই log-এর ফাঁকে ধরা পড়েনি)। অর্থাৎ seed ০ ভাঙা ছিল না, ধীর ছিল। ন্যায্যতার জন্য উল্টো পরীক্ষাও: DCGAN-কে WGAN-GP-র ৩,০০০ ধাপের প্রায় সমান সময় দেওয়া হলো, ১১,৫০০টি generator update, ৮৪.৬ সেকেন্ড, seed ০: FID ৪১.৫ → ২৪.৭ → ২০.১ → ১৫.৪ → ১৪.৩. একই সময়ে এই ছোট সমস্যায় DCGAN অনেক এগিয়ে; WGAN-GP-র সুবিধা এখানে গতি নয়। দুটিই একটি seed-এর ফল।

সংখ্যার পাশাপাশি নমুনা নিজের চোখে দেখা জরুরি। নিচের কোড desktop run-এর আসল নমুনা আঁকে: DCGAN seed ০; WGAN-GP seed ০ (৩,০০০ আর ১০,০০০ ধাপে, লেবেল GP s0 3k আর 10k); WGAN-GP seed ২; আর conditional DCGAN seed ০-এর এক সারি, প্রতিটি কলামে একটি শ্রেণি (T-shirt, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot). প্রতিটি সারি মূল্যায়নের একই z-এর প্রথম ১০টি নমুনা। পাতা ছোট রাখতে প্রতিটি pixel ৪ bit-এ (১৬টি ধূসর ধাপ), প্রতি byte-এ দুটি, base64-এ লেখা। শেষে run-এর class histogram থেকে coverage-এর সারাংশ।

import base64
import numpy as np
import matplotlib.pyplot as plt

pics = {}
for line in open("gan_pics.txt").read().split("\n"):
    tag, b64 = line.split()
    raw = np.frombuffer(base64.b64decode(b64), dtype=np.uint8).reshape(-1, 392)
    px = np.stack([raw >> 4, raw & 15], axis=2).reshape(-1, 28, 28)   # 2টি pixel প্রতি byte-এ
    pics[tag] = px / 15.0

rows = [("DCGAN s0", pics["dcgan_s0"]), ("GP s0 3k", pics["wgangp_s0"]),
        ("GP s0 10k", pics["wgangp_long_s0"]), ("GP s2 3k", pics["wgangp_s2"]),
        ("cDCGAN", pics["cdcgan_s0"])]
fig, ax = plt.subplots(5, 10, figsize=(7.5, 4.4))
for r, (name, imgs) in enumerate(rows):
    for c in range(10):
        ax[r, c].imshow(imgs[c], cmap="gray", vmin=0, vmax=1)
        ax[r, c].set_xticks([]); ax[r, c].set_yticks([])
    ax[r, 0].set_ylabel(name, fontsize=7)
fig.subplots_adjust(wspace=0.05, hspace=0.08)
plt.show()

names = ["T-shirt", "Trouser", "Pullover", "Dress", "Coat", "Sandal", "Shirt", "Sneaker", "Bag", "Boot"]
hist = {"DCGAN s0":   [471, 606, 881, 385, 260, 265, 337, 568, 679, 548],   # desktop run, 5000 নমুনা
        "WGAN-GP s0": [33, 720, 93, 74, 21, 11, 448, 6, 3461, 133],
        "WGAN-GP s2": [476, 600, 808, 367, 312, 303, 397, 591, 623, 523]}
for k, h in hist.items():
    p = np.array(h) / sum(h)
    ent = -(p * np.log2(p)).sum()
    print(f"{k:11s} entropy {ent:.3f} bits (max 3.322)  top: {names[p.argmax()]} {p.max():.1%}"
          f"  rarest: {names[p.argmin()]} {p.min():.1%}")

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

DCGAN s0    entropy 3.222 bits (max 3.322)  top: Pullover 17.6%  rarest: Coat 5.2%
WGAN-GP s0  entropy 1.530 bits (max 3.322)  top: Bag 69.2%  rarest: Sneaker 0.1%
WGAN-GP s2  entropy 3.256 bits (max 3.322)  top: Pullover 16.2%  rarest: Sandal 6.1%

কী দেখা গেল: DCGAN আর WGAN-GP seed ২-এর সারিতে জামা, প্যান্ট, জুতা চেনা যায়, যদিও খুঁটিনাটি ঝাপসা। WGAN-GP seed ০-এর সারি আলাদা: জামা আর প্যান্টের আকার আছে, কিন্তু পুরো ছবিতে একটি নিয়মিত সাদা বিন্দুর জাল, যা আসল ছবিতে নেই। সম্ভবত transposed convolution-এর upsampling থেকে আসা নিয়মিত নকশা, যা critic এই run-এ যথেষ্ট শাস্তি দেয়নি; কারণটি নিশ্চিত করা হয়নি। বড় শিক্ষা: বিচারক এগুলোর ৬৯%-কে Bag বলেছে, অথচ চোখে বৈচিত্র্য আছে। অর্থাৎ coverage-এর মাপ এখানে mode collapse নয়, বিচারককে বিভ্রান্ত করা একটি চিহ্ন ধরছে; classifier-ভিত্তিক মাপ তখনই অর্থবহ যখন নমুনা আসলের কাছাকাছি, তাই সংখ্যা আর চোখ দুটিই লাগে। একই seed ১০,০০০ ধাপে (GP s0 10k) জাল নেই, জামা আর জুতা চেনা যায়। Conditional সারির প্রতিটি কলাম চাওয়া শ্রেণির সঙ্গে মোটামুটি মেলে।

৮ · Progressive growing আর StyleGAN — style আর truncation

বড় ছবিতে GAN শুরু থেকেই খুব অস্থির। Karras ও সহলেখকেরা (NVIDIA, ২০১৭; ICLR ২০১৮) progressive growing প্রস্তাব করেন: প্রথমে ৪ × ৪ ছবিতে শেখানো, তারপর নতুন স্তর মিশিয়ে ধীরে ধীরে ৮ × ৮, ১৬ × ১৬ … করে বড় করা, যেমন আঁকার শিক্ষক আগে মোটা দাগ, পরে খুঁটিনাটি শেখান।

একই দলের StyleGAN (২০১৮; CVPR ২০১৯) generator-এর গঠন বদলায়। পেপার অনুযায়ী মূল ধারণাগুলো:

  • Mapping network: z (৫১২ মাত্রা) সরাসরি ছবিতে যায় না; ৮টি fully connected স্তরের একটি network একে আরেকটি ৫১২ মাত্রার vector w-তে নেয়। ধারণা: শেখা w-জায়গায় বৈশিষ্ট্যগুলো z-এর চেয়ে বেশি আলাদা আলাদা দিকে থাকে।
  • Style modulation: generator-এর শুরু একটি শেখা ধ্রুবক tensor থেকে; প্রতিটি মাপের স্তরে w থেকে বানানো একটি “style” feature-গুলোর scale আর bias বদলায় (প্রথম সংস্করণে AdaIN, StyleGAN2-এ ওজনের modulation/demodulation). মোটা স্তরের style আকারে, সূক্ষ্ম স্তরের style রং আর টেক্সচারে প্রভাব ফেলে; সঙ্গে প্রতিটি স্তরে এলোমেলো খুঁটিনাটির জন্য noise.
  • Style mixing: দুটি আলাদা w-এর একটি মোটা স্তরে, আরেকটি সূক্ষ্ম স্তরে দিলে একটির আকার আর অন্যটির রং-টেক্সচার মেশানো যায়।
  • Truncation trick: w-কে গড় w̄-এর দিকে টেনে আনা: w′ = w̄ + ψ(w − w̄), যেখানে ψ ০ থেকে ১। ψ কম হলে নমুনা বেশি “গড়পড়তা” আর সাধারণত পরিষ্কার, কিন্তু বৈচিত্র্য কম; ψ = ০ দিলে সব নমুনা এক। BigGAN (Brock ও সহলেখক, ২০১৮) z-এর বড় মানগুলো আবার নমুনা নিয়ে একই রকম আপস করে।

StyleGAN-এর বিখ্যাত ফল মানুষের মুখ (FFHQ ডেটাসেট)। এই পর্বে মুখ বানানো হবে না: বানানো মুখ প্রতারণা আর deepfake-এ ব্যবহৃত হতে পারে (প্রতিরোধ পর্ব ৮১-এ)। নিচের কোডে দুই মাত্রার z একটি ছোট খেলনা mapping দিয়ে w-তে যায়, তারপর তিনটি ψ-তে truncation.

import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)

z512 = rng.normal(size=(2000, 512))           # StyleGAN-এর মাপের latent
r = np.linalg.norm(z512, axis=1)
print(f"512-d: |z| mean {r.mean():.1f}, sqrt(512) = {np.sqrt(512):.1f}, min {r.min():.1f}")

def mapping(z):                               # খেলনা mapping network: z -> w
    return np.c_[z[:, 0] + 0.5 * np.sin(2 * z[:, 1]), 0.6 * z[:, 1] + 0.3 * z[:, 0]**2]

z = rng.normal(size=(1500, 2))
w = mapping(z)
w_bar = mapping(rng.normal(size=(100000, 2))).mean(0)   # অনেক নমুনার গড় w
print("w_bar =", np.round(w_bar, 3))

fig, axes = plt.subplots(1, 3, figsize=(7.5, 2.7), sharex=True, sharey=True)
for ax, psi in zip(axes, (1.0, 0.7, 0.3)):
    wt = w_bar + psi * (w - w_bar)            # truncation trick
    spread = np.linalg.norm(wt - w_bar, axis=1).mean()
    far = (np.linalg.norm(wt - w_bar, axis=1) > 2).mean()
    print(f"psi={psi}: mean distance to w_bar {spread:.3f}, share > 2 away {far:.3f}")
    ax.scatter(wt[:, 0], wt[:, 1], s=3, alpha=0.4)
    ax.plot(*w_bar, "r*", ms=10)
    ax.set_title(f"psi = {psi}")
fig.subplots_adjust(wspace=0.08)
plt.show()

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

512-d: |z| mean 22.6, sqrt(512) = 22.6, min 20.5
w_bar = [0.003 0.302]
psi=1.0: mean distance to w_bar 1.091, share > 2 away 0.083
psi=0.7: mean distance to w_bar 0.763, share > 2 away 0.019
psi=0.3: mean distance to w_bar 0.327, share > 2 away 0.000

কী দেখা গেল: ৫১২ মাত্রার Gaussian-এর নমুনা প্রায় সবই কেন্দ্র থেকে √৫১২ ≈ ২২.৬ দূরত্বের একটি পাতলা খোলসে থাকে (সবচেয়ে কাছেরটিও ২০-এর বেশি দূরে); “গড়ের কাছের” নমুনা আসলে বিরল, আর truncation সেখানেই টেনে আনে। খেলনা w-এর জায়গায় ψ = ১-এ গড় দূরত্ব ১.০৯১ আর ৮.৩% নমুনা ২-এর বেশি দূরে; ψ = ০.৭-এ ০.৭৬৩ আর ১.৯%; ψ = ০.৩-এ ০.৩২৭ আর ০%. দূরত্ব ঠিক ψ গুণ কমে (০.৭ × ১.০৯১ ≈ ০.৭৬৩), কারণ truncation একটি সরল সংকোচন। ছবিতে বিন্দুর মেঘ লাল তারার দিকে গুটিয়ে যায়: বিরল, প্রান্তের নমুনা আগে হারায়।

Desktop-এ আসল DCGAN-এ truncation. DCGAN-এর (seed ০) z-কে ψ দিয়ে গুণ করে (z-এর গড় ০, তাই z′ = ψz) ৫,০০০টি নমুনা, CPU-তে। সত্যিকারের run:

$ python eval_trunc.py dcgan_s0
psi   FID    min_class  max_class  entropy_bits  mean_pair_dist  mean_max_prob
1.0    31.23  0.0488     0.1722     3.223         22.62           0.754
0.8    41.03  0.0404     0.1812     3.201         20.87           0.745
0.6    62.22  0.0274     0.1864     3.174         18.57           0.722
0.4   114.59  0.0140     0.1468     3.174         15.57           0.659
0.2   276.71  0.0024     0.3130     2.820         10.43           0.491
0.0   670.32  0.0000     1.0000     -0.000         0.00           0.229

কী দেখা গেল: এখানে truncation সাহায্য করেনি। ψ ১ থেকে ০.৪-এ FID ৩১.২৩ থেকে ১১৪.৫৯-এ বেড়েছে, নমুনার জোড়ার গড় দূরত্ব (বৈচিত্র্য) ২২.৬২ থেকে ১৫.৫৭-এ নেমেছে, আর বিচারকের গড় আত্মবিশ্বাস (mean_max_prob) ০.৭৫৪ থেকে ০.৬৫৯-এ নেমেছে, অর্থাৎ নমুনা আরও স্পষ্টও হয়নি। ψ = ০-এ সব ৫,০০০টি নমুনা একটি ছবি, একটি শ্রেণিতে ১০০%. সম্ভাব্য কারণ: ছোট DCGAN-এর z-জায়গার কেন্দ্র কোনো “গড় জামা” নয়, কয়েকটি শ্রেণির ঝাপসা মিশ্রণ; StyleGAN-এ truncation হয় শেখা w-জায়গায়, যেখানে w̄ একটি অর্থবহ বিন্দু। একটি ছোট model আর একটি seed-এর ফল, তবু শিক্ষা পরিষ্কার: কৌশল কোন জায়গায় প্রয়োগ হচ্ছে, সেটিই আসল।

৯ · Image-to-image GAN আর কোনটি কখন

GAN-এর শর্ত শুধু label হতে হবে, এমন নয়; শর্ত একটি পুরো ছবিও হতে পারে। pix2pix (Isola ও সহলেখক, ২০১৭) জোড়া ছবি থেকে শেখে, যেমন রেখাচিত্র → রঙিন ছবি বা মানচিত্র → উপগ্রহ-ছবি: generator একটি U-Net (পর্ব ২৫ · Detection আর segmentation), discriminator ছবির ছোট ছোট টুকরো বিচার করে (PatchGAN), আর adversarial loss-এর সঙ্গে আসল জোড়ার L1 দূরত্ব যোগ হয়। CycleGAN (Zhu ও সহলেখক, ২০১৭) জোড়া ছাড়াই শেখে, যেমন ঘোড়া ↔ জেব্রা: দুটি generator, আর একটি cycle-consistency শর্ত, অর্থাৎ A → B → A করলে আসল ছবি ফিরে আসা চাই। এগুলো এই পর্বে চালানো হয়নি; image-to-image কাজ পরে computer vision-এর আলাদা কোর্সে আরও গভীরে আসবে।

রূপমূল পরিবর্তনকখন ভাবা যায়দাম বা সীমা
DCGANConv, stride, BatchNorm, LeakyReLUছোট ছবির প্রথম baselineSeed আর lr-এ সংবেদনশীল; mode collapse সম্ভব
WGAN (clipping)Critic, W-এর অনুমান, ওজন কাটাইতিহাস আর ধারণা বোঝাধীর; clipping critic-কে দুর্বল করে
WGAN-GPGradient penalty, critic-এ BatchNorm নেইযখন loss দিয়ে training দেখা দরকারCritic-এর ৫ ধাপ আর double backprop: ধীর
Spectral normপ্রতিটি স্তরের সবচেয়ে বড় singular value ১বেশির ভাগ আধুনিক GAN-এর discriminatorDiscriminator-এর ক্ষমতা কিছুটা সীমিত
Conditional GANLabel দুই network-এনির্দিষ্ট শ্রেণির ছবি চাইLabel-যুক্ত ডেটা লাগে
StyleGANMapping, style, noise, truncationএক ধরনের বস্তুর উচ্চ মানের বড় ছবিবড় compute; একটি সরু ডোমেইনে
pix2pix / CycleGANশর্ত হিসেবে পুরো ছবিছবি থেকে ছবিতে রূপান্তরজোড়া ডেটা (pix2pix) বা দুর্বল নিশ্চয়তা (CycleGAN)

আজ text থেকে ছবি বানানোর বড় system-গুলোর বেশির ভাগ diffusion-ভিত্তিক (পর্ব ৭৯ আর ৮০)। তবু GAN এক ধাপে নমুনা দেয় বলে দ্রুত, আর adversarial loss অনেক জায়গায় সহায়ক হিসেবে বসে, যেমন latent diffusion-এর autoencoder শেখাতে।

আরও গভীরে: Lucic ও সহলেখকেরা (“Are GANs Created Equal?”, ২০১৮) বড় আকারের পরীক্ষায় দেখান, যথেষ্ট hyperparameter খোঁজ আর অনেক seed দিলে অনেক GAN রূপের FID-এর পার্থক্য প্রায়ই seed-এর ওঠানামার মধ্যে পড়ে যায়; তাই একটি run দেখে “নতুন রূপটি ভালো” বলা বিপজ্জনক। Wasserstein দূরত্বের গণিত optimal transport তত্ত্ব থেকে (Villani-র বই একটি প্রচলিত উৎস)। StyleGAN2 (২০২০) AdaIN-এর বদলে ওজনের demodulation আনে, আর StyleGAN3 (২০২১) aliasing কমায়।

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

  • আলাদা জায়গার দুটি বণ্টনে JS কত? ln ২ (ভিত্তি e), দূরত্ব যা-ই হোক; তাই gradient শূন্য।
  • WGAN critic-এ sigmoid নেই কেন? Output একটি score, সম্ভাবনা নয়; W-এর অনুমান হলো আসল আর নকলের গড় score-এর পার্থক্য।
  • Gradient penalty কোথায় মাপা হয়? আসল আর নকলের মাঝের এলোমেলো বিন্দু x̂-এ, (‖∇D(x̂)‖ − ১)², λ = ১০।
  • WGAN-GP critic-এ BatchNorm কেন নয়? Penalty প্রতি নমুনার gradient-এর ওপর; BatchNorm নমুনাগুলোকে batch-এর মাধ্যমে জুড়ে দেয়।
  • ConvT(k ৪, s ২, p ১) মাপ কী করে? দ্বিগুণ: (H − ১)·২ − ২ + ৪ = ২H.

১০ · AI-এর যুগে — GAN-এর রূপ নিয়ে AI-এর কোড আর দাবি যাচাই

GAN-এর ভুল কোড-ও চলে, শুধু খারাপ ছবি দেয়; আর “FID ১২ পেয়েছি” দাবি কোন feature, কত নমুনা আর কত seed-এ, তা না বললে অর্থহীন।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
WGAN-GP critic-এ BatchNorm রেখে দেওয়া, বা শেষে sigmoidPenalty-র অর্থ নষ্ট; score সম্ভাবনায় আটকে যায়Critic-এর স্তর গুনে দেখা; পেপারের নিয়মের সঙ্গে মেলানো
autograd.grad-এ create_graph=True বাদPenalty থেকে critic-এর ওজনে gradient যায় না; penalty কাজ করে নাPenalty চালু আর বন্ধ করে critic-এর gradient-এর মাপ তুলনা
Conditional GAN-এ label শুধু generator-এLabel না মানলেও শাস্তি নেইপ্রতি শ্রেণির নমুনায় classifier-এর label accuracy
নিজের classifier-এর FID-কে পেপারের Inception FID-এর সঙ্গে তুলনাআলাদা feature, আলাদা স্কেলFeature, নমুনার সংখ্যা আর reference সেট লিখে রাখা
একটি seed-এর ফলে “WGAN-GP ভালো” বা “খারাপ”এই পর্বের run-এ একই কোডে seed-ভেদে FID কয়েক গুণ বদলেছেঅন্তত তিনটি seed, গড় আর পরিসর

কোড চাওয়ার সময় যাচাইও চেয়ে নিন:

আমি PyTorch-এ (version লিখে দিচ্ছি) ২৮×২৮ ধূসর ছবির জন্য একটি WGAN-GP আর একটি conditional DCGAN বানাতে চাই। ১. Generator আর critic-এর প্রতিটি স্তরের পরে tensor-এর আকার মন্তব্যে লেখো, আর মোট parameter হাতে হিসাব করে দেখাও। ২. Critic-এ BatchNorm বা sigmoid দেবে না; কেন দিলে না, এক লাইনে বলো। ৩. Gradient penalty-তে autograd.grad-এর create_graph কেন লাগে ব্যাখ্যা করো, আর penalty-র মান log করার কোড দাও। ৪. মূল্যায়নের জন্য একটি আলাদা classifier দিয়ে শ্রেণির histogram আর FID-এর কোড দাও, আর কোন feature ব্যবহার করছ তা স্পষ্ট লেখো। ৫. তিনটি seed-এ চালানোর ব্যবস্থা রাখো। কোনো FID বা accuracy-র সংখ্যা বানিয়ে লিখবে না; নিশ্চিত না হলে বলো।

AI-এর লেখা GAN কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১DCGAN-এর নিয়মগুলো তত্ত্ব থেকে প্রমাণিত নয়, পরীক্ষা থেকে পাওয়া। তাহলে এগুলো এত বছর টিকে থাকল কেন, আর কোনগুলো পরে বদলেছে?

২০১৪ সালে Goodfellow ও সহলেখকদের মূল GAN পেপার বের হওয়ার পর অনেকেই convolution দিয়ে বড় ছবি বানানোর চেষ্টা করেন, কিন্তু শেখা প্রায়ই ভেঙে পড়ত: generator একই ছবি বারবার দিত, বা discriminator এত দ্রুত জিতত যে generator কিছুই শিখত না। Radford, Metz আর Chintala ২০১৫ সালে নিজেদের পরীক্ষার ভিত্তিতে একটি “রেসিপি” দেন। তাঁরা নিজেরাই এগুলোকে নির্দেশিকা বলেছেন, প্রমাণ নয়। তবু রেসিপিটি টিকে যায়, কারণ এটি স্পষ্ট সমস্যা সামলায়। Stride-যুক্ত convolution মাপ কমানোর উপায় নিজে শেখে। BatchNorm মাঝের মান একটি পরিসরে রেখে শুরুর দিকের ধসে পড়া কমায়। LeakyReLU discriminator-এর ঋণাত্মক দিকেও gradient রাখে, যা generator-এর শেখার সংকেতের উৎস।

পরে কয়েকটি নিয়ম বদলেছে। WGAN-GP দেখায়, penalty-র কারণে critic-এ BatchNorm বাদ দিতে হয়। Spectral normalization (২০১৮) আসার পর অনেক discriminator-এ BatchNorm-এর বদলে সেটি বসে। Progressive GAN আর StyleGAN generator-এ BatchNorm-এর বদলে pixel-ভিত্তিক normalization বা style modulation ব্যবহার করে। Odena ও সহলেখকেরা (২০১৬) দেখান, transposed convolution-এর kernel stride দিয়ে বিভাজ্য না হলে দাবার ছকের মতো দাগ আসে, তাই অনেকে আগে upsample, তারপর সাধারণ convolution ব্যবহার করেন।

এই পর্বের run একটি সতর্কতাও দেয়। একই generator আর kernel ৪, stride ২ থাকা সত্ত্বেও WGAN-GP-র seed ০-এর নমুনায় নিয়মিত বিন্দুর জাল দেখা গেছে, অথচ seed ২-এ নেই। অর্থাৎ নকশার নিয়ম ঝুঁকি কমায়, পুরোপুরি দূর করে না; শেখার গতিপথও ফল ঠিক করে। “নিয়ম কেন কাজ করে”, তার পুরো তাত্ত্বিক উত্তর আজও অনেক ক্ষেত্রে নেই।

মূল উপলব্ধি: DCGAN-এর নিয়ম একটি পরীক্ষিত শুরুর বিন্দু, আইন নয়; প্রতিটি নিয়ম কোন সমস্যা সামলায় তা জানলে কখন বদলাতে হবে তা বোঝা যায়।

প্র ০২WGAN পেপার বলে critic-এর loss নমুনার মানের সঙ্গে মেলে। তাহলে কি WGAN-GP-র loss দেখেই বলা যায় model কতটা ভালো?

Arjovsky, Chintala আর Bottou ২০১৭ সালে দেখান, সাধারণ GAN-এর discriminator loss প্রায় কিছুই বলে না: discriminator জিতলে loss শূন্যের কাছে যায়, ছবি ভালো হোক বা খারাপ। WGAN-এর critic যেহেতু W-এর একটি অনুমান দেয়, আর W দুই বণ্টনের দূরত্ব, তাই তাঁদের পরীক্ষায় এই অনুমান training-এর সঙ্গে নামে আর নমুনার মান বাড়ে।

তবে সতর্কতা অনেক। প্রথমত, critic-এর অনুমান তখনই W-এর কাছাকাছি যখন critic যথেষ্ট ক্ষমতাশালী আর যথেষ্ট শেখানো; সীমিত network আর সীমিত ধাপে এটি একটি নিচের সীমা মাত্র। দ্বিতীয়ত, penalty-র ওজন λ, network-এর আকার আর learning rate বদলালে অনুমানের স্কেলও বদলায়; তাই দুই ভিন্ন model-এর loss তুলনা চলে না। তৃতীয়ত, Arora ও সহলেখকেরা (২০১৭) দেখান, সীমিত নমুনায় শেখা দূরত্ব সত্যিকারের বণ্টনের দূরত্ব থেকে অনেক আলাদা হতে পারে, বিশেষ করে বৈচিত্র্যের ঘাটতি এতে সহজে ধরা পড়ে না।

এই পর্বের run-ও একটি উদাহরণ দেয়। WGAN-GP-র seed ০-এর log-এ W-এর অনুমান ২৫০তম ধাপে ২১.২ থেকে ধীরে ১৭-এর আশপাশে নেমে প্রায় থেমে যায়, আর FID ২৯১ থেকে ২৫১-এ সামান্য নামে; নমুনায় বিন্দুর জাল থেকে যায়। অনুমান থেমে থাকা বলে দিচ্ছিল কিছু আটকে আছে, কিন্তু কী আটকে আছে তা বলেনি। তাই loss-কে একটি স্বাস্থ্য-সংকেত হিসেবে দেখুন, চূড়ান্ত বিচার হিসেবে নয়: সঙ্গে নমুনা চোখে দেখা, coverage আর একটি FID-জাতীয় মাপ রাখুন।

মূল উপলব্ধি: WGAN-এর loss একই run-এর অগ্রগতি দেখতে কাজের, কিন্তু ভিন্ন model-এর মান তুলনা বা মানের প্রমাণ হিসেবে যথেষ্ট নয়।

প্র ০৩WGAN-GP-কে “বেশি স্থিতিশীল” বলা হয়, অথচ এই পর্বের run-এ তিনটি seed-এর একটিতে সে সবচেয়ে খারাপ ফল দিয়েছে। এটি কি পেপারের দাবির বিরোধী?

সরাসরি বিরোধী নয়, কিন্তু দাবিটি কোন অর্থে সত্য তা ভাবতে বাধ্য করে। Gulrajani ও সহলেখকদের ২০১৭ সালের মূল দাবি ছিল, WGAN-GP অনেক রকম architecture-এ প্রায় hyperparameter না বদলেই শেখে, এমনকি যেখানে সাধারণ GAN একেবারে ব্যর্থ হয় (যেমন BatchNorm ছাড়া বা খুব গভীর ResNet-এ)। সেখানে “স্থিতিশীল” মানে “সহজে পুরোপুরি ভেঙে পড়ে না”, “সবসময় দ্রুত ভালো ছবি দেয়” নয়।

আমাদের পরীক্ষার budget ছিল ৩,০০০টি generator update, যা DCGAN-এর জন্য যথেষ্ট কিন্তু WGAN-GP-র জন্য কম হতে পারে; Adam-এর β১ = ০ আর lr ০.০০০১-এ শেখা ধীর। Seed ০-এ নমুনায় একটি নিয়মিত বিন্দুর জাল এসে আটকে ছিল, seed ২-এ FID ৩৪.২, যা DCGAN-এর তিন seed-এর সেরাটির কাছাকাছি। একই কোডে seed বদলালে FID সাত গুণের বেশি বদলেছে, আর seed ০-কেই ১০,০০০ ধাপ দিলে FID ২৭.৪-এ নেমেছে: ব্যর্থতা নয়, ধীরতা। Lucic ও সহলেখকদের ২০১৮ সালের বড় গবেষণাও প্রায় এই কথা বলে: যথেষ্ট খোঁজ আর অনেক seed দিলে বিভিন্ন GAN রূপের পার্থক্য প্রায়ই seed-এর ওঠানামার মধ্যে পড়ে যায়, আর কম budget-এ যার hyperparameter ভালো মেলে সে-ই জেতে।

আরেকটি বিষয় হলো সময়। WGAN-GP-র প্রতিটি generator ধাপে ৫টি critic ধাপ আর double backprop, তাই আমাদের GPU-তে তার সময় DCGAN-এর প্রায় ৪ গুণ। “একই সংখ্যক update” আর “একই সময়” দুটি আলাদা ন্যায্যতা; কোনটি বেছে নিলেন, তা ফলাফলের সঙ্গে লিখে রাখা দরকার।

মূল উপলব্ধি: “স্থিতিশীল” দাবি সবসময় একটি নির্দিষ্ট অর্থে আর নির্দিষ্ট budget-এ; কয়েকটি seed, পরিসর আর সময়ের হিসাব ছাড়া GAN রূপের তুলনা বিশ্বাসযোগ্য নয়।

প্র ০৪Conditional DCGAN এই পর্বের সব মাপে সবচেয়ে ভালো এসেছে। এতে কি প্রমাণ হলো label দিলে generator “ভালো শেখে”? এই তুলনায় কোন অন্যায্য সুবিধা থাকতে পারে?

Conditional model-এর FID তিন seed-এ ১০.৮ থেকে ১৫.০, DCGAN-এর ৩১.৯ থেকে ৪১.২। কিন্তু তুলনাটি পুরোপুরি সমান নয়, আর তার অন্তত দুটি কারণ আছে। প্রথমত, মূল্যায়নে আমরা প্রতিটি শ্রেণির জন্য সমান সংখ্যক নমুনা চেয়েছি। FID বণ্টনের গড় আর ছড়ানো দুটিই মাপে, তাই শুধু শ্রেণির ভারসাম্য ঠিক হলেই FID অনেকটা কমে। দ্বিতীয়ত, label একটি বাড়তি তথ্য, যা শেখার কাজকে সহজ করে: generator-কে আর ঠিক করতে হয় না কোন z কোন শ্রেণিতে যাবে।

তবু label-এর একটি সত্যিকারের সুবিধা আছে, যা গবেষণাতেও দেখা যায়: শর্তযুক্ত model প্রায়ই mode collapse-এ কম পড়ে, কারণ discriminator প্রতিটি শ্রেণির জন্য আলাদা করে আসল-নকল যাচাই করে, তাই একটি শ্রেণি বাদ দিলে ধরা পড়ে। Odena ও সহলেখকদের AC-GAN আর Miyato ও Koyama-র projection discriminator এই সুবিধা আরও কাজে লাগায়; ImageNet-এর মতো বৈচিত্র্যময় ডেটায় বড় GAN-গুলো প্রায় সবই শর্তযুক্ত।

Label accuracy-ও সাবধানে পড়তে হয়। আমাদের মান ০.৮৪০ থেকে ০.৮৬১, আর বিচারক classifier নিজে আসল test-এ ০.৯০৪ ঠিক। অর্থাৎ নকল ছবির বড় অংশ চাওয়া শ্রেণির মতোই দেখায়, কিন্তু Shirt আর T-shirt-এর মতো কাছাকাছি শ্রেণিতে বিচারকের নিজের ভুলও এতে মিশে আছে। ন্যায্য তুলনা চাইলে unconditional model-এর নমুনাকেও শ্রেণি অনুযায়ী ভারসাম্যে এনে, বা শ্রেণি-ভিত্তিক FID আলাদা করে মাপা যায়।

মূল উপলব্ধি: Conditional model-এর ভালো সংখ্যার একটি অংশ মূল্যায়নের নকশা থেকে আসে; তুলনার সময় কোন তথ্য কাকে দেওয়া হয়েছে, তা স্পষ্ট লিখুন।

প্র ০৫StyleGAN বাস্তবের মতো মানুষের মুখ বানাতে পারে বলে বিখ্যাত। এই পর্ব কেন মুখ বানায়নি, আর এই প্রযুক্তির ক্ষতি ঠেকাতে কোন দিকগুলো ভাবা দরকার?

Karras ও সহলেখকেরা StyleGAN শেখান FFHQ নামের একটি ডেটাসেটে, যা তাঁদের বর্ণনা অনুযায়ী Flickr থেকে সংগৃহীত, অনুমতিমূলক লাইসেন্সের প্রায় ৭০,০০০ মুখের ছবি। ফলস্বরূপ যে মুখগুলো আসে, সেগুলো কোনো বাস্তব মানুষের নয়, অথচ খুব বিশ্বাসযোগ্য। একই ক্ষমতা ভুয়া প্রোফাইল, প্রতারণা আর ভুল তথ্য ছড়াতে ব্যবহৃত হতে পারে, আর পরের প্রজন্মের model বাস্তব মানুষের মুখ বদলে deepfake বানাতেও ব্যবহৃত হয়েছে বলে বিভিন্ন প্রতিবেদনে এসেছে।

এই কোর্স তাই নিয়ম মেনেছে: কোনো মুখ বানানো নয়, কোনো বাস্তব মানুষের ছবি দিয়ে শেখানো নয়, আর ক্ষতির আলোচনা শুধু প্রতিরোধের দিক থেকে। প্রযুক্তিগত প্রতিরোধের কয়েকটি দিক আছে। শুরুর দিকের GAN ছবিতে চোখের মণির অমিলের মতো চিহ্ন থাকত, আর transposed convolution-এর upsampling ছবির frequency spectrum-এ নিয়মিত দাগ রাখে বলে গবেষণায় দেখা গেছে; এমন সংকেত দিয়ে detector বানানো হয়। কিন্তু প্রতিটি নতুন model পুরোনো চিহ্ন সরায়, তাই শুধু detector-এ ভরসা চলে না। এই পর্বের WGAN-GP seed ০-এর বিন্দুর জাল এমনই একটি চিহ্নের ছোট উদাহরণ।

তাই উৎস প্রমাণের (provenance) দিকে নজর বাড়ছে: ছবি তৈরির সময় অদৃশ্য watermark বা স্বাক্ষরিত metadata যোগ করা, যাতে পরে যাচাই করা যায় কোথা থেকে এসেছে। এর পাশাপাশি মানুষের সচেতনতা লাগে: অপরিচিত প্রোফাইলের ছবিতে সহজে বিশ্বাস না করা আর ক্ষতিকর জিনিস রিপোর্ট করা। সামাজিক প্রকৌশল থেকে বাঁচার উপায় সাইবার নিরাপত্তা কোর্সের পর্ব ৫২-এ আছে; watermark আর provenance-এর মাপজোখ পর্ব ৮১-এ আসবে।

মূল উপলব্ধি: বাস্তবসম্মত ছবি বানানোর ক্ষমতা নিজেই ঝুঁকি; দায়িত্বশীল কাজ মানে ডেটার সম্মতি, মুখের মতো স্পর্শকাতর বিষয় এড়ানো, আর detection-এর পাশাপাশি provenance ও সচেতনতায় জোর দেওয়া।

অনুশীলন

খাতা-কলমে করুন, তারপর উত্তর মেলান। পরীক্ষা আর ইন্টারভিউতে এই ধরনের হিসাব কাজে লাগে।

  1. হিসাব করুন: ৩২ × ৩২ রঙিন ছবির একটি DCGAN generator: z (১২৮) → ConvT(১২৮, ২৫৬, k ৪, s ১, p ০) → ConvT(২৫৬, ১২৮, k ৪, s ২, p ১) → ConvT(১২৮, ৬৪, k ৪, s ২, p ১) → ConvT(৬৪, ৩, k ৪, s ২, p ১) → tanh. প্রথম তিন স্তরে bias নেই, শেষটিতে আছে; BatchNorm গুনবেন না। প্রতিটি স্তরের output-এর আকার আর ওজনের সংখ্যা বের করুন।

    মাপ: ১ → (১ − ১)·১ − ০ + ৪ = ৪; ৪ → (৪ − ১)·২ − ২ + ৪ = ৮; ৮ → ১৬; ১৬ → ৩২. আকার ২৫৬ × ৪ × ৪, ১২৮ × ৮ × ৮, ৬৪ × ১৬ × ১৬, ৩ × ৩২ × ৩২। ওজন: ১২৮ × ২৫৬ × ১৬ = ৫,২৪,২৮৮; ২৫৬ × ১২৮ × ১৬ = ৫,২৪,২৮৮; ১২৮ × ৬৪ × ১৬ = ১,৩১,০৭২; ৬৪ × ৩ × ১৬ + ৩ = ৩,০৭৫. মোট ১১,৮২,৭২৩। লক্ষ করুন, k ৪, s ২, p ১ প্রতিবার মাপ ঠিক দ্বিগুণ করে।

  2. আউটপুট অনুমান করুন: কোড না চালিয়ে চারটি print-এর ফল লিখুন (দুই দশমিক পর্যন্ত যথেষ্ট)।
    import numpy as np
    x = np.array([0, 1, 2, 3])
    P  = np.array([0.5, 0.5, 0, 0])
    Q1 = np.array([0, 0, 0.5, 0.5])
    Q2 = np.array([0, 0.5, 0.5, 0])
    def js(p, q):
        m = (p + q) / 2
        kl = lambda a: np.sum(a[a > 0] * np.log(a[a > 0] / m[a > 0]))
        return 0.5 * kl(p) + 0.5 * kl(q)
    def w1(p, q):
        return np.abs(np.cumsum(p) - np.cumsum(q)).sum()   # ধাপ 1
    print(round(js(P, Q1), 4), w1(P, Q1))
    print(round(js(P, Q2), 4), w1(P, Q2))
    0.6931 2.0
    0.3466 1.0

    Q1 আর P কোনো বিন্দুতে মেলে না, তাই JS = ln ২ ≈ ০.৬৯৩১। এক মাত্রায় W১ হলো দুই CDF-এর পার্থক্যের যোগ: P-এর CDF [০.৫, ১, ১, ১], Q1-এর [০, ০, ০.৫, ১], পার্থক্য ০.৫ + ১ + ০.৫ + ০ = ২.০; প্রতিটি ভর দুই ঘর সরেছে। Q2 একটি বিন্দুতে P-এর সঙ্গে মেলে: M = [০.২৫, ০.৫, ০.২৫, ০], KL(P‖M) = ০.৫ ln ২ + ০.৫ ln ১ = ০.৩৪৬৬, KL(Q2‖M)-ও তা-ই, তাই JS = ০.৩৪৬৬। CDF-এর পার্থক্য ০.৫ + ০.৫ + ০ + ০ = ১.০। দুটি ক্ষেত্রেই W সরানোর দূরত্ব মাপে; JS শুধু আংশিক মিলের সময় বদলায়।

  3. নকশা আর সমালোচনা: বাজারঘরের একজন সহকর্মী লিখেছেন: “আমাদের conditional GAN-এর FID ৮, পেপারের StyleGAN-এর FID-এর চেয়েও ভালো। একটি seed-এ চালিয়েছি, নমুনা দেখতে সুন্দর। এখন এটি দিয়ে পণ্যের নকল ছবি বানিয়ে বিজ্ঞাপনে দেব।” তিনটি সমস্যা চিহ্নিত করুন আর একটি ভালো মূল্যায়ন পরিকল্পনা দিন।

    সমস্যা: (১) ভিন্ন feature-এর FID তুলনা: নিজের classifier-এর FID আর পেপারের Inception FID-এর স্কেল আলাদা, ডেটাসেটও আলাদা। (২) একটি seed: এই পর্বের run-এ একই কোডে seed-ভেদে FID কয়েক গুণ বদলেছে। (৩) সততার প্রশ্ন: বানানো ছবি আসল পণ্যের ছবি হিসেবে বিজ্ঞাপনে দিলে ক্রেতা ভুল বুঝবেন; generated বলে চিহ্নিত করতে হবে, আর আসল পণ্যের সঙ্গে না মিললে তা প্রতারণা। পরিকল্পনা: অন্তত তিনটি seed; একই feature, একই reference সেট আর একই নমুনার সংখ্যায় baseline (যেমন unconditional DCGAN) আর আসল-বনাম-আসল খুঁটি; শ্রেণি-ভিত্তিক label accuracy আর coverage; train সেটের সবচেয়ে কাছের ছবির সঙ্গে তুলনা, যাতে model ছবি মুখস্থ করে কপি করছে কি না ধরা যায়; আর মানুষ দিয়ে কিছু নমুনা যাচাই।

  4. WGAN-GP হাতে: এক মাত্রার critic D(x) = ২x, আসল নমুনা [১, ৩], নকল [০, ১], λ = ১০। (ক) W-এর অনুমান, gradient penalty আর critic-এর loss কত? ε-এর মান কি উত্তর বদলায়? (খ) যদি D(x) = x হয়, তাহলে? (গ) কোন critic loss কম, আর তা থেকে কী শিখলেন?

    (ক) D(আসল) = [২, ৬], গড় ৪; D(নকল) = [০, ২], গড় ১; W-এর অনুমান ৩. D′(x) = ২ সব জায়গায়, তাই ε যা-ই হোক ‖∇‖ = ২, penalty (২ − ১)² = ১, λ·GP = ১০। Loss = ১ − ৪ + ১০ = ৭; ε উত্তর বদলায় না, কারণ সরলরেখার ঢাল সব জায়গায় এক। (খ) D(x) = x: গড় ২ আর ০.৫, W-এর অনুমান ১.৫, ঢাল ১, penalty ০, loss = ০.৫ − ২ = −১.৫. (গ) দ্বিতীয়টির loss কম, যদিও তার W-এর অনুমান ছোট। শুধু ঢাল বাড়িয়ে W বড় দেখানো penalty-তে মার খায়; সঠিক মাপের (ঢাল ১) critic-ই আসল W-এর অনুমান দেয়, যা এখানে আসল দূরত্ব: আসলের গড় ২ আর নকলের গড় ০.৫-এর পার্থক্য ১.৫।

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

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

ক্রমWGAN-GP-র এক দফা training-এর ধাপগুলো সাজান (▲▼ চেপে সরান)

  1. একটি আসল batch নেওয়া, আর z থেকে generator দিয়ে একটি নকল batch বানানো (generator-এর gradient বন্ধ রেখে)
  2. প্রতিটি জোড়ার জন্য এলোমেলো ε দিয়ে আসল আর নকলের মাঝের বিন্দু x̂ বানানো
  3. x̂-এ critic-এর gradient বের করা, create_graph রেখে, আর (‖∇‖ − ১)²-এর গড় নেওয়া
  4. D(নকল) − D(আসল) + λ·penalty-র গড় কমিয়ে critic-এর ওজন বদলানো
  5. এভাবে critic-এর পাঁচটি ধাপের পরে −D(G(z))-এর গড় কমিয়ে generator-এর একটি ধাপ

nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1)-এ 256 × 7 × 7 input দিলে output-এর আকার কত?

  • 128 × 3 × 3
  • 128 × 14 × 14
  • 128 × 13 × 13
  • 256 × 14 × 14

আসল বণ্টন P-এর সব ভর ০ বিন্দুতে আর generator-এর বণ্টন Q-এর সব ভর ৩ বিন্দুতে। JS divergence (স্বাভাবিক log) আর Wasserstein-1 দূরত্ব কত?

  • JS = ০, W = ৩
  • JS = ৩, W = ৩
  • JS = ln ২ ≈ ০.৬৯৩, W = ৩
  • JS = অসীম, W = ln ২

WGAN-GP-র critic-এ সাধারণত BatchNorm ব্যবহার করা হয় না। মূল কারণ কোনটি?

  • Penalty প্রতি নমুনার gradient-এর ওপর, আর BatchNorm নমুনাগুলোকে batch-এর মাধ্যমে জুড়ে দেয়
  • BatchNorm খুব বেশি GPU মেমরি নেয়
  • BatchNorm থাকলে generator-এর gradient শূন্য হয়ে যায়
  • BatchNorm critic-এর output-কে ০ থেকে ১-এ আটকে দেয়

মূল WGAN-এ weight clipping (যেমন c = ০.০১)-এর প্রধান সমস্যা কোনটি বলে পরিচিত?

  • এটি Lipschitz শর্ত একেবারেই রাখে না
  • প্রতিটি clip করতে double backprop লাগে
  • Generator-এর ছবি −c থেকে c-এর মধ্যে আটকে যায়
  • c বাছাই কঠিন, আর critic-এর ক্ষমতা সীমিত হয়ে খুব সরল ফাংশন শেখে

StyleGAN-এর truncation trick w′ = w̄ + ψ(w − w̄)-এ ψ = ০.৫ দিলে সাধারণত কী হয়?

  • বৈচিত্র্য বাড়ে, প্রতিটি নমুনার মান কমে
  • নমুনা বেশি গড়পড়তা আর সাধারণত পরিষ্কার হয়, কিন্তু বৈচিত্র্য কমে
  • Generator-কে আবার শেখাতে হয়
  • সব নমুনা হুবহু একই ছবি হয়

একটি conditional GAN-এ শ্রেণির label শুধু generator-কে দেওয়া হলো, discriminator-কে নয়। সবচেয়ে সম্ভাব্য সমস্যা কী?

  • Generator-এর input-এর আকার মিলবে না
  • Discriminator আর আসল-নকল আলাদা করতে পারবে না
  • Generator চাওয়া শ্রেণির বদলে যেকোনো বাস্তবসম্মত ছবি দিলেও ধরা পড়বে না
  • Loss অসীমে চলে যাবে

দুটি দল নিজ নিজ GAN-এর FID জানাল: একটি নিজের Fashion-MNIST classifier-এর feature দিয়ে ১২, অন্যটি Inception feature দিয়ে ২০। কোন সিদ্ধান্ত ঠিক?

  • সংখ্যা দুটি তুলনা করা যায় না; একই feature আর একই নমুনার সংখ্যায় মাপতে হবে
  • প্রথম GAN ভালো, কারণ তার FID কম
  • দ্বিতীয় GAN ভালো, কারণ Inception বেশি নির্ভরযোগ্য
  • FID বেশি মানে ভালো, তাই দ্বিতীয়টি

শব্দকোষ

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

মনে রাখুন

  1. DCGAN: stride-যুক্ত conv আর transposed conv, BatchNorm, ReLU/LeakyReLU, শেষে tanh. ConvT(k ৪, s ২, p ১) মাপ দ্বিগুণ করে: ৭ → ১৪ → ২৮ (প্রথম ধাপ ১ → ৭ আসে k ৭, s ১ থেকে)।
  2. JS বনাম W: না মেলা দুই বণ্টনে JS = ln ২, gradient শূন্য; W = ভর × দূরত্ব, মসৃণ আর দিক দেখায়।
  3. WGAN: sigmoid ছাড়া critic, W-এর অনুমান = গড় D(আসল) − গড় D(নকল), 1-Lipschitz শর্ত; clipping রূঢ়, gradient penalty (λ = ১০, মাঝের বিন্দুতে) বা spectral norm ভালো।
  4. Conditional আর StyleGAN: label দুই network-এ; mapping network z → w, স্তরে স্তরে style, truncation ψ মান বনাম বৈচিত্র্যের আপস, আর তা শেখা w-জায়গায় অর্থবহ।
  5. সৎ তুলনা: একই budget, কয়েকটি seed, নিজের classifier-এর FID আর coverage, সঙ্গে নমুনা চোখে দেখা। এখানে একই WGAN-GP কোডে seed-ভেদে FID সাত গুণের বেশি বদলেছে।

পরের পর্বে: পর্ব ৭৯-এ diffusion model: ছবিতে ধাপে ধাপে noise দেওয়া, সেই noise অনুমান করতে শেখানো, আর DDPM ও DDIM দিয়ে উল্টো পথে নমুনা বানানো। Adversarial খেলা ছাড়াই কীভাবে স্থিতিশীল training আসে, সেটিই প্রশ্ন।

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

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