GAN Workbench
Ten GAN Variants on One Training Loop
Ten GAN variants behind one training loop: same data, same budget, same metrics, so only the method changes.
- cGAN FID 29.6
- pix2pix IoU 0.977
- 8 GANs compared
Built withPyTorch
torchvisionMatplotlib
pytest
Kaggle

Overview
A GAN is two networks trained against each other: a generator that turns noise into images and a discriminator that tries to tell them from real ones. Since 2014 that game has been patched many times with new losses, new networks and new ways to keep the discriminator in check, and the papers rarely compare under the same conditions. I wanted to watch each patch work on its own, so I wrote ten variants in plain PyTorch: the original GAN, DCGAN, conditional GAN, ACGAN, LSGAN, WGAN, WGAN-GP, SNGAN (hinge loss with spectral norm), pix2pix and CycleGAN.
They share one training loop, one data pipeline and one evaluation. A method is a small object that defines one discriminator step and one generator step; the loop decides when they run and what is measured, so switching method is one flag and every run gets the same seeds, data order and number of generator updates. The networks are small on purpose (0.1-3 M parameters) so that each mechanism can be watched on a CPU in minutes, and a Kaggle/Colab notebook runs the same code longer on a GPU.
On MNIST, conditioning won (cGAN FID 29.6, ACGAN 44.1), spectral norm with the hinge loss was the best unconditional method (57.5), and WGAN with weight clipping failed to train in the budget (1035.5) until the gradient penalty replaced clipping (127.2). On synthetic shapes, pix2pix learned photo-to-mask almost perfectly (IoU 0.977) while CycleGAN found the known 'steganography' shortcut and reached only IoU 0.28. The repo grew from two coursework notebooks; their eleven bugs, including optimisers built for networks that were then replaced, are documented and covered by tests.
The question
If ten GAN variants share the same data, budget, loop and metrics, which of their differences (loss, conditioning, Lipschitz control, convolutions) actually matter on a CPU-sized run?
Key features
- Ten methods behind one --method flag (gan, dcgan, cgan, acgan, lsgan, wgan, wgan-gp, sngan, pix2pix, cyclegan), each with its own networks, loss, conditioning, Lipschitz control and optimiser defaults.
- Shared tricks as flags of the same loop: label smoothing, TTUR, n critic steps, an EMA generator, the R1 penalty, feature matching and the original saturating loss.
- Evaluation built for 28-px digits: a classifier-feature FID and Inception-Score analogue from a small MNIST CNN (98.6% test accuracy), mode coverage, label accuracy, and L1, mask IoU and cycle L1 for the image-to-image methods.
- Four commands, gans-data, gans-train, gans-sample (grids, per-class grids, latent interpolations, translations) and gans-eval, plus six runnable example scripts.
- Three offline synthetic datasets (shapes, paired shapes for pix2pix, unpaired shapes for CycleGAN) where the right answer is known exactly, so image-to-image quality becomes a number instead of a judgment call.
- Training curves with three panels: the losses, D(x) against D(G(z)), and the real per-step update ratio of each network measured on the actual Adam step.
- 43 CPU tests in about a minute, including 'DCGAN learns a single image' and 'pix2pix overfits a batch', which prove that gradients reach the right weights.
How it works
One loop
Each step runs n_critic discriminator updates and then one generator update, optionally updates an EMA copy of G, and logs the losses, D(x), D(G(z)) and the update ratios. The loop decides when things run; the method decides what they are.
A method is two steps
NoiseGAN, Pix2Pix and CycleGAN each define a D step and a G step from a METHODS table of networks, loss (BCE, least squares, Wasserstein, hinge), conditioning, Lipschitz control and optimiser defaults.
Keep D in check
DCGAN relies on BatchNorm; WGAN clips every critic weight to ±0.01 after each step; WGAN-GP penalises the gradient norm on real/fake interpolates and drops BatchNorm; SNGAN divides every D layer by its largest singular value.
Image to image
pix2pix uses a U-Net generator, a PatchGAN discriminator that judges (input, output) pairs and a 100x L1 term. CycleGAN uses two U-Nets, two PatchGANs, a least-squares loss and a 10x cycle L1, with no pairs at all.
Evaluate
5,000 samples are compared with 5,000 test images in the 128-d feature space of the small classifier: Fréchet distance, an IS analogue, the number of classes that receive at least 1/(4K) of the samples, and label accuracy for conditional models.
Examples



Input
gans-train --method cgan --dataset mnist --steps 2000 && gans-eval checkpoints/cgan/last.pt
Output
FID (clf) 29.64, IS (clf) 8.02, modes 10/10, label accuracy 87.5% (0.27 M / 0.04 M parameters, 9.7 min on one CPU thread)
Tech stack
Networks, losses, the training loop and the evaluation
MNIST, FashionMNIST and CIFAR-10 downloads
Training curves, sample grids and comparison figures
43 CPU tests, including the two end-to-end 'learns' tests
Notebook for longer GPU runs on MNIST, CIFAR-10 and Facades
What I learned
- Conditioning is the biggest win: giving the label to G and D removes the hardest part of the problem, choosing which digit to draw
- Convolutions matter more than the loss: the same BCE loss goes from FID 399 with MLPs to 94 with DCGAN networks
- CycleGAN can satisfy its cycle loss by hiding the input in a faint high-frequency pattern instead of learning the real mapping
Limitations
- All numbers are short single-thread CPU runs (2,000 generator steps, 0.1-3 M parameters), not benchmarks; the FID uses a small MNIST classifier instead of Inception, so it is only comparable within the repo
- CycleGAN is only partly solved at this budget (IoU 0.28 against the true outline)
Keywords
GAN · generative adversarial networks · DCGAN · conditional GAN · ACGAN · LSGAN · WGAN · WGAN-GP · gradient penalty · spectral normalization · hinge loss · pix2pix · CycleGAN · image-to-image translation · mode collapse · FID · MNIST · PyTorch · generative models