GANs — Generative Adversarial Networks
GANs (Goodfellow, 2014) train a Generator and Discriminator in an adversarial game: Generator produces fake images trying to fool the Discriminator, which tries to distinguish real from fake. Diffusion models now dominate for image quality, but GANs are still faster for inference and used in real-time video and style transfer.
GAN Architecture and Training
import torch
import torch.nn as nn
import torch.optim as optim
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# DCGAN -- Deep Convolutional GAN (standard baseline)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class Generator(nn.Module):
'''Maps random noise z to fake image. z: [B, latent_dim] -> image: [B, C, H, W]'''
def __init__(self, latent_dim: int = 100, channels: int = 3, image_size: int = 64):
super().__init__()
ngf = 64 # number of generator filters
self.net = nn.Sequential(
# Upsample from latent to spatial: z[B,100] -> feature maps
nn.ConvTranspose2d(latent_dim, ngf * 8, 4, 1, 0, bias=False), # [B, 512, 4, 4]
nn.BatchNorm2d(ngf * 8), nn.ReLU(inplace=True),
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), # [B, 256, 8, 8]
nn.BatchNorm2d(ngf * 4), nn.ReLU(inplace=True),
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False), # [B, 128, 16, 16]
nn.BatchNorm2d(ngf * 2), nn.ReLU(inplace=True),
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False), # [B, 64, 32, 32]
nn.BatchNorm2d(ngf), nn.ReLU(inplace=True),
nn.ConvTranspose2d(ngf, channels, 4, 2, 1, bias=False), # [B, 3, 64, 64]
nn.Tanh(), # output in [-1, 1]
)
def forward(self, z: torch.Tensor) -> torch.Tensor:
return self.net(z.view(-1, 100, 1, 1))
class Discriminator(nn.Module):
'''Classifies real vs fake. image: [B, C, H, W] -> [B, 1] (real probability)'''
def __init__(self, channels: int = 3):
super().__init__()
ndf = 64
self.net = nn.Sequential(
nn.Conv2d(channels, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf, ndf*2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*2), nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf*2, ndf*4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*4), nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf*4, ndf*8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*8), nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(ndf*8, 1, 4, 1, 0, bias=False),
nn.Sigmoid(),
)
def forward(self, img: torch.Tensor) -> torch.Tensor:
return self.net(img).flatten(1)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# GAN TRAINING LOOP
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
device = "cuda" if torch.cuda.is_available() else "cpu"
G = Generator().to(device)
D = Discriminator().to(device)
# Separate optimizers for G and D -- different learning rates
opt_G = optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999)) # beta1=0.5 critical!
opt_D = optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999))
loss_fn = nn.BCELoss()
def gan_train_step(real_images: torch.Tensor) -> dict:
B = real_images.size(0)
real_labels = torch.ones(B, 1, device=device) # real = 1
fake_labels = torch.zeros(B, 1, device=device) # fake = 0
# --- TRAIN DISCRIMINATOR ---
z = torch.randn(B, 100, device=device)
fake_images = G(z).detach() # .detach() so G doesn't get D's gradients
real_loss = loss_fn(D(real_images), real_labels)
fake_loss = loss_fn(D(fake_images), fake_labels)
d_loss = (real_loss + fake_loss) / 2
opt_D.zero_grad(); d_loss.backward(); opt_D.step()
# --- TRAIN GENERATOR ---
z = torch.randn(B, 100, device=device)
fake_images = G(z)
g_loss = loss_fn(D(fake_images), real_labels) # G wants D to say "real"
opt_G.zero_grad(); g_loss.backward(); opt_G.step()
return {"d_loss": d_loss.item(), "g_loss": g_loss.item()}
# GAN training instabilities and fixes:
gan_problems = {
"Mode collapse": "G produces only a few image types. Fix: Minibatch discrimination, Wasserstein loss",
"Training instability": "D loss -> 0 or G loss diverges. Fix: Spectral normalization on D, gradient penalty",
"Vanishing gradients": "G gets no signal when D is too strong. Fix: Label smoothing, one-sided label smoothing",
}
print("GAN common issues:")
for problem, fix in gan_problems.items():
print(f" {problem:25s}: {fix}")Tip
Tip
Practice GANs Generative Adversarial Networks in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Technical diagram.
Practice Task
Note
Practice Task — (1) Write a working example of GANs Generative Adversarial Networks from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with GANs Generative Adversarial Networks is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready ai code.