Neither network is told what "realistic" means. At the optimum
For a fixed generator, the best discriminator is
Where it comes from: at each
At a point where the data are 3 times denser than the generator's samples, what is
Lab, section 2: data = ring of 8 Gaussians (standard deviation 0.05); a fixed "generator" = the same ring with standard deviation 0.2.
| distance from a mode centre | ||
|---|---|---|
| 0.00 | 0.941 | 0.957 |
| 0.10 | 0.710 | 0.791 |
| 0.15 | 0.191 | 0.380 |
| 0.30 | 0.000 | 0.001 |
JS: Jensen–Shannon divergence, 0 when the two distributions are equal,
At equilibrium
| quantity | at equilibrium | where it comes from |
|---|---|---|
| 0.5 | ||
| discriminator loss | ||
| generator loss |
A discriminator loss near zero is not progress: it means the discriminator separates real from fake perfectly.
detached (cut from the graph: no gradient reaches Two networks, two optimizers, two opposite objectives. It is a game, not an optimization: each loss is measured against an opponent that keeps moving.
With
| loss | gradient | at |
|---|---|---|
| minimax (2014): minimize |
? | |
| non-saturating: minimize |
? |
The discriminator rejects a fake with
To check the gradients:
| loss | gradient: where it comes from | at |
|---|---|---|
| minimax | ||
| non-saturating |
Discriminator given a 300-step head start, then the generator's gradient under each loss (lab, sections 4 and 8):
| ring | MNIST DCGAN | |
|---|---|---|
| 0.0001 | 0.0005 | |
| ratio per fake, |
≈ 10,000 | ≈ 2,000 |
| measured: ‖∇G‖ non-saturating / minimax | ≈ 8,000× | ≈ 1,900× |
‖∇G‖: norm of the gradient with respect to all of
What training sees:
What is happening: coverage (how many of the ring's 8 modes get samples), measured apart by code the loop never runs.
Eight runs at the default settings, same orange line: 7, 5, 8, 6, 6, 8, 8, 8 modes.
A GAN has no loss you can read: no early stopping, no comparing runs by their curves, no telling whether a checkpoint is better without a separate metric.
That is why diffusion — an ordinary regression loss that goes down — replaced it.
The discriminator loss falls to 0.05, the generator loss climbs to 6. Good news?
Why does the objective allow a generator that makes only a few outputs?
Lab, section 5: the ring, 2,000 steps, three seeds each.
| setting | modes (3 seeds) | loss D | |
|---|---|---|---|
| balanced: both |
7, 8, 7 | 0.45–0.47 | 1.24–1.42 |
| slow D: |
0, 0, 0 | 0.48–0.51 | 1.32–1.43 |
| slow D, |
6, 8, 7 | 0.47–0.50 | 1.22–1.32 |
lr: Adam's learning rate ·
| failure | looks like | why |
|---|---|---|
| mode collapse | a few outputs, whatever |
nothing rewards diversity |
| vanishing gradient | stalls, |
|
| oscillation | quality up, down, up | games need not converge |
| D overfitting | quality improves, then degrades |
The fixes that stuck — WGAN-GP, spectral norm, R1, ADA — do one thing: stop the discriminator from becoming too sharp.
WGAN-GP: Wasserstein GAN with gradient penalty, on the gradient of its discriminator (there called the critic) · spectral norm: bounds how fast
Why
Kahng et al., IEEE VIS 2018 — an interactive GAN trained live in the browser on 2-D data.
| element | in this class |
|---|---|
| green / purple dots | real and generated samples |
| background heatmap | |
| the warped grid (manifold) | |
| pink lines | the direction |
| loss bars vs. Metrics (KL, JS) | what training sees vs. a measurement done apart |
DCGAN: deep convolutional GAN. Upsample + convolve (the CNN chapter: transposed convolutions leave a checkerboard).
| generator | output | discriminator | output |
|---|---|---|---|
| 64 | image | 1 × 28 × 28 | |
Linear(64, 6272) + BN + ReLU |
128 × 7 × 7 | Conv2d(1, 32, 4, 2, 1) + LReLU |
32 × 14 × 14 |
Upsample + Conv2d(128, 64, 3, 1, 1) + BN + ReLU |
64 × 14 × 14 | Conv2d(32, 64, 4, 2, 1) + BN + LReLU |
64 × 7 × 7 |
Upsample + Conv2d(64, 32, 3, 1, 1) + BN + ReLU |
32 × 28 × 28 | Linear(3136, 1) |
1 logit |
Conv2d(32, 1, 3, 1, 1) + Tanh |
1 × 28 × 28 |
Conv2d(in, out, k, stride, padding) · BN: BatchNorm · LReLU: LeakyReLU · Upsample doubles height and width · outputs in channels × height × width.
Side in Upsample; each 3×3 with padding 1 keeps the side.
Three epochs
| layer | count | parameters |
|---|---|---|
Linear(64, 6272) + BN |
420,224 | |
Conv2d(128, 64, 3, 1, 1) + BN |
73,920 | |
Conv2d(64, 32, 3, 1, 1) + BN |
18,528 | |
Conv2d(32, 1, 3, 1, 1) |
289 | |
| generator | 512,961 | |
Conv2d(1, 32, 4, 2, 1) |
544 | |
Conv2d(32, 64, 4, 2, 1) + BN |
32,960 | |
Linear(3136, 1) |
3,137 | |
| discriminator | 36,641 |
Conv: Upsample, ReLU, LReLU and Tanh: none.
Judge: a CNN trained on real MNIST (test accuracy 0.979), over 2,000 samples.
| loss D / loss G | confident | effective classes | ||
|---|---|---|---|---|
| real test images | — | — | 0.91 | 9.94 |
| DCGAN, 3 epochs | 0.99 / 1.22 | 0.33 | 0.48 | 8.71 |
| same, slow D ( |
1.40 / 0.69 | 0.50 | 0.38 | 7.85 |
confident: fraction of samples the judge labels with probability above 0.9 · effective classes:
| model | the contribution |
|---|---|
| DCGAN (2015) | convolutional players, rules that made training reproducible |
| Conditional GAN (2014) | a label fed to both networks |
| pix2pix / CycleGAN (2017) | image-to-image translation, even without pairs |
| WGAN / WGAN-GP (2017) | another distance, a loss that tracks quality |
| StyleGAN 1–3 (2019–21) | the best GAN faces; the editable |
| GigaGAN (2023) | text-to-image, 0.13 s per 512 px image — after the field had moved |
| where | what the discriminator does |
|---|---|
| VAE decoder of latent diffusion | punishes the blur of the reconstruction loss (VQGAN recipe: reconstruction + perceptual + adversarial, VAE chapter) |
| super-resolution | Real-ESRGAN: one low-res image, many sharp answers |
| neural vocoders | HiFi-GAN: spectrogram → waveform |
| 1–4 step distillation | ADD (adversarial diffusion distillation), SDXL-Turbo: "looks real", not "matches the teacher's mean" |
| domain adaptation | features the domain cannot be read from |
Use it when the target is one of many valid answers and their average is itself a bad answer.
Colab notebook, ~2 h. Ring of 8 Gaussians on CPU, then MNIST on a T4 GPU. Each section ends in a checkpoint.
Outline: (1) a loss you cannot write down, and the two players; (2) what the discriminator learns; (3) the two generator losses; (4) the panel: what training sees vs. what happens; (5) mode collapse, provoked and fixed; (6) GAN Lab; (7) a DCGAN on MNIST, measured; (8) where the adversarial loss lives in 2026. Closes with the Colab lab.
The trained D is close, and smoother at the edge: a finite network trained for a finite time.
Default case = the previous slide: D rejects the first fake with D(G(z)) = 0.01. Step line by line: loss_d = 0.0295 (near zero: D is winning), D's own step is tiny. At the last line: σ − 1 = −0.99, c moves −1.00 → −0.71. Switch to minimax: −σ = −0.01, c only reaches −0.99; per fake 99× (and 30×). Then "long head start" (ratio ≈ 9,900, as in the lab), "equilibrium" (1.386 and 0.693, nothing moves) and "D fooled" (the fakes move left, towards where D says real, not towards the data).
Default first: point at the orange line (D(G(z))) near 0.5 and the green line (coverage). Then latent dimension 1: 2 of 8 modes in our run, orange line unchanged. Then retrain at identical settings a couple of times. Minimax trains here because D never gets far ahead.
About 20 minutes. The page has the same session, step by step, tied to the chapter's sections.