In this guide
AI that generates photorealistic faces, turns sketches into photos, and creates synthetic medical scans for training other models — much of it started with one idea from 2014: two neural networks competing against each other. That idea is the Generative Adversarial Network, or GAN.
This guide explains the adversarial game behind GANs, what the generator and discriminator each compute, how to read the minimax loss without fear, a minimal DCGAN you can actually train on your own machine, and how to recognise and fix the two classic failure modes that make beginners quit: mode collapse and unstable training.
The core idea: a counterfeiter and a detective
Imagine a counterfeiter printing fake banknotes and a detective examining them. The counterfeiter starts clumsy — the detective catches every fake. Each caught fake teaches the counterfeiter which details to fix; each improved fake forces the detective to look harder. Both improve because they compete.
A GAN works exactly like this:
- The generator (G) is the counterfeiter. It takes random noise (a vector of, say, 100 random numbers) and transforms it into something that looks like a real data sample — an image, an audio clip, a row of tabular data.
- The discriminator (D) is the detective. It takes an input — real or generated — and outputs a single probability: how likely is this sample real?
- The generator never sees the real data directly. Its only feedback is the discriminator's verdict.
- Training is a zero-sum game: the discriminator tries to maximise its accuracy at telling real from fake; the generator tries to minimise the discriminator's accuracy (maximise its confusion).
At equilibrium — the theoretical end state — the generator produces samples so realistic that the discriminator guesses at random (50/50). The generator has then learned the distribution of the real data, not just memorised examples.
How the game works, step by step
Training alternates between the two networks, roughly like this:
- Draw a real batch. Sample a batch of genuine images (or whatever your data is) from the training set. Label them real (1).
- Draw a noise batch. Sample random vectors from a simple distribution (usually a standard normal). Pass them through the generator to get fake samples. Label these fake (0).
- Train the discriminator one step. Show it the real batch and the fake batch; update its weights so it gets better at scoring real samples near 1 and fake samples near 0. The generator's weights are frozen during this step.
- Train the generator one step. Generate a fresh batch of fakes, show them to the discriminator, and update the generator's weights so the discriminator's output moves toward 1 (i.e. the fakes get classified as real). The discriminator's weights are frozen here.
- Repeat for many epochs. Thousands of iterations of this alternating game is what training a GAN actually is.
The critical design detail: in step 4 the generator is updated based on the discriminator's error signal, backpropagated through the frozen discriminator into the generator. The discriminator acts as a learned, adaptive loss function — far richer than "compare pixels," because it learns which features actually distinguish real from fake.
What each network computes
The generator: noise to data
The generator is usually a stack of transposed-convolution ("deconvolution") layers in image GANs — the mirror image of a classifier's downsampling pipeline. A typical student-scale generator:
- Input: a 100-dimensional noise vector.
- Dense layer reshaped to a small spatial map, e.g. 4x4x512.
- Three or four transposed-convolution layers, each doubling width/height and halving channels (4x4 -> 8x8 -> 16x16 -> 32x32), with batch normalisation and ReLU between them.
- Final layer with tanh activation, outputting values in [-1, 1] — so you must normalise your training images to [-1, 1] too.
The noise vector is the source of variety: different random vectors map to different generated samples. A well-trained generator maps nearby noise vectors to similar-looking outputs — the noise space becomes a meaningful, navigable space of faces, digits, or whatever you trained on.
The discriminator: data to a verdict
The discriminator is a standard image classifier — convolutional layers, leaky ReLU activations, downsampling — ending in a single sigmoid output: P(real). Leaky ReLU (small slope for negative inputs) is used instead of plain ReLU because dead gradients in the discriminator kill the learning signal for the generator.
A healthy discriminator is a fair one: too strong and it crushes the generator (no useful gradient); too weak and the generator learns to fool a fool (bad samples). Most training instability is this balance going wrong.
The loss, in plain words
The original GAN objective is a two-player minimax game:
- The discriminator wants to maximise: log D(real) + log(1 - D(fake)). In words: "score real samples high, fake samples low."
- The generator wants to minimise: log(1 - D(fake)). In words: "make my fakes score high."
In practice, everyone trains the generator with the equivalent-but-better-behaved objective maximise log D(fake) instead. Why? Early in training, when the discriminator is strong, log(1 - D(fake)) saturates near zero and its gradient vanishes — the generator gets almost no learning signal. The flipped objective keeps gradients strong exactly when the generator is worst. This one-line change is one of the most practically important details in the original GAN paper.
You do not need to derive anything from scratch — but you should know why the loss looks adversarial (each network's gain is the other's loss) and why the training loop must alternate: neither network can be trained to convergence against a frozen opponent and then expect the other to catch up. They must co-evolve.
A minimal DCGAN you can actually run
DCGAN (Deep Convolutional GAN, 2016) is the architecture to start with: stable, small, and still the best teaching tool. The stripped-down generator below captures the essential structure in PyTorch:
import torch.nn as nn
class Generator(nn.Module):
def __init__(self, nz=100, ngf=64, nc=3):
super().__init__()
self.net = nn.Sequential(
nn.ConvTranspose2d(nz, ngf*8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf*8), nn.ReLU(True),
nn.ConvTranspose2d(ngf*8, ngf*4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf*4), nn.ReLU(True),
nn.ConvTranspose2d(ngf*4, ngf*2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf*2), nn.ReLU(True),
nn.ConvTranspose2d(ngf*2, nc, 4, 2, 1, bias=False),
nn.Tanh()
)
def forward(self, z):
return self.net(z)
The training loop alternates exactly as described above — update D on real and fake batches, then update G to fool D:
criterion = nn.BCELoss()
for epoch in range(epochs):
for real in dataloader:
# 1. Train discriminator
netD.zero_grad()
real_labels = torch.ones(real.size(0), 1, 1, 1, device=device)
loss_real = criterion(netD(real), real_labels)
z = torch.randn(real.size(0), 100, 1, 1, device=device)
fake = netG(z)
loss_fake = criterion(netD(fake.detach()), torch.zeros_like(real_labels))
loss_D = loss_real + loss_fake
loss_D.backward(); optD.step()
# 2. Train generator (fool the discriminator)
netG.zero_grad()
loss_G = criterion(netD(fake), real_labels) # flipped objective
loss_G.backward(); optG.step()
Note fake.detach() in the discriminator step: the discriminator's gradient must not flow into the generator. This one method call is easy to forget and silently corrupts training.
A realistic first target: 64x64 images of a single simple class (digits, flowers, faces from a small public dataset) on a laptop or Colab GPU. Train for a few thousand iterations and save a grid of generated samples every epoch — watching the samples sharpen over time is how you learn to read GAN training.
Reading training curves
GAN training does not look like classifier training, and the loss curves confuse every beginner:
- Both losses oscillating around 0.5-1.0 for D and G is NORMAL. Unlike classification, you do not want either loss to go to zero. A discriminator loss of zero means it has won completely and the generator is learning nothing.
- Watch the samples, not the loss. The generated image grid every epoch is your real progress metric. Loss values in GANs correlate weakly with sample quality.
- D loss crashing to ~0, G loss exploding: the discriminator overpowered the generator. Fixes: fewer D updates per G update, label smoothing (train D with 0.9 instead of 1.0 for real samples), or a smaller discriminator learning rate.
- D loss stuck near 1.0 (random guessing): the generator is producing garbage so obvious that D learned a trivial giveaway — or the discriminator is too weak. Check your normalisation (images to [-1, 1] matching tanh) first.
- Samples look identical (mode collapse): covered below.
- FID score (Frechet Inception Distance) is the standard quantitative metric: it compares the distribution of generated features against real features. Lower is better. Compute it occasionally; do not optimise by it alone.
The two classic failure modes
1. Mode collapse
The generator discovers one trick that fools the discriminator — say, one particular face — and produces it (with tiny variations) for every noise vector. The discriminator eventually adapts, the generator shifts to a new single trick, and the cycle repeats. You get sharp-looking but identical samples.
Why it happens: the generator optimises only against the current discriminator, and a single convincing mode is the easiest way to minimise the loss. Nothing in the basic objective rewards diversity.
Practical mitigations:
- Mini-batch discrimination: let the discriminator see statistics across the batch (e.g. batch standard deviation as an extra feature), so it can detect "all samples look the same." This is the single most effective beginner fix.
- Unrolled GAN / two-timescale updates: update the discriminator slightly faster than the generator (TTUR: e.g. 2x the learning rate for D), so it stays a step ahead and the generator cannot settle on a trick.
- Wasserstein GAN (WGAN): changes the loss to the Earth-Mover distance, which provides meaningful gradients even when the discriminator is strong. More stable, at the cost of a different training setup (weight clipping or gradient penalty).
2. Unstable / oscillating training
Both networks' updates move the target for each other, so training is a coupled dynamical system that can orbit without converging — losses swing, sample quality flickers between good and garbage.
Practical stabilisers:
- Batch normalisation in both networks (except D's first layer and G's output layer).
- Adam with beta1=0.5 instead of the default 0.9 — this specific hyperparameter choice comes from the DCGAN paper and reduces oscillation.
- Learning rate ~0.0002 for both; lower if still unstable.
- Label smoothing: train D on 0.9/0.1 instead of 1/0 — prevents overconfident gradients.
A practical student workflow: synthetic data augmentation
One of the most defensible uses of a GAN in a student project is augmenting a small dataset for a classifier:
- Train a classifier on your limited real data and record the baseline accuracy.
- Train a GAN on the minority/rare classes (where data is scarcest).
- Generate synthetic samples, mix them into the training set at a stated ratio (e.g. 20% synthetic), and retrain the classifier.
- Report the delta honestly — if it does not help, say so. Synthetic data that mirrors the training set's biases does not fix a biased model.
This pattern is used in real work (medical imaging, defect detection) and gives you a clean evaluation story: the GAN is a means to an end, judged by the downstream model's performance — not by how pretty the samples look.
Common mistakes
- Training on unnormalised images. The generator's tanh outputs [-1, 1]; feed it images in [0, 1] and training fights itself from step one.
- Forgetting detach() on fake samples in the discriminator step, silently training the generator through the discriminator's update.
- Evaluating by eyeballing alone. Pretty samples with no diversity are mode collapse wearing a disguise — check variation across noise vectors.
- Claiming invented results. Never report FID scores, accuracy deltas, or "trained for N hours" numbers you did not measure. GAN training is famously seed-sensitive; fabricated numbers are easy to spot and hard to defend.
- Starting with 1024x1024. Train at 32x32 or 64x64 first. High-resolution GANs need progressive growing or StyleGAN-style architectures — a different project.
- Expecting the discriminator loss to converge to zero. In a healthy GAN it should not.
Frequently asked questions
Is a GAN project suitable for a final-year project?
Yes, with a scoped deliverable: train a DCGAN on a small dataset and evaluate it (FID + downstream-classifier delta), or build a creative tool (sketch-to-image, style transfer with a pretrained GAN). Avoid promising "photorealistic faces at high resolution" — that is a StyleGAN-scale endeavour.
Should I use a GAN or a diffusion model in 2026?
Diffusion models produce higher-quality images today and train more stably; GANs train faster, sample much faster (one forward pass vs hundreds of denoising steps), and are simpler to understand end-to-end. For a student project with limited compute, GANs remain the better first generative model to learn from.
Can I train a GAN on a laptop CPU?
A tiny DCGAN on 32x32 MNIST-style data: yes, slowly. Anything at 64x64 with colour: use free Colab GPUs — CPU training of a real image GAN takes orders of magnitude longer and is how beginners conclude "GANs don't work."
How much data do I need?
A few thousand images of reasonable variety for 64x64. Below ~1,000 images, the discriminator memorises the dataset and training collapses — use heavy augmentation on real images (DiffAugment-style) if data is scarce.
What does the noise vector's size control?
Nothing magical — 100 is conventional. Too small (e.g. 10) and the generator cannot express enough variety; too large (e.g. 1000) and training slows with no benefit. Keep 100 and tune things that matter.
Limitations
- GANs are the least stable mainstream architecture to train: expect hyperparameter sensitivity and seed dependence far beyond classifiers.
- They are hard to evaluate rigorously — FID is the standard metric but correlates imperfectly with human judgement, and computing it needs a few thousand samples.
- Training a GAN on copyrighted or scraped face datasets raises consent and licensing issues; use datasets with clear licences and say which one you used.
- Generated imagery can be misused (deepfakes, forged documents); treat synthetic outputs like any powerful tool — document intended use.
- This guide covers DCGAN-style architectures. Modern large-scale generators (StyleGAN, diffusion) build on these ideas but add significant machinery.
Where to go from here
- Diffusion Models: How They Actually Work — the other dominant generative family, and when to choose it over a GAN.
- CNN Transfer Learning for Final-Year Projects — the classifier side: what you augment with GAN-generated data.
- Data Augmentation Techniques for Image Models — classical augmentation to try before (and with) synthetic data.
- Best 10 Deep Learning Image-Classification Projects — project ideas where a GAN fits as a data source or a creative module.