Generative adversarial networks (GAN): the forger versus the detective
The game of generator and discriminator, a GAN written from scratch, the non-saturating loss, eight clusters on a circle and mode collapse, digits sharper than with a VAE but not all of them, and the road from GANs to diffusion models.
Series AI basics Part 45 of 52
- Map of artificial intelligence: what is what and how it all fits together
- A brief history of artificial intelligence: from Turing to language models
- Data, model, training, inference: the basic terms on a single example
- How to read mathematical notation: symbols, indices, sums and quantifiers
- Sets and relations: the building blocks of definitions in AI
- Functions and mappings: why every AI model is a function
- Logic and proofs: the rules AI reasons by
- Graphs and trees: the structure that both search and neural networks stand on
- Linear algebra for machine learning: vectors, matrices and why AI mostly multiplies
- Derivatives, the gradient and the chain rule: how a model finds out which way to learn
- Gradient descent: how a model learns
- What probability is: from rolling dice to the Kolmogorov axioms
- Conditional probability and Bayes’ theorem: how to update your estimate when you learn something new
- Random variables and the cumulative distribution function: one function that tells you everything about a distribution
- Probability density and probability mass functions: why a continuous variable has zero probability at a point
- Expected value, variance and covariance: where a distribution lies, how wide it is and what goes with what
- Overview of probability distributions: Bernoulli, binomial, Poisson, uniform, normal and exponential
- Law of large numbers and central limit theorem: why averages behave so predictably
- Statistics and parameter estimation: maximum likelihood, MAP and confidence intervals
- Information theory: entropy, cross-entropy and KL divergence — where loss functions come from
- State-space search: BFS, DFS, UCS and A* with animations
- Games and adversaries: minimax, alpha-beta pruning and Monte Carlo tree search
- Constraint satisfaction: backtracking, constraint propagation and AC-3 on sudoku and a map of Czech regions
- Logic and knowledge systems: forward and backward chaining, resolution and expert systems
- Planning: STRIPS, the blocks world, heuristics from relaxation and planning in AI agents
- Local search and simulated annealing: a travelling salesman through the Czech regional capitals
- Genetic algorithms: population, crossover, mutation and selection on a backpack for a hike
- Evolution strategies and genetic programming: from the one-fifth rule to Kepler’s law
- Ant colony optimization: how a pheromone trail finds a short path
- Particle swarm optimisation: inertia, personal experience and the swarm’s experience
- Neuroevolution: evolving the weights and shape of neural networks, and the NEAT algorithm
- Fuzzy sets and membership functions: how to write down “somewhat warm”
- Fuzzy logic and probability: why “somewhat warm” does not mean “probably warm”
- Fuzzy logic: t-norms, implications and IF–THEN rules
- Fuzzy control: Mamdani and Sugeno from a fan to a pole on a cart
- Fuzzy clustering: the fuzzy c-means algorithm and points between clusters
- Neuro-fuzzy systems (ANFIS): fuzzy rules that learn from data
- Markov chains: the transition matrix, the stationary distribution and PageRank
- Hidden Markov models: the Viterbi algorithm and the dishonest casino
- Bayesian networks: why the grass is wet and what it means for the sprinkler
- The Kalman filter: how to estimate where a car is and how fast it goes from imprecise measurements
- Types of machine learning: supervised, unsupervised, semi-supervised and more
- Prompts, context and RAG: how to give a model the right information
- Variational autoencoders (VAE): new images from a continuous latent space
- Generative adversarial networks (GAN): the forger versus the detective
- Diffusion models: how pure noise becomes an image
- Markov decision processes: states, actions, rewards and the Bellman equations
- Q-learning and DQN: learning the value of actions, from a table to a neural network
- Policy gradient and PPO: learning the policy directly
- How an AI agent works: a language model in a loop with tools
- Programming with AI agents: the task, tests and review
- Evaluating and securing AI systems: how to measure and how to keep watch
Full series syllabus 52 of 83 topics published
- How the generator learns only from the discriminator's gradient.
- Why GAN training is unstable and what mode collapse is.
- Where GANs beat VAEs, where they lose and why diffusion has replaced them today.
Useful to know first: Variational autoencoders (VAE): new images from a continuous latent space, Games and adversaries: minimax, alpha-beta pruning and Monte Carlo tree search
The variational autoencoder from the previous part generates honestly, but blurrily. In 2014 Ian Goodfellow came up with an idea that looks almost like a trick: instead of measuring how much a sample differs from the data, we let another network judge it. Two networks play a game — one forges, the other spots the forgery — and both get better in the process. That is how the first machine-generated faces you cannot tell from photos came about. In this part we write both networks from scratch and see why their training is so fickle.
It builds on the parts Variational autoencoders, Logistic regression (the sigmoid and cross-entropy) and Games and adversaries.
1. The forger and the detective
Definition — generative adversarial network (GAN)
Let 𝒳 ⊆ ℝd be the data space, pdata the data distribution on 𝒳 (with density pdata), k ≥ 1, and pz = N(0, I) the noise distribution on ℝk. Logarithms in this part are natural (log = ln). A generative adversarial network (GAN; Ian Goodfellow et al., 2014) is formed by two neural networks:
- the generator G : ℝk → 𝒳, which turns noise z ~ pz into a sample G(z); we denote the distribution of the samples G(z) by pG;
- the discriminator D : 𝒳 → (0, 1), where D(x) is an estimate of the probability that x comes from the real data and not from the generator.
The value of the game is
V(D, G) = Ex ~ pdata[log D(x)] + Ez ~ pz[log(1 − D(G(z)))]
and training looks for the equilibrium of the two-player zero-sum game
minG maxD V(D, G):
the discriminator increases V, the generator decreases it, and what one gains, the other loses. For a fixed G the best discriminator is D*(x) = pdata(x)/pdata(x) + pG(x) (theorem below). At equilibrium G generates exactly the data distribution, pG = pdata, and D* returns 1/2 everywhere.
A forger prints banknotes, a detective sorts them into genuine and fake. The detective learns from where they got it wrong. The forger has never seen a genuine banknote — they learn only from what gave them away to the detective. As both improve, the forgeries get better and better. End of the game: the detective can only guess, and guesses fifty-fifty.
minG maxD Ex ~ pdata[ log D(x) ] + Ez ~ pz[ log(1 − D(G(z))) ]
- Ex ~ pdata[log D(x)]
- the discriminator on real data — largest (0) when D gives real samples 1; the logarithm punishes a confident mistake (D → 0) without bound
- Ez[log(1 − D(G(z)))]
- the discriminator on forgeries — largest when it gives them 0; it is the only term in which G appears, so the generator learns from it alone
- maxD
- the discriminator increases the value — both terms together are the negative binary cross-entropy, so this is ordinary training of a “real / forgery” classifier
- minG
- the generator decreases the value — it wants the discriminator to be wrong on forgeries; what one player gains, the other loses (zero sum)
Theorem — the best discriminator and what the generator minimizes
Let pdata and pG be densities on 𝒳 and let the generator G be fixed. Then for every discriminator D : 𝒳 → (0, 1)
V(D, G) = ∫𝒳 [pdata(x) log D(x) + pG(x) log(1 − D(x))] dx
and at every point x where pdata(x) > 0 and pG(x) > 0, the integrand is maximal for
D*(x) = pdata(x)/pdata(x) + pG(x).
With this discriminator
V(D*, G) = −log 4 + 2 · JSD(pdata ‖ pG),
where for densities p, q on 𝒳, m = 1/2(p + q) and the Jensen–Shannon divergence is JSD(p ‖ q) = 1/2 DKL(p ‖ m) + 1/2 DKL(q ‖ m) (DKL is the KL divergence, for densities with an integral instead of a sum, see the part on VAEs). Against the best discriminator, then, the generator minimizes the JSD.
Proof. The sample G(z) has density pG, so Ez[log(1 − D(G(z)))] = ∫ pG(x) log(1 − D(x)) dx; this gives the first formula. The integral is largest when the integrand is largest at every point x, i.e. the function f(y) = a log y + b log(1 − y) for y ∈ (0, 1), where a = pdata(x) > 0, b = pG(x) > 0. Its derivative
f′(y) = a/y − b/1 − y
is zero exactly for y = a/a + b, and since f″(y) = −a/y² − b/(1 − y)² < 0, f is concave and this is a maximum. Hence D*. Since pdata + pG = 2m, we have D* = pdata/2m and 1 − D* = pG/2m, so
V(D*, G) = Epdata[log pdata/m] − log 2 + EpG[log pG/m] − log 2 = DKL(pdata ‖ m) + DKL(pG ‖ m) − log 4,
which is −log 4 + 2 · JSD(pdata ‖ pG). ∎
The Jensen–Shannon divergence is non-negative and zero exactly when pG = pdata (both KL divergences are non-negative and zero exactly for p = m = q). The minimum −log 4 ≈ −1.386 is therefore reached exactly when the generator generates exactly the data.
Example: where the data density is 0.3 and the forgery density 0.1, D* = 0.3/0.3 + 0.1 = 0.75 (the point is more likely real); where the generator overshoots (data 0.1, forgeries 0.3), D* = 0.1/0.1 + 0.3 = 0.25. Where the densities are equal, D* = 1/2 and the discriminator’s loss (binary cross-entropy per sample) is −log 1/2 = ln 2 ≈ 0.693.
The discriminator is an ordinary classifier with a sigmoid and cross-entropy, like logistic regression, just with extra layers. The magic is in the generator: the gradient of its loss flows by backpropagation through the discriminator all the way into the image and from there into the generator's weights. The discriminator thus tells it in which direction to move each pixel to look more convincing.
2. Training: alternating steps
Training alternates: a discriminator step (a batch of real data and a batch of forgeries, raise D on the real ones, lower it on the fakes), then a generator step (move the forgeries to where D considers them real). Both networks here have two hidden layers with leaky ReLU and are trained with Adam.
Definition — non-saturating generator loss
Let σ(s) = 1/1 + e−s be the sigmoid and let the discriminator have the form D(x) = σ(s(x)), where s(x) ∈ ℝ is its output before the sigmoid (the logit). For a forgery G(z) with logit s = s(G(z)),
- the original (minimax) loss of the generator is ℓmm(s) = log(1 − σ(s)) = log(1 − D(G(z))), a term of the value of the game V;
- the non-saturating loss of the generator is ℓns(s) = −log σ(s) = −log D(G(z)).
The generator minimizes the expectation of the chosen loss over z ~ pz. Both losses are decreasing functions of D, so they have the same goal (to raise D on forgeries), but a different gradient: with D = σ(s)
∂ℓmm/∂s = −D, ∂ℓns/∂s = −(1 − D).
The non-saturating loss thus gives a strong gradient precisely for forgeries the discriminator has exposed (D ≈ 0). Goodfellow et al. proposed it already in the same paper.
At the start the forgeries are bad and the detective spots them easily; D is close to zero for them. The original loss sends almost no signal at that moment — the forger does not learn what to improve exactly when they need it most. The reversed question (“how do I convince the detective”) gives a strong signal right from the start.
∂/∂s log(1 − σ(s)) = −D, ∂/∂s (−log σ(s)) = −(1 − D)
- s
- the logit — the discriminator's output before the sigmoid; we differentiate with respect to it because from there the gradient flows back into the discriminator and through it into the forgery
- D = σ(s)
- the probability that the forgery is real; we use σ′(s) = σ(s)(1 − σ(s)), which is why the expressions come out so simple
- log(1 − σ(s))
- the original generator loss (minimized) — its gradient has magnitude D, so when the discriminator safely exposes a forgery (D ≈ 0), it is almost zero: saturation
- −log σ(s)
- the non-saturating loss — its gradient has magnitude 1 − D, so it is largest precisely for forgeries the discriminator has exposed
To be honest: in our small experiments the original loss trained too, because the discriminator was not that confident at the start. For large networks on images, where the discriminator wins quickly, the difference is crucial. From here on we use the non-saturating loss.
How the gradient gets all the way to the generator is shown by the chain rule. The forgery x = G(z) is just an input to the discriminator, so backpropagation does not have to stop there:
∂LG/∂θG = ∂LG/∂s · ∂s/∂x · ∂x/∂θG
- ∂LG/∂s
- a single number from the loss, for the non-saturating one −(1 − D) — “how much” the forgery should improve
- ∂s/∂x
- the gradient of the discriminator with respect to its input (2 numbers for the circle, 64 for a digit) — “in which direction” to move each coordinate so the forgery looks more real; the discriminator's weights do not change in this step
- ∂x/∂θG
- ordinary backpropagation through the generator — turns the desired shift of the forgery into a change of the generator’s weights θG
Definition — the discriminator loss and softplus
Let σ(s) = 1/1 + e−s be the sigmoid, let the discriminator return a logit s(x) ∈ ℝ and D(x) = σ(s(x)). The softplus function is softplus : ℝ → (0, ∞),
softplus(u) = ln(1 + eu).
Let xr be a real sample with logit sr = s(xr) and xf = G(z) a forgery with logit sf = s(xf). The discriminator loss on this pair is the binary cross-entropy with targets 1 (real) and 0 (forgery):
LD = −log σ(sr) − log(1 − σ(sf)) = softplus(−sr) + softplus(sf).
The second equality follows from −log σ(s) = ln(1 + e−s) = softplus(−s) and from 1 − σ(s) = σ(−s). Minimizing LD is the same as maximizing V. The program computes softplus from the logit, not from D: for s = 40, σ(s) in floating point is exactly 1 and log(1 − D) would come out as −∞, whereas softplus(40) ≈ 40. Gradients: ∂LD/∂sr = σ(sr) − 1, ∂LD/∂sf = σ(sf).
for t in range(kroku):
# 1. discriminator step: real → 1, forgeries → 0
gD = 0
for _ in range(davka):
x = nahodny_vzorek(data)
gD += D.zpet(D(x), dL_ds=sigmoid(D(x)) - 1)
f = G(sum()) # a forgery, G is not training now
gD += D.zpet(D(f), dL_ds=sigmoid(D(f)))
adam_D(gD / davka)
# 2. generator step: the gradient passes through D into the forgery
gG = 0
for _ in range(davka):
x = G(sum())
dL_ds = sigmoid(D(x)) - 1 # non-saturating −log σ(s)
dL_dx = D.zpet_do_vstupu(dL_ds) # D's weights unchanged
gG += G.zpet(dL_dx)
adam_G(gG / davka) # Adam with β₁ = 0.5 as in DCGAN
3. Eight clusters on a circle
A classic experiment: the data are points from eight small normal clusters (σ = 0.05) arranged on a circle of radius 2. The generator receives two-dimensional noise and returns a point in the plane. We measure quality (the share of samples within 3σ of some centre) and coverage (how many clusters receive at least 2% of the samples).
Definition — data, quality and coverage
Let r = 2 be the radius of the circle and σ = 0.05 the width of the clusters. The cluster centres are the points c0, …, c7 ∈ ℝ2,
cj = (r cos 2πj/8, r sin 2πj/8), j = 0, …, 7.
A data point is x = cJ + ε, where J is uniformly random from {0, …, 7} and ε ~ N(0, σ²I) is noise independent of J (I is the 2 × 2 identity matrix).
Let x1, …, xn ∈ ℝ2 be generator samples, for a finite set M let #M denote the number of its elements, and for j = 0, …, 7 let nj = #{i ∈ {1, …, n} : ‖xi − cj‖ < 3σ} be the number of samples within 3σ of the centre cj. Then
- quality is the share of samples close to some centre, quality = 1/n · #{i ∈ {1, …, n} : minj ‖xi − cj‖ < 3σ};
- coverage is the number of clusters that receive at least 2% of the samples, coverage = #{j ∈ {0, …, 7} : nj ≥ 0.02 · n}.
The numbers: 3σ = 0.15, neighbouring centres are 2r sin π/8 ≈ 1.53 apart, i.e. more than ten times farther, so no sample counts towards two centres. The snapshots have n = 400 samples, so a cluster counts as covered from 8 samples on.
Not even a perfect generator has 100% quality: the distance of a point from the centre of a two-dimensional normal distribution N(0, σ²I) follows the Rayleigh distribution, P(‖ε‖ < t) = 1 − exp(−t²/2σ²), so the share falling within 3σ is 1 − exp(−9/2) ≈ 0.989, about 98.9%.
After 500 steps the generator produces a blurry cloud (quality 6%), after 2,000 steps the points gather into 6 clusters, and at the end (step 8,000) it covers all 8 with quality 73%. The generator never saw a single real point — it learned everything from the discriminator's gradient.
4. Mode collapse
Definition — mode collapse
Let pdata be the data distribution and let M1, …, MK ⊆ 𝒳 be pairwise disjoint regions among which the data falls apart into separate groups, the so-called modes (clusters, kinds of images), each with probability Pdata(Mm) > 0. Let PG(Mm) = P(G(z) ∈ Mm), z ~ pz, be the probability that the generator produces a sample from mode m. The generator suffers from mode collapse if and only if for some mode m, PG(Mm) is much smaller than Pdata(Mm), in the extreme case zero — it produces only part of the data distribution and leaves out the other modes. (The threshold for “much smaller” is not fixed; in the circle experiment below we consider a mode left out when it receives less than 2% of the samples.) In complete collapse the generator produces nearly the same thing for every z, i.e. pG is concentrated in a small neighbourhood of a single point.
The loss does not punish this directly: the discriminator judges each sample separately, and a good forgery of one kind is just as convincing to it as a varied collection.
The forger finds out that the detective is worst at recognizing fake hundreds. So they print only hundreds. In time the detective notices and starts to suspect hundreds — the forger jumps to two-hundreds. The game goes round in circles and the forger never produces all the banknotes at once.
Theorem — against a fixed discriminator a single point is best
Let D : 𝒳 → (0, 1) be a fixed discriminator that attains its maximum at a point x* ∈ 𝒳, i.e. D(x) ≤ D(x*) for all x ∈ 𝒳. Then for every generator G : ℝk → 𝒳 the non-saturating loss satisfies
LG(G) = Ez ~ pz[−log D(G(z))] ≥ −log D(x*),
and equality holds for the constant generator G(z) = x* for all z ∈ ℝk.
Proof. For every z, D(G(z)) ≤ D(x*), and since −log is decreasing, −log D(G(z)) ≥ −log D(x*); the expectation preserves the inequality. For the constant generator, −log D(G(z)) = −log D(x*) for every z. ∎
The loss is an average over individual samples and nothing in it rewards diversity. Against a fixed opponent, complete collapse is therefore optimal; the only thing preventing it is that the discriminator reacts and starts to suspect x*. If the generator jumps faster than the discriminator forgets, the game goes round in circles. That is why modifications help that give the discriminator a view of the whole batch (minibatch discrimination, Tim Salimans et al., 2016), or a different distance between distributions (Wasserstein, below).
The same network, the same data, the same settings — just a different random seed:
| seed | clusters covered after steps 1,000 … 8,000 | at the end | quality at the end |
|---|---|---|---|
| 1 | 0 · 2 · 7 · 6 · 6 · 7 · 7 · 7 | 7 of 8 | 66% |
| 2 | 4 · 5 · 8 · 8 · 8 · 8 · 8 · 8 | 8 of 8 | 73% |
| 3 | 1 · 4 · 4 · 4 · 5 · 5 · 5 · 5 | 5 of 8 | 54% |
| 4 | 0 · 5 · 6 · 6 · 6 · 6 · 6 · 6 | 6 of 8 | 49% |
Only one of the four runs covered all eight clusters. The run with seed 3 ended at 5 and from step 5,000 on it did not budge. This is typical of GANs: training does not have a single loss that goes down, but a two-player game, and the result depends on chance more than we would like.
5. Digits: sharper, but not all of them
Now the same 8 × 8 digits as with the VAE. Generator: eight-dimensional noise → 64 → 64 → 64 pixels with a sigmoid; the discriminator mirrors it. 6,000 steps, two runs with different seeds. We measure quality the same way as in the previous part — by the distance of a sample to the nearest training digit.
The GAN samples are sharper: distance 0.049 and 0.042 versus 0.054 for the VAE (real digits 0.017). A GAN does not punish blurriness by averaging like the VAE's reconstruction loss — the discriminator easily recognizes a blurry image. But how many of which digits does the generator produce?
In the data every digit is equally frequent. Yet the run with seed 2 produces mostly sixes (69 of 200), eights and threes, and the digits 0, 1, 2, 4, 7 hardly at all (3 samples at most). The run with seed 1 is more varied, but even there the digits 0, 4, 9 get less than half their share (at most 6 samples instead of 20). It is the same mode collapse as on the circle, just on images: the GAN wins on sharpness, the VAE on coverage.
6. From GANs to diffusion
- DCGAN (Alec Radford et al., 2015) showed how to build convolutional GANs that can be trained reliably on photos.
- Wasserstein GAN (Martin Arjovsky et al., 2017) replaced the classifier with a “critic” that measures the distance between distributions; training is more stable and less prone to mode collapse.
- StyleGAN (Tero Karras et al., Nvidia, 2018–2020) generates photorealistic faces at a resolution of 1024 × 1024 — the well-known website “this person does not exist”.
- Around 2021 GANs were overtaken by diffusion models (Prafulla Dhariwal and Alex Nichol: “Diffusion Models Beat GANs on Image Synthesis”). They are slower, but train stably and cover the whole distribution. The idea of the discriminator lives on, though — as an auxiliary loss that gives images sharpness, for example in image or audio codecs.
Why Wasserstein helps can be seen in the simplest example (a simplified version of the example from the paper by Arjovsky et al., 2017): the data are a single point 0, the generator produces a single point θ. As long as θ ≠ 0, the distributions do not overlap and JSD = ln 2 ≈ 0.693 no matter how far away θ is — the gradient with respect to θ is zero and the generator does not know where to go. The Wasserstein distance is |θ|, so it points the way even from far away.
W(pdata, pG) = sup‖f‖L ≤ 1 ( Ex ~ pdata[f(x)] − Ez ~ pz[f(G(z))] )
- sup‖f‖L ≤ 1
- the supremum over critics f : 𝒳 → ℝ — a network that returns an arbitrary number instead of a probability; the “1-Lipschitz” condition (|f(a) − f(b)| ≤ ‖a − b‖) does not let it separate two nearby points arbitrarily sharply, otherwise the difference could grow to infinity
- Ex ~ pdata[f(x)]
- the average score of the real data — the critic wants it as large as possible
- Ez[f(G(z))]
- the average score of the forgeries — the critic wants it as small as possible, the generator as large as possible; the difference is continuous in the generator's parameters, so the gradient does not vanish even when the distributions do not overlap
7. Summary
- GAN = a generator and a discriminator in a game; the generator learns only from the gradient the discriminator sends it.
- The non-saturating loss −log D(G(z)) gives the generator a strong signal even at the start, when the discriminator is winning.
- On eight clusters only one of the four runs covered all of them (7, 8, 5, 6); the result depends heavily on chance.
- On digits the samples are sharper than with the VAE (0.042 versus 0.054), but some digits are missing — mode collapse.
- Today images are generated mainly by diffusion models; the discriminator has stayed on as a helper for sharpness.
Want to get a feel for it? Have an AI agent train a small GAN on eight clusters on a circle and run it five times with different seeds. Then have it try a Wasserstein GAN with the same network and compare how many clusters it covers each time.
Do you see it differently? Write to support@coden-app.com.