AI Knowledge Tree

Generative adversarial networks

Train a generator and discriminator with the minimax objective; recognize mode collapse and instability.

⏱ 28 minPrerequisites:Autoencoders

A generative adversarial network (GAN) trains two networks at war: a generator tries to produce samples that look like real data, and a discriminator tries to tell real from fake. The equilibrium of the game is a generator that matches the data distribution. GANs dominated image generation from 2014 to 2021 and remain useful where sample quality matters and speed matters more than likelihood.

The minimax game

Goodfellow et al. (2014). Generator Gθ:Z→XG_\theta: \mathcal{Z} \to \mathcal{X} maps random noise z∼p(z)z \sim p(z) to samples[1]; discriminator Dϕ:X→[0,1]D_\phi: \mathcal{X} \to [0, 1] assigns probability that an input is real. The objective:

min⁡θmax⁡ϕ V(θ,ϕ)=Ex∼pdata[log⁡Dϕ(x)]+Ez∼p(z)[log⁡(1−Dϕ(Gθ(z)))].\min_\theta \max_\phi\ V(\theta, \phi) = \mathbb{E}_{x \sim p_{\text{data}}}[\log D_\phi(x)] + \mathbb{E}_{z \sim p(z)}[\log(1 - D_\phi(G_\theta(z)))].

The discriminator maximizes its log-likelihood of correctly classifying real vs fake; the generator minimizes the discriminator's success.

At the unique optimum:

  • D⋆(x)=pdata(x)/(pdata(x)+pg(x))D^\star(x) = p_{\text{data}}(x) / (p_{\text{data}}(x) + p_g(x)).
  • pg=pdatap_g = p_{\text{data}} when this is achievable.

The proof is short: substitute D⋆D^\star back into the objective; what remains is the Jensen-Shannon divergence between data and model distributions, minimized when they are equal.

Training procedure

Alternate updates:

  1. Sample a batch of real data {xi}\{x_i\} and a batch of noise {zi}\{z_i\}.
  2. Discriminator step: compute the loss using both real and fake samples; update ϕ\phi.
  3. Generator step: sample new noise; compute the loss using only fake samples; update θ\theta.
  4. Repeat.

In practice the generator is trained with the non-saturating loss −log⁡D(G(z))-\log D(G(z)) instead of log⁡(1−D(G(z)))\log(1 - D(G(z))). The original form saturates when the discriminator is winning (gradient vanishes); the non-saturating form has stronger gradient early in training.

Mode collapse

The pathological failure mode of GAN training: the generator finds a single (or a few) modes that fool the discriminator and produces only those, ignoring the rest of the data distribution.

Causes:

  • The minimax game has saddle points where the generator can stably produce limited variety.
  • The discriminator can't always tell "you're missing modes" because it only sees one batch at a time.

Mitigations:

  • Minibatch discrimination: discriminator sees a batch and looks at diversity across samples.
  • Spectral normalization, gradient penalty: improve discriminator training stability.
  • Diverse architectures: BigGAN, StyleGAN have specific architectural choices that resist collapse.

Wasserstein GAN

Arjovsky et al. (2017). Replace the JS-divergence-based loss with the Wasserstein distance (Earth Mover's distance)[2]:

min⁡θmax⁡ϕ∈1-LipschitzEx[Dϕ(x)]−Ez[Dϕ(Gθ(z))].\min_\theta \max_{\phi \in \text{1-Lipschitz}} \mathbb{E}_{x}[D_\phi(x)] - \mathbb{E}_z[D_\phi(G_\theta(z))].

The 1-Lipschitz constraint on DD is enforced via weight clipping (WGAN) or gradient penalty (WGAN-GP).

Why it helps: the JS divergence saturates when distributions don't overlap, giving no gradient. The Wasserstein distance always has a useful gradient. Training is more stable, and the discriminator loss correlates with sample quality (useful for monitoring).

DCGAN

Radford et al. (2015). The first practical recipe for stable image GAN training:

  • Generator: stack of transposed convolutions with BatchNorm and ReLU.
  • Discriminator: stack of strided convolutions with BatchNorm and LeakyReLU.
  • No fully connected layers except at the bottleneck.
  • Tanh output for the generator (matching x∈[−1,1]x \in [-1, 1]).

DCGAN demonstrated that GANs could produce coherent natural images and enabled the field to scale up.

Evaluation

GANs don't have a likelihood, so evaluation is tricky:

  • Inception Score (IS): based on a pretrained classifier; rewards confident, diverse classifications. Known to be flawed.
  • Fréchet Inception Distance (FID): compute feature statistics for real and generated samples in Inception space; report the Fréchet distance. Standard metric for image GANs.
  • Precision and recall: separate fidelity (precision) from diversity (recall).
  • Human evaluation: ultimately the gold standard for visual quality.

What killed GANs (for a while)

Diffusion models surpassed GANs on most image-generation benchmarks around 2021-2022. The pure-GAN line lost the lead, although:

  • Training-time efficiency: GANs sample in one forward pass; diffusion needs many.
  • Latent space structure: StyleGAN's latents are exceptionally manipulable.
  • Combined methods: many state-of-the-art image models use adversarial losses together with diffusion or autoencoder losses (e.g., consistency models, VQ-GAN).

GANs are not dead; they're a tool in a larger toolbox.

Mode collapse: the data has two modes but the generator covers only one. The optimal discriminator D*(x)=p_data/(p_data+p_g) stays near 1 on the abandoned mode: the gradient signal that should pull the generator to cover it.
Mode collapse: the data has two modes but the generator covers only one. The optimal discriminator D*(x)=p_data/(p_data+p_g) stays near 1 on the abandoned mode: the gradient signal that should pull the generator to cover it.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# 1D toy: real data is a mixture of two Gaussians at -2 and +2
def real_data(n): return np.concatenate([rng.normal(-2, 0.3, n // 2), rng.normal(2, 0.3, n // 2)])

# Tiny generator: z -> linear -> output. Tiny discriminator: x -> linear -> sigmoid.
d_z = 4
W_g1 = rng.normal(scale=0.3, size=(d_z, 8))
W_g2 = rng.normal(scale=0.3, size=(8, 1))
W_d1 = rng.normal(scale=0.3, size=(1, 8))
W_d2 = rng.normal(scale=0.3, size=(8, 1))

def relu(x): return np.maximum(0, x)
def sigmoid(z): return 1 / (1 + np.exp(-z))

def G(z): return relu(z @ W_g1) @ W_g2
def D(x): return sigmoid(relu(x[:, None] @ W_d1) @ W_d2).ravel()

def disc_loss(real, fake):
    return -(np.log(D(real) + 1e-8) + np.log(1 - D(fake) + 1e-8)).mean()

def gen_loss(fake):
    return -np.log(D(fake) + 1e-8).mean()

# Show initial discriminator behavior on real vs fake samples
n_batch = 64
x_real = real_data(n_batch)
z = rng.normal(size=(n_batch, d_z))
x_fake = G(z).ravel()

print(f"discriminator D(real) average: {D(x_real).mean():.3f}")
print(f"discriminator D(fake) average: {D(x_fake).mean():.3f}")
print(f"discriminator loss: {disc_loss(x_real, x_fake):.3f}")
print(f"generator loss:     {gen_loss(x_fake):.3f}")
print(f"fake-sample range: [{x_fake.min():.2f}, {x_fake.max():.2f}]")
print(f"real-sample range: [{x_real.min():.2f}, {x_real.max():.2f}]")

The discriminator initially has near-random output, the generator produces noise; training would alternate updates until DD cannot distinguish G(z)G(z) from real data.

Exercises

Exercise 1. At a fixed generator, the discriminator maximizes V=Ex∼pdata[log⁡D(x)]+Ex∼pg[log⁡(1−D(x))]V = \mathbb{E}_{x\sim p_{\text{data}}}[\log D(x)] + \mathbb{E}_{x\sim p_g}[\log(1 - D(x))]. Derive the optimal discriminator D⋆(x)=pdata(x)/(pdata(x)+pg(x))D^\star(x) = p_{\text{data}}(x)/(p_{\text{data}}(x) + p_g(x)) by maximizing the integrand pointwise.

Solution

Step 1. Write VV as a single integral over xx by expanding both expectations against their densities:

V=∫[pdata(x)log⁡D(x)+pg(x)log⁡(1−D(x))]dx.V = \int \left[p_{\text{data}}(x)\log D(x) + p_g(x)\log(1 - D(x))\right] dx.

Step 2. For each fixed xx, the bracket depends on D(x)=u∈(0,1)D(x) = u \in (0, 1) through h(u)=alog⁡u+blog⁡(1−u)h(u) = a\log u + b\log(1 - u) with a=pdata(x)a = p_{\text{data}}(x) and b=pg(x)b = p_g(x). Maximize hh pointwise: h′(u)=a/u−b/(1−u)=0h'(u) = a/u - b/(1 - u) = 0.

Step 3. Solve a(1−u)=bua(1 - u) = b u, giving u=a/(a+b)u = a/(a + b). Since h′′(u)=−a/u2−b/(1−u)2<0h''(u) = -a/u^2 - b/(1-u)^2 < 0, this is the maximum. Substituting back,

D⋆(x)=pdata(x)pdata(x)+pg(x).D^\star(x) = \frac{p_{\text{data}}(x)}{p_{\text{data}}(x) + p_g(x)}.

At pg=pdatap_g = p_{\text{data}} the optimal discriminator is 12\tfrac12 everywhere: it cannot tell real from fake, which is the equilibrium the generator drives toward.

Exercise 2. Substitute D⋆D^\star back into VV and show the generator then minimizes 2 DJS(pdata ∥ pg)−log⁡42\, D_{\text{JS}}(p_{\text{data}}\,\|\,p_g) - \log 4, where DJSD_{\text{JS}} is the Jensen-Shannon divergence. Conclude the minimum is at pg=pdatap_g = p_{\text{data}}.

Solution

Step 1. Plug D⋆(x)=pdatapdata+pgD^\star(x) = \tfrac{p_{\text{data}}}{p_{\text{data}} + p_g} and 1−D⋆(x)=pgpdata+pg1 - D^\star(x) = \tfrac{p_g}{p_{\text{data}} + p_g} into VV:

V(D⋆)=Epdata ⁣[log⁡pdatapdata+pg]+Epg ⁣[log⁡pgpdata+pg].V(D^\star) = \mathbb{E}_{p_{\text{data}}}\!\left[\log\frac{p_{\text{data}}}{p_{\text{data}} + p_g}\right] + \mathbb{E}_{p_g}\!\left[\log\frac{p_g}{p_{\text{data}} + p_g}\right].

Step 2. Insert a factor of 22 inside each log and compensate with −log⁡2-\log 2 per term, so the denominators become the mixture m=12(pdata+pg)m = \tfrac12(p_{\text{data}} + p_g):

V(D⋆)=−log⁡4+Epdata ⁣[log⁡pdatam]+Epg ⁣[log⁡pgm].V(D^\star) = -\log 4 + \mathbb{E}_{p_{\text{data}}}\!\left[\log\frac{p_{\text{data}}}{m}\right] + \mathbb{E}_{p_g}\!\left[\log\frac{p_g}{m}\right].

Step 3. The two expectations are DKL(pdata ∥ m)+DKL(pg ∥ m)=2 DJS(pdata ∥ pg)D_{\text{KL}}(p_{\text{data}}\,\|\,m) + D_{\text{KL}}(p_g\,\|\,m) = 2\,D_{\text{JS}}(p_{\text{data}}\,\|\,p_g), by the definition of Jensen-Shannon divergence. So V(D⋆)=2 DJS−log⁡4V(D^\star) = 2\,D_{\text{JS}} - \log 4.

Step 4. DJS≥0D_{\text{JS}} \ge 0 with equality iff pg=pdatap_g = p_{\text{data}}, so the generator that minimizes V(D⋆)V(D^\star) matches the data distribution, reaching the global minimum −log⁡4-\log 4.

Exercise 3 (code). With both distributions known, the optimal discriminator is computable in closed form. On the lesson's two-Gaussian data and a mode-collapsed generator that covers only the right mode, evaluate D⋆D^\star and confirm it stays near 11 on the abandoned left mode, the gradient signal that should pull the generator to cover it.

Solution
python · numpy
import numpy as np

def gauss(x, mu, sd):
    return np.exp(-0.5 * ((x - mu) / sd)**2) / (sd * np.sqrt(2 * np.pi))

# Data: equal mixture of N(-2, 0.3) and N(+2, 0.3)
def p_data(x):
    return 0.5 * gauss(x, -2, 0.3) + 0.5 * gauss(x, 2, 0.3)

# Mode-collapsed generator: only the right mode N(+2, 0.3)
def p_g(x):
    return gauss(x, 2, 0.3)

def D_star(x):
    pd, pg = p_data(x), p_g(x)
    return pd / (pd + pg)

x_left = np.array([-2.0])           # abandoned mode
x_right = np.array([2.0])           # covered mode
print(f"D*(-2) on abandoned mode: {D_star(x_left)[0]:.4f}")
print(f"D*(+2) on covered mode:   {D_star(x_right)[0]:.4f}")

assert D_star(x_left)[0] > 0.99     # near 1: generator missing this mode
assert np.isclose(D_star(x_right)[0], 1/3, atol=1e-2)   # p_g = 2*p_data here

On the abandoned left mode pg≈0p_g \approx 0, so D⋆≈1D^\star \approx 1: the discriminator is certain those points are real, the strong gradient that a non-collapsed generator would follow to cover the mode. On the covered right mode pg=2 pdatap_g = 2\,p_{\text{data}}, so D⋆=1/3D^\star = 1/3.

The next lesson surveys the production GAN families: DCGAN, StyleGAN, BigGAN.


References

  1. [1]Goodfellow, I., et al. (2014). "Generative adversarial nets". NeurIPS. arXiv:1406.2661. The minimax game and the JS-divergence optimum.↩
  2. [2]Arjovsky, M., Chintala, S., & Bottou, L. (2017). "Wasserstein GAN". ICML. arXiv:1701.07875. The Wasserstein objective and its non-vanishing gradient.↩