LadaGAN 做得太逼真之後:一場生成器與判別器的拉鋸

LadaGAN 做得太逼真之後:一場生成器與判別器的拉鋸

東華大學資工系深度學習基石與實務第二個作業:從 LadaGAN 人臉生成、150K 合成資料,到 EfficientNetV2-B1 判別器的完整實驗記錄。

這是我在國立東華大學資訊工程學系「深度學習基石與實務」課程的第二個作業。作業表面上是在 CelebA 上訓練 GAN、生成一批人臉,實際上卻把三件事綁在一起:生成影像、建立可追蹤的 synthetic dataset,以及訓練一個能分辨真假的模型。

我一開始把問題想得很直覺:生成器越逼真,成果就越好。真正跑過幾輪模型之後,才發現事情沒有這麼單純。生成器越接近真實資料,判別器越難找到可以泛化的線索;判別器若只記住資料前處理或某一個 checkpoint 的風格,漂亮的 accuracy 也沒有太大意義。這篇筆記保留模型失敗、中間 epoch、loss 曲線與評估圖,記錄我如何在清晰度、多樣性、穩定性和運算成本之間做取捨。

Purpose:這份作業在問什麼

這份作業的目標不是只交出一張「看起來像人臉」的圖片,而是一條可以被檢查、被重跑的影像生成與分類流程:

  1. 在 CelebA 上訓練 LadaGAN,產生有多樣性的 64×64 人臉。
  2. 保留不同 epoch 的 checkpoint,從每個 checkpoint 生成固定數量的圖片,建立足夠大的 synthetic dataset。
  3. 把真實圖片和生成圖片混合,訓練多個分類 backbone,找出比較能辨識兩種影像分布的判別器。
  4. 使用最後的 EfficientNetV2-B1 對測試資料做真偽判斷,輸出 test.csv。

所以我想回答的問題不是「能不能生成一張好看的臉」,而是:

當生成器已經做得很逼真時,判別器到底還能不能學到穩定、可泛化的差異?

作業流程:從生成到判別

HackMD 報告把整個工作拆成三個階段。第一階段訓練 LadaGAN;第二階段從生成 checkpoint 建立判別器資料;第三階段用教師提供的 1,000 張測試圖片產生最後的分類結果。

graph LR A["CelebA real faces
64x64"] --> B["LadaGAN training
36 epochs"] B --> C["Checkpoints
epoch 1-30"] C --> D["150K synthetic faces"] A --> E["Real images"] D --> F["300K discriminator set"] E --> F F --> G["EfficientNetV2-B1
240x240"] G --> H["Real / Fake prediction"] H --> I["test.csv
1,000 images"]

三個階段的實際數字

第一階段以 64×64、像素值正規化到 [-1, 1] 的 CelebA 人臉訓練 LadaGAN。訓練曲線記錄到第 36 個 epoch;生成資料的腳本則以第 1 到第 30 個 checkpoint、每個 checkpoint 5,000 張圖片為主要設定:

30 checkpoints × 5,000 images = 150,000 synthetic faces

第二階段再把 150,000 張生成圖片和 202,600 張真實圖片按報告中的比例抽樣,組成 300,000 張判別器資料。報告中提到的分布是 127,650 張 fake、172,350 張 real;這個比例比單純各半抽樣更接近原始資料配置。

第三階段是教師提供的 1,000 張測試圖片,模型輸出 test.csv。這 1,000 張資料是最後要預測的測試集,不能和前面的 30,000 張評估資料混稱。

報告中的流程圖有一處把生成總數標成 180,000,但 generate_image.py 的設定與後續資料描述是 30 個 checkpoint、每個 5,000 張,也就是 150,000 張。本文以實作段落的 150,000 為主,並保留這個差異,避免把兩段數字拼成一個看似精確、其實無法追溯的結論。

LadaGAN 的架構與訓練方式

LadaGAN 的生成器不是單純把卷積層一路堆高,而是把 Transformer-style attention、residual connection 和逐步上採樣放在同一個生成流程裡。報告中記錄的主要設定是:

元件設定
latent noise128 維
generator output64×64 RGB
generator model dimensions[1024, 256, 64]
attention heads[4, 4, 4]
MLP dimensions[512, 512, 512]
discriminator input64×64 影像 patch embedding
discriminator patch size4

我把訓練流程理解成兩個互相拉扯的目標:

  • 生成器要讓 fake image 通過判別器,同時維持 feature matching,避免只追求某一種容易成功的臉型。
  • 判別器要同時看 real 和 fake,學會把生成影像的細微分布差異轉成可用的 binary signal。

報告中的生成器 loss 可以概括成:

generator_loss = adversarial_loss + 10 × feature_matching_loss

判別器則使用 real/fake 的 sigmoid cross entropy。這個 10 不是裝飾性超參數:它直接改變了生成器在「騙過判別器」和「維持特徵分布」之間的重心。

先看失敗:模型演進的四個階段

我沒有把最終的 LadaGAN 當成一開始就知道的答案。實驗中比較過的模型,剛好把 GAN 常見的問題逐一暴露出來:

階段得到的改善暴露出的問題
DCGAN建立最基本的生成流程圖片較模糊、局部紋理不足,容易出現 mode collapse
DCGAN + Self-Attention更早捕捉全局關係,細節變清楚模式集中更嚴重,生成結果可能突然崩潰
WGAN-GP多樣性與訓練穩定度改善gradient penalty 加上多次 critic update,速度約慢 3–4 倍,細節反而變糊
LadaGAN以 Linear Additive-Attention 取得清晰度、多樣性和效率的折衷圖片太像真的之後,判別器更難找到穩定的泛化線索

以下是報告中各階段保留下來的實際 sample grid。epoch 數字是各階段自己的訓練進度,不能直接拿來互相比較;它們的用途是呈現每種架構在當時暴露出什麼問題。

DCGAN epoch 196 generated face samples
DCGAN · epoch 196:建立基準,但局部細節仍不穩定。
Attention DCGAN epoch 18 generated face samples
Attention-DCGAN · epoch 18:細節變清楚,樣式開始集中。
Attention DCGAN epoch 19 generated face samples
Attention-DCGAN · epoch 19:短時間內出現更明顯的模式偏移。
WGAN GP epoch 10 generated face samples
WGAN-GP · epoch 10:多樣性仍在,但影像偏模糊。
WGAN GP epoch 30 generated face samples
WGAN-GP · epoch 30:穩定度改善,細節仍需要更多成本。
WGAN GP epoch 60 generated face samples
WGAN-GP · epoch 60:沒有明顯 collapse,但清晰度沒有跟上。
LadaGAN epoch 36 generated face samples
LadaGAN · epoch 36:清晰度與多樣性的折衷結果。

為什麼 attention 不是萬靈丹

Self-Attention 讓模型可以在更大的範圍內交換資訊,這確實有助於眼睛、鼻子、臉型等區域形成比較一致的關係。但它也會把「一組已經成功的特徵組合」快速傳播到更多位置。當梯度主要鼓勵某幾種臉型時,attention 可能讓這些成功模式被反覆複製,清晰度提高的同時,多樣性反而下降。

WGAN-GP 解決的是另一個問題:讓 critic 的學習訊號更平滑,降低 mode collapse 的風險。但每次 generator update 前需要多次 critic update,再加上 gradient penalty,訓練成本明顯增加。這就是為什麼我最後沒有只看「哪一個模型的理論保證比較漂亮」,而是回到這份作業要交付的完整 pipeline,選擇成本可接受、輸出也足夠清楚的 LadaGAN。

36 個 epoch 的 loss:生成器變強,也讓判別器更難工作

下面是 HackMD 報告中的原始 loss 與 score 圖。左側是 generator/discriminator loss,右側是 real/fake score;圖上的 fake score 是判別器記錄的 raw score,不是 sigmoid 後的機率。

LadaGAN epoch 36 generator discriminator loss and real fake score curves
LadaGAN 第 36 個 epoch 的 loss 與 real/fake score。圖片來自 HackMD 原始報告。

報告把曲線分成四個階段,這樣比只看最後一個數字更能看出訓練發生了什麼:

訓練階段Generator lossDiscriminator lossReal scoreFake score
epoch 0 → 53.74 → 0.981.55 → 1.170.26 → 0.38-3.71 → -0.44
epoch 6 → 150.98 → 0.951.17 → 1.180.38 → 0.35-0.44 → -0.38
epoch 16 → 250.95 → 0.941.18 → 1.190.35 → 0.34-0.38 → -0.36
epoch 26 → 360.94 → 0.941.19 → 1.190.34 → 0.34-0.36 → -0.36

最後一個記錄點是:

  • generator loss:0.936
  • discriminator loss:1.185
  • real score:0.339
  • fake score:-0.358

我對這張圖的解讀是:前五個 epoch 是主要的快速學習期,生成器 loss 大幅下降,real/fake score 也快速靠近相對穩定的區間;第 6 個 epoch 之後,兩個 loss 幾乎進入平台期。這不代表生成器「停止變好」,而是代表後面的改善需要從圖片品質和多樣性觀察,不能只靠 loss 繼續下降來判斷。

把每個 checkpoint 都留下來

這一組圖是報告中不同 epoch 的實際生成結果。每張大圖是 contact sheet,裡面包含同一個 checkpoint 產生的多張人臉;我保留 epoch 1、2、5、10、15、20、25、30、36,讓讀者可以沿著同一條時間軸看品質與多樣性如何變化。

LadaGAN epoch 1 generated samples
Epoch 01
LadaGAN epoch 2 generated samples
Epoch 02
LadaGAN epoch 5 generated samples
Epoch 05
LadaGAN epoch 10 generated samples
Epoch 10
LadaGAN epoch 15 generated samples
Epoch 15
LadaGAN epoch 20 generated samples
Epoch 20
LadaGAN epoch 25 generated samples
Epoch 25
LadaGAN epoch 30 generated samples
Epoch 30
LadaGAN epoch 36 generated samples final
Epoch 36 · final sample grid

我在這裡特別不挑一張最漂亮的臉當 hero image。對 GAN 來說,整個 grid 更重要:它能讓人看見是否只剩一兩種臉型、背景是否反覆出現、細節變清楚時多樣性有沒有同步保留。

判別器:從接近 coin flip 到 EfficientNetV2-B1

生成器完成之後,真正的第二個實驗才開始。判別器資料不是只拿一張 real 和一張 fake,而是把不同 checkpoint 的生成結果集中起來,再和真實資料混合。這個設計讓判別器面對的是一個比較寬的 fake distribution,而不是某一個 epoch 的單一風格。

Backbone 比較

初期比較 EfficientNet B0–B5 和 MobileNetV3-Large 時,結果一度接近 coin flip。報告把這段失敗保留下來,因為它提醒我:模型名稱更大,不代表在目前的輸入尺寸、資料比例和 preprocessing 下就一定更會分類。

Backbone報告中的觀察我怎麼使用這個結果
EfficientNet B0–B5validation 約 0.48–0.53視為初期 baseline,沒有直接沿用
MobileNetV3-Large從頭訓練約 0.76有改善,但仍不足以支撐最後判斷
EfficientNetV2-B1README 摘要約 90% validation;HackMD 的 30,000 張評估接近 99.907%作為最後的判別器

EfficientNetV2-B1 的設定

最後使用 ImageNet 預訓練的 EfficientNetV2-B1,輸入尺寸調整為 240×240×3。backbone 大部分先 freeze,只解凍最後 20 層做微調;分類 head 則是:

GlobalAveragePooling2D → Dropout(0.3) → Dense(256, relu) → BatchNorm → Dropout(0.5) → Dense(1, sigmoid)

訓練時使用 mixed precision、AdamW(learning rate 1e-4、weight decay 1e-5),並搭配 EarlyStopping、ReduceLROnPlateau 和 ModelCheckpoint。這裡的重點不是把網路堆到最大,而是讓輸入尺度、資料數量和微調範圍一起對齊。

EfficientNetV2 training and validation history with accuracy overfit and learning rate curves
EfficientNetV2-B1 的訓練歷史圖。原始檔名保留報告中的命名。

這張圖比單一 validation accuracy 多提供了三種線索:

  1. loss 是否一起下降,還是只有 accuracy 看起來變好。
  2. train/validation 的差距是否逐步放大,作為 overfit 警訊。
  3. learning rate 調整後,模型是否還能繼續改善。

30,000 張評估資料的結果

EfficientNetV2 discriminator ROC PR curve confusion matrix and evaluation metrics
報告中的 discriminator evaluation:ROC/PR、confusion matrix 與進階指標。

依照 HackMD 評估圖中的 confusion matrix:

  • 12,755 張 fake 被正確判為 fake。
  • 0 張 fake 被誤判為 real。
  • 28 張 real 被誤判為 fake。
  • 17,217 張 real 被正確判為 real。
  • 總數 30,000 張,正確 29,972 張,計算為約 99.907%。

圖中另外列出 balanced accuracy 0.9992、sensitivity 0.9984、specificity 1.0000,ROC AUC 和 PR AUC 都是 1.0。這些結果很亮眼,但不能只用一句「判別器很強」帶過:它們也可能表示 fake/real 的資料處理流程仍留下了某些穩定的 domain cue,值得在下一輪實驗中用跨來源資料或更嚴格的 split 驗證。

我刻意保留的數據界線

這份專案的 README 和 HackMD 報告有不同層次的結果摘要:

  • README 寫的是約 90% validation accuracy。
  • HackMD 另外記錄 30,000 張評估資料中 28 張誤判,也就是約 99.907%。
  • 作業最後還有 1,000 張教師測試圖片,輸出 test.csv;沒有可對照的公開標籤時,不能把它宣稱成同樣的 accuracy。

我把這三個數字分開寫,因為它們很可能對應不同的 split、不同階段或不同評估設定。除非補上 manifest、切分方式與每個數字的 checkpoint,否則把它們合併成一個「最後準確率」會讓文章看起來完整,卻無法真正重現。

工程問題:150K 張圖不是按一下就完成

GPU 記憶體

一次把 150,000 張圖片全部放進記憶體,生成階段很容易遇到 GPU memory overflow。我最後採用 batch generation,定期清理 TensorFlow session,並在適合的地方使用 mixed precision。這些做法不改變模型概念,但決定了實驗能不能跑完。

訓練環境

主要環境是 autoDL 上的 RTX 4090 24GB、Ubuntu 20.04、Python 3.9.16、TensorFlow 2.13.1、PyTorch 2.0.1、CUDA 11.8、cuDNN 8.6;另外在 Ubuntu 22.04、Python 3.10.12 的本地環境做驗證。

HackMD 附錄另出現 PyTorch 2.7.1 和 120GB RAM 的紀錄,我把它視為附錄中的環境快照,而不是把不同段落拼成一套「唯一正確」的環境。若要重新跑,我會把版本、資料 manifest、checkpoint 命名和 random seed 鎖在同一份設定檔。

分類不應只看模型大小

初期模型接近 coin flip 時,我沒有直接換成更大的 backbone,而是一起檢查:

  • fake/real 的數量比例與 label 產生腳本。
  • 生成圖片與真實圖片的 resize、normalize 和色彩格式。
  • EfficientNet 的輸入尺寸是否適合目前的資訊量。
  • validation split 是否把同一個 checkpoint 的風格洩漏到 train 和 validation。
  • classification head 是否太容易記住背景、壓縮痕跡或前處理差異。

這些檢查比單純把 B0 換成 B5 更接近問題本身。

報告中的主要取捨

HackMD 將 LadaGAN 的 FID 記錄為 1.81,並以 SOTA-level 描述;報告也記錄約 19M 參數、單張 RTX 4090/3080Ti 上 30 epochs 小於 5 小時。因為這些數字缺少完整的 benchmark 設定、資料版本和對照組,我在這篇文章把它們標成「報告中的結果」,不把 FID 1.81 直接宣稱成可以和所有外部論文公平比較的結論。

這次最重要的取捨可以濃縮成下面這張表:

方案清晰度多樣性collapse成本整體判斷
DCGAN + Attention嚴重細節好看,但不夠可靠
WGAN-GP明顯改善約 3–4×穩定,但速度與細節成本高
LadaGAN輕微中等、報告記錄小於 2×最符合這次作業的交付條件

我從這份作業真正學到的事

第一,生成模型的品質不是單一排行榜。清晰度、多樣性、訓練穩定性和生成成本必須放在同一張表上看;只挑最漂亮的 sample,會忽略 mode collapse 和不可重現性。

第二,判別器的 accuracy 必須連同資料切分一起解讀。當 fake image 來自多個 checkpoint,模型可能學到 checkpoint 的壓縮痕跡、背景或 resize 差異,而不是真正的「生成影像特徵」。這也是為什麼我把 90% validation、30,000 張評估和 1,000 張教師測試資料分開記錄。

第三,失敗的模型不是多餘的歷史。Attention-DCGAN 讓我看到清晰度和多樣性的衝突;WGAN-GP 讓我看到穩定性有實際的運算成本;最後選 LadaGAN,是因為它在這個作業需要的 pipeline 中取得比較合理的平衡,而不是因為它在所有指標都絕對最好。

如果再做一次

下一輪我會先補上資料治理,再追求更高的指標:

  • 為每張圖片保存 source_type、source_epoch、checkpoint、seed 和 preprocessing 版本。
  • 明確分開 train、validation、hold-out test,避免同一個 checkpoint 的風格同時出現在不同 split。
  • 對每個 epoch 做固定 seed 的 sample grid,並加上 FID、KID、precision/recall for generative models。
  • 讓判別器在跨 checkpoint、跨來源與不同壓縮設定的資料上測試,確認它學到的是生成分布,而不是資料管線的指紋。
  • 用同一份 environment lock 和命名規則重跑全部 pipeline,讓別人可以從 train_celeba.py、generate_image.py、LabelData.py、efficientnetv2.py 到 test_face.py 逐段核對。