Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our journey into generative models.
In our last lesson, we designed the architectures for our two key players: the Generator (the art forger) and the Discriminator (the art detective). With these models defined in PyTorch, we now have the components ready.
Today, we'll bring them into the training arena. Our goal is to implement the GAN training loop with separate updates for the generator and discriminator. This isn't a typical training process with a single loss function. Instead, we'll orchestrate the two-player minimax game where each network learns from the other in a carefully choreographed dance.
1. Setting the Stage for Training
Before we start the loop, we need to set up our training environment. This involves defining the loss function and, crucially, separate optimizers for our two networks.

Loss Function and Labels
For a GAN's binary classification task (real vs. fake), the Binary Cross Entropy (BCE) Loss is the perfect tool. In PyTorch, this is nn.BCELoss. The loss for a single prediction is given by:
Here, is the Discriminator's output (a probability) and is the ground-truth label. By setting the label to 1 for real images and 0 for fake images, we can use this single function to calculate the loss for both scenarios.
Two Separate Optimizers
Since the Generator and Discriminator are two distinct networks with different objectives, they must be trained by two separate optimizers. One optimizer will update the Discriminator's parameters, and the other will update only the Generator's parameters. This is the core of implementing separate updates. We'll use the Adam optimizer for both, as recommended in the original DCGAN paper.
DCGAN Tutorial — PyTorch Tutorials
The PyTorch DCGAN tutorial provides a concise explanation of setting up the loss function and optimizers. It clearly defines the real and fake labels and shows how to instantiate separate Adam optimizers for the Generator and Discriminator.
Please read the section titled 'Loss Functions and Optimizers'. Focus on how BCELoss is initialized, how real_label and fake_label are defined, and the creation of optimizerD and optimizerG.
2. The Adversarial Training Loop
The training loop proceeds for a number of epochs, and within each epoch, it iterates through batches of our real data. For each batch, we perform a two-part update: first, we train the Discriminator, and second, we train the Generator.
DCGAN Tutorial — PyTorch Tutorials
Before diving into the code, let's get a conceptual overview of this two-part training process from the same PyTorch tutorial. This will clarify the goals for each network.
Read the section 'Training', focusing on the text in 'Part 1 - Train the Discriminator' and 'Part 2 - Train the Generator'. This explains the objective for each network in plain English.
Now, let's see how this is implemented step-by-step.
3. Part 1: Train the Discriminator
The Discriminator's goal is to get better at distinguishing real images from fake ones. Its training involves two mini-steps: learning from a batch of real images and learning from a batch of fake images.
Hands-on 5: Building a Generative Adversarial Network (GAN) from Scratch in PyTorch
This video from BrainOmega provides an excellent, step-by-step walkthrough of the training loop. This first segment covers the Discriminator's training phase.
Watch from 11:47 to 13:35. Pay close attention to the explanation of the .detach() call at 12:52. This is one of the most critical details in the entire GAN training process.
Let's summarize the steps to train the Discriminator for one batch:
-
Zero the gradients: Clear out any old gradients from the previous iteration.
optimizerD.zero_grad() -
Train on real images:
- Take a batch of
real_imagesfrom your dataloader. - Pass them through the Discriminator:
output_real = netD(real_images). - Calculate the loss by comparing the output to "real" labels (a tensor of all
1s):lossD_real = criterion(output_real, real_labels). - Compute the gradients for this loss:
lossD_real.backward().
- Take a batch of
-
Train on fake images:
- Generate a batch of
fake_imagesusing the Generator. - Pass these fakes through the Discriminator. Crucially, you must
detach()the fake images from the Generator's computational graph:output_fake = netD(fake_images.detach()). - Why
.detach()? We are only training the Discriminator right now. By detaching, we tell PyTorch to treat the fake images as fixed inputs (like an image loaded from a file). This prevents gradients from this step from flowing backward into the Generator, which would wrongly update the Generator based on the Discriminator's loss. - Calculate the loss by comparing the output to "fake" labels (a tensor of all
0s):lossD_fake = criterion(output_fake, fake_labels). - Compute and accumulate the gradients for this loss:
lossD_fake.backward().
- Generate a batch of
-
Update the Discriminator:
- The total loss is the sum of the real and fake losses:
lossD = lossD_real + lossD_fake. - Now, step the Discriminator's optimizer to update its weights using the accumulated gradients:
optimizerD.step().
- The total loss is the sum of the real and fake losses:
The detective is now a little bit smarter.
4. Part 2: Train the Generator
The Generator's goal is to produce fakes that are so good they fool the (now smarter) Discriminator.
The original GAN paper proposed that the Generator should try to minimize the probability that its fakes are caught, which corresponds to minimizing . However, this objective can lead to weak gradients early in training. A more effective and widely-used approach is to train the Generator to maximize the probability that its fakes are classified as real, i.e., maximize .
Hands-on 5: Building a Generative Adversarial Network (GAN) from Scratch in PyTorch
Let's continue with the BrainOmega video to see how the Generator's training phase is implemented.
Watch from 13:35 to 15:03. Notice that .detach() is NOT used here. Focus on the explanation at 14:20 about using 'real' labels (a tensor of all 1s) to calculate the Generator's loss. This is the clever trick that makes the training work.
Here are the steps to train the Generator:
-
Zero the gradients: Clear the Generator's old gradients.
optimizerG.zero_grad() -
Classify the fake images:
- Take the
fake_imageswe generated in the previous part and pass them through the updated Discriminator:output = netD(fake_images). - No
.detach()this time! We need the gradients to be able to flow from this output, back through the Discriminator's layers (without changing them), and all the way back into the Generator's layers so it can learn.
- Take the
-
Calculate Generator's loss:
- This is the clever part. We calculate the Generator's loss by comparing the Discriminator's output on the fake images to real labels (a tensor of all
1s):lossG = criterion(output, real_labels). - This effectively "lies" to the loss function. We are telling the Generator its goal is to produce images that the Discriminator classifies as
1(real). The resulting losslossGmeasures how well the Generator failed at this goal.
- This is the clever part. We calculate the Generator's loss by comparing the Discriminator's output on the fake images to real labels (a tensor of all
-
Update the Generator:
- Compute the gradients that will teach the Generator how to better fool the Discriminator:
lossG.backward(). - Step the Generator's optimizer to update its weights:
optimizerG.step().
- Compute the gradients that will teach the Generator how to better fool the Discriminator:
The forger is now a little more sophisticated.
5. The Complete Training Loop in Code
By placing these two parts inside a for loop that iterates over our dataset, we have our complete GAN training algorithm.
Generative Adversarial Networks: Build Your First Models
The Real Python article provides a clear, complete, and relatively simple implementation of this training loop for a basic MLP-based GAN. This is an excellent textual reference to see all the pieces together.
Please read the section 'Training the Models'. Focus on the final, large code block that contains the training loop. Trace the two main sections: 'Training the discriminator' (lines 14-19) and 'Training the generator' (lines 25-32), and map them back to the steps we just discussed.
For a more advanced DCGAN example, the PyTorch tutorial provides the full code. The logic is identical, just with more complex models.
DCGAN Tutorial — PyTorch Tutorials
For a more advanced reference, the PyTorch DCGAN tutorial provides the complete training loop code for a convolutional GAN.
Review the code block in the 'Training Loop' section. Identify Part 1 (Update D network) and Part 2 (Update G network). This is a great example of what production-level GAN training code looks like.
Conclusion
In this lesson, you've learned how to construct the heart of a GAN: the adversarial training loop. This is a significant step, as the interplay between the two networks is what makes GANs so powerful and unique.
Key Takeaways:
- GAN training requires two separate optimizers, one for the Generator and one for the Discriminator.
- Each training iteration has two parts: updating the Discriminator and updating the Generator.
- When training the Discriminator, you must
.detach()the fake images from the Generator's graph to prevent its gradients from flowing backward. - When training the Generator, you calculate its loss against real labels (
1s), effectively teaching it to produce images that the Discriminator classifies as real.
Preview of the Next Lesson
We have now defined the networks and implemented the training logic. All the pieces are in place. In the next lesson, we will finally train a GAN, generate synthetic images, and compare GANs vs. VAEs in terms of their output quality and training characteristics. We'll run the code we've prepared and analyze the results of our adversarial game.