# can comment out after executing
!unzip processed_celeba_small.zip
Archive:  processed_celeba_small.zip
replace processed_celeba_small/.DS_Store? [y]es, [n]o, [A]ll, [N]one, [r]ename: ^C
data_dir = 'processed_celeba_small/'

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import pickle as pkl
import matplotlib.pyplot as plt
import numpy as np
import problem_unittests as tests
#import helper

%matplotlib inline

Visualize the CelebA Data

The CelebA dataset contains over 200,000 celebrity images with annotations. Since you're going to be generating faces, you won't need the annotations, you'll only need the images. Note that these are color images with 3 color channels (RGB)#RGB_Images) each.

Pre-process and Load the Data

Since the project's main focus is on building the GANs, we've done some of the pre-processing for you. Each of the CelebA images has been cropped to remove parts of the image that don't include a face, then resized down to 64x64x3 NumPy images. This pre-processed dataset is a smaller subset of the very large CelebA data.

There are a few other steps that you'll need to transform this data and create a DataLoader.

Exercise:Complete the following get_dataloader function, such that it satisfies these requirements:

  • Your images should be square, Tensor images of size image_size x image_size in the x and y dimension.
  • Your function should return a DataLoader that shuffles and batches these Tensor images.

ImageFolder

To create a dataset given a directory of images, it's recommended that you use PyTorch's ImageFolder wrapper, with a root directory processed_celeba_small/ and data transformation passed in.

# necessary imports
import torch
from torchvision import datasets
from torchvision import transforms
def get_dataloader(batch_size, image_size, data_dir='processed_celeba_small/'):
    """
    Batch the neural network data using DataLoader
    :param batch_size: The size of each batch; the number of images in a batch
    :param img_size: The square size of the image data (x, y)
    :param data_dir: Directory where image data is located
    :return: DataLoader with batched data
    """
    
    # TODO: Implement function and return a dataloader
    transform = transforms.Compose([transforms.Resize(image_size),
                                   transforms.ToTensor()]) 
    dataset = datasets.ImageFolder(data_dir, transform)
    return torch.utils.data.DataLoader(dataset, batch_size = batch_size, shuffle=True)

Create a DataLoader

Exercise: Create a DataLoader celeba_train_loader with appropriate hyperparameters.

Call the above function and create a dataloader to view images.

  • You can decide on any reasonable batch_size parameter
  • Your image_size must be 32. Resizing the data to a smaller size will make for faster training, while still creating convincing images of faces!
# Define function hyperparameters
batch_size = 40
img_size = 32

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
# Call your function and get a dataloader
celeba_train_loader = get_dataloader(batch_size, img_size)

Next, you can view some images! You should seen square images of somewhat-centered faces.

Note: You'll need to convert the Tensor images into a NumPy type and transpose the dimensions to correctly display an image, suggested imshow code is below, but it may not be perfect.

# helper display function
def imshow(img):
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
# obtain one batch of training images
dataiter = iter(celeba_train_loader)
images, _ = dataiter.next() # _ for no labels

# plot the images in the batch, along with the corresponding labels
fig = plt.figure(figsize=(20, 4))
plot_size=20
for idx in np.arange(plot_size):
    ax = fig.add_subplot(2, plot_size/2, idx+1, xticks=[], yticks=[])
    imshow(images[idx])

Exercise: Pre-process your image data and scale it to a pixel range of -1 to 1

You need to do a bit of pre-processing; you know that the output of a tanh activated generator will contain pixel values in a range from -1 to 1, and so, we need to rescale our training images to a range of -1 to 1. (Right now, they are in a range from 0-1.)

# TODO: Complete the scale function
def scale(x, feature_range=(-1, 1)):
    ''' Scale takes in an image x and returns that image, scaled
       with a feature_range of pixel values from -1 to 1. 
       This function assumes that the input x is already scaled from 0-1.'''
    # assume x is scaled to (0, 1)
    # scale to feature_range and return scaled x
    min, max = feature_range
    return x * (max - min) + min
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
# check scaled range
# should be close to -1 to 1
img = images[0]
scaled_img = scale(img)

print('Min: ', scaled_img.min())
print('Max: ', scaled_img.max())
Min:  tensor(-0.8902)
Max:  tensor(0.5451)

Define the Model

A GAN is comprised of two adversarial networks, a discriminator and a generator.

Discriminator

Your first task will be to define the discriminator. This is a convolutional classifier like you've built before, only without any maxpooling layers. To deal with this complex data, it's suggested you use a deep network with normalization. You are also allowed to create any helper functions that may be useful.

Exercise: Complete the Discriminator class

  • The inputs to the discriminator are 32x32x3 tensor images
  • The output should be a single value that will indicate whether a given image is real or fake
import torch.nn as nn
import torch.nn.functional as F
#conv function
def conv(in_channels, out_channels, kernel_size, stride=2, padding = 1, batch_norm= True):
    layers = []
    conv_layer = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,kernel_size = kernel_size, stride = stride, padding = padding, bias = False)
    layers.append(conv_layer)
    if batch_norm:
        layers.append(nn.BatchNorm2d(out_channels))
    return nn.Sequential(*layers)
class Discriminator(nn.Module):

    def __init__(self, conv_dim):
        """
        Initialize the Discriminator Module
        :param conv_dim: The depth of the first convolutional layer
        """
        super(Discriminator, self).__init__()

        # complete init function
        self.conv_dim = conv_dim
        self.conv1 = conv(3, conv_dim, 4, batch_norm=False) # x, y = 64 depth = 3
        self.conv2 = conv(conv_dim, conv_dim * 2, 4) # x, y = 32 depth = 64
        self.conv3 = conv(conv_dim * 2, conv_dim * 4, 4) # x, y = 16 depth = 128        
        self.fc = nn.Linear(conv_dim*4*4*4, 1)
        self.out = nn.Sigmoid()
        self.dropout = nn.Dropout(0.3)

    def forward(self, x):
        """
        Forward propagation of the neural network
        :param x: The input to the neural network     
        :return: Discriminator logits; the output of the neural network
        """
        # define feedforward behavior
        x = F.leaky_relu(self.conv1(x), 0.2)
        x = F.leaky_relu(self.conv2(x), 0.2)
        x = F.leaky_relu(self.conv3(x), 0.2)        
        x = x.view(-1, self.conv_dim*4*4*4)
        x = self.fc(x)
        x = self.dropout(x)        
        return x

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_discriminator(Discriminator)
Tests Passed

Generator

The generator should upsample an input and generate a new image of the same size as our training data 32x32x3. This should be mostly transpose convolutional layers with normalization applied to the outputs.

Exercise: Complete the Generator class

  • The inputs to the generator are vectors of some length z_size
  • The output should be a image of shape 32x32x3
def deconv(in_channels, out_channels, kernel_size, stride=2, padding=1, batch_norm=True):
    layers = []
    layers.append(nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding, bias=False))
    if batch_norm:
        layers.append(nn.BatchNorm2d(out_channels))
    return nn.Sequential(*layers)

class Generator(nn.Module):
    
    def __init__(self, z_size, conv_dim):
        """
        Initialize the Generator Module
        :param z_size: The length of the input latent vector, z
        :param conv_dim: The depth of the inputs to the *last* transpose convolutional layer
        """
        super(Generator, self).__init__()
        
        # complete init function
        self.conv_dim = conv_dim
        self.t_conv1 = deconv(conv_dim*4, conv_dim*2, 4 )
        self.t_conv2 = deconv(conv_dim*2, conv_dim, 4)
        self.t_conv3 = deconv(conv_dim, 3, 4, batch_norm=False)
        self.fc = nn.Linear(z_size, conv_dim*4*4*4)
        self.dropout = nn.Dropout(0.3)
        

    def forward(self, x):
        """
        Forward propagation of the neural network
        :param x: The input to the neural network     
        :return: A 32x32x3 Tensor image as output
        """
        # define feedforward behavior
        x = self.fc(x)
        x = self.dropout(x)
        x = x.view(-1, self.conv_dim*4, 4, 4)
        x = F.relu(self.t_conv1(x))
        x = F.relu(self.t_conv2(x))
        x = F.tanh(self.t_conv3(x))        
        return x


"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_generator(Generator)
Tests Passed

Initialize the weights of your networks

To help your models converge, you should initialize the weights of the convolutional and linear layers in your model. From reading the original DCGAN paper, they say:

All weights were initialized from a zero-centered Normal distribution with standard deviation 0.02.

So, your next task will be to define a weight initialization function that does just this!

You can refer back to the lesson on weight initialization or even consult existing model code, such as that from the networks.py file in CycleGAN Github repository to help you complete this function.

Exercise: Complete the weight initialization function

  • This should initialize only convolutional and linear layers
  • Initialize the weights to a normal distribution, centered around 0, with a standard deviation of 0.02.
  • The bias terms, if they exist, may be left alone or set to 0.
def weights_init_normal(m):
    """
    Applies initial weights to certain layers in a model .
    The weights are taken from a normal distribution 
    with mean = 0, std dev = 0.02.
    :param m: A module or layer in a network    
    """
    # classname will be something like:
    # `Conv`, `BatchNorm2d`, `Linear`, etc.
    classname = m.__class__.__name__
    
    # TODO: Apply initial weights to convolutional and linear layers
    if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1):
        nn.init.normal_(m.weight.data, 0.0, 0.02)
        if hasattr(m.bias, 'data'):
            nn.init.constant_(m.bias.data, 0.0)
    

Build complete network

Define your models' hyperparameters and instantiate the discriminator and generator from the classes defined above. Make sure you've passed in the correct input arguments.

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
def build_network(d_conv_dim, g_conv_dim, z_size):
    # define discriminator and generator
    D = Discriminator(d_conv_dim)
    G = Generator(z_size=z_size, conv_dim=g_conv_dim)

    # initialize model weights
    D.apply(weights_init_normal)
    G.apply(weights_init_normal)

    print(D)
    print()
    print(G)
    
    return D, G

Exercise: Define model hyperparameters

# Define model hyperparams
d_conv_dim = 32
g_conv_dim = 32
z_size = 100

"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
D, G = build_network(d_conv_dim, g_conv_dim, z_size)
Discriminator(
  (conv1): Sequential(
    (0): Conv2d(3, 32, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
  (conv2): Sequential(
    (0): Conv2d(32, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv3): Sequential(
    (0): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (fc): Linear(in_features=2048, out_features=1, bias=True)
  (out): Sigmoid()
  (dropout): Dropout(p=0.3)
)

Generator(
  (t_conv1): Sequential(
    (0): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (t_conv2): Sequential(
    (0): ConvTranspose2d(64, 32, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (t_conv3): Sequential(
    (0): ConvTranspose2d(32, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
  (fc): Linear(in_features=100, out_features=2048, bias=True)
  (dropout): Dropout(p=0.3)
)

Training on GPU

Check if you can train on GPU. Here, we'll set this as a boolean variable train_on_gpu. Later, you'll be responsible for making sure that

  • Models,
  • Model inputs, and
  • Loss function arguments

Are moved to GPU, where appropriate.

"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import torch

# Check for a GPU
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print('No GPU found. Please use a GPU to train your neural network.')
else:
    print('Training on GPU!')
Training on GPU!

Discriminator and Generator Losses

Now we need to calculate the losses for both types of adversarial networks.

Discriminator Losses

  • For the discriminator, the total loss is the sum of the losses for real and fake images, d_loss = d_real_loss + d_fake_loss.
  • Remember that we want the discriminator to output 1 for real images and 0 for fake images, so we need to set up the losses to reflect that.

Generator Loss

The generator loss will look similar only with flipped labels. The generator's goal is to get the discriminator to think its generated images are real.

Exercise:Complete real and fake loss functions

You may choose to use either cross entropy or a least squares error loss to complete the following real_loss and fake_loss functions.

def real_loss(D_out):
    '''Calculates how close discriminator outputs are to being real.
       param, D_out: discriminator logits
       return: real loss'''
    batch_size = D_out.size(0)
    labels = torch.ones(batch_size)
    if train_on_gpu:
        labels = labels.cuda()
    
    criterion = nn.BCEWithLogitsLoss()
    loss = criterion(D_out.squeeze(), labels)
    return loss

def fake_loss(D_out):
    '''Calculates how close discriminator outputs are to being fake.
       param, D_out: discriminator logits
       return: fake loss'''
    batch_size = D_out.size(0)
    labels = torch.zeros(batch_size)
    if train_on_gpu:
        labels = labels.cuda()
    
    criterion = nn.BCEWithLogitsLoss()
    loss = criterion(D_out.squeeze(), labels)
    return loss

Optimizers

Exercise: Define optimizers for your Discriminator (D) and Generator (G)

Define optimizers for your models with appropriate hyperparameters.

import torch.optim as optim

# Create optimizers for the discriminator D and generator G
d_optimizer = optim.Adam(D.parameters(), lr=0.0005, betas=(0.5, 0.999))
g_optimizer = optim.Adam(G.parameters(), lr=0.0005, betas=(0.5, 0.999))

Training

Training will involve alternating between training the discriminator and the generator. You'll use your functions real_loss and fake_loss to help you calculate the discriminator losses.

  • You should train the discriminator by alternating on real and fake images
  • Then the generator, which tries to trick the discriminator and should have an opposing loss function

Saving Samples

You've been given some code to print out some loss statistics and save some generated "fake" samples.

Exercise: Complete the training function

Keep in mind that, if you've moved your models to GPU, you'll also have to move any model inputs to GPU.

def train(D, G, n_epochs, print_every=50):
    '''Trains adversarial networks for some number of epochs
       param, D: the discriminator network
       param, G: the generator network
       param, n_epochs: number of epochs to train for
       param, print_every: when to print and record the models' losses
       return: D and G losses'''
    
    # move models to GPU
    if train_on_gpu:
        D.cuda()
        G.cuda()

    # keep track of loss and generated, "fake" samples
    samples = []
    losses = []

    # Get some fixed data for sampling. These are images that are held
    # constant throughout training, and allow us to inspect the model's performance
    sample_size=16
    fixed_z = np.random.uniform(-1, 1, size=(sample_size, z_size))
    fixed_z = torch.from_numpy(fixed_z).float()
    # move z to GPU if available
    if train_on_gpu:
        fixed_z = fixed_z.cuda()

    # epoch training loop
    for epoch in range(n_epochs):

        # batch training loop
        for batch_i, (real_images, _) in enumerate(celeba_train_loader):

            batch_size = real_images.size(0)
            real_images = scale(real_images)

            # ===============================================
            #         YOUR CODE HERE: TRAIN THE NETWORKS
            # ===============================================
            
            # 1. Train the discriminator on real and fake images
            if train_on_gpu:
                real_images = real_images.cuda()
            
            d_optimizer.zero_grad()
            D_real = D(real_images)
            d_real_loss = real_loss(D_real)
            z_flex = np.random.uniform(-1, 1, size=(batch_size, z_size))
            z_flex = torch.from_numpy(z_flex).float()
            
            if train_on_gpu:
                z_flex = z_flex.cuda()
                
            fake_images = G(z_flex)
            D_fake = D(fake_images)
            d_fake_loss = fake_loss(D_fake)
                
            d_loss = d_real_loss + d_fake_loss
            d_loss.backward()
            d_optimizer.step()

            # 2. Train the generator with an adversarial loss
            g_optimizer.zero_grad()
            z_flex = np.random.uniform(-1, 1, size=(batch_size, z_size))
            z_flex = torch.from_numpy(z_flex).float()
            
            if train_on_gpu:
                z_flex = z_flex.cuda()
                
            fake_images = G(z_flex)
            D_fake = D(fake_images)
            g_loss = real_loss(D_fake)
            g_loss.backward()
            g_optimizer.step()
            
            
            # ===============================================
            #              END OF YOUR CODE
            # ===============================================

            # Print some loss stats
            if batch_i % print_every == 0:
                # append discriminator loss and generator loss
                losses.append((d_loss.item(), g_loss.item()))
                # print discriminator and generator loss
                print('Epoch [{:5d}/{:5d}] | d_loss: {:6.4f} | g_loss: {:6.4f}'.format(
                        epoch+1, n_epochs, d_loss.item(), g_loss.item()))


        ## AFTER EACH EPOCH##    
        # this code assumes your generator is named G, feel free to change the name
        # generate and save sample, fake images
        G.eval() # for generating samples
        samples_z = G(fixed_z)
        samples.append(samples_z)
        G.train() # back to training mode

    # Save training generator samples
    with open('train_samples.pkl', 'wb') as f:
        pkl.dump(samples, f)
    
    # finally return losses
    return losses

Set your number of training epochs and train your GAN!

# set number of epochs 
n_epochs = 4


"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
# call training function
losses = train(D, G, n_epochs=n_epochs)
Epoch [    1/    4] | d_loss: 0.9543 | g_loss: 1.5350
Epoch [    1/    4] | d_loss: 0.5929 | g_loss: 3.2429
Epoch [    1/    4] | d_loss: 0.6430 | g_loss: 2.0835
Epoch [    1/    4] | d_loss: 0.6437 | g_loss: 1.9817
Epoch [    1/    4] | d_loss: 0.6648 | g_loss: 2.7845
Epoch [    1/    4] | d_loss: 0.7008 | g_loss: 1.9217
Epoch [    1/    4] | d_loss: 0.6131 | g_loss: 1.6540
Epoch [    1/    4] | d_loss: 0.4068 | g_loss: 3.4382
Epoch [    1/    4] | d_loss: 0.7518 | g_loss: 3.2530
Epoch [    1/    4] | d_loss: 0.6950 | g_loss: 2.3203
Epoch [    1/    4] | d_loss: 0.6637 | g_loss: 2.9369
Epoch [    1/    4] | d_loss: 0.5528 | g_loss: 2.3518
Epoch [    1/    4] | d_loss: 1.0430 | g_loss: 2.4640
Epoch [    1/    4] | d_loss: 0.5910 | g_loss: 2.4881
Epoch [    1/    4] | d_loss: 0.7038 | g_loss: 1.4659
Epoch [    1/    4] | d_loss: 0.7219 | g_loss: 1.4134
Epoch [    1/    4] | d_loss: 0.5838 | g_loss: 3.3896
Epoch [    1/    4] | d_loss: 0.5991 | g_loss: 2.9405
Epoch [    1/    4] | d_loss: 0.6622 | g_loss: 2.8994
Epoch [    1/    4] | d_loss: 0.7982 | g_loss: 2.2967
Epoch [    1/    4] | d_loss: 0.8091 | g_loss: 4.5961
Epoch [    1/    4] | d_loss: 0.9745 | g_loss: 2.0797
Epoch [    1/    4] | d_loss: 0.5561 | g_loss: 2.8210
Epoch [    1/    4] | d_loss: 1.3526 | g_loss: 1.9864
Epoch [    1/    4] | d_loss: 0.7917 | g_loss: 3.1085
Epoch [    1/    4] | d_loss: 0.6917 | g_loss: 1.7029
Epoch [    1/    4] | d_loss: 0.5557 | g_loss: 3.7095
Epoch [    1/    4] | d_loss: 0.5642 | g_loss: 2.7531
Epoch [    1/    4] | d_loss: 0.5263 | g_loss: 2.8755
Epoch [    1/    4] | d_loss: 0.6113 | g_loss: 2.3179
Epoch [    1/    4] | d_loss: 0.5461 | g_loss: 2.6818
Epoch [    1/    4] | d_loss: 0.7528 | g_loss: 2.1431
Epoch [    1/    4] | d_loss: 0.7829 | g_loss: 2.3606
Epoch [    1/    4] | d_loss: 0.4917 | g_loss: 3.0870
Epoch [    1/    4] | d_loss: 0.8348 | g_loss: 1.6196
Epoch [    1/    4] | d_loss: 0.4584 | g_loss: 4.6504
Epoch [    1/    4] | d_loss: 0.5668 | g_loss: 1.6971
Epoch [    1/    4] | d_loss: 0.6959 | g_loss: 2.6015
Epoch [    1/    4] | d_loss: 0.8386 | g_loss: 3.0574
Epoch [    1/    4] | d_loss: 0.8319 | g_loss: 2.1612
Epoch [    1/    4] | d_loss: 0.8606 | g_loss: 1.9073
Epoch [    1/    4] | d_loss: 0.7202 | g_loss: 3.1903
Epoch [    1/    4] | d_loss: 0.8003 | g_loss: 2.5057
Epoch [    1/    4] | d_loss: 0.7087 | g_loss: 1.5643
Epoch [    1/    4] | d_loss: 0.5708 | g_loss: 3.1067
Epoch [    2/    4] | d_loss: 0.6891 | g_loss: 2.3912
Epoch [    2/    4] | d_loss: 0.6651 | g_loss: 3.4993
Epoch [    2/    4] | d_loss: 0.6724 | g_loss: 3.8782
Epoch [    2/    4] | d_loss: 0.6112 | g_loss: 2.7862
Epoch [    2/    4] | d_loss: 0.6992 | g_loss: 2.2919
Epoch [    2/    4] | d_loss: 0.5953 | g_loss: 3.5657
Epoch [    2/    4] | d_loss: 0.9395 | g_loss: 1.3864
Epoch [    2/    4] | d_loss: 0.6467 | g_loss: 2.6107
Epoch [    2/    4] | d_loss: 0.5340 | g_loss: 2.6843
Epoch [    2/    4] | d_loss: 0.6760 | g_loss: 1.9171
Epoch [    2/    4] | d_loss: 0.7779 | g_loss: 1.9786
Epoch [    2/    4] | d_loss: 0.6068 | g_loss: 2.8820
Epoch [    2/    4] | d_loss: 0.5188 | g_loss: 3.2137
Epoch [    2/    4] | d_loss: 0.6281 | g_loss: 2.0057
Epoch [    2/    4] | d_loss: 0.6045 | g_loss: 3.0687
Epoch [    2/    4] | d_loss: 0.7373 | g_loss: 3.0363
Epoch [    2/    4] | d_loss: 0.7076 | g_loss: 2.6595
Epoch [    2/    4] | d_loss: 0.5447 | g_loss: 1.9639
Epoch [    2/    4] | d_loss: 0.6645 | g_loss: 1.8272
Epoch [    2/    4] | d_loss: 0.5412 | g_loss: 2.1932
Epoch [    2/    4] | d_loss: 0.5731 | g_loss: 2.6391
Epoch [    2/    4] | d_loss: 0.6522 | g_loss: 3.2824
Epoch [    2/    4] | d_loss: 0.6336 | g_loss: 3.0962
Epoch [    2/    4] | d_loss: 0.5573 | g_loss: 1.9972
Epoch [    2/    4] | d_loss: 0.9897 | g_loss: 2.0034
Epoch [    2/    4] | d_loss: 0.4868 | g_loss: 3.6994
Epoch [    2/    4] | d_loss: 0.6374 | g_loss: 2.6883
Epoch [    2/    4] | d_loss: 0.5325 | g_loss: 3.0683
Epoch [    2/    4] | d_loss: 0.5658 | g_loss: 2.6274
Epoch [    2/    4] | d_loss: 0.8498 | g_loss: 1.6561
Epoch [    2/    4] | d_loss: 0.7226 | g_loss: 2.9423
Epoch [    2/    4] | d_loss: 0.4388 | g_loss: 3.7304
Epoch [    2/    4] | d_loss: 0.9574 | g_loss: 1.5787
Epoch [    2/    4] | d_loss: 0.7695 | g_loss: 2.5766
Epoch [    2/    4] | d_loss: 0.6120 | g_loss: 2.8754
Epoch [    2/    4] | d_loss: 0.6594 | g_loss: 2.6452
Epoch [    2/    4] | d_loss: 0.5776 | g_loss: 2.6482
Epoch [    2/    4] | d_loss: 0.8700 | g_loss: 3.0829
Epoch [    2/    4] | d_loss: 1.1771 | g_loss: 0.3470
Epoch [    2/    4] | d_loss: 0.9872 | g_loss: 1.9041
Epoch [    2/    4] | d_loss: 0.4907 | g_loss: 2.8731
Epoch [    2/    4] | d_loss: 0.6997 | g_loss: 3.1965
Epoch [    2/    4] | d_loss: 0.4731 | g_loss: 2.5641
Epoch [    2/    4] | d_loss: 0.4805 | g_loss: 2.4071
Epoch [    2/    4] | d_loss: 0.6692 | g_loss: 2.5874
Epoch [    3/    4] | d_loss: 0.8139 | g_loss: 2.6130
Epoch [    3/    4] | d_loss: 0.5827 | g_loss: 1.8789
Epoch [    3/    4] | d_loss: 0.5186 | g_loss: 2.6960
Epoch [    3/    4] | d_loss: 0.4982 | g_loss: 2.3930
Epoch [    3/    4] | d_loss: 0.5548 | g_loss: 2.8024
Epoch [    3/    4] | d_loss: 0.5767 | g_loss: 2.8569
Epoch [    3/    4] | d_loss: 0.6634 | g_loss: 3.0444
Epoch [    3/    4] | d_loss: 0.6918 | g_loss: 3.1840
Epoch [    3/    4] | d_loss: 0.8059 | g_loss: 2.6495
Epoch [    3/    4] | d_loss: 0.7115 | g_loss: 2.4581
Epoch [    3/    4] | d_loss: 0.7276 | g_loss: 1.9877
Epoch [    3/    4] | d_loss: 1.6601 | g_loss: 4.7520
Epoch [    3/    4] | d_loss: 0.8051 | g_loss: 3.0616
Epoch [    3/    4] | d_loss: 0.8769 | g_loss: 4.9627
Epoch [    3/    4] | d_loss: 0.7601 | g_loss: 1.6775
Epoch [    3/    4] | d_loss: 0.6340 | g_loss: 2.1903
Epoch [    3/    4] | d_loss: 0.5626 | g_loss: 3.2477
Epoch [    3/    4] | d_loss: 0.6305 | g_loss: 2.9768
Epoch [    3/    4] | d_loss: 0.4766 | g_loss: 3.5111
Epoch [    3/    4] | d_loss: 0.6240 | g_loss: 2.7864
Epoch [    3/    4] | d_loss: 0.5936 | g_loss: 3.0884
Epoch [    3/    4] | d_loss: 0.6003 | g_loss: 3.2051
Epoch [    3/    4] | d_loss: 0.4966 | g_loss: 1.3002
Epoch [    3/    4] | d_loss: 0.6618 | g_loss: 2.7635
Epoch [    3/    4] | d_loss: 0.6841 | g_loss: 3.6588
Epoch [    3/    4] | d_loss: 0.6752 | g_loss: 1.7802
Epoch [    3/    4] | d_loss: 0.5633 | g_loss: 2.0170
Epoch [    3/    4] | d_loss: 0.6335 | g_loss: 3.3707
Epoch [    3/    4] | d_loss: 0.5484 | g_loss: 2.2442
Epoch [    3/    4] | d_loss: 0.6060 | g_loss: 2.1775
Epoch [    3/    4] | d_loss: 0.6508 | g_loss: 3.7853
Epoch [    3/    4] | d_loss: 0.6711 | g_loss: 2.3415
Epoch [    3/    4] | d_loss: 0.7224 | g_loss: 1.3784
Epoch [    3/    4] | d_loss: 0.9187 | g_loss: 3.2816
Epoch [    3/    4] | d_loss: 0.6265 | g_loss: 2.0665
Epoch [    3/    4] | d_loss: 0.4166 | g_loss: 2.6612
Epoch [    3/    4] | d_loss: 0.5769 | g_loss: 3.6045
Epoch [    3/    4] | d_loss: 0.5875 | g_loss: 2.2835
Epoch [    3/    4] | d_loss: 0.9718 | g_loss: 1.8930
Epoch [    3/    4] | d_loss: 0.6753 | g_loss: 2.7544
Epoch [    3/    4] | d_loss: 0.6786 | g_loss: 2.3325
Epoch [    3/    4] | d_loss: 0.5801 | g_loss: 2.6541
Epoch [    3/    4] | d_loss: 0.7057 | g_loss: 3.6706
Epoch [    3/    4] | d_loss: 0.5865 | g_loss: 2.4676
Epoch [    3/    4] | d_loss: 0.4876 | g_loss: 3.2982
Epoch [    4/    4] | d_loss: 0.7296 | g_loss: 2.6619
Epoch [    4/    4] | d_loss: 0.7047 | g_loss: 3.0570
Epoch [    4/    4] | d_loss: 0.5510 | g_loss: 3.4973
Epoch [    4/    4] | d_loss: 0.7429 | g_loss: 2.6766
Epoch [    4/    4] | d_loss: 0.6061 | g_loss: 3.5634
Epoch [    4/    4] | d_loss: 0.5285 | g_loss: 2.2621
Epoch [    4/    4] | d_loss: 0.5936 | g_loss: 1.7881
Epoch [    4/    4] | d_loss: 0.6385 | g_loss: 2.0635
Epoch [    4/    4] | d_loss: 0.4948 | g_loss: 3.1917
Epoch [    4/    4] | d_loss: 0.8046 | g_loss: 2.4466
Epoch [    4/    4] | d_loss: 0.6530 | g_loss: 1.7120
Epoch [    4/    4] | d_loss: 0.8158 | g_loss: 3.3746
Epoch [    4/    4] | d_loss: 0.8810 | g_loss: 3.0753
Epoch [    4/    4] | d_loss: 0.6491 | g_loss: 2.2416
Epoch [    4/    4] | d_loss: 0.5951 | g_loss: 3.4942
Epoch [    4/    4] | d_loss: 0.6886 | g_loss: 3.4805
Epoch [    4/    4] | d_loss: 0.6784 | g_loss: 1.9638
Epoch [    4/    4] | d_loss: 0.6050 | g_loss: 2.8853
Epoch [    4/    4] | d_loss: 0.6489 | g_loss: 2.2782
Epoch [    4/    4] | d_loss: 0.8394 | g_loss: 2.9367
Epoch [    4/    4] | d_loss: 0.4878 | g_loss: 2.9896
Epoch [    4/    4] | d_loss: 0.7408 | g_loss: 3.6535
Epoch [    4/    4] | d_loss: 0.7079 | g_loss: 2.2508
Epoch [    4/    4] | d_loss: 0.6588 | g_loss: 2.0329
Epoch [    4/    4] | d_loss: 0.4121 | g_loss: 2.2812
Epoch [    4/    4] | d_loss: 0.5546 | g_loss: 2.4180
Epoch [    4/    4] | d_loss: 0.6394 | g_loss: 3.0900
Epoch [    4/    4] | d_loss: 0.9115 | g_loss: 3.4219
Epoch [    4/    4] | d_loss: 0.8249 | g_loss: 2.5315
Epoch [    4/    4] | d_loss: 0.6281 | g_loss: 3.1169
Epoch [    4/    4] | d_loss: 0.7264 | g_loss: 3.1522
Epoch [    4/    4] | d_loss: 0.6780 | g_loss: 2.9495
Epoch [    4/    4] | d_loss: 0.5666 | g_loss: 2.9044
Epoch [    4/    4] | d_loss: 0.5735 | g_loss: 2.9984
Epoch [    4/    4] | d_loss: 7.0042 | g_loss: 1.7333
Epoch [    4/    4] | d_loss: 0.6597 | g_loss: 2.9417
Epoch [    4/    4] | d_loss: 0.4735 | g_loss: 2.9011
Epoch [    4/    4] | d_loss: 0.6370 | g_loss: 3.5389
Epoch [    4/    4] | d_loss: 0.4648 | g_loss: 2.7975
Epoch [    4/    4] | d_loss: 0.5486 | g_loss: 2.5830
Epoch [    4/    4] | d_loss: 0.6145 | g_loss: 2.6738
Epoch [    4/    4] | d_loss: 0.5349 | g_loss: 5.3156
Epoch [    4/    4] | d_loss: 0.6774 | g_loss: 2.9797
Epoch [    4/    4] | d_loss: 0.5850 | g_loss: 2.2509
Epoch [    4/    4] | d_loss: 0.4623 | g_loss: 2.7988

Training loss

Plot the training losses for the generator and discriminator, recorded after each epoch.

fig, ax = plt.subplots()
losses = np.array(losses)
plt.plot(losses.T[0], label='Discriminator', alpha=0.5)
plt.plot(losses.T[1], label='Generator', alpha=0.5)
plt.title("Training Losses")
plt.legend()
<matplotlib.legend.Legend at 0x7f047c093550>

Generator samples from training

View samples of images from the generator, and answer a question about the strengths and weaknesses of your trained models.

# helper function for viewing a list of passed in sample images
def view_samples(epoch, samples):
    fig, axes = plt.subplots(figsize=(16,4), nrows=2, ncols=8, sharey=True, sharex=True)
    for ax, img in zip(axes.flatten(), samples[epoch]):
        img = img.detach().cpu().numpy()
        img = np.transpose(img, (1, 2, 0))
        img = ((img + 1)*255 / (2)).astype(np.uint8)
        ax.xaxis.set_visible(False)
        ax.yaxis.set_visible(False)
        im = ax.imshow(img.reshape((32,32,3)))
# Load samples from generator, taken while training
with open('train_samples.pkl', 'rb') as f:
    samples = pkl.load(f)
_ = view_samples(-1, samples)

Question: What do you notice about your generated samples and how might you improve this model?

When you answer this question, consider the following factors:

  • The dataset is biased; it is made of "celebrity" faces that are mostly white
  • Model size; larger models have the opportunity to learn more features in a data feature space
  • Optimization strategy; optimizers and number of epochs affect your final result

Answer: (Write your answer in this cell)

The dataset is biased I Think More faces might help while Training GAN for better results to generate a new type of faces.

I trained the model for 10 epochs with different optimizer and network size. and i got learning rate value as 0.0002

  • I Used Adam, I Think it is the best choice for GAN's

  • Reducing the epochs can produce the best result.

The images are of very low resulution, which makes it harder to add more CNN layers

Submitting This Project

When submitting this project, make sure to run all the cells before saving the notebook. Save the notebook file as "dlnd_face_generation.ipynb" and save it as a HTML file under "File" -> "Download as". Include the "problem_unittests.py" files in your submission.