Danh sách bài viết

Bài 44: GAN intuition — Generator vs Discriminator

Generative Adversarial Network (GAN, Goodfellow và cộng sự, 2014) đặt 2 mạng neural đối kháng nhau trong một minimax game: generator \( G \) ánh xạ noise \( z \sim \mathcal{N}(0, I) \) thành sample giả \( G(z) \); discriminator \( D \) cố phân biệt real (\( x \sim p_{\text{data}} \)) với fake. \( G \) cố lừa \( D \), \( D \) cố phát hiện — equilibrium là khi \( G \) sinh ra phân phối khớp \( p_{\text{data}} \) và \( D \) chỉ đoán 50/50. Bài này dẫn objective gốc \( \min_G \max_D V(D, G) \), non-saturating loss của Goodfellow, training loop alternating, các vấn đề thực hành (mode collapse, vanishing gradient cho \( G \), oscillation), variants chính (DCGAN, WGAN, CycleGAN, StyleGAN, BigGAN), so sánh với VAE (B43) và Diffusion (modern), evaluation FID/IS, và PyTorch DCGAN skeleton cho MNIST. Cuối bài tổng kết Module 6 và toàn bộ Series 3 (44 bài qua 6 module), dẫn sang Series 4 LLM & Generative AI.

24/05/2026
14 phút đọc
2 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Mô tả được kiến trúc 2 network của GAN: generator \( G \) sinh fake từ noise, discriminator \( D \) phân biệt real vs fake.
  • Viết được minimax objective \( \min_G \max_D V(D, G) \) gốc của Goodfellow 2014.
  • Trình bày training loop alternating: 1 bước update \( D \), 1 bước update \( G \).
  • Phân biệt original loss \( \log(1 - D(G(z))) \) và non-saturating loss \( -\log D(G(z)) \), lý giải vấn đề vanishing gradient cho \( G \).
  • Mô tả được 3 vấn đề kinh điển khi train GAN: mode collapse, vanishing gradient, oscillation; và các biện pháp khắc phục.
  • Liệt kê các variant chính (DCGAN, WGAN, CycleGAN, StyleGAN, BigGAN) và task mỗi variant giải.
  • So sánh GAN với VAE (B43) và Diffusion theo các trục: chất lượng output, ổn định train, likelihood, control.
  • Implement DCGAN skeleton bằng PyTorch cho MNIST.
  • Đánh giá GAN bằng FID và Inception Score, biết giới hạn của metric này.

Bài này nối Bài 43 — VAE intuition và kết Module 6 + Series 3. Bài tiếp theo là Series 4 — LLMs & Generative AI.

2

GAN là gì

Generative Adversarial Network (Goodfellow, Pouget-Abadie, Mirza, Xu, Warde-Farley, Ozair, Courville, Bengio, 2014) — paper "Generative Adversarial Nets" — đề xuất một framework học phân phối dữ liệu \( p_{\text{data}}(x) \) bằng cách đặt 2 network neural đối kháng nhau:

  • Generator \( G_\theta(z) \): nhận noise \( z \sim p_z \) (thường \( \mathcal{N}(0, I) \) hoặc \( \mathcal{U}[-1, 1] \)), trả ra một sample \( G(z) \) trong không gian dữ liệu (vd ảnh).
  • Discriminator \( D_\phi(x) \): nhận một sample (có thể real hoặc fake), trả ra xác suất "real" \( \in [0, 1] \).

Khác Module 6 trước đó:

  • Autoencoder (B41) học compress + reconstruct, không sinh sample mới chất lượng.
  • VAE (B43) học một latent space có cấu trúc xác suất, có likelihood (ELBO), output thường mờ.
  • GAN bỏ likelihood, học implicit qua adversarial loss, output thường sắc nét hơn nhưng train khó.

GAN không có công thức loss closed-form cho \( G \) theo nghĩa "khoảng cách tới target"; loss của \( G \) phụ thuộc trực tiếp vào \( D \) — chính điều này tạo cả sức mạnh lẫn vấn đề.

3

Ý tưởng cốt lõi — adversarial game

Analogy Goodfellow dùng trong paper gốc:

  • \( G \) như kẻ làm tiền giả — sinh ra tờ tiền giả.
  • \( D \) như cảnh sát phát hiện — phân biệt tiền thật và tiền giả.
  • Hai bên cùng cải thiện qua thời gian: \( G \) làm giả ngày càng giống thật, \( D \) phát hiện ngày càng giỏi.
  • Tới một điểm, \( G \) sinh ra tiền giả không phân biệt được với tiền thật — đây là Nash equilibrium.

Sơ đồ data flow:

z ~ N(0, I) ──► [Generator G] ──► G(z)  (fake)
                                       │
                                       ▼
x ~ p_data ─────────────────────► [Discriminator D] ──► p(real) ∈ [0, 1]

Loss D: phân loại đúng real (1) và fake (0).
Loss G: lừa D, muốn D(G(z)) ≈ 1.

Hai mạng có objective ngược nhau: \( D \) cố tối đa hoá khả năng phân loại đúng, \( G \) cố tối thiểu hoá khả năng đó. Đây là một minimax game (mục 6).

4

Generator

Generator \( G_\theta : \mathbb{R}^{d_z} \to \mathbb{R}^{d_x} \) là một network mapping noise sang data space.

  • Input: \( z \sim p_z \), thường \( \mathcal{N}(0, I) \) với \( d_z = 100 \) cho ảnh \( 64 \times 64 \) hoặc \( d_z = 512 \) cho StyleGAN.
  • Output: \( G(z) \) cùng shape với data thật. Vd MNIST: \( 1 \times 28 \times 28 \); CIFAR-10: \( 3 \times 32 \times 32 \); StyleGAN-FFHQ: \( 3 \times 1024 \times 1024 \).
  • Activation cuối: tanh để output trong \( [-1, 1] \) (data thật cũng phải normalize về cùng range).

Mục tiêu của \( G \): \( D(G(z)) \approx 1 \) — tức là \( D \) tưởng output của \( G \) là real.

\( G \) không bao giờ thấy data thật trực tiếp — nó học gián tiếp qua gradient từ \( D \). Đây là điểm khác cốt yếu so với VAE/AE (vốn có loss reconstruction trực tiếp với input).

5

Discriminator

Discriminator \( D_\phi : \mathbb{R}^{d_x} \to [0, 1] \) là binary classifier real-vs-fake.

  • Input: một sample \( x \) — có thể là real (\( x \sim p_{\text{data}} \)) hoặc fake (\( x = G(z) \)).
  • Output: scalar \( D(x) \in [0, 1] \), xác suất sample là real. Activation cuối: sigmoid (trừ WGAN dùng linear).
  • Mục tiêu: \( D(x) \approx 1 \) khi \( x \) real, \( D(G(z)) \approx 0 \) khi fake.

Architecture: thường CNN với conv-stride-2 thay cho pooling (DCGAN), LeakyReLU thay vì ReLU (gradient không tắt hoàn toàn), BatchNorm có hoặc không (DCGAN có, WGAN-GP thường không).

Loss của \( D \) khi train (BCE):

\[ \mathcal{L}_D = -\mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] - \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))] \]

Minimize \( \mathcal{L}_D \) tương đương maximize \( V(D, G) \) ở mục 6.

6

Minimax objective

Objective gốc của Goodfellow 2014:

\[ \min_G \max_D V(D, G) = \mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))] \]

Đọc theo 2 phía:

  • \( D \) maximize \( V \): muốn \( \log D(x) \) lớn (real → 1) và \( \log(1 - D(G(z))) \) lớn (fake → 0).
  • \( G \) minimize \( V \) qua chỉ term thứ 2 (term thứ 1 không phụ thuộc \( G \)): muốn \( \log(1 - D(G(z))) \) nhỏ, tức \( D(G(z)) \) gần 1.

Hai bên có objective ngược chiều — đây không phải optimization thông thường (minimize 1 loss), mà là saddle-point problem. Thuật toán thực hành (mục 8) làm gradient descent xen kẽ trên \( D \) và \( G \).

7

Equilibrium lý thuyết

Cho \( G \) cố định, optimal discriminator:

\[ D^*_G(x) = \frac{p_{\text{data}}(x)}{p_{\text{data}}(x) + p_g(x)} \]

với \( p_g \) là phân phối induced bởi \( G \) (phân phối của \( G(z) \) khi \( z \sim p_z \)).

Thay \( D^* \) vào \( V \), Goodfellow chứng minh \( G \) tối ưu thoả \( p_g = p_{\text{data}} \), khi đó \( D^*(x) = 1/2 \) khắp nơi — \( D \) chỉ đoán 50/50. Tại equilibrium:

\[ V(D^*, G^*) = -\log 4 \approx -1.386 \]

Loss này tương đương với Jensen–Shannon divergence:

\[ C(G) = -\log 4 + 2 \cdot \mathrm{JSD}(p_{\text{data}} \,\|\, p_g) \]

Tức GAN gốc tối thiểu hoá JSD giữa phân phối thật và sinh. Lưu ý: lý thuyết đẹp này giả định capacity vô hạn và optimization perfect — thực tế \( G, D \) là network giới hạn, train không bao giờ chạm equilibrium chính xác.

8

Training loop alternating

Mỗi iteration train xen kẽ 2 bước:

  1. Bước 1 — update D (fix G):
    • Sample real batch \( \{x^{(i)}\} \sim p_{\text{data}} \).
    • Sample noise \( \{z^{(i)}\} \sim p_z \), tạo fake \( \{G(z^{(i)})\} \).
    • Forward \( D \) cho cả 2; compute \( \mathcal{L}_D = -[\log D(x) + \log(1 - D(G(z)))] \) trung bình.
    • Backward, update \( \phi \) (params \( D \)).
  2. Bước 2 — update G (fix D):
    • Sample noise mới \( \{z^{(i)}\} \).
    • Forward: \( G(z) \) rồi \( D(G(z)) \).
    • Compute \( \mathcal{L}_G \) (mục 9 giải thích chọn loss nào).
    • Backward qua \( D \) (nhưng không update \( D \)) tới \( G \), update \( \theta \) (params \( G \)).
repeat for each iteration:
    # --- D step ---
    real = sample_batch(data)
    z    = sample_noise()
    fake = G(z)
    loss_D = -mean(log D(real) + log(1 - D(fake.detach())))
    backward(loss_D); step(opt_D)

    # --- G step ---
    z    = sample_noise()
    fake = G(z)
    loss_G = -mean(log D(fake))     # non-saturating, xem mục 9
    backward(loss_G); step(opt_G)

Một số biến thể train \( k \) bước \( D \) trên 1 bước \( G \) (\( k = 1 \) phổ biến với DCGAN; \( k = 5 \) với WGAN). detach() ở bước \( D \) ngăn gradient chảy ngược về \( G \).

9

Non-saturating loss cho G

Theo công thức gốc, \( G \) minimize \( \mathbb{E}_z[\log(1 - D(G(z)))] \). Vấn đề thực hành:

  • Khi đầu training \( G \) còn yếu, \( D(G(z)) \approx 0 \).
  • \( \log(1 - 0) = 0 \), gradient \( \partial / \partial G \) của term này ≈ 0.
  • \( G \) "đói" gradient — không học được khi \( D \) đang thắng.

Goodfellow đề xuất luôn dùng non-saturating loss cho \( G \): thay maximize \( \log(1 - D(G(z))) \) bằng minimize \( -\log D(G(z)) \), tức:

\[ \mathcal{L}_G = -\mathbb{E}_{z \sim p_z}[\log D(G(z))] \]

Hai loss có cùng điểm tối ưu (\( D(G(z)) \to 1 \)) nhưng gradient khác hẳn:

  • Khi \( D(G(z)) \approx 0 \): \( -\log D(G(z)) \) rất lớn, gradient mạnh — \( G \) học nhanh.
  • Khi \( D(G(z)) \approx 1 \): gradient nhỏ — không cần học thêm.

Đây là loss mặc định trong gần như mọi GAN implementation. Trong PyTorch, đơn giản dùng BCEWithLogitsLoss với target = 1 cho fake khi update \( G \).

10

Vì sao GAN khó train

Minimax không có một loss duy nhất giảm đều theo step. Các vấn đề điển hình:

  • Equilibrium không stable: gradient descent xen kẽ không đảm bảo hội tụ tới saddle point; có thể đi vòng quanh.
  • Vanishing gradient cho G: nếu \( D \) quá mạnh (loss \( D \) gần 0), gradient cho \( G \) ≈ 0, \( G \) không học. Non-saturating loss giúp ban đầu, nhưng vẫn xảy ra khi \( D \) hoàn hảo.
  • Mode collapse (mục 11): \( G \) chỉ sinh một vài mode của \( p_{\text{data}} \).
  • Oscillation: \( G \) và \( D \) thay nhau "thắng" — chất lượng tăng giảm bất thường giữa các epoch.
  • Hyperparameter rất nhạy: learning rate, kiến trúc, batch size đều ảnh hưởng mạnh. Một số setup không hội tụ dù đúng code.

Quy tắc thực hành phổ biến:

  • Adam với \( \beta_1 = 0.5 \) (DCGAN paper), learning rate \( 2 \times 10^{-4} \).
  • Normalize input về \( [-1, 1] \); output \( G \) qua tanh.
  • Label smoothing: target real = 0.9 thay vì 1.0 (Salimans 2016).
  • Theo dõi cả 2 loss qua epoch — nếu loss \( D \) → 0 quá nhanh, \( G \) sẽ stuck.
11

Mode collapse

Hiện tượng: \( G \) phát hiện một số sample \( G(z) \) lừa được \( D \) tốt và bắt đầu chỉ sinh đúng vài kiểu đó, bỏ qua phần lớn các mode của \( p_{\text{data}} \).

Ví dụ kinh điển trên MNIST: \( G \) chỉ sinh ra chữ số 6 và 1, bỏ luôn 8 chữ số còn lại. Trên CIFAR-10: chỉ sinh ô tô và ngựa, không sinh máy bay, chim, ếch.

Nguyên nhân trực giác:

  • \( G \) optimize per-sample, không có ràng buộc về diversity.
  • Nếu một mode đặc biệt khó để \( D \) phân biệt, \( G \) "đậu" ở đó.
  • Khi \( D \) bắt đầu phát hiện mode đó, \( G \) chuyển sang mode khác — nhưng vẫn không sinh đủ tất cả mode.

Giải pháp đã được đề xuất:

  • Mini-batch discrimination (Salimans 2016): \( D \) nhìn cả batch thay vì từng sample, có thể phát hiện sự thiếu đa dạng.
  • Unrolled GAN (Metz 2017): \( G \) update dựa trên dự đoán của \( D \) sau \( k \) bước update tiếp theo.
  • Wasserstein loss (WGAN, mục 13): khoảng cách Wasserstein nhạy hơn với toàn bộ phân phối.
  • Spectral normalization (Miyato 2018): chuẩn hoá \( D \) để giữ Lipschitz, gradient ổn định hơn.
  • Diverse latent sampling: tăng \( d_z \), dùng noise injection ở nhiều layer (StyleGAN).
12

Architectural tricks — DCGAN

DCGAN (Radford, Metz, Chintala, 2015) — "Unsupervised Representation Learning with Deep Convolutional GANs" — đưa ra bộ rule thiết kế giúp GAN trên ảnh train ổn định hơn vanilla MLP GAN:

  • Thay pooling bằng strided conv (D) và transposed conv (G).
  • BatchNorm trong cả \( G \) và \( D \) (trừ layer cuối \( G \) và layer đầu \( D \)).
  • Bỏ fully-connected layer ở giữa, chỉ giữ conv.
  • Activation: ReLU trong \( G \), tanh ở layer cuối; LeakyReLU(0.2) trong \( D \).
  • Adam, lr \( 2 \times 10^{-4} \), \( \beta_1 = 0.5 \).

Các trick stabilize sau này:

  • Spectral Normalization (Miyato 2018): chuẩn hoá weight matrix của \( D \) bằng singular value lớn nhất, đảm bảo \( D \) là 1-Lipschitz. Train ổn định, không cần BatchNorm trong \( D \).
  • Self-attention (SAGAN, Zhang 2018): chèn self-attention layer vào \( G \) và \( D \), cải thiện coherence ở scale lớn.
  • Progressive growing (ProGAN, Karras 2018): train từ resolution thấp lên cao dần — foundation của StyleGAN.
  • Gradient penalty (WGAN-GP, Gulrajani 2017): thay weight clipping của WGAN bằng penalty trên norm gradient của \( D \).
13

Loss variants — WGAN, LSGAN, RaGAN

Nhiều thay đổi loss đã được đề xuất để giải mode collapse và vanishing gradient:

  • WGAN (Arjovsky 2017): thay JS divergence bằng Wasserstein-1 (earth mover's) distance. \( D \) trở thành "critic" trả về scalar tự do (không sigmoid). Loss:

    \[ \mathcal{L} = \mathbb{E}_{x \sim p_{\text{data}}}[D(x)] - \mathbb{E}_{z \sim p_z}[D(G(z))] \]

    Cần \( D \) 1-Lipschitz, thực hiện bằng weight clipping (gốc) hoặc gradient penalty (WGAN-GP). Loss có ý nghĩa giảm đều, tương quan với chất lượng — debug dễ hơn nhiều.
  • LSGAN (Mao 2017, Least-Squares GAN): thay BCE bằng MSE. \( \mathcal{L}_D = \mathbb{E}[(D(x) - 1)^2] + \mathbb{E}[D(G(z))^2] \); \( \mathcal{L}_G = \mathbb{E}[(D(G(z)) - 1)^2] \). Gradient mượt hơn, ít vanishing.
  • RaGAN (Jolicoeur-Martineau 2018, Relativistic GAN): \( D \) không phân loại real vs fake tuyệt đối, mà so sánh — sample này "thật hơn" hay "giả hơn" sample kia. Cải thiện stability trên một số task.
  • Hinge loss (Lim & Ye 2017, dùng trong SAGAN, BigGAN): \( \mathcal{L}_D = \mathbb{E}[\max(0, 1 - D(x))] + \mathbb{E}[\max(0, 1 + D(G(z)))] \).

Trong các benchmark gần đây (Lucic 2018, "Are GANs Created Equal?"), khác biệt giữa các loss biến mất khi tuning hyperparameter cẩn thận — chọn loss nào không quan trọng bằng setup tốt.

14

Variants nổi tiếng

Variant Năm Đóng góp chính Task
DCGAN 2015 Conv-based, BatchNorm, LeakyReLU. Image generation cơ bản (CIFAR, LSUN).
Pix2Pix 2017 Conditional GAN cho paired image translation. Edge→ảnh, day→night (cần dataset cặp).
CycleGAN 2017 Cycle consistency loss cho unpaired translation. Horse↔zebra, photo↔Monet.
WGAN / WGAN-GP 2017 Wasserstein loss, train stable. General — base loss cho nhiều paper sau.
ProGAN 2018 Progressive growing, high-res. CelebA-HQ \( 1024^2 \).
BigGAN 2018 Class-conditional, lớn (158 M params), batch 2048. ImageNet \( 512^2 \) high-fidelity.
StyleGAN 2019 Mapping network \( z \to w \), AdaIN style injection. FFHQ face \( 1024^2 \), interpolation đẹp.
StyleGAN2 2020 Fix artefact "blob", weight modulation thay AdaIN. SOTA face generation thời điểm đó.
StyleGAN3 2021 Fix aliasing, equivariance translation/rotation. Video generation, anim.

Mỗi variant tập trung một khía cạnh: chất lượng (BigGAN, StyleGAN), task chuyển dịch (Pix2Pix, CycleGAN), ổn định train (WGAN, ProGAN). Đọc paper gốc để hiểu chi tiết — bài này chỉ tóm tắt.

15

GAN vs VAE

So sánh trực tiếp 2 model trong cùng Module 6:

Tiêu chí VAE (B43) GAN
Loss ELBO = reconstruction + KL (explicit). Adversarial (implicit qua \( D \)).
Likelihood Có (ELBO là lower bound). Không.
Chất lượng output Mờ (Gaussian decoder + KL pull). Sắc nét.
Diversity Tốt (cover phân phối qua KL). Risk mode collapse.
Ổn định train Stable, gradient sạch. Khó, nhạy hyperparameter.
Latent space Có structure xác suất, interpolate được. StyleGAN có \( w \)-space đẹp; vanilla GAN ít structure hơn.
Encoder Có (\( q_\phi(z \mid x) \)). Không (chỉ \( G \), trừ BiGAN/ALI).
Inference từ \( x \) → \( z \) Trực tiếp. Không trực tiếp (cần GAN inversion).

Hybrid VAE-GAN (Larsen 2016, "Autoencoding beyond pixels using a learned similarity metric") kết hợp 2 cách tiếp cận, dùng adversarial loss thay cho pixel-MSE để giảm blur.

16

GAN vs Diffusion

Từ 2020, Diffusion model (DDPM, Ho 2020) chiếm phần lớn ứng dụng image generation. Mô hình diffusion học denoise dần một sample noise pure về data clean, qua nhiều bước (vd 1000 step).

Cột mốc: DDPM 2020 (foundation), Latent Diffusion / Stable Diffusion 2022 (chạy diffusion trong latent VAE → khả thi với GPU thường), Imagen 2022, DALL-E 2 2022, SDXL 2023.

Tiêu chí GAN Diffusion
Tốc độ inference 1 forward pass — real-time. Nhiều step (20–1000), chậm hơn.
Chất lượng Sắc nét, SOTA face một thời. SOTA hiện tại cho hầu hết task ảnh.
Diversity Mode collapse risk. Cover phân phối tốt.
Ổn định train Khó. Stable (single loss).
Control / conditioning Class-conditional, style mixing — phức tạp hơn. Classifier-free guidance, text-to-image dễ.
Use case modern Real-time avatar, super-resolution, video face swap. Text-to-image, image editing, video generation.

GAN không "chết" — vẫn cạnh tranh khi cần latency thấp (vd Adobe Firefly, NVIDIA face animation). Một số paper gần đây (GigaGAN 2023) cho thấy GAN scaling lên text-to-image cũng cạnh tranh diffusion về chất lượng với inference nhanh hơn nhiều.

17

Use case

  • Image generation: face (StyleGAN trên FFHQ), art (Artbreeder), pokemon, anime character.
  • Image-to-image translation:
    • Paired: Pix2Pix (edge → ảnh, sketch → màu).
    • Unpaired: CycleGAN (horse ↔ zebra, photo ↔ Monet).
  • Super-resolution: SRGAN (Ledig 2017), ESRGAN — upscale ảnh thấp resolution.
  • Data augmentation: sinh synthetic data cho domain hiếm dữ liệu (medical imaging).
  • Anomaly detection: AnoGAN (Schlegl 2017) train GAN trên data normal; sample không reconstruct được tốt → anomaly.
  • Inpainting: điền vào vùng bị che (Context Encoder, Pathak 2016).
  • Face editing: GAN inversion + latent manipulation cho age, smile, glasses.
  • Deepfake: face swap video. Đây là lĩnh vực có ethical concern lớn — misuse cho disinformation, non-consensual content. Nhiều quốc gia đã có luật pháp về deepfake.

Ethical note: bất kỳ ai làm GAN/diffusion cho ảnh người cần đọc về deepfake regulation tại khu vực mình hoạt động (EU AI Act 2024, US các state law, etc.) và quy ước Content Provenance (C2PA).

18

Evaluation — FID, Inception Score

GAN không có likelihood → không có log-likelihood để báo cáo. Metric chuẩn:

  • Inception Score (IS) (Salimans 2016): chạy fake sample qua Inception-v3 pretrained ImageNet, đo \( \mathrm{KL}(p(y \mid x) \,\|\, p(y)) \). IS cao = mỗi sample phân loại confident (sharp) và tổng thể đa dạng. Range \( [1, 1000] \), CIFAR-10 real ~11.2. Hạn chế: không so với data thật, model overfit ImageNet, không đo coverage.
  • FID — Fréchet Inception Distance (Heusel 2017): embed cả real và fake qua Inception-v3, fit Gaussian, đo Fréchet distance:

    \[ \mathrm{FID} = \|\mu_r - \mu_g\|^2 + \mathrm{tr}(\Sigma_r + \Sigma_g - 2(\Sigma_r \Sigma_g)^{1/2}) \]

    FID thấp = sample gần phân phối thật. Standard hiện tại; cần ≥ 10k sample để estimate ổn định. FFHQ-StyleGAN2 FID ~2.8.
  • Precision & Recall (Kynkäänniemi 2019): tách chất lượng (precision = bao nhiêu fake nằm trong manifold real) và coverage (recall = bao nhiêu real được cover bởi fake) — phát hiện mode collapse mà FID đôi khi miss.
  • Visual inspection: vẫn quan trọng. Mỗi 5–10 epoch sinh grid 64 sample cố định để theo dõi qualitatively.

Cảnh báo: cả IS và FID dùng Inception-v3 train trên ImageNet — nếu domain khác (medical, anime), chúng không nhất thiết phản ánh đúng chất lượng. CLIP-FID (Kynkäänniemi 2023) dùng CLIP backbone tổng quát hơn.

19

Limitations

  • Train khó, nhạy hyperparameter; cùng code chạy 2 lần có thể ra chất lượng khác nhau.
  • Không có likelihood — không so sánh trực tiếp được giữa model bằng metric chuẩn của xác suất.
  • Mode collapse risk thường trực, cần monitor diversity riêng.
  • Không có encoder inverse — sample mới dễ, nhưng map từ ảnh thật về \( z \) cần GAN inversion (optimization thủ công hoặc encoder bổ sung).
  • Conditioning phức tạp hơn diffusion cho text-to-image; phải thiết kế cẩn thận.
  • Ethical concern lớn cho domain face/voice.

Mặc dù vậy, GAN vẫn là một trong những milestone quan trọng nhất của generative modeling và là foundation để hiểu các framework hiện đại (cũng như những phần lai như VAE-GAN, GAN-distilled diffusion).

20

PyTorch DCGAN skeleton

DCGAN tối giản cho MNIST (1 channel, 28×28 → resize 32×32 hoặc 64×64 để khớp DCGAN gốc):

import torch
import torch.nn as nn

NZ   = 100   # noise dim
NGF  = 64    # generator feature base
NDF  = 64    # discriminator feature base
NC   = 1     # MNIST 1 channel

class Generator(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            # z (NZ, 1, 1) → (NGF*4, 4, 4)
            nn.ConvTranspose2d(NZ, NGF * 4, 4, 1, 0, bias=False),
            nn.BatchNorm2d(NGF * 4), nn.ReLU(True),
            # → (NGF*2, 8, 8)
            nn.ConvTranspose2d(NGF * 4, NGF * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(NGF * 2), nn.ReLU(True),
            # → (NGF, 16, 16)
            nn.ConvTranspose2d(NGF * 2, NGF, 4, 2, 1, bias=False),
            nn.BatchNorm2d(NGF), nn.ReLU(True),
            # → (NC, 32, 32)
            nn.ConvTranspose2d(NGF, NC, 4, 2, 1, bias=False),
            nn.Tanh(),
        )
    def forward(self, z):                       # z: (B, NZ, 1, 1)
        return self.net(z)

class Discriminator(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(NC, NDF, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, True),
            nn.Conv2d(NDF, NDF * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(NDF * 2), nn.LeakyReLU(0.2, True),
            nn.Conv2d(NDF * 2, NDF * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(NDF * 4), nn.LeakyReLU(0.2, True),
            nn.Conv2d(NDF * 4, 1, 4, 1, 0, bias=False),
            nn.Sigmoid(),
        )
    def forward(self, x):                       # x: (B, NC, 32, 32)
        return self.net(x).view(-1)             # (B,)

G = Generator().to(device)
D = Discriminator().to(device)

bce   = nn.BCELoss()
opt_G = torch.optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999))
opt_D = torch.optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999))

real_label, fake_label = 0.9, 0.0       # label smoothing

for epoch in range(EPOCHS):
    for x, _ in loader:                  # x: (B, 1, 32, 32), normalized [-1, 1]
        B = x.size(0); x = x.to(device)

        # --- (1) update D ---
        opt_D.zero_grad()
        # real
        out = D(x)
        loss_D_real = bce(out, torch.full((B,), real_label, device=device))
        # fake
        z    = torch.randn(B, NZ, 1, 1, device=device)
        fake = G(z)
        out  = D(fake.detach())
        loss_D_fake = bce(out, torch.full((B,), fake_label, device=device))
        loss_D = loss_D_real + loss_D_fake
        loss_D.backward(); opt_D.step()

        # --- (2) update G (non-saturating) ---
        opt_G.zero_grad()
        out    = D(fake)                                  # không detach
        loss_G = bce(out, torch.full((B,), 1.0, device=device))
        loss_G.backward(); opt_G.step()

# Sinh sample sau training
G.eval()
with torch.no_grad():
    z = torch.randn(25, NZ, 1, 1, device=device)
    samples = G(z).cpu()                                  # (25, 1, 32, 32)

Một số chi tiết thực dụng:

  • Normalize MNIST về \( [-1, 1] \) (transforms.Normalize((0.5,), (0.5,))) để khớp output tanh của \( G \).
  • bias=False trong conv khi sau là BatchNorm (BN có bias riêng).
  • Label smoothing: target real = 0.9 thay vì 1.0 → \( D \) không quá tự tin, gradient cho \( G \) không vanish quá sớm.
  • Mỗi epoch sinh fixed-noise sample (cùng \( z \) qua epoch) để xem chất lượng tiến triển.
  • Trên MNIST, DCGAN cho chữ số nhận diện được sau 5–10 epoch trên GPU thường.

Tutorial chuẩn: PyTorch DCGAN Tutorial trên CelebA, có code đầy đủ.

21

Bài tập

  1. Train DCGAN trên MNIST với code skeleton mục 20, 20 epoch, batch 128. Sinh grid 25 ảnh sau mỗi 5 epoch. Quan sát: epoch nào chữ số bắt đầu nhận ra được, epoch nào nhìn "thật"?
  2. Plot loss \( D \) và loss \( G \) theo iteration. Mô tả pattern bạn thấy: có giảm đều không, có dao động không? So sánh với loss của một model thường (vd CNN classification) bạn từng train.
  3. Implement label smoothing: real target 0.9 thay vì 1.0. So sánh FID hoặc visual sample sau 20 epoch giữa có và không label smoothing. Khác biệt rõ rệt không?
  4. Tắt BatchNorm trong cả \( G \) và \( D \) và train lại. Mô tả hiện tượng (thường mode collapse hoặc không hội tụ). Bật lại chỉ ở \( G \). Bật lại chỉ ở \( D \). Cấu hình nào ổn nhất?
  5. Tăng \( G \) learning rate gấp 5 lần \( D \). Quan sát ảnh hưởng. Đảo ngược: tăng \( D \) lr gấp 5 lần \( G \). Cả 2 trường hợp đều cần bao lâu để fail, fail kiểu gì?
  6. Sau khi train xong, lấy 1000 sample của \( G \) và 1000 ảnh real MNIST, compute FID bằng torchmetrics.image.fid.FrechetInceptionDistance. Giá trị FID là bao nhiêu? Train thêm 20 epoch, FID có giảm không?
  7. Interpolate trong noise space: chọn \( z_1, z_2 \sim \mathcal{N}(0, I) \), sinh ảnh tại \( z_t = (1-t) z_1 + t z_2 \) với \( t \in \{0, 0.1, \ldots, 1\} \). Quá trình chuyển có mượt không? Có khác interpolation trong latent space VAE bài 43?
  8. So sánh tay-tay output của DCGAN (bài này) với VAE (B43) cùng train trên MNIST. Ảnh nào sắc nét hơn? Ảnh nào diverse hơn (đếm số chữ số khác nhau xuất hiện trong 100 sample)?
Gợi ý đáp án ngắn
  1. MNIST DCGAN: chữ số nhận ra được sau epoch 3–5, "thật" sau epoch 10–15. Tuỳ seed có khi sớm hơn.
  2. Loss \( D \) và \( G \) dao động không đơn điệu — đặc trưng GAN. Khác hẳn CNN classification (loss giảm trơn).
  3. Label smoothing thường cải thiện FID 5–15%, đặc biệt ở giai đoạn cuối training. Sample ít artefact "trắng quá".
  4. Không BN: thường stuck, không hội tụ. BN chỉ ở \( G \): ổn. BN chỉ ở \( D \): khá. BN cả 2 (DCGAN gốc): ổn nhất với BCE; với WGAN-GP thì bỏ BN ở \( D \).
  5. \( G \) lr cao: \( G \) chạy quá nhanh, sinh artefact lạ \( D \) chưa kịp học. \( D \) lr cao: \( D \) hoàn hảo quá nhanh, \( G \) vanishing gradient, output không tiến.
  6. DCGAN MNIST sau 20 epoch FID thường ~20–40 (tuỳ implementation chính xác chuẩn nào). Train thêm thường giảm tiếp đến epoch 50, sau đó tăng giảm bất thường.
  7. DCGAN interpolation mượt nếu \( z \) gần nhau nhưng có thể "nhảy" chữ số tại giữa. VAE B43 mượt hơn nhờ KL pull latent có structure.
  8. DCGAN sắc nét hơn, đường nét rõ. VAE mờ nhưng cover đủ 10 chữ số. DCGAN có thể chỉ sinh 7–9 chữ số nếu nhẹ mode collapse.
22

Tổng kết Module 6 và Series 3

Module 6 (B41–B44) đi qua nền tảng generative:

Bài Chủ đề Đóng góp chính
B41 Autoencoder Encoder–decoder cho compress + reconstruct, latent bottleneck.
B42 Denoising AE Train AE với input noise, học representation robust.
B43 VAE Latent xác suất, ELBO = reconstruction + KL, reparameterization trick.
B44 GAN Adversarial training, minimax, mode collapse, variants.

Toàn Series 3 (44 bài) qua 6 module:

Module Phạm vi bài Chủ đề
1 B01–B12 ANN nền tảng: perceptron, MLP, activation, forward/backprop, loss.
2 B13–B21 PyTorch: tensor, autograd, nn.Module, dataset/dataloader, training loop, GPU, optimizer.
3 B22–B27 Regularization: overfitting, dropout, BatchNorm, data aug, weight init, early stopping, lr schedule.
4 B28–B33 CNN: vì sao CNN, conv/pooling/flatten, CIFAR-10, kiến trúc kinh điển, transfer learning.
5 B34–B40 RNN: sequential data, vanilla RNN, vanishing, LSTM/GRU, time series, seq2seq + attention.
6 B41–B44 Generative: AE, denoising AE, VAE, GAN.

Sau Series 3, bạn có nền tảng:

  • Toán: gradient descent, backprop, chain rule, xác suất cho ELBO/KL/JSD (Series 1).
  • ML cổ điển: feature engineering, train/val split, regularization, evaluation (Series 2).
  • Deep learning: ANN, CNN, RNN, attention, generative — đủ để đọc paper foundation và implement bằng PyTorch.

Series 4 — LLM & Generative AI tiếp tục với:

  • Transformer architecture (Vaswani 2017): self-attention, multi-head, positional encoding.
  • LLM cụ thể: GPT family, Claude, LLaMA, Mistral; pretraining và alignment (RLHF, DPO).
  • Prompt engineering, in-context learning, chain-of-thought.
  • RAG (retrieval augmented generation), vector database.
  • Fine-tuning (LoRA, QLoRA), tool use, agent.

Kiến thức Series 1 (toán), Series 2 (ML), Series 3 (DL) tiếp tục dùng trực tiếp — đặc biệt attention từ B40 và generative training từ Module 6.

23

Tóm tắt

  • GAN (Goodfellow 2014): generator \( G \) sinh fake từ noise \( z \sim \mathcal{N}(0, I) \), discriminator \( D \) phân biệt real vs fake.
  • Minimax objective: \( \min_G \max_D V(D, G) = \mathbb{E}_x[\log D(x)] + \mathbb{E}_z[\log(1 - D(G(z)))] \). Equilibrium: \( p_g = p_{\text{data}} \), \( D = 1/2 \).
  • Training loop alternating: 1 bước update \( D \) (BCE real=1, fake=0), 1 bước update \( G \).
  • Non-saturating loss cho \( G \): \( -\log D(G(z)) \) thay \( \log(1 - D(G(z))) \) — gradient không vanish khi \( G \) còn yếu.
  • Vấn đề: equilibrium không stable, vanishing gradient cho \( G \) khi \( D \) quá mạnh, mode collapse, oscillation.
  • Mode collapse: \( G \) chỉ sinh vài mode. Khắc phục: mini-batch discrimination, unrolled GAN, WGAN, spectral norm.
  • DCGAN (Radford 2015): conv-based, BatchNorm, LeakyReLU, Adam \( \beta_1 = 0.5 \) lr \( 2 \times 10^{-4} \). Standard skeleton image GAN.
  • Loss variants: WGAN (Wasserstein, 1-Lipschitz), WGAN-GP (gradient penalty), LSGAN (MSE), RaGAN (relativistic), hinge (BigGAN).
  • Variants chính: Pix2Pix (paired translation), CycleGAN (unpaired), ProGAN (progressive), BigGAN (ImageNet conditional), StyleGAN/2/3 (face high-res).
  • GAN vs VAE: GAN sắc nét, train khó, không likelihood; VAE mờ, train stable, có ELBO.
  • GAN vs Diffusion: GAN 1 forward — nhanh; Diffusion nhiều step — chất lượng và control tốt hơn cho text-to-image hiện đại.
  • Use case: image generation, super-resolution, image translation, augmentation, anomaly detection, inpainting. Deepfake có ethical concern.
  • Evaluation: Inception Score, FID (chuẩn), Precision/Recall, visual inspection.
  • PyTorch DCGAN skeleton cho MNIST: \( G \) ConvTranspose + BN + ReLU + tanh; \( D \) Conv + BN + LeakyReLU + sigmoid. Label smoothing real = 0.9 hữu ích.
  • Module 6 (B41–B44) hoàn tất Generative foundation. Series 3 (44 bài, 6 module) hoàn tất nền Deep Learning. Series 4 tiếp tục với Transformer + LLM + RAG + agent.