Note

Click here to download the full example code

# DCGAN Tutorial¶

**Author**: Nathan Inkawhich

## Introduction¶

This tutorial will give an introduction to DCGANs through an example. We will train a generative adversarial network (GAN) to generate new celebrities after showing it pictures of many real celebrities. Most of the code here is from the dcgan implementation in pytorch/examples, and this document will give a thorough explanation of the implementation and shed light on how and why this model works. But don’t worry, no prior knowledge of GANs is required, but it may require a first-timer to spend some time reasoning about what is actually happening under the hood. Also, for the sake of time it will help to have a GPU, or two. Lets start from the beginning.

## Generative Adversarial Networks¶

### What is a GAN?¶

GANs are a framework for teaching a DL model to capture the training
data’s distribution so we can generate new data from that same
distribution. GANs were invented by Ian Goodfellow in 2014 and first
described in the paper Generative Adversarial
Nets.
They are made of two distinct models, a *generator* and a
*discriminator*. The job of the generator is to spawn ‘fake’ images that
look like the training images. The job of the discriminator is to look
at an image and output whether or not it is a real training image or a
fake image from the generator. During training, the generator is
constantly trying to outsmart the discriminator by generating better and
better fakes, while the discriminator is working to become a better
detective and correctly classify the real and fake images. The
equilibrium of this game is when the generator is generating perfect
fakes that look as if they came directly from the training data, and the
discriminator is left to always guess at 50% confidence that the
generator output is real or fake.

Now, lets define some notation to be used throughout tutorial starting with the discriminator. Let \(x\) be data representing an image. \(D(x)\) is the discriminator network which outputs the (scalar) probability that \(x\) came from training data rather than the generator. Here, since we are dealing with images, the input to \(D(x)\) is an image of CHW size 3x64x64. Intuitively, \(D(x)\) should be HIGH when \(x\) comes from training data and LOW when \(x\) comes from the generator. \(D(x)\) can also be thought of as a traditional binary classifier.

For the generator’s notation, let \(z\) be a latent space vector sampled from a standard normal distribution. \(G(z)\) represents the generator function which maps the latent vector \(z\) to data-space. The goal of \(G\) is to estimate the distribution that the training data comes from (\(p_{data}\)) so it can generate fake samples from that estimated distribution (\(p_g\)).

So, \(D(G(z))\) is the probability (scalar) that the output of the generator \(G\) is a real image. As described in Goodfellow’s paper, \(D\) and \(G\) play a minimax game in which \(D\) tries to maximize the probability it correctly classifies reals and fakes (\(logD(x)\)), and \(G\) tries to minimize the probability that \(D\) will predict its outputs are fake (\(log(1-D(G(z)))\)). From the paper, the GAN loss function is

In theory, the solution to this minimax game is where \(p_g = p_{data}\), and the discriminator guesses randomly if the inputs are real or fake. However, the convergence theory of GANs is still being actively researched and in reality models do not always train to this point.

### What is a DCGAN?¶

A DCGAN is a direct extension of the GAN described above, except that it explicitly uses convolutional and convolutional-transpose layers in the discriminator and generator, respectively. It was first described by Radford et. al. in the paper Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks. The discriminator is made up of strided convolution layers, batch norm layers, and LeakyReLU activations. The input is a 3x64x64 input image and the output is a scalar probability that the input is from the real data distribution. The generator is comprised of convolutional-transpose layers, batch norm layers, and ReLU activations. The input is a latent vector, \(z\), that is drawn from a standard normal distribution and the output is a 3x64x64 RGB image. The strided conv-transpose layers allow the latent vector to be transformed into a volume with the same shape as an image. In the paper, the authors also give some tips about how to setup the optimizers, how to calculate the loss functions, and how to initialize the model weights, all of which will be explained in the coming sections.

```
from __future__ import print_function
#%matplotlib inline
import argparse
import os
import random
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
# Set random seed for reproducibility
manualSeed = 999
#manualSeed = random.randint(1, 10000) # use if you want new results
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
```

```
Random Seed: 999
<torch._C.Generator object at 0x7f0644566f30>
```

## Inputs¶

Let’s define some inputs for the run:

**dataroot**- the path to the root of the dataset folder. We will talk more about the dataset in the next section**workers**- the number of worker threads for loading the data with the DataLoader**batch_size**- the batch size used in training. The DCGAN paper uses a batch size of 128**image_size**- the spatial size of the images used for training. This implementation defaults to 64x64. If another size is desired, the structures of D and G must be changed. See here for more details**nc**- number of color channels in the input images. For color images this is 3**nz**- length of latent vector**ngf**- relates to the depth of feature maps carried through the generator**ndf**- sets the depth of feature maps propagated through the discriminator**num_epochs**- number of training epochs to run. Training for longer will probably lead to better results but will also take much longer**lr**- learning rate for training. As described in the DCGAN paper, this number should be 0.0002**beta1**- beta1 hyperparameter for Adam optimizers. As described in paper, this number should be 0.5**ngpu**- number of GPUs available. If this is 0, code will run in CPU mode. If this number is greater than 0 it will run on that number of GPUs

```
# Root directory for dataset
dataroot = "data/celeba"
# Number of workers for dataloader
workers = 2
# Batch size during training
batch_size = 128
# Spatial size of training images. All images will be resized to this
# size using a transformer.
image_size = 64
# Number of channels in the training images. For color images this is 3
nc = 3
# Size of z latent vector (i.e. size of generator input)
nz = 100
# Size of feature maps in generator
ngf = 64
# Size of feature maps in discriminator
ndf = 64
# Number of training epochs
num_epochs = 5
# Learning rate for optimizers
lr = 0.0002
# Beta1 hyperparam for Adam optimizers
beta1 = 0.5
# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1
```

## Data¶

In this tutorial we will use the Celeb-A Faces
dataset which can
be downloaded at the linked site, or in Google
Drive.
The dataset will download as a file named *img_align_celeba.zip*. Once
downloaded, create a directory named *celeba* and extract the zip file
into that directory. Then, set the *dataroot* input for this notebook to
the *celeba* directory you just created. The resulting directory
structure should be:

```
/path/to/celeba
-> img_align_celeba
-> 188242.jpg
-> 173822.jpg
-> 284702.jpg
-> 537394.jpg
...
```

This is an important step because we will be using the ImageFolder dataset class, which requires there to be subdirectories in the dataset’s root folder. Now, we can create the dataset, create the dataloader, set the device to run on, and finally visualize some of the training data.

```
# We can use an image folder dataset the way we have it setup.
# Create the dataset
dataset = dset.ImageFolder(root=dataroot,
transform=transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]))
# Create the dataloader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
shuffle=True, num_workers=workers)
# Decide which device we want to run on
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
# Plot some training images
real_batch = next(iter(dataloader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=2, normalize=True).cpu(),(1,2,0)))
```

```
<matplotlib.image.AxesImage object at 0x7f06218e06d0>
```

## Implementation¶

With our input parameters set and the dataset prepared, we can now get into the implementation. We will start with the weight initialization strategy, then talk about the generator, discriminator, loss functions, and training loop in detail.

### Weight Initialization¶

From the DCGAN paper, the authors specify that all model weights shall
be randomly initialized from a Normal distribution with mean=0,
stdev=0.02. The `weights_init`

function takes an initialized model as
input and reinitializes all convolutional, convolutional-transpose, and
batch normalization layers to meet this criteria. This function is
applied to the models immediately after initialization.

```
# custom weights initialization called on netG and netD
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
nn.init.normal_(m.weight.data, 0.0, 0.02)
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
nn.init.constant_(m.bias.data, 0)
```

### Generator¶

The generator, \(G\), is designed to map the latent space vector (\(z\)) to data-space. Since our data are images, converting \(z\) to data-space means ultimately creating a RGB image with the same size as the training images (i.e. 3x64x64). In practice, this is accomplished through a series of strided two dimensional convolutional transpose layers, each paired with a 2d batch norm layer and a relu activation. The output of the generator is fed through a tanh function to return it to the input data range of \([-1,1]\). It is worth noting the existence of the batch norm functions after the conv-transpose layers, as this is a critical contribution of the DCGAN paper. These layers help with the flow of gradients during training. An image of the generator from the DCGAN paper is shown below.

Notice, how the inputs we set in the input section (*nz*, *ngf*, and
*nc*) influence the generator architecture in code. *nz* is the length
of the z input vector, *ngf* relates to the size of the feature maps
that are propagated through the generator, and *nc* is the number of
channels in the output image (set to 3 for RGB images). Below is the
code for the generator.

```
# Generator Code
class Generator(nn.Module):
def __init__(self, ngpu):
super(Generator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
# input is Z, going into a convolution
nn.ConvTranspose2d( nz, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 8),
nn.ReLU(True),
# state size. (ngf*8) x 4 x 4
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
# state size. (ngf*4) x 8 x 8
nn.ConvTranspose2d( ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
# state size. (ngf*2) x 16 x 16
nn.ConvTranspose2d( ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf),
nn.ReLU(True),
# state size. (ngf) x 32 x 32
nn.ConvTranspose2d( ngf, nc, 4, 2, 1, bias=False),
nn.Tanh()
# state size. (nc) x 64 x 64
)
def forward(self, input):
return self.main(input)
```

Now, we can instantiate the generator and apply the `weights_init`

function. Check out the printed model to see how the generator object is
structured.

```
# Create the generator
netG = Generator(ngpu).to(device)
# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
netG = nn.DataParallel(netG, list(range(ngpu)))
# Apply the weights_init function to randomly initialize all weights
# to mean=0, stdev=0.02.
netG.apply(weights_init)
# Print the model
print(netG)
```

```
Generator(
(main): Sequential(
(0): ConvTranspose2d(100, 512, kernel_size=(4, 4), stride=(1, 1), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): ConvTranspose2d(512, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
(6): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(10): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(13): Tanh()
)
)
```

### Discriminator¶

As mentioned, the discriminator, \(D\), is a binary classification network that takes an image as input and outputs a scalar probability that the input image is real (as opposed to fake). Here, \(D\) takes a 3x64x64 input image, processes it through a series of Conv2d, BatchNorm2d, and LeakyReLU layers, and outputs the final probability through a Sigmoid activation function. This architecture can be extended with more layers if necessary for the problem, but there is significance to the use of the strided convolution, BatchNorm, and LeakyReLUs. The DCGAN paper mentions it is a good practice to use strided convolution rather than pooling to downsample because it lets the network learn its own pooling function. Also batch norm and leaky relu functions promote healthy gradient flow which is critical for the learning process of both \(G\) and \(D\).

Discriminator Code

```
class Discriminator(nn.Module):
def __init__(self, ngpu):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.main = nn.Sequential(
# input is (nc) x 64 x 64
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf) x 32 x 32
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 2),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*2) x 16 x 16
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*4) x 8 x 8
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*8) x 4 x 4
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid()
)
def forward(self, input):
return self.main(input)
```

Now, as with the generator, we can create the discriminator, apply the
`weights_init`

function, and print the model’s structure.

```
# Create the Discriminator
netD = Discriminator(ngpu).to(device)
# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
netD = nn.DataParallel(netD, list(range(ngpu)))
# Apply the weights_init function to randomly initialize all weights
# to mean=0, stdev=0.2.
netD.apply(weights_init)
# Print the model
print(netD)
```

```
Discriminator(
(main): Sequential(
(0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(1): LeakyReLU(negative_slope=0.2, inplace=True)
(2): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(3): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(4): LeakyReLU(negative_slope=0.2, inplace=True)
(5): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(6): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(7): LeakyReLU(negative_slope=0.2, inplace=True)
(8): Conv2d(256, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(9): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(10): LeakyReLU(negative_slope=0.2, inplace=True)
(11): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), bias=False)
(12): Sigmoid()
)
)
```

### Loss Functions and Optimizers¶

With \(D\) and \(G\) setup, we can specify how they learn through the loss functions and optimizers. We will use the Binary Cross Entropy loss (BCELoss) function which is defined in PyTorch as:

Notice how this function provides the calculation of both log components in the objective function (i.e. \(log(D(x))\) and \(log(1-D(G(z)))\)). We can specify what part of the BCE equation to use with the \(y\) input. This is accomplished in the training loop which is coming up soon, but it is important to understand how we can choose which component we wish to calculate just by changing \(y\) (i.e. GT labels).

Next, we define our real label as 1 and the fake label as 0. These labels will be used when calculating the losses of \(D\) and \(G\), and this is also the convention used in the original GAN paper. Finally, we set up two separate optimizers, one for \(D\) and one for \(G\). As specified in the DCGAN paper, both are Adam optimizers with learning rate 0.0002 and Beta1 = 0.5. For keeping track of the generator’s learning progression, we will generate a fixed batch of latent vectors that are drawn from a Gaussian distribution (i.e. fixed_noise) . In the training loop, we will periodically input this fixed_noise into \(G\), and over the iterations we will see images form out of the noise.

```
# Initialize BCELoss function
criterion = nn.BCELoss()
# Create batch of latent vectors that we will use to visualize
# the progression of the generator
fixed_noise = torch.randn(64, nz, 1, 1, device=device)
# Establish convention for real and fake labels during training
real_label = 1.
fake_label = 0.
# Setup Adam optimizers for both G and D
optimizerD = optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
```

### Training¶

Finally, now that we have all of the parts of the GAN framework defined, we can train it. Be mindful that training GANs is somewhat of an art form, as incorrect hyperparameter settings lead to mode collapse with little explanation of what went wrong. Here, we will closely follow Algorithm 1 from Goodfellow’s paper, while abiding by some of the best practices shown in ganhacks. Namely, we will “construct different mini-batches for real and fake” images, and also adjust G’s objective function to maximize \(logD(G(z))\). Training is split up into two main parts. Part 1 updates the Discriminator and Part 2 updates the Generator.

**Part 1 - Train the Discriminator**

Recall, the goal of training the discriminator is to maximize the
probability of correctly classifying a given input as real or fake. In
terms of Goodfellow, we wish to “update the discriminator by ascending
its stochastic gradient”. Practically, we want to maximize
\(log(D(x)) + log(1-D(G(z)))\). Due to the separate mini-batch
suggestion from ganhacks, we will calculate this in two steps. First, we
will construct a batch of real samples from the training set, forward
pass through \(D\), calculate the loss (\(log(D(x))\)), then
calculate the gradients in a backward pass. Secondly, we will construct
a batch of fake samples with the current generator, forward pass this
batch through \(D\), calculate the loss (\(log(1-D(G(z)))\)),
and *accumulate* the gradients with a backward pass. Now, with the
gradients accumulated from both the all-real and all-fake batches, we
call a step of the Discriminator’s optimizer.

**Part 2 - Train the Generator**

As stated in the original paper, we want to train the Generator by
minimizing \(log(1-D(G(z)))\) in an effort to generate better fakes.
As mentioned, this was shown by Goodfellow to not provide sufficient
gradients, especially early in the learning process. As a fix, we
instead wish to maximize \(log(D(G(z)))\). In the code we accomplish
this by: classifying the Generator output from Part 1 with the
Discriminator, computing G’s loss *using real labels as GT*, computing
G’s gradients in a backward pass, and finally updating G’s parameters
with an optimizer step. It may seem counter-intuitive to use the real
labels as GT labels for the loss function, but this allows us to use the
\(log(x)\) part of the BCELoss (rather than the \(log(1-x)\)
part) which is exactly what we want.

Finally, we will do some statistic reporting and at the end of each epoch we will push our fixed_noise batch through the generator to visually track the progress of G’s training. The training statistics reported are:

**Loss_D**- discriminator loss calculated as the sum of losses for the all real and all fake batches (\(log(D(x)) + log(1 - D(G(z)))\)).**Loss_G**- generator loss calculated as \(log(D(G(z)))\)**D(x)**- the average output (across the batch) of the discriminator for the all real batch. This should start close to 1 then theoretically converge to 0.5 when G gets better. Think about why this is.**D(G(z))**- average discriminator outputs for the all fake batch. The first number is before D is updated and the second number is after D is updated. These numbers should start near 0 and converge to 0.5 as G gets better. Think about why this is.

**Note:** This step might take a while, depending on how many epochs you
run and if you removed some data from the dataset.

```
# Training Loop
# Lists to keep track of progress
img_list = []
G_losses = []
D_losses = []
iters = 0
print("Starting Training Loop...")
# For each epoch
for epoch in range(num_epochs):
# For each batch in the dataloader
for i, data in enumerate(dataloader, 0):
############################
# (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
###########################
## Train with all-real batch
netD.zero_grad()
# Format batch
real_cpu = data[0].to(device)
b_size = real_cpu.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
# Forward pass real batch through D
output = netD(real_cpu).view(-1)
# Calculate loss on all-real batch
errD_real = criterion(output, label)
# Calculate gradients for D in backward pass
errD_real.backward()
D_x = output.mean().item()
## Train with all-fake batch
# Generate batch of latent vectors
noise = torch.randn(b_size, nz, 1, 1, device=device)
# Generate fake image batch with G
fake = netG(noise)
label.fill_(fake_label)
# Classify all fake batch with D
output = netD(fake.detach()).view(-1)
# Calculate D's loss on the all-fake batch
errD_fake = criterion(output, label)
# Calculate the gradients for this batch, accumulated (summed) with previous gradients
errD_fake.backward()
D_G_z1 = output.mean().item()
# Compute error of D as sum over the fake and the real batches
errD = errD_real + errD_fake
# Update D
optimizerD.step()
############################
# (2) Update G network: maximize log(D(G(z)))
###########################
netG.zero_grad()
label.fill_(real_label) # fake labels are real for generator cost
# Since we just updated D, perform another forward pass of all-fake batch through D
output = netD(fake).view(-1)
# Calculate G's loss based on this output
errG = criterion(output, label)
# Calculate gradients for G
errG.backward()
D_G_z2 = output.mean().item()
# Update G
optimizerG.step()
# Output training stats
if i % 50 == 0:
print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
% (epoch, num_epochs, i, len(dataloader),
errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
# Save Losses for plotting later
G_losses.append(errG.item())
D_losses.append(errD.item())
# Check how the generator is doing by saving G's output on fixed_noise
if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)):
with torch.no_grad():
fake = netG(fixed_noise).detach().cpu()
img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
iters += 1
```

```
Starting Training Loop...
[0/5][0/1583] Loss_D: 1.6264 Loss_G: 5.5242 D(x): 0.5733 D(G(z)): 0.5501 / 0.0065
[0/5][50/1583] Loss_D: 0.0031 Loss_G: 32.0320 D(x): 0.9970 D(G(z)): 0.0000 / 0.0000
[0/5][100/1583] Loss_D: 0.5037 Loss_G: 10.4481 D(x): 0.9689 D(G(z)): 0.2969 / 0.0004
[0/5][150/1583] Loss_D: 2.5276 Loss_G: 11.2529 D(x): 0.9866 D(G(z)): 0.8513 / 0.0001
[0/5][200/1583] Loss_D: 1.6489 Loss_G: 6.7398 D(x): 0.3406 D(G(z)): 0.0007 / 0.0034
[0/5][250/1583] Loss_D: 0.5429 Loss_G: 5.6368 D(x): 0.8300 D(G(z)): 0.2387 / 0.0071
[0/5][300/1583] Loss_D: 0.7450 Loss_G: 6.3654 D(x): 0.8171 D(G(z)): 0.2901 / 0.0072
[0/5][350/1583] Loss_D: 0.9040 Loss_G: 4.9200 D(x): 0.9561 D(G(z)): 0.4751 / 0.0175
[0/5][400/1583] Loss_D: 0.5630 Loss_G: 3.2944 D(x): 0.7442 D(G(z)): 0.1140 / 0.0862
[0/5][450/1583] Loss_D: 0.5734 Loss_G: 5.1450 D(x): 0.8917 D(G(z)): 0.3035 / 0.0142
[0/5][500/1583] Loss_D: 0.3765 Loss_G: 4.5770 D(x): 0.8593 D(G(z)): 0.1561 / 0.0167
[0/5][550/1583] Loss_D: 0.3210 Loss_G: 4.8230 D(x): 0.9444 D(G(z)): 0.2066 / 0.0179
[0/5][600/1583] Loss_D: 0.6250 Loss_G: 3.8915 D(x): 0.6717 D(G(z)): 0.0266 / 0.0392
[0/5][650/1583] Loss_D: 0.4896 Loss_G: 4.6794 D(x): 0.8084 D(G(z)): 0.1811 / 0.0140
[0/5][700/1583] Loss_D: 0.3009 Loss_G: 4.4123 D(x): 0.8911 D(G(z)): 0.1401 / 0.0226
[0/5][750/1583] Loss_D: 2.3580 Loss_G: 4.3032 D(x): 0.1892 D(G(z)): 0.0010 / 0.0248
[0/5][800/1583] Loss_D: 1.5044 Loss_G: 2.0247 D(x): 0.3783 D(G(z)): 0.0031 / 0.2348
[0/5][850/1583] Loss_D: 0.4925 Loss_G: 3.4441 D(x): 0.7477 D(G(z)): 0.1017 / 0.0586
[0/5][900/1583] Loss_D: 0.5900 Loss_G: 4.6416 D(x): 0.8486 D(G(z)): 0.2786 / 0.0182
[0/5][950/1583] Loss_D: 0.5623 Loss_G: 1.5159 D(x): 0.7458 D(G(z)): 0.0966 / 0.3459
[0/5][1000/1583] Loss_D: 0.6813 Loss_G: 4.7263 D(x): 0.8661 D(G(z)): 0.3602 / 0.0182
[0/5][1050/1583] Loss_D: 0.4883 Loss_G: 3.8482 D(x): 0.9162 D(G(z)): 0.2927 / 0.0352
[0/5][1100/1583] Loss_D: 0.5801 Loss_G: 4.1083 D(x): 0.7980 D(G(z)): 0.2106 / 0.0266
[0/5][1150/1583] Loss_D: 0.5493 Loss_G: 4.6200 D(x): 0.8198 D(G(z)): 0.2419 / 0.0177
[0/5][1200/1583] Loss_D: 0.3730 Loss_G: 5.1868 D(x): 0.8857 D(G(z)): 0.1917 / 0.0095
[0/5][1250/1583] Loss_D: 2.1912 Loss_G: 3.2550 D(x): 0.2705 D(G(z)): 0.0023 / 0.0762
[0/5][1300/1583] Loss_D: 0.5141 Loss_G: 3.3861 D(x): 0.8101 D(G(z)): 0.2178 / 0.0473
[0/5][1350/1583] Loss_D: 0.7316 Loss_G: 5.0135 D(x): 0.9478 D(G(z)): 0.4351 / 0.0124
[0/5][1400/1583] Loss_D: 0.2876 Loss_G: 5.0306 D(x): 0.8178 D(G(z)): 0.0462 / 0.0147
[0/5][1450/1583] Loss_D: 0.5461 Loss_G: 3.3917 D(x): 0.7133 D(G(z)): 0.0807 / 0.0608
[0/5][1500/1583] Loss_D: 0.6771 Loss_G: 4.5383 D(x): 0.8023 D(G(z)): 0.2880 / 0.0208
[0/5][1550/1583] Loss_D: 0.4153 Loss_G: 2.6543 D(x): 0.8346 D(G(z)): 0.0817 / 0.1609
[1/5][0/1583] Loss_D: 0.7629 Loss_G: 5.1281 D(x): 0.9014 D(G(z)): 0.4261 / 0.0103
[1/5][50/1583] Loss_D: 1.2714 Loss_G: 5.7987 D(x): 0.9364 D(G(z)): 0.6118 / 0.0091
[1/5][100/1583] Loss_D: 0.6039 Loss_G: 3.7586 D(x): 0.7906 D(G(z)): 0.2643 / 0.0341
[1/5][150/1583] Loss_D: 0.4319 Loss_G: 4.3373 D(x): 0.8713 D(G(z)): 0.2200 / 0.0218
[1/5][200/1583] Loss_D: 0.4551 Loss_G: 2.8342 D(x): 0.8100 D(G(z)): 0.1626 / 0.0959
[1/5][250/1583] Loss_D: 1.4037 Loss_G: 2.8547 D(x): 0.3776 D(G(z)): 0.0444 / 0.1176
[1/5][300/1583] Loss_D: 0.5193 Loss_G: 4.9522 D(x): 0.9317 D(G(z)): 0.3226 / 0.0127
[1/5][350/1583] Loss_D: 0.3906 Loss_G: 4.2122 D(x): 0.8778 D(G(z)): 0.1972 / 0.0226
[1/5][400/1583] Loss_D: 0.4508 Loss_G: 5.9906 D(x): 0.9202 D(G(z)): 0.2499 / 0.0050
[1/5][450/1583] Loss_D: 0.8011 Loss_G: 6.4218 D(x): 0.9491 D(G(z)): 0.4639 / 0.0032
[1/5][500/1583] Loss_D: 0.3029 Loss_G: 4.8523 D(x): 0.9245 D(G(z)): 0.1744 / 0.0147
[1/5][550/1583] Loss_D: 0.4976 Loss_G: 3.3111 D(x): 0.7768 D(G(z)): 0.1461 / 0.0586
[1/5][600/1583] Loss_D: 0.3800 Loss_G: 4.0671 D(x): 0.8605 D(G(z)): 0.1770 / 0.0271
[1/5][650/1583] Loss_D: 0.3983 Loss_G: 3.1839 D(x): 0.7659 D(G(z)): 0.0806 / 0.0650
[1/5][700/1583] Loss_D: 0.4295 Loss_G: 3.1892 D(x): 0.8106 D(G(z)): 0.1616 / 0.0638
[1/5][750/1583] Loss_D: 1.3514 Loss_G: 0.8976 D(x): 0.3623 D(G(z)): 0.0271 / 0.4786
[1/5][800/1583] Loss_D: 0.4414 Loss_G: 2.9091 D(x): 0.7414 D(G(z)): 0.0968 / 0.0801
[1/5][850/1583] Loss_D: 0.5706 Loss_G: 2.8657 D(x): 0.7620 D(G(z)): 0.1829 / 0.0845
[1/5][900/1583] Loss_D: 0.4867 Loss_G: 4.8972 D(x): 0.9327 D(G(z)): 0.2946 / 0.0145
[1/5][950/1583] Loss_D: 0.5642 Loss_G: 4.5583 D(x): 0.9325 D(G(z)): 0.3446 / 0.0167
[1/5][1000/1583] Loss_D: 0.3660 Loss_G: 3.2894 D(x): 0.7636 D(G(z)): 0.0613 / 0.0552
[1/5][1050/1583] Loss_D: 0.3969 Loss_G: 3.6469 D(x): 0.8376 D(G(z)): 0.1479 / 0.0371
[1/5][1100/1583] Loss_D: 0.6594 Loss_G: 5.2682 D(x): 0.9198 D(G(z)): 0.3916 / 0.0081
[1/5][1150/1583] Loss_D: 0.4534 Loss_G: 2.0998 D(x): 0.7609 D(G(z)): 0.1110 / 0.1731
[1/5][1200/1583] Loss_D: 0.4645 Loss_G: 4.1148 D(x): 0.8942 D(G(z)): 0.2529 / 0.0287
[1/5][1250/1583] Loss_D: 1.0562 Loss_G: 5.6668 D(x): 0.9217 D(G(z)): 0.5445 / 0.0086
[1/5][1300/1583] Loss_D: 0.4542 Loss_G: 3.1229 D(x): 0.9280 D(G(z)): 0.2696 / 0.0724
[1/5][1350/1583] Loss_D: 0.7544 Loss_G: 4.8632 D(x): 0.8862 D(G(z)): 0.4084 / 0.0116
[1/5][1400/1583] Loss_D: 0.7291 Loss_G: 5.2082 D(x): 0.9272 D(G(z)): 0.4360 / 0.0086
[1/5][1450/1583] Loss_D: 0.3810 Loss_G: 3.2008 D(x): 0.8269 D(G(z)): 0.1339 / 0.0558
[1/5][1500/1583] Loss_D: 0.6377 Loss_G: 2.1144 D(x): 0.7408 D(G(z)): 0.2013 / 0.1646
[1/5][1550/1583] Loss_D: 0.3511 Loss_G: 3.1798 D(x): 0.8433 D(G(z)): 0.1443 / 0.0622
[2/5][0/1583] Loss_D: 0.3102 Loss_G: 3.0884 D(x): 0.8614 D(G(z)): 0.1285 / 0.0645
[2/5][50/1583] Loss_D: 1.0732 Loss_G: 5.1261 D(x): 0.9353 D(G(z)): 0.5692 / 0.0132
[2/5][100/1583] Loss_D: 0.5945 Loss_G: 2.8002 D(x): 0.7331 D(G(z)): 0.2006 / 0.0854
[2/5][150/1583] Loss_D: 1.0868 Loss_G: 1.1617 D(x): 0.4372 D(G(z)): 0.0517 / 0.3857
[2/5][200/1583] Loss_D: 1.0129 Loss_G: 0.8565 D(x): 0.4568 D(G(z)): 0.0426 / 0.4822
[2/5][250/1583] Loss_D: 0.5757 Loss_G: 1.9530 D(x): 0.6910 D(G(z)): 0.1400 / 0.1762
[2/5][300/1583] Loss_D: 0.4394 Loss_G: 2.6463 D(x): 0.8239 D(G(z)): 0.1881 / 0.0966
[2/5][350/1583] Loss_D: 0.4858 Loss_G: 1.5850 D(x): 0.7471 D(G(z)): 0.1341 / 0.2448
[2/5][400/1583] Loss_D: 0.7529 Loss_G: 3.3570 D(x): 0.7886 D(G(z)): 0.3429 / 0.0520
[2/5][450/1583] Loss_D: 0.4939 Loss_G: 3.2202 D(x): 0.8672 D(G(z)): 0.2654 / 0.0533
[2/5][500/1583] Loss_D: 1.0204 Loss_G: 2.6266 D(x): 0.9063 D(G(z)): 0.5137 / 0.1135
[2/5][550/1583] Loss_D: 0.7289 Loss_G: 1.8583 D(x): 0.5544 D(G(z)): 0.0439 / 0.2139
[2/5][600/1583] Loss_D: 0.5643 Loss_G: 2.9024 D(x): 0.8629 D(G(z)): 0.2957 / 0.0781
[2/5][650/1583] Loss_D: 0.6088 Loss_G: 1.7750 D(x): 0.7214 D(G(z)): 0.1881 / 0.2243
[2/5][700/1583] Loss_D: 0.4905 Loss_G: 1.9261 D(x): 0.7413 D(G(z)): 0.1388 / 0.1883
[2/5][750/1583] Loss_D: 0.6973 Loss_G: 2.2707 D(x): 0.6621 D(G(z)): 0.1925 / 0.1399
[2/5][800/1583] Loss_D: 0.7145 Loss_G: 3.8642 D(x): 0.9081 D(G(z)): 0.4228 / 0.0291
[2/5][850/1583] Loss_D: 0.6614 Loss_G: 2.0747 D(x): 0.7273 D(G(z)): 0.2428 / 0.1575
[2/5][900/1583] Loss_D: 0.4065 Loss_G: 2.4808 D(x): 0.8496 D(G(z)): 0.1979 / 0.1027
[2/5][950/1583] Loss_D: 1.2986 Loss_G: 4.8258 D(x): 0.9351 D(G(z)): 0.6317 / 0.0134
[2/5][1000/1583] Loss_D: 0.7212 Loss_G: 1.4380 D(x): 0.5741 D(G(z)): 0.0660 / 0.2883
[2/5][1050/1583] Loss_D: 1.4568 Loss_G: 0.5030 D(x): 0.2859 D(G(z)): 0.0112 / 0.6480
[2/5][1100/1583] Loss_D: 1.4096 Loss_G: 5.1087 D(x): 0.9892 D(G(z)): 0.6994 / 0.0102
[2/5][1150/1583] Loss_D: 1.4664 Loss_G: 1.5081 D(x): 0.3385 D(G(z)): 0.0676 / 0.2935
[2/5][1200/1583] Loss_D: 0.3828 Loss_G: 2.7701 D(x): 0.8036 D(G(z)): 0.1252 / 0.0822
[2/5][1250/1583] Loss_D: 0.4490 Loss_G: 2.1291 D(x): 0.7326 D(G(z)): 0.0983 / 0.1570
[2/5][1300/1583] Loss_D: 1.1455 Loss_G: 5.3777 D(x): 0.9518 D(G(z)): 0.5932 / 0.0088
[2/5][1350/1583] Loss_D: 0.5898 Loss_G: 2.5553 D(x): 0.8418 D(G(z)): 0.3004 / 0.1049
[2/5][1400/1583] Loss_D: 0.6226 Loss_G: 3.3191 D(x): 0.8289 D(G(z)): 0.3102 / 0.0486
[2/5][1450/1583] Loss_D: 0.8505 Loss_G: 4.6470 D(x): 0.9456 D(G(z)): 0.4989 / 0.0136
[2/5][1500/1583] Loss_D: 0.5384 Loss_G: 3.4295 D(x): 0.8509 D(G(z)): 0.2792 / 0.0462
[2/5][1550/1583] Loss_D: 1.1080 Loss_G: 1.5154 D(x): 0.4390 D(G(z)): 0.1032 / 0.2995
[3/5][0/1583] Loss_D: 0.5853 Loss_G: 1.7235 D(x): 0.6298 D(G(z)): 0.0559 / 0.2385
[3/5][50/1583] Loss_D: 0.7044 Loss_G: 3.3396 D(x): 0.7756 D(G(z)): 0.3160 / 0.0487
[3/5][100/1583] Loss_D: 0.9356 Loss_G: 2.0633 D(x): 0.6154 D(G(z)): 0.2437 / 0.1662
[3/5][150/1583] Loss_D: 0.7703 Loss_G: 4.8197 D(x): 0.9499 D(G(z)): 0.4661 / 0.0113
[3/5][200/1583] Loss_D: 0.9332 Loss_G: 5.1428 D(x): 0.9467 D(G(z)): 0.5296 / 0.0084
[3/5][250/1583] Loss_D: 0.5898 Loss_G: 4.4041 D(x): 0.9157 D(G(z)): 0.3377 / 0.0182
[3/5][300/1583] Loss_D: 0.7830 Loss_G: 0.9558 D(x): 0.5570 D(G(z)): 0.1034 / 0.4489
[3/5][350/1583] Loss_D: 0.6392 Loss_G: 2.4584 D(x): 0.8555 D(G(z)): 0.3415 / 0.1145
[3/5][400/1583] Loss_D: 0.7427 Loss_G: 1.8937 D(x): 0.5576 D(G(z)): 0.0495 / 0.1988
[3/5][450/1583] Loss_D: 0.5596 Loss_G: 3.6405 D(x): 0.8843 D(G(z)): 0.3225 / 0.0381
[3/5][500/1583] Loss_D: 0.6635 Loss_G: 3.5162 D(x): 0.8503 D(G(z)): 0.3471 / 0.0412
[3/5][550/1583] Loss_D: 0.5399 Loss_G: 2.0626 D(x): 0.7206 D(G(z)): 0.1498 / 0.1674
[3/5][600/1583] Loss_D: 1.3322 Loss_G: 1.0997 D(x): 0.3482 D(G(z)): 0.0304 / 0.3971
[3/5][650/1583] Loss_D: 0.5226 Loss_G: 2.7249 D(x): 0.8513 D(G(z)): 0.2753 / 0.0870
[3/5][700/1583] Loss_D: 0.4392 Loss_G: 2.6286 D(x): 0.8007 D(G(z)): 0.1723 / 0.0898
[3/5][750/1583] Loss_D: 0.7246 Loss_G: 3.9288 D(x): 0.9162 D(G(z)): 0.4307 / 0.0275
[3/5][800/1583] Loss_D: 0.6057 Loss_G: 1.4376 D(x): 0.6275 D(G(z)): 0.0738 / 0.2877
[3/5][850/1583] Loss_D: 0.4908 Loss_G: 2.6217 D(x): 0.8199 D(G(z)): 0.2195 / 0.0955
[3/5][900/1583] Loss_D: 1.2157 Loss_G: 6.1448 D(x): 0.9442 D(G(z)): 0.6400 / 0.0038
[3/5][950/1583] Loss_D: 0.5185 Loss_G: 2.8125 D(x): 0.7789 D(G(z)): 0.1950 / 0.0828
[3/5][1000/1583] Loss_D: 0.5253 Loss_G: 2.7709 D(x): 0.8240 D(G(z)): 0.2510 / 0.0798
[3/5][1050/1583] Loss_D: 0.7109 Loss_G: 1.1548 D(x): 0.6216 D(G(z)): 0.1478 / 0.3612
[3/5][1100/1583] Loss_D: 0.7805 Loss_G: 3.7183 D(x): 0.9215 D(G(z)): 0.4532 / 0.0362
[3/5][1150/1583] Loss_D: 0.8000 Loss_G: 2.2760 D(x): 0.5467 D(G(z)): 0.0574 / 0.1409
[3/5][1200/1583] Loss_D: 0.5093 Loss_G: 2.0259 D(x): 0.7095 D(G(z)): 0.1184 / 0.1699
[3/5][1250/1583] Loss_D: 2.4448 Loss_G: 5.1373 D(x): 0.9887 D(G(z)): 0.8569 / 0.0149
[3/5][1300/1583] Loss_D: 0.5337 Loss_G: 2.2856 D(x): 0.6872 D(G(z)): 0.1058 / 0.1318
[3/5][1350/1583] Loss_D: 0.9180 Loss_G: 0.7422 D(x): 0.4832 D(G(z)): 0.0580 / 0.5251
[3/5][1400/1583] Loss_D: 0.7372 Loss_G: 3.2275 D(x): 0.8250 D(G(z)): 0.3765 / 0.0520
[3/5][1450/1583] Loss_D: 0.5664 Loss_G: 2.3172 D(x): 0.7729 D(G(z)): 0.2265 / 0.1277
[3/5][1500/1583] Loss_D: 0.6276 Loss_G: 2.7298 D(x): 0.8155 D(G(z)): 0.2954 / 0.0887
[3/5][1550/1583] Loss_D: 0.4411 Loss_G: 2.9188 D(x): 0.8691 D(G(z)): 0.2393 / 0.0714
[4/5][0/1583] Loss_D: 0.8753 Loss_G: 1.8748 D(x): 0.5020 D(G(z)): 0.0386 / 0.2189
[4/5][50/1583] Loss_D: 1.1025 Loss_G: 3.8965 D(x): 0.8526 D(G(z)): 0.5443 / 0.0307
[4/5][100/1583] Loss_D: 0.9152 Loss_G: 2.1728 D(x): 0.4716 D(G(z)): 0.0228 / 0.1497
[4/5][150/1583] Loss_D: 1.0140 Loss_G: 3.2510 D(x): 0.8280 D(G(z)): 0.4950 / 0.0571
[4/5][200/1583] Loss_D: 0.5944 Loss_G: 2.6893 D(x): 0.7887 D(G(z)): 0.2561 / 0.0909
[4/5][250/1583] Loss_D: 0.4067 Loss_G: 2.6009 D(x): 0.8427 D(G(z)): 0.1888 / 0.0983
[4/5][300/1583] Loss_D: 0.6108 Loss_G: 3.5265 D(x): 0.8286 D(G(z)): 0.2953 / 0.0407
[4/5][350/1583] Loss_D: 0.5497 Loss_G: 3.3491 D(x): 0.9195 D(G(z)): 0.3422 / 0.0460
[4/5][400/1583] Loss_D: 0.4802 Loss_G: 2.8139 D(x): 0.8022 D(G(z)): 0.1987 / 0.0830
[4/5][450/1583] Loss_D: 0.6150 Loss_G: 1.7636 D(x): 0.6518 D(G(z)): 0.1224 / 0.2104
[4/5][500/1583] Loss_D: 0.5590 Loss_G: 3.3947 D(x): 0.8740 D(G(z)): 0.3142 / 0.0443
[4/5][550/1583] Loss_D: 0.4895 Loss_G: 2.3253 D(x): 0.7240 D(G(z)): 0.1157 / 0.1305
[4/5][600/1583] Loss_D: 0.6252 Loss_G: 3.3920 D(x): 0.8780 D(G(z)): 0.3369 / 0.0442
[4/5][650/1583] Loss_D: 0.8645 Loss_G: 2.4966 D(x): 0.8954 D(G(z)): 0.4662 / 0.1155
[4/5][700/1583] Loss_D: 0.5746 Loss_G: 3.0229 D(x): 0.8596 D(G(z)): 0.3111 / 0.0654
[4/5][750/1583] Loss_D: 1.3794 Loss_G: 0.6133 D(x): 0.3243 D(G(z)): 0.0260 / 0.5831
[4/5][800/1583] Loss_D: 0.6256 Loss_G: 2.0188 D(x): 0.6178 D(G(z)): 0.0586 / 0.1683
[4/5][850/1583] Loss_D: 2.1094 Loss_G: 0.0934 D(x): 0.1773 D(G(z)): 0.0244 / 0.9155
[4/5][900/1583] Loss_D: 0.6528 Loss_G: 3.0057 D(x): 0.8357 D(G(z)): 0.3374 / 0.0611
[4/5][950/1583] Loss_D: 1.0851 Loss_G: 5.4879 D(x): 0.9384 D(G(z)): 0.5960 / 0.0065
[4/5][1000/1583] Loss_D: 0.5145 Loss_G: 1.9459 D(x): 0.7825 D(G(z)): 0.1991 / 0.1792
[4/5][1050/1583] Loss_D: 0.5283 Loss_G: 2.1203 D(x): 0.7549 D(G(z)): 0.1816 / 0.1546
[4/5][1100/1583] Loss_D: 0.5814 Loss_G: 3.7276 D(x): 0.8335 D(G(z)): 0.3020 / 0.0304
[4/5][1150/1583] Loss_D: 0.6248 Loss_G: 2.4235 D(x): 0.7690 D(G(z)): 0.2674 / 0.1101
[4/5][1200/1583] Loss_D: 0.4908 Loss_G: 2.0202 D(x): 0.7510 D(G(z)): 0.1477 / 0.1614
[4/5][1250/1583] Loss_D: 0.7716 Loss_G: 4.4816 D(x): 0.9215 D(G(z)): 0.4434 / 0.0163
[4/5][1300/1583] Loss_D: 0.6042 Loss_G: 2.8811 D(x): 0.7466 D(G(z)): 0.2177 / 0.0870
[4/5][1350/1583] Loss_D: 0.6920 Loss_G: 3.5099 D(x): 0.9051 D(G(z)): 0.4003 / 0.0444
[4/5][1400/1583] Loss_D: 1.0022 Loss_G: 4.4231 D(x): 0.9545 D(G(z)): 0.5545 / 0.0175
[4/5][1450/1583] Loss_D: 0.6450 Loss_G: 1.5365 D(x): 0.6892 D(G(z)): 0.2009 / 0.2584
[4/5][1500/1583] Loss_D: 0.4590 Loss_G: 2.1604 D(x): 0.7641 D(G(z)): 0.1426 / 0.1530
[4/5][1550/1583] Loss_D: 0.7811 Loss_G: 1.7861 D(x): 0.5751 D(G(z)): 0.1110 / 0.2232
```

## Results¶

Finally, lets check out how we did. Here, we will look at three different results. First, we will see how D and G’s losses changed during training. Second, we will visualize G’s output on the fixed_noise batch for every epoch. And third, we will look at a batch of real data next to a batch of fake data from G.

**Loss versus training iteration**

Below is a plot of D & G’s losses versus training iterations.

**Visualization of G’s progression**

Remember how we saved the generator’s output on the fixed_noise batch after every epoch of training. Now, we can visualize the training progression of G with an animation. Press the play button to start the animation.

```
fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)
HTML(ani.to_jshtml())
```