ml-models / Generative modeling / Generative adversarial networks core

Generative adversarial networks

A GAN trains a generator to turn noise into samples by pitting it against a discriminator that tries to tell those samples from real data, with no likelihood ever written down. This page derives the optimal discriminator and the Jensen–Shannon objective on a discrete example, shows why the original generator loss saturates, why training can circle forever instead of converging, and how DCGAN, Wasserstein critics, gradient penalties, spectral norm and FID fit into PyTorch.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. The minimax game
  5. The optimal discriminator and Jensen–Shannon
  6. The non-saturating generator loss
  7. Generators, discriminators and DCGAN
  8. Wasserstein critics and Lipschitz constraints
  9. Convergence and regularisation
  10. Worked example
  11. The value at the optimal discriminator
  12. Disjoint supports
  13. Saturation in numbers
  14. The Dirac-GAN, simulated
  15. Fréchet distance
  16. Implementation
  17. Tradeoffs
  18. Connections
  19. Read first
  20. Read next
  21. References

Problem

A generative model should produce new samples that look like the training data: images, audio, molecules. The variational autoencoder does this by maximising a bound on the log-likelihood, and the autoregressive models by factorising the likelihood exactly. Both need a density they can evaluate, and for images both pay for it: a VAE's Gaussian decoder averages over plausible outputs and blurs them, and an autoregressive model generates one pixel or token at a time.

The question a GAN asks instead is whether a network could be trained to produce samples directly, in one forward pass from random noise, judged only by whether they can be told apart from real ones. There is no formula for "looks real", so a second network learns one: the discriminator. The generator's loss is whatever the discriminator currently thinks of its samples, which makes training a two-player game rather than the minimisation of a fixed function, and most of what is hard about GANs follows from that.

Intuition

A forger and a detective. The detective is shown a mix of real banknotes and forgeries and learns what gives the forgeries away. The forger never sees a real note; it only learns, through the detective's verdict, which direction would have made a forgery more convincing. Each improves against the other, and if the game ends well the forgeries are indistinguishable and the detective can do no better than guess, outputting ½ for everything.

Two failure modes are already visible in the story. If the detective becomes too good too fast, its verdict on every forgery is "certainly fake" and says nothing about how to improve, so the forger stops learning. And the forger can fool a detective by perfecting one kind of note and never producing the others: this is mode collapse, and nothing in the detective's job of judging one sample at a time directly forbids it.

Data distribution, a one-mode generator and the optimal discriminator over eight bins Eight bins on the horizontal axis. The data distribution has two modes: probabilities 0, 0.1, 0.3, 0.1, 0, 0.1, 0.3, 0.1. The one-mode generator puts 0, 0.2, 0.6, 0.2, 0, 0, 0, 0, covering only the first mode. Dots show the optimal discriminator D* = p_data / (p_data + p_g): undefined, 0.33, 0.33, 0.33, undefined, 1.00, 1.00, 1.00. It is one third on the covered mode, where the generator puts twice as much mass as the data, and 1 on the dropped mode, where only real samples appear. 0 0.2 0.4 0.6 0 0.5 1 0 1 2 3 4 5 6 7 probability D*(x), right axis data p_data generator p_g (one mode) ● optimal discriminator bins 0 and 4 have no mass under either, so D* is undefined there

The optimal discriminator is a likelihood ratio in disguise. Where the generator overproduces, D* drops below ½; on the mode it drops, D* is exactly 1, the signal that should push mass back there, but the generator only receives gradients at its own samples, all of which sit in the first mode.

Mechanics

The minimax game

A generator G maps a noise vector z ~ pz (usually a standard normal) to a sample G(z); the samples have some distribution pg that is never written down. A discriminator D maps a sample to the probability that it is real. Goodfellow et al. defined the game

minG maxD V(D, G) = 𝔼x~pdata[log D(x)] + 𝔼z~pz[log(1 − D(G(z)))]

The discriminator's half is ordinary binary cross-entropy, as on the loss functions page and in logistic regression: real samples labelled 1, generated samples labelled 0. Training alternates one or more gradient steps on D with one step on G, each using a minibatch of real data and a minibatch of noise; the generator's gradient flows back through D into G by ordinary backpropagation, with D's weights held fixed for that step.

The optimal discriminator and Jensen–Shannon

For a fixed generator, V is maximised pointwise. At each x the discriminator chooses y = D(x) to maximise pdata(x) log y + pg(x) log(1 − y), and setting the derivative to zero gives

D*(x) = pdata(x) / (pdata(x) + pg(x))
V(D*, G) = −log 4 + 2 · JS(pdata ‖ pg)

where JS is the Jensen–Shannon divergence, the average KL divergence of each distribution from their mixture M = (pdata + pg)/2. JS is zero only when the two distributions are equal, so against a perfect discriminator the generator's best move is pg = pdata, where D* = ½ everywhere and V = −log 4 ≈ −1.3863. Unlike KL, JS is symmetric and bounded by log 2, and that bound is both a strength (it stays finite when supports differ) and, below, the source of a weakness.

The non-saturating generator loss

Early in training the generator's samples are poor and D rejects them confidently. Write D(x) = σ(a) with a the discriminator's logit. The generator's term log(1 − σ(a)) has derivative −σ(a) with respect to a, which goes to zero exactly when D is confident the sample is fake (a ≪ 0): the loss saturates where the generator most needs a signal. Goodfellow et al. therefore train G to maximise log D(G(z)) instead, whose derivative is 1 − σ(a), near 1 in the same region. This non-saturating loss has the same fixed point, and it is what almost every implementation uses: BCEWithLogitsLoss on D's logits for the fakes, with the target label set to 1.

Generator gradient magnitude against the discriminator's logit for the two losses Horizontal axis: the discriminator logit a on a generated sample, from −6 (confidently fake) to 4 (believed real). Vertical axis: the magnitude of the generator loss's derivative with respect to a, from 0 to 1. The minimax loss log(1 − σ(a)) has gradient magnitude σ(a): 0.0025 at a = −6, 0.0474 at a = −3, 0.5000 at a = 0, 0.9526 at a = 3. The non-saturating loss −log σ(a) has gradient magnitude 1 − σ(a): 0.9975 at a = −6, 0.9526 at a = −3, 0.5000 at a = 0, 0.0474 at a = 3. The curves cross at a = 0 with value ½. 0 0.5 1 −6 −4 −2 0 2 4 ← D sure it is fake D fooled → non-saturating −log D(G(z)): 1 − σ(a) minimax log(1 − D(G(z))): σ(a) a = 0, D = ½

Gradient each loss sends back through the discriminator's logit. The minimax loss gives almost nothing where the generator is losing; the non-saturating loss gives almost nothing where it is already winning, which is the right way round.

Generators, discriminators and DCGAN

Any differentiable networks will do, but image GANs became reliable with the conventions of DCGAN (Radford et al.): a generator made of transposed convolutions that grow a 1×1 latent to an image, a convolutional discriminator with strided convolutions instead of pooling, batch normalisation in both (except the generator's output and the discriminator's input), ReLU in the generator with a Tanh output so pixels lie in [−1, 1], LeakyReLU with slope 0.2 in the discriminator, weights drawn from N(0, 0.02), and Adam with learning rate 2·10−4 and β1 = 0.5. A transposed convolution's output size is

Hout = (Hin − 1) · stride − 2 · padding + dilation · (kernel − 1) + output_padding + 1

so kernel 4, stride 2, padding 1 exactly doubles the size, and kernel 4, stride 1, padding 0 turns 1×1 into 4×4: 1 → 4 → 8 → 16 → 32 → 64 in DCGAN's five layers.

Wasserstein critics and Lipschitz constraints

JS has a blind spot. If pdata and pg do not overlap, which is typical early on when both live on thin low-dimensional sets inside pixel space, then JS = log 2 no matter how far apart they are, and a perfect discriminator's gradient carries no information about which direction is closer. Arjovsky et al. replaced it with the Wasserstein-1 (earth mover's) distance, the least total mass × distance needed to move one distribution onto the other, which shrinks smoothly as the generator approaches the data. By Kantorovich–Rubinstein duality

W1(pdata, pg) = sup‖f‖L ≤ 1 𝔼pdata[f(x)] − 𝔼pg[f(x)]

so a critic f with no sigmoid, constrained to be 1-Lipschitz, is trained to maximise the gap in mean scores and the generator to minimise it. The constraint is the hard part. WGAN clipped every weight to a small box, which works poorly; WGAN-GP (Gulrajani et al.) instead adds λ(‖∇x̂f(x̂)‖ − 1)² at random interpolations x̂ between real and fake samples, with λ = 10 and several critic steps per generator step; computing that penalty needs the gradient of an output with respect to an input, kept in the graph so it can itself be differentiated: torch.autograd.grad(..., create_graph=True). Spectral normalisation (Miyato et al.) bounds the Lipschitz constant layer by layer instead, dividing each weight matrix by its largest singular value, estimated with one power iteration per forward pass.

Convergence and regularisation

Gradient descent on a game is not gradient descent on a loss. Mescheder et al. made this concrete with the Dirac-GAN: real data is a single point at 0, the generator is a single point θ, and the discriminator is D(x) = σ(ψx). The only equilibrium is θ = ψ = 0, but near it the two players' updates rotate around it rather than descending into it, and simultaneous gradient steps of any fixed size spiral outward. The paper shows that unregularised GANs, and also WGAN and WGAN-GP, are not guaranteed to converge on this example, and that adding the R1 penalty γ/2 · 𝔼pdata‖∇xD(x)‖², a zero-centred gradient penalty on real data only, makes it converge. R1 and spectral normalisation are the stabilisers most later image GANs use, along with separate learning rates for D and G (the two time-scale update rule of Heusel et al.).

ComponentShape/ParamsMeaningSource
BCEWithLogitsLoss weight=None, reduction="mean", pos_weight=None; input = logits, target ∈ [0, 1] Sigmoid and binary cross-entropy in one, using the log-sum-exp form so confident logits do not overflow. The discriminator loss (targets 1 and 0) and the non-saturating generator loss (target 1 on fakes). loss.py 719-844, forward 836-844
BCELoss input = probabilities The same loss on probabilities after an explicit Sigmoid; clamps its log outputs at −100, so a saturated discriminator gives a bounded but uninformative loss. Less stable than the logits version. 633-716, forward 710-716
torch.autograd.grad outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False Gradient of outputs with respect to chosen inputs, returned rather than accumulated into .grad. With create_graph=True the result is itself differentiable, which R1 and WGAN-GP penalties need; retain_graph defaults to create_graph. autograd/__init__.py 434-621
ConvTranspose2d in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, dilation=1; (N, Cin, H, W) → (N, Cout, Hout, Wout) The generator's upsampling layer: the gradient of a convolution with respect to its input, used as a layer. Docstring gives the Hout formula above. conv.py 1012-1207, Hout 1085-1092, forward 1169-1207
LeakyReLU negative_slope=1e-2, inplace=False Keeps a small gradient for negative inputs so the discriminator passes signal back for every sample; DCGAN sets the slope to 0.2. activation.py 875-930
Tanh none Generator output nonlinearity, matching images scaled to [−1, 1]. 407-433
init.normal_ tensor, mean=0.0, std=1.0 Fills a tensor in place from N(mean, std²); DCGAN calls it with std 0.02 on every convolution weight. init.py 274-298
parametrizations.spectral_norm module, name="weight", n_power_iterations=1, eps=1e-12, dim=None (1 for transposed convolutions, else 0) Registers a parametrization so the layer always uses W / σ(W), bounding each layer's Lipschitz constant by 1. parametrizations.py 535-636
_SpectralNorm buffers u, v Reshapes the weight to a matrix, runs the power iteration in training mode only, and divides by σ = uᵀWv. 403-532, forward 512-527

PyTorch has no GAN class or GAN loss: a GAN is two ordinary modules, two optimizers and the loop that alternates them, which is why the table is a list of building blocks.

The discriminator ends with nn.Sigmoid() and the loss is BCELoss. Late in a run the generator loss sits at exactly 100 and stops moving. What happened?

The discriminator saturated: its probability on fakes underflowed to 0, log 0 was clamped to −100 by BCELoss, and the clamp has zero gradient, so nothing flows back to G. Using raw logits with BCEWithLogitsLoss keeps a finite gradient (1 − σ(a) with the non-saturating target), and a gradient penalty or spectral norm keeps D from becoming that confident.

Worked example

Everything below is computed by stdlib helpers in ml_models_docs.py and checked by the test suite, on an eight-bin data distribution with two modes, 0, 0.1, 0.3, 0.1, 0, 0.1, 0.3, 0.1.

The value at the optimal discriminator

For each candidate generator the helper computes D* bin by bin, evaluates V(D*, G) directly, and compares it with −log 4 + 2·JS; the tests also nudge each D* value up and down and check V falls, confirming it is the maximum. Forward KL is KL(pdata ‖ pg), what maximum likelihood minimises; reverse KL is KL(pg ‖ pdata); W1 treats the bins as points one unit apart.

GeneratorpgV(D*, G)JSKL(data ‖ g)KL(g ‖ data)W1
matched 0, 0.1, 0.3, 0.1, 0, 0.1, 0.3, 0.1 −1.3863 0.0000 0.0000 0.0000 0.00
broad 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125 −1.1332 0.1266 0.4360 ∞ 0.60
one mode 0, 0.2, 0.6, 0.2, 0, 0, 0, 0 −0.9548 0.2158 ∞ 0.6931 2.00
shifted 0, 0, 0.1, 0.3, 0.1, 0, 0.1, 0.4 −0.9250 0.2306 ∞ ∞ 0.90

The matched generator reaches V = −log 4 with D* = ½ wherever there is mass. The one-mode generator, which drops half the data, has infinite forward KL, so maximum likelihood would rule it out entirely, while JS gives it a finite 0.2158: worse than the broad generator's 0.1266, but bounded by log 2 ≈ 0.6931 like every JS value. Its reverse KL is exactly log 2: every generated sample is twice as likely under pg as under pdata. The broad generator is the mirror image, finite forward KL but infinite reverse KL because it puts mass where the data has none. JS is symmetric and sits between the two. The non-saturating generator loss leans the other way: Arjovsky and Bottou show that against the optimal discriminator its gradient is the gradient of KL(pg ‖ pdata) − 2·JS, a reverse KL that forgives dropped modes and punishes mass off the data. That is one way to see why GANs tend towards sharp samples that may miss modes, while likelihood models cover every mode and blur.

Disjoint supports

Put all the data at x = 0 and all the generator's mass at x = θ. For every θ ≠ 0 the two never overlap:

θ0.5124
JS0.69310.69310.69310.6931
W10.5124

JS is log 2 at every distance, so its gradient with respect to θ is zero; W1 = |θ| and points straight back to the data. That is the whole argument for Wasserstein critics in one row.

Saturation in numbers

Logit aD = σ(a)∂/∂a log(1 − D)∂/∂a (−log D)
−60.0025−0.0025−0.9975
−30.0474−0.0474−0.9526
00.5000−0.5000−0.5000
30.9526−0.9526−0.0474

At a = −6, a sample D is sure is fake, the minimax loss passes back 0.0025, about 403 times smaller than the non-saturating loss. Both derivatives are negative, so a descent step on either loss raises a, towards "real"; they differ only in how hard they push. Their difference is always 1 − 2σ(a), zero at a = 0.

The Dirac-GAN, simulated

Starting from θ = 1, ψ = 1, both players take simultaneous gradient steps of size 0.1: ψ ← ψ − h·θσ(ψθ) for the discriminator, and for the generator either θ ← θ + h·ψσ(ψθ) (minimax) or θ ← θ + h·ψσ(−ψθ) (non-saturating). The R1 run adds γψ with γ = 1 to the discriminator's gradient, the derivative of γ/2 · ψ², since ∇x of the logit ψx is ψ. Distance from the equilibrium after each number of steps:

Runstep 0step 100step 300step 1000
minimax, simultaneous GD1.4141.5871.8682.756
non-saturating1.4140.8040.5920.467
minimax + R1 penalty1.4140.1870.0000.000

The minimax run moves further away at every checkpoint, as the paper predicts for simultaneous steps. The non-saturating run is not a fix: it shrinks at first, is still circling the equilibrium at a radius of 0.47 at step 1000, and in a ten-times-longer run levels off above 0.4 rather than reaching it. Only the R1 run converges, to within 10−9 by step 1000. The penalty damps ψ, so the discriminator cannot keep growing a slope that the generator then overshoots.

Dirac-GAN trajectories in the generator–discriminator parameter plane Horizontal axis: generator position θ; vertical axis: discriminator slope ψ; both from −3 to 3, with the equilibrium at the origin. All three runs start at (1, 1) and take 1000 simultaneous gradient steps of size 0.1. The minimax run spirals outward, reaching distance 2.76 from the origin. The non-saturating run spirals inward at first and then settles onto a loop of radius about 0.47. The R1-regularised run dips below the θ axis and spirals tightly into the origin. −3 −3 −2 −2 −1 −1 1 1 2 2 3 3 θ (generator) ψ (discriminator) start minimax, simultaneous GD non-saturating minimax + R1 penalty

Every fourth step of each run. Without a penalty the players chase each other around the equilibrium; the R1 penalty turns the rotation into a spiral that ends there.

Fréchet distance

The Fréchet Inception Distance (Heusel et al.) fits a Gaussian to the features of real images and another to generated ones, using the 2048-dimensional pooled activations of an Inception-v3 network, and reports

FID = ‖μr − μg‖² + Tr(Σr + Σg − 2(ΣrΣg)1/2)

In two dimensions Tr(ΣrΣg)1/2 has the closed form √(tr(ΣrΣg) + 2√(det Σr det Σg)), which the helper uses (cross-checked against a matrix square root). Against a standard normal "real" distribution:

GeneratedμgΣgFID
identical(0, 0)[[1, 0], [0, 1]]0.0000
mean shifted(0.5, 0)[[1, 0], [0, 1]]0.2500
collapsed(0, 0)[[0.25, 0], [0, 0.25]]0.5000
correlated(0, 0)[[1, 0.8], [0.8, 1]]0.4223

A collapsed generator, half the spread in each direction, scores worse than a mean shift of half a standard deviation, so FID does penalise lost diversity, unlike a score of sample quality alone. Its weakness is sample size: two independent sets of samples from the same standard normal give FID 0.3371 with 20 samples each, FID 0.0575 with 200 samples each, FID 0.0024 with 2000 samples each. The estimate is biased upward and the bias shrinks as the sample grows, so FIDs are only comparable at the same sample count (50,000 is the common choice) and the same feature extractor.

Implementation

A DCGAN for 64×64 RGB images, trained with the non-saturating loss and an R1 penalty on real images. The discriminator ends in a logit, not a probability, so the loss is BCEWithLogitsLoss.

import torch
from torch import nn

nz, ngf, ndf = 100, 64, 64

def up(c_in, c_out, stride=2, padding=1):
    return [nn.ConvTranspose2d(c_in, c_out, 4, stride, padding, bias=False),
            nn.BatchNorm2d(c_out), nn.ReLU(True)]

def down(c_in, c_out, norm=True):
    layers = [nn.Conv2d(c_in, c_out, 4, 2, 1, bias=False)]
    if norm:
        layers.append(nn.BatchNorm2d(c_out))
    return layers + [nn.LeakyReLU(0.2, True)]

G = nn.Sequential(                       # (N, nz, 1, 1) -> (N, 3, 64, 64)
    *up(nz, ngf * 8, stride=1, padding=0),           # 4x4
    *up(ngf * 8, ngf * 4), *up(ngf * 4, ngf * 2),    # 8x8, 16x16
    *up(ngf * 2, ngf),                               # 32x32
    nn.ConvTranspose2d(ngf, 3, 4, 2, 1, bias=False), # 64x64
    nn.Tanh(),
)
D = nn.Sequential(                       # (N, 3, 64, 64) -> (N, 1) logits
    *down(3, ndf, norm=False), *down(ndf, ndf * 2),
    *down(ndf * 2, ndf * 4), *down(ndf * 4, ndf * 8),
    nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False), nn.Flatten(),
)

def init_weights(m):
    if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
        nn.init.normal_(m.weight, 0.0, 0.02)

G.apply(init_weights)
D.apply(init_weights)
bce = nn.BCEWithLogitsLoss()
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))

def train_step(real, gamma=10.0):
    z = torch.randn(real.size(0), nz, 1, 1)
    fake = G(z)

    real = real.requires_grad_(True)
    logit_real, logit_fake = D(real), D(fake.detach())
    loss_d = (bce(logit_real, torch.ones_like(logit_real))
              + bce(logit_fake, torch.zeros_like(logit_fake)))
    grad, = torch.autograd.grad(logit_real.sum(), real, create_graph=True)
    r1 = grad.pow(2).flatten(1).sum(1).mean()
    opt_d.zero_grad()
    (loss_d + gamma / 2 * r1).backward()
    opt_d.step()

    logit = D(fake)                          # non-saturating: label fakes real
    loss_g = bce(logit, torch.ones_like(logit))
    opt_g.zero_grad()
    loss_g.backward()
    opt_g.step()
    return loss_d.item(), loss_g.item()

fake.detach() keeps the discriminator's loss from reaching G. The generator step still backpropagates through D, which also leaves gradients in D's parameters; they are harmless because opt_d.zero_grad() clears them before D's next update. For a Wasserstein critic, drop the BCE and read D's output as an unbounded score: the critic loss is f(fake).mean() - f(real).mean() plus the gradient penalty at interpolates, the generator loss is -f(fake).mean(), and batch norm in the critic is replaced (Gulrajani et al. suggest layer norm) because the penalty is defined per sample. Spectral norm is one call per layer: torch.nn.utils.parametrizations.spectral_norm(nn.Conv2d(...)).

Tradeoffs

  • Sharp, fast samples. One forward pass per sample and no pixel-wise likelihood that rewards averaging, so GAN images are sharp and generation is far cheaper than iterative samplers. The same objective gives no likelihood to evaluate, compare or use for anomaly scores.
  • Mode collapse. The generator can cover part of the data well and ignore the rest; the reverse-KL-like behaviour in the worked example is the mild form. Minibatch statistics in the discriminator, gradient penalties and larger discriminators reduce it, but none rules it out.
  • Unstable training. A game has no loss that decreases; D and G losses oscillate, and neither one tracks sample quality. Regularisers (R1, spectral norm), balanced learning rates and checkpoint selection by FID are standard, and training still needs more tuning than a likelihood model.
  • Evaluation. FID is the default but depends on the Inception network, the sample count and preprocessing, and it compresses quality and diversity into one number. Precision and recall for generative models separate the two.
  • Conditioning and editing. Class labels or text can be fed to both networks (conditional GANs), and a well-trained generator's latent space supports smooth interpolation and editing, which likelihood models do not offer as directly.
  • Where they are used now. For general text-to-image generation, diffusion models have largely replaced GANs on diversity and training stability. Adversarial losses survive as components: the discriminator loss in the decoders of image and audio tokenizers, super-resolution, and distilling slow samplers into few-step ones.
Generated samples look good, but every sample in a batch is a near-copy of the same image. The discriminator loss is low and steady. What is going on, and why does the discriminator not fix it?

Mode collapse. Each discriminator judgement is about one sample, and a single convincing image passes each judgement. By the time D learns to reject that image, G moves to another single mode, and the pair can cycle. Showing the discriminator batch-level statistics, for example a minibatch standard deviation feature, gives it a way to detect missing diversity; FID on a held-out batch catches it in evaluation because the covariance term grows.

Connections

Read first

Read next

No page builds on this one yet.

The discriminator is a logistic regression on learned features, trained with the binary cross-entropy from loss functions, and the generator learns entirely through backpropagation into that classifier. The divergences in the worked example are the ones on entropy and KL. The variational autoencoder is the likelihood-based alternative with the opposite failure mode (blur rather than dropped modes), and the autoencoder page covers the decoder that a generator resembles. The architecture is built from convolutional networks and activation functions, initialised as on initialization, and trained with the Adam settings explained on optimizers.

References

  • Goodfellow, Pouget-Abadie, Mirza, Xu, Warde-Farley, Ozair, Courville and Bengio. Generative Adversarial Nets. NeurIPS, 2014 (arXiv 1406.2661; the game, D*, the JS result and the non-saturating loss).
  • Radford, Metz and Chintala. Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks. ICLR, 2016 (arXiv 1511.06434; DCGAN).
  • Arjovsky and Bottou. Towards Principled Methods for Training Generative Adversarial Networks. ICLR, 2017 (arXiv 1701.04862; disjoint supports and saturation).
  • Arjovsky, Chintala and Bottou. Wasserstein Generative Adversarial Networks. ICML, 2017 (arXiv 1701.07875).
  • Gulrajani, Ahmed, Arjovsky, Dumoulin and Courville. Improved Training of Wasserstein GANs. NeurIPS, 2017 (arXiv 1704.00028; WGAN-GP).
  • Heusel, Ramsauer, Unterthiner, Nessler and Hochreiter. GANs Trained by a Two Time-Scale Update Rule Converge to a Local Nash Equilibrium. NeurIPS, 2017 (arXiv 1706.08500; TTUR and FID).
  • Miyato, Kataoka, Koyama and Yoshida. Spectral Normalization for Generative Adversarial Networks. ICLR, 2018 (arXiv 1802.05957).
  • Mescheder, Geiger and Nowozin. Which Training Methods for GANs do actually Converge? ICML, 2018 (arXiv 1801.04406; the Dirac-GAN and R1).
  • Chong and Forsyth. Effectively Unbiased FID and Inception Score and Where to Find Them. CVPR, 2020 (arXiv 1911.07023; FID's sample-size bias).
  • PyTorch v2.15.0: torch/nn/modules/loss.py, torch/autograd/__init__.py, torch/nn/utils/parametrizations.py.