Generative Adversarial Networks

Generative Adversarial Networks (GANs) are a class of deep generative models in which two neural networks — a Generator and a Discriminator — are trained simultaneously via an adversarial minimax game, with the Generator learning to produce samples indistinguishable from real data and the Discriminator learning to tell them apart. They matter because they unlocked high-fidelity, controllable synthesis of images, video, audio, and text at a scale previously impossible, directly enabling modern creative AI tools and data augmentation pipelines. Remarkably, the original GAN paper was written in a single weekend and initially rejected from a major conference before becoming one of the most cited papers in the history of machine learning.

The core insight

The generator never sees real data directly — it only receives gradient signals from the discriminator telling it how unconvincing its fakes are. This indirect training signal is the entire trick: by pitting a forger against an ever-improving art critic, both are forced to improve until the forger produces work so convincing the critic can do no better than random guessing. The "aha moment" is realizing you don't need to define a tractable likelihood function — you just need a differentiable judge that can be fooled.

How It Works

The Minimax Objective

Let GθG_\theta be the generator parameterized by θ\theta, mapping a latent vector z∼pz(z)z \sim p_z(z) (typically N(0,I)\mathcal{N}(0, I)) to data space, and let DϕD_\phi be the discriminator parameterized by ϕ\phi, outputting a scalar probability that its input is real. The original GAN objective is:

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

Explanation: The discriminator maximizes its ability to assign high probability to real samples and low probability to fake ones, while the generator minimizes the same quantity — pulling in opposite directions on a shared objective. At the global optimum, the optimal discriminator for a fixed generator is:

2
Dϕ∗(x)=pdata(x)pdata(x)+pG(x)\quad D^*_\phi(x) = \frac{p_{\text{data}}(x)}{p_{\text{data}}(x) + p_G(x)}

Explanation: The ideal critic assigns each point a probability proportional to how much more likely it is to come from real data than from the generator, interpolating smoothly between 0 and 1. Substituting D∗D^* back into the objective yields a reformulation in terms of Jensen-Shannon divergence:

3
L(G,D∗)=−log⁡4+2⋅JSD(pdata∥pG)\quad \mathcal{L}(G, D^*) = -\log 4 + 2 \cdot \mathrm{JSD}(p_{\text{data}} \| p_G)

Explanation: At optimality, the generator is being trained to minimize the Jensen-Shannon divergence between the generated distribution and the real data distribution, which equals zero only when the two distributions are identical.

The Non-Saturating Generator Loss

In practice, log⁡(1−Dϕ(Gθ(z)))\log(1 - D_\phi(G_\theta(z))) saturates early in training when the discriminator easily rejects fakes. The standard fix is to flip the generator's loss:

4
LGns=−Ez∼pz[log⁡Dϕ(Gθ(z))]\quad \mathcal{L}_G^{\text{ns}} = -\mathbb{E}_{z \sim p_z}[\log D_\phi(G_\theta(z))]

Explanation: Instead of minimizing the discriminator's confidence in rejecting fakes, the generator now maximizes the discriminator's confidence in accepting them, providing much stronger gradients when outputs are obviously fake.

Wasserstein GAN Distance

The Wasserstein GAN replaces the Jensen-Shannon divergence with the Earth Mover's distance, enforced via a Lipschitz constraint (weight clipping or gradient penalty):

5
LWGAN=Ex∼pdata[fϕ(x)]−Ez∼pz[fϕ(Gθ(z))],∥fϕ∥L≤1\quad \mathcal{L}_{\text{WGAN}} = \mathbb{E}_{x \sim p_{\text{data}}}[f_\phi(x)] - \mathbb{E}_{z \sim p_z}[f_\phi(G_\theta(z))], \quad \|f_\phi\|_L \leq 1

Explanation: The critic fϕf_\phi (no longer a probability) is constrained to be 1-Lipschitz, and its expected output gap between real and fake data is a meaningful, continuous distance metric that provides stable gradients even when distributions do not overlap.

Gradient Penalty (WGAN-GP)

Rather than weight clipping, WGAN-GP enforces the Lipschitz constraint by penalizing the gradient norm at interpolated points x^=ϵx+(1−ϵ)G(z)\hat{x} = \epsilon x + (1-\epsilon)G(z):

6
LGP=λ Ex^[(∥∇x^fϕ(x^)∥2−1)2]\quad \mathcal{L}_{\text{GP}} = \lambda \, \mathbb{E}_{\hat{x}}[(\|\nabla_{\hat{x}} f_\phi(\hat{x})\|_2 - 1)^2]

Explanation: By sampling points between real and generated distributions and penalizing deviations of the critic's gradient norm from 1, we softly enforce the Lipschitz condition without clipping and without introducing optimization artifacts. ---

researcher · Ian Goodfellow | b. 1985 | Invented Generative Adversarial Networks

Goodfellow completed his PhD at the Université de Montréal under Yoshua Bengio, then worked at Google Brain and OpenAI before joining Apple. In a single evening in 2014 — reportedly after an argument with a colleague at a bar — he wrote and ran the first GAN experiment, obtaining convincing results that same night, leading to a landmark NeurIPS 2014 paper that accumulated over 50,000 citations. Curiously, his earlier PhD thesis work on maxout networks and dropout regularization had little surface resemblance to the adversarial training idea he would become famous for.

researcher · Martin Arjovsky | b. 1990 | Developed Wasserstein GAN

Arjovsky completed his PhD at New York University under Léon Bottou and made the critical theoretical observation that when generator and discriminator distributions have non-overlapping support — common in high-dimensional spaces — the Jensen-Shannon divergence becomes useless as a training signal (it is constant at log 2). His 2017 WGAN paper replaced the JS divergence with the Wasserstein-1 (Earth Mover's) distance, fundamentally stabilizing GAN training and inspiring a wave of geometrically motivated generative model research. He later pivoted to causal inference, applying similarly rigorous distributional thinking to out-of-distribution generalization.

---

Figure
GAN Training Architecture
Latent Space z ~ N(0, I) dim=100 Generator G_θ(z) Transposed Convs Real Data x ~ p_data Fake Data G(z) ~ p_G Discriminator D_φ(x) Convolutions Real=1 / Fake=0 sigmoid output ∇θ L flows back to G
Figure
Training Dynamics and Mode Collapse
Ideal Convergence training steps p_data p_G→p_data D*(x) → 0.5 everywhere Mode Collapse p_data (3 modes) p_G Generator ignores 2 modes Loss Curves steps loss D G ln2≈0.693 Oscillation → unstable

---

PyTorch Implementation

```python import torch import torch.nn as nn import torch.optim as optim

# ── Minimal DCGAN-style components ────────────────────────────────────────── class Generator(nn.Module): def __init__(self, z_dim=100, img_channels=1, features=64): super().__init__() self.net = nn.Sequential(

# z_dim → 7×7×(features2) nn.ConvTranspose2d(z_dim, features 2, 7, 1, 0, bias=False), nn.BatchNorm2d(features * 2), nn.ReLU(True),

# 7×7 → 14×14 nn.ConvTranspose2d(features * 2, features, 4, 2, 1, bias=False), nn.BatchNorm2d(features), nn.ReLU(True),

# 14×14 → 28×28 nn.ConvTranspose2d(features, img_channels, 4, 2, 1, bias=False), nn.Tanh(), # output ∈ [-1, 1] ) def forward(self, z): return self.net(z.view(-1, z.shape[1], 1, 1))

class Discriminator(nn.Module): def __init__(self, img_channels=1, features=64): super().__init__() self.net = nn.Sequential(

# 28×28 → 14×14 nn.Conv2d(img_channels, features, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True),

# 14×14 → 7×7 nn.Conv2d(features, features 2, 4, 2, 1, bias=False), nn.BatchNorm2d(features 2), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(features * 2, 1, 7, 1, 0, bias=False), nn.Sigmoid(), ) def forward(self, x): return self.net(x).view(-1)

# ── Training loop ──────────────────────────────────────────────────────────── def train_gan(G, D, loader, z_dim=100, n_epochs=50, device="cuda"): G, D = G.to(device), D.to(device) opt_G = optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999)) opt_D = optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999)) bce = nn.BCELoss() for epoch in range(n_epochs): for real, _ in loader: real = real.to(device) B = real.size(0)

# ── Step 1: Update Discriminator ────────────────────────────── z = torch.randn(B, z_dim, device=device) fake = G(z).detach() # stop gradient through G real_loss = bce(D(real), torch.ones(B, device=device)) fake_loss = bce(D(fake), torch.zeros(B, device=device)) loss_D = (real_loss + fake_loss) / 2 opt_D.zero_grad(); loss_D.backward(); opt_D.step()

# ── Step 2: Update Generator (non-saturating loss) ──────────── z = torch.randn(B, z_dim, device=device) fake = G(z) # fresh forward pass loss_G = bce(D(fake), torch.ones(B, device=device)) # fool D opt_G.zero_grad(); loss_G.backward(); opt_G.step() print(f"Epoch {epoch+1:3d} | D: {loss_D:.4f} | G: {loss_G:.4f}")

# ── WGAN-GP gradient penalty ───────────────────────────────────────────────── def gradient_penalty(D, real, fake, device, lambda_gp=10.0): B = real.size(0) eps = torch.rand(B, 1, 1, 1, device=device) # random interpolation x_hat = (eps real + (1 - eps) fake).requires_grad_(True) d_hat = D(x_hat) grad = torch.autograd.grad( outputs=d_hat, inputs=x_hat, grad_outputs=torch.ones_like(d_hat), create_graph=True, retain_graph=True )[0]

# ||∇D(x_hat)||₂ should equal 1 gp = lambda_gp * ((grad.norm(2, dim=[1,2,3]) - 1) ** 2).mean() return gp ``` ---

Intuition

The Mental Model

Imagine a master forger trying to create paintings indistinguishable from Rembrandt, and a Sotheby's art authenticator whose sole job is to expose them. The forger starts by printing blurry photocopies — obviously fake. The authenticator easily spots them and sends back a rejection slip. The forger studies those rejections, improves: better brushwork, better aging on the canvas. The authenticator upgrades their techniques: UV light, canvas thread-count analysis. This arms race continues until the forger produces work so convincing that even the world's best authenticator is reduced to flipping a coin. The mathematical machinery of GANs