Build the mental model
In a generative adversarial network (GAN), two networks train simultaneously. The generator takes a random noise vector and transforms it into a 'fake' image, while the discriminator tries to tell real images from the training set apart from the generator's fakes. The two are trained as an adversarial minimax game — the generator's gradient signal comes from how well it fooled the discriminator, and the discriminator's signal comes from its classification accuracy. As training progresses, the generator gets better at producing realistic images while the discriminator gets better at spotting fakes, and in the ideal outcome the generator's output becomes indistinguishable from real data.
Connect it to a real scenario
The Tutorial Platform needs a way to flag whether an uploaded course thumbnail is a genuine screenshot or diagram versus a suspicious AI-generated synthetic image, for content-policy compliance. The Trust & Safety team adversarially trains a discriminator-style classifier that scores each upload for how 'authentic-looking' it is, routing suspiciously synthetic images to a manual review queue — the generator itself is discarded after training and only ever exists to make the discriminator a better judge.
Try the working example
import torch
import torch.nn as nn
torch.manual_seed(0)
class Generator(nn.Module):
def __init__(self, noise_dim=16, image_size=32):
super().__init__()
self.image_size = image_size
self.net = nn.Sequential(
nn.Linear(noise_dim, 64),
nn.ReLU(),
nn.Linear(64, 3 * image_size * image_size),
nn.Tanh(), # output pixels squashed to [-1, 1]
)
def forward(self, noise):
out = self.net(noise)
return out.view(-1, 3, self.image_size, self.image_size)
class Discriminator(nn.Module):
def __init__(self, image_size=32):
super().__init__()
self.net = nn.Sequential(
nn.Flatten(),
nn.Linear(3 * image_size * image_size, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid(), # score in [0, 1]: probability the image is "real"
)
def forward(self, image):
return self.net(image)
generator = Generator()
discriminator = Discriminator()
batch_size = 4
noise = torch.randn(batch_size, 16) # random noise vectors
print("Noise shape:", noise.shape)
fake_images = generator(noise) # noise -> generator -> fake image
print("Fake image batch shape:", fake_images.shape)
scores = discriminator(fake_images) # fake image -> discriminator -> real/fake score
print("Discriminator score shape:", scores.shape)
print("Discriminator scores:", scores.squeeze(1))
Prints Noise shape: torch.Size([4, 16]). Then Fake image batch shape: torch.Size([4, 3, 32, 32]) — four noise vectors turned into four RGB-shaped images. Then Discriminator score shape: torch.Size([4, 1]), followed by the squeezed Discriminator scores: tensor([...]) containing four floats between 0.0 and 1.0 (the Sigmoid output). Since neither network is trained, these scores are effectively random-ish values scattered around 0.5, and don't yet reflect any real judgment about image realism.5-minute try-it
Modify the code to create a batch of pretend 'real' images with torch.rand(batch_size, 3, 32, 32) and pass them through the same discriminator (note that torch.rand's [0, 1] range doesn't match the generator's Tanh output range of [-1, 1]). Print and compare the discriminator's score distribution for the fake images versus the pretend-real images.
One important caution
The generator's output lives in the [-1, 1] range because of the Tanh activation; forgetting to rescale it to [0, 1] or [0, 255] before treating it as displayable image pixels produces washed-out or inverted-looking images.
It's tempting to read a discriminator score near 0.5 as 'the generator is fooling the discriminator,' but with untrained, randomly initialized networks those scores are just near-random noise, not a meaningful judgment about image realism — early scores don't tell you anything about generation quality yet.
Wikipedia — Generative adversarial network — Computer Vision