Deep Convolutional GANs

In this notebook, you'll build a GAN using convolutional layers in the generator and discriminator. This is called a Deep Convolutional GAN, or DCGAN for short. The DCGAN architecture was first explored last year and has seen impressive results in generating new images, you can read the original paper here.

You'll be training DCGAN on the Street View House Numbers (SVHN) dataset. These are color images of house numbers collected from Google street view. SVHN images are in color and much more variable than MNIST.

So, we'll need a deeper and more powerful network. This is accomplished through using convolutional layers in the discriminator and generator. It's also necessary to use batch normalization to get the convolutional networks to train. The only real changes compared to what you saw previously are in the generator and discriminator, otherwise the rest of the implementation is the same.


In [1]:
%matplotlib inline

import pickle as pkl

import matplotlib.pyplot as plt
import numpy as np
from scipy.io import loadmat
import tensorflow as tf

In [2]:
!mkdir data


A subdirectory or file data already exists.

Getting the data

Here you can download the SVHN dataset. Run the cell above and it'll download to your machine.


In [3]:
from urllib.request import urlretrieve
from os.path import isfile, isdir
from tqdm import tqdm

data_dir = 'data/'

if not isdir(data_dir):
    raise Exception("Data directory doesn't exist!")

class DLProgress(tqdm):
    last_block = 0

    def hook(self, block_num=1, block_size=1, total_size=None):
        self.total = total_size
        self.update((block_num - self.last_block) * block_size)
        self.last_block = block_num

if not isfile(data_dir + "train_32x32.mat"):
    with DLProgress(unit='B', unit_scale=True, miniters=1, desc='SVHN Training Set') as pbar:
        urlretrieve(
            'http://ufldl.stanford.edu/housenumbers/train_32x32.mat',
            data_dir + 'train_32x32.mat',
            pbar.hook)

if not isfile(data_dir + "test_32x32.mat"):
    with DLProgress(unit='B', unit_scale=True, miniters=1, desc='SVHN Training Set') as pbar:
        urlretrieve(
            'http://ufldl.stanford.edu/housenumbers/test_32x32.mat',
            data_dir + 'test_32x32.mat',
            pbar.hook)

These SVHN files are .mat files typically used with Matlab. However, we can load them in with scipy.io.loadmat which we imported above.


In [4]:
trainset = loadmat(data_dir + 'train_32x32.mat')
testset = loadmat(data_dir + 'test_32x32.mat')

Here I'm showing a small sample of the images. Each of these is 32x32 with 3 color channels (RGB). These are the real images we'll pass to the discriminator and what the generator will eventually fake.


In [5]:
idx = np.random.randint(0, trainset['X'].shape[3], size=36)
fig, axes = plt.subplots(6, 6, sharex=True, sharey=True, figsize=(5,5),)
for ii, ax in zip(idx, axes.flatten()):
    ax.imshow(trainset['X'][:,:,:,ii], aspect='equal')
    ax.xaxis.set_visible(False)
    ax.yaxis.set_visible(False)
plt.subplots_adjust(wspace=0, hspace=0)


Here we need to do a bit of preprocessing and getting the images into a form where we can pass batches to the network. First off, we need to rescale the images to a range of -1 to 1, since the output of our generator is also in that range. We also have a set of test and validation images which could be used if we're trying to identify the numbers in the images.


In [6]:
def scale(x, feature_range=(-1, 1)):
    # scale to (0, 1)
    x = ((x - x.min())/(255 - x.min()))
    
    # scale to feature_range
    min, max = feature_range
    x = x * (max - min) + min
    return x

In [7]:
class Dataset:
    def __init__(self, train, test, val_frac=0.5, shuffle=False, scale_func=None):
        split_idx = int(len(test['y'])*(1 - val_frac))
        self.test_x, self.valid_x = test['X'][:,:,:,:split_idx], test['X'][:,:,:,split_idx:]
        self.test_y, self.valid_y = test['y'][:split_idx], test['y'][split_idx:]
        self.train_x, self.train_y = train['X'], train['y']
        
        self.train_x = np.rollaxis(self.train_x, 3)
        self.valid_x = np.rollaxis(self.valid_x, 3)
        self.test_x = np.rollaxis(self.test_x, 3)
        
        if scale_func is None:
            self.scaler = scale
        else:
            self.scaler = scale_func
        self.shuffle = shuffle
        
    def batches(self, batch_size):
        if self.shuffle:
            idx = np.arange(len(dataset.train_x))
            np.random.shuffle(idx)
            self.train_x = self.train_x[idx]
            self.train_y = self.train_y[idx]
        
        n_batches = len(self.train_y)//batch_size
        for ii in range(0, len(self.train_y), batch_size):
            x = self.train_x[ii:ii+batch_size]
            y = self.train_y[ii:ii+batch_size]
            
            yield self.scaler(x), self.scaler(y)

Network Inputs

Here, just creating some placeholders like normal.


In [8]:
def model_inputs(real_dim, z_dim):
    inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='input_real')
    inputs_z = tf.placeholder(tf.float32, (None, z_dim), name='input_z')
    
    return inputs_real, inputs_z

Generator

Here you'll build the generator network. The input will be our noise vector z as before. Also as before, the output will be a $tanh$ output, but this time with size 32x32 which is the size of our SVHN images.

What's new here is we'll use convolutional layers to create our new images. The first layer is a fully connected layer which is reshaped into a deep and narrow layer, something like 4x4x1024 as in the original DCGAN paper. Then we use batch normalization and a leaky ReLU activation. Next is a transposed convolution where typically you'd halve the depth and double the width and height of the previous layer. Again, we use batch normalization and leaky ReLU. For each of these layers, the general scheme is convolution > batch norm > leaky ReLU.

You keep stacking layers up like this until you get the final transposed convolution layer with shape 32x32x3. Below is the archicture used in the original DCGAN paper:

Note that the final layer here is 64x64x3, while for our SVHN dataset, we only want it to be 32x32x3.

Exercise: Build the transposed convolutional network for the generator in the function below. Be sure to use leaky ReLUs on all the layers except for the last tanh layer, as well as batch normalization on all the transposed convolutional layers except the last one.


In [9]:
def convolutional_layer_transpose(input_layer, filter_size, kernal_size, strides, trainingFLAG, alpha):
    
    internal_convolutional_layer = tf.layers.conv2d_transpose(input_layer, filter_size, kernal_size, 
                                                    strides, 'same')
    
    batch_normalized_layer = tf.layers.batch_normalization(internal_convolutional_layer,
                                                     training = trainingFLAG)
    
    activated_layer = tf.maximum(alpha * batch_normalized_layer,  batch_normalized_layer)
    
    return activated_layer

In [10]:
def generator(z, output_dim, reuse=False, alpha=0.2, trainingFLAG=True):
    with tf.variable_scope('generator', reuse=reuse):
        # First fully connected layer
        # why not ?
        #  x = tf.layers.dense(z, 4 * 4 * 512, use_bias = False, activation = None)
        x = tf.layers.dense(z, 4 * 4 * 512)
        x = tf.reshape(x, (-1, 4, 4, 512))
        x = tf.layers.batch_normalization(x, training = trainingFLAG)
        x = tf.maximum( alpha * x,  x)
                
        convolutional_layer_1 = convolutional_layer_transpose(x, 256, 5, 2, trainingFLAG, alpha)
        
        convolutional_layer_2 = convolutional_layer_transpose(convolutional_layer_1, 128, 5, 2, 
                                                    trainingFLAG, alpha)
        
        # why not this layer too???
        #convolutional_layer_3 = convolutional_layer(convolutional_layer_2, 64, 5, 2, trainingFLAG, alpha)
           
        # Output layer, 32x32x3
        # Transpose convolutional layer
        logits = tf.layers.conv2d_transpose(convolutional_layer_2, output_dim, 5, strides = 2,
                                            padding = 'same')
        
        out = tf.tanh(logits)
        
        return out

Discriminator

Here you'll build the discriminator. This is basically just a convolutional classifier like you've build before. The input to the discriminator are 32x32x3 tensors/images. You'll want a few convolutional layers, then a fully connected layer for the output. As before, we want a sigmoid output, and you'll need to return the logits as well. For the depths of the convolutional layers I suggest starting with 16, 32, 64 filters in the first layer, then double the depth as you add layers. Note that in the DCGAN paper, they did all the downsampling using only strided convolutional layers with no maxpool layers.

You'll also want to use batch normalization with tf.layers.batch_normalization on each layer except the first convolutional and output layers. Again, each layer should look something like convolution > batch norm > leaky ReLU.

Exercise: Build the convolutional network for the discriminator. The input is a 32x32x3 images, the output is a sigmoid plus the logits. Again, use Leaky ReLU activations and batch normalization on all the layers except the first.


In [11]:
def convolutional_layer(input_layer, filter_size, kernal_size, strides, trainingFLAG, alpha):
    
    internal_convolutional_layer = tf.layers.conv2d(input_layer, filter_size, kernal_size, 
                                                    strides, 'same')
    
    batch_normalized_layer = tf.layers.batch_normalization(internal_convolutional_layer,
                                                     training = trainingFLAG)
    
    activated_layer = tf.maximum(alpha * batch_normalized_layer,  batch_normalized_layer)
    
    return activated_layer

In [12]:
def discriminator(x, reuse=False, alpha=0.2):
    with tf.variable_scope('discriminator', reuse=reuse):
        # Input layer is 32x32x3
        
        convolutional_layer_no_normalize = tf.layers.conv2d(x, 64, 5, 2, 'same')
    
        convolutional_layer_no_normalize = tf.maximum(alpha * convolutional_layer_no_normalize,
                                                      convolutional_layer_no_normalize)
        
        convolutional_layer_2 = convolutional_layer(convolutional_layer_no_normalize, 
                                                    128, 5, 2, True, alpha)
        
        convolutional_layer_3 = convolutional_layer(convolutional_layer_2,
                                                    256, 5, 2, True, alpha)
        
        flattened = tf.reshape(convolutional_layer_3, (-1, 4 * 4 * 256))
        logits = tf.layers.dense(flattened, 1)
        out = tf.sigmoid(logits)
        
        return out, logits

Model Loss

Calculating the loss like before, nothing new here.


In [13]:
def model_loss(input_real, input_z, output_dim, alpha=0.2):
    """
    Get the loss for the discriminator and generator
    :param input_real: Images from the real dataset
    :param input_z: Z input
    :param out_channel_dim: The number of channels in the output image
    :return: A tuple of (discriminator loss, generator loss)
    """
    g_model = generator(input_z, output_dim, alpha=alpha)
    d_model_real, d_logits_real = discriminator(input_real, alpha=alpha)
    d_model_fake, d_logits_fake = discriminator(g_model, reuse=True, alpha=alpha)

    d_loss_real = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_model_real)))
    d_loss_fake = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake)))
    g_loss = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake)))

    d_loss = d_loss_real + d_loss_fake

    return d_loss, g_loss

Optimizers

Again, nothing new here.


In [14]:
def model_opt(d_loss, g_loss, learning_rate, beta1):
    """
    Get optimization operations
    :param d_loss: Discriminator loss Tensor
    :param g_loss: Generator loss Tensor
    :param learning_rate: Learning Rate Placeholder
    :param beta1: The exponential decay rate for the 1st moment in the optimizer
    :return: A tuple of (discriminator training operation, generator training operation)
    """
    # Get weights and bias to update
    t_vars = tf.trainable_variables()
    d_vars = [var for var in t_vars if var.name.startswith('discriminator')]
    g_vars = [var for var in t_vars if var.name.startswith('generator')]

    # Optimize
    d_train_opt = tf.train.AdamOptimizer(learning_rate, beta1=beta1).minimize(d_loss, var_list=d_vars)
    g_train_opt = tf.train.AdamOptimizer(learning_rate, beta1=beta1).minimize(g_loss, var_list=g_vars)

    return d_train_opt, g_train_opt

Building the model

Here we can use the functions we defined about to build the model as a class. This will make it easier to move the network around in our code since the nodes and operations in the graph are packaged in one object.


In [15]:
class GAN:
    def __init__(self, real_size, z_size, learning_rate, alpha=0.2, beta1=0.5):
        tf.reset_default_graph()
        
        self.input_real, self.input_z = model_inputs(real_size, z_size)
        
        self.d_loss, self.g_loss = model_loss(self.input_real, self.input_z,
                                              real_size[2], alpha=0.2)
        
        self.d_opt, self.g_opt = model_opt(self.d_loss, self.g_loss, learning_rate, 0.5)

Here is a function for displaying generated images.


In [16]:
def view_samples(epoch, samples, nrows, ncols, figsize=(5,5)):
    fig, axes = plt.subplots(figsize=figsize, nrows=nrows, ncols=ncols, 
                             sharey=True, sharex=True)
    for ax, img in zip(axes.flatten(), samples[epoch]):
        ax.axis('off')
        img = ((img - img.min())*255 / (img.max() - img.min())).astype(np.uint8)
        ax.set_adjustable('box-forced')
        im = ax.imshow(img)
   
    plt.subplots_adjust(wspace=0, hspace=0)
    return fig, axes

And another function we can use to train our network.


In [17]:
def train(net, dataset, epochs, batch_size, print_every=10, show_every=100, figsize=(5,5)):
    saver = tf.train.Saver()
    sample_z = np.random.uniform(-1, 1, size=(50, z_size))

    samples, losses = [], []
    steps = 0

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for e in range(epochs):
            for x, y in dataset.batches(batch_size):
                steps += 1

                # Sample random noise for G
                batch_z = np.random.uniform(-1, 1, size=(batch_size, z_size))

                # Run optimizers
                _ = sess.run(net.d_opt, feed_dict={net.input_real: x, net.input_z: batch_z})
                _ = sess.run(net.g_opt, feed_dict={net.input_z: batch_z})

                if steps % print_every == 0:
                    # At the end of each epoch, get the losses and print them out
                    train_loss_d = net.d_loss.eval({net.input_z: batch_z, net.input_real: x})
                    train_loss_g = net.g_loss.eval({net.input_z: batch_z})

                    print("Epoch {}/{}...".format(e+1, epochs),
                          "Discriminator Loss: {:.4f}...".format(train_loss_d),
                          "Generator Loss: {:.4f}".format(train_loss_g))
                    # Save losses to view after training
                    losses.append((train_loss_d, train_loss_g))

                if steps % show_every == 0:
                    gen_samples = sess.run(
                                   generator(net.input_z, 3, reuse=True),
                                   feed_dict={net.input_z: sample_z})
                    samples.append(gen_samples)
                    _ = view_samples(-1, samples, 5, 10, figsize=figsize)
                    plt.show()

        saver.save(sess, './checkpoints/generator.ckpt')

    with open('samples.pkl', 'wb') as f:
        pkl.dump(samples, f)
    
    return losses, samples

Hyperparameters

GANs are very senstive to hyperparameters. A lot of experimentation goes into finding the best hyperparameters such that the generator and discriminator don't overpower each other. Try out your own hyperparameters or read the DCGAN paper to see what worked for them.

Exercise: Find hyperparameters to train this GAN. The values found in the DCGAN paper work well, or you can experiment on your own. In general, you want the discriminator loss to be around 0.3, this means it is correctly classifying images as fake or real about 50% of the time.


In [ ]:
real_size = (32,32,3)
z_size = 100
learning_rate = 0.001
batch_size = 128
epochs = 25
alpha = 0.2
beta1 = 0.5

# Create the network
net = GAN(real_size, z_size, learning_rate, alpha=alpha, beta1=beta1)

In [ ]:
# Load the data and train the network here
dataset = Dataset(trainset, testset)
losses, samples = train(net, dataset, epochs, batch_size, figsize=(10,5))


Epoch 1/25... Discriminator Loss: 0.1223... Generator Loss: 9.0821
Epoch 1/25... Discriminator Loss: 0.0637... Generator Loss: 14.0253
Epoch 1/25... Discriminator Loss: 0.0652... Generator Loss: 7.7366
Epoch 1/25... Discriminator Loss: 0.8927... Generator Loss: 1.1866
Epoch 1/25... Discriminator Loss: 1.0097... Generator Loss: 2.5242
Epoch 1/25... Discriminator Loss: 0.6197... Generator Loss: 1.2220
Epoch 1/25... Discriminator Loss: 0.5721... Generator Loss: 1.4835
Epoch 1/25... Discriminator Loss: 2.3731... Generator Loss: 2.0081
Epoch 1/25... Discriminator Loss: 1.2314... Generator Loss: 1.9398
Epoch 1/25... Discriminator Loss: 0.8475... Generator Loss: 1.1598
Epoch 1/25... Discriminator Loss: 3.1915... Generator Loss: 0.6441
Epoch 1/25... Discriminator Loss: 2.7053... Generator Loss: 0.1569
Epoch 1/25... Discriminator Loss: 0.5937... Generator Loss: 1.2114
Epoch 1/25... Discriminator Loss: 0.4769... Generator Loss: 1.6933
Epoch 1/25... Discriminator Loss: 1.0755... Generator Loss: 3.3488
Epoch 1/25... Discriminator Loss: 0.7407... Generator Loss: 1.4392
Epoch 1/25... Discriminator Loss: 0.8636... Generator Loss: 1.2138
Epoch 1/25... Discriminator Loss: 1.1686... Generator Loss: 1.6491
Epoch 1/25... Discriminator Loss: 0.8009... Generator Loss: 2.4813
Epoch 1/25... Discriminator Loss: 1.7636... Generator Loss: 1.4451
Epoch 1/25... Discriminator Loss: 1.1854... Generator Loss: 0.9282
Epoch 1/25... Discriminator Loss: 0.6670... Generator Loss: 1.0083
Epoch 1/25... Discriminator Loss: 0.5611... Generator Loss: 2.0351
Epoch 1/25... Discriminator Loss: 1.2998... Generator Loss: 0.4844
Epoch 1/25... Discriminator Loss: 1.2968... Generator Loss: 0.7023
Epoch 1/25... Discriminator Loss: 0.7700... Generator Loss: 1.4015
Epoch 1/25... Discriminator Loss: 0.2754... Generator Loss: 2.0866
Epoch 1/25... Discriminator Loss: 0.6074... Generator Loss: 1.9385
Epoch 1/25... Discriminator Loss: 0.7240... Generator Loss: 2.0890
Epoch 1/25... Discriminator Loss: 1.2129... Generator Loss: 0.6913
Epoch 1/25... Discriminator Loss: 0.5309... Generator Loss: 1.8370
Epoch 1/25... Discriminator Loss: 0.2699... Generator Loss: 1.7941
Epoch 1/25... Discriminator Loss: 0.9115... Generator Loss: 0.9221
Epoch 1/25... Discriminator Loss: 4.5767... Generator Loss: 0.0879
Epoch 1/25... Discriminator Loss: 0.9300... Generator Loss: 1.1239
Epoch 1/25... Discriminator Loss: 1.1546... Generator Loss: 0.8581
Epoch 1/25... Discriminator Loss: 0.9880... Generator Loss: 0.7173
Epoch 1/25... Discriminator Loss: 0.6222... Generator Loss: 2.8230
Epoch 1/25... Discriminator Loss: 1.1827... Generator Loss: 0.9200
Epoch 1/25... Discriminator Loss: 1.4993... Generator Loss: 0.6931
Epoch 1/25... Discriminator Loss: 1.3280... Generator Loss: 0.7951
Epoch 1/25... Discriminator Loss: 1.0316... Generator Loss: 0.9802
Epoch 1/25... Discriminator Loss: 1.0675... Generator Loss: 0.8528
Epoch 1/25... Discriminator Loss: 1.0235... Generator Loss: 0.8966
Epoch 1/25... Discriminator Loss: 0.8922... Generator Loss: 1.2624
Epoch 1/25... Discriminator Loss: 1.1174... Generator Loss: 0.6635
Epoch 1/25... Discriminator Loss: 1.3929... Generator Loss: 0.4707
Epoch 1/25... Discriminator Loss: 1.2958... Generator Loss: 1.3259
Epoch 1/25... Discriminator Loss: 0.5818... Generator Loss: 1.5265
Epoch 1/25... Discriminator Loss: 1.0856... Generator Loss: 1.1187
Epoch 1/25... Discriminator Loss: 1.2117... Generator Loss: 0.5976
Epoch 1/25... Discriminator Loss: 1.0272... Generator Loss: 0.9801
Epoch 1/25... Discriminator Loss: 1.2998... Generator Loss: 0.5382
Epoch 1/25... Discriminator Loss: 1.1916... Generator Loss: 2.7021
Epoch 1/25... Discriminator Loss: 1.6294... Generator Loss: 2.3056
Epoch 1/25... Discriminator Loss: 1.3243... Generator Loss: 0.7948
Epoch 1/25... Discriminator Loss: 1.0644... Generator Loss: 2.0693
Epoch 2/25... Discriminator Loss: 1.0437... Generator Loss: 0.8897
Epoch 2/25... Discriminator Loss: 0.9334... Generator Loss: 1.0744
Epoch 2/25... Discriminator Loss: 1.8343... Generator Loss: 0.6004
Epoch 2/25... Discriminator Loss: 1.5101... Generator Loss: 0.9077
Epoch 2/25... Discriminator Loss: 1.1350... Generator Loss: 0.8561
Epoch 2/25... Discriminator Loss: 1.3818... Generator Loss: 1.1988
Epoch 2/25... Discriminator Loss: 1.2644... Generator Loss: 0.9146
Epoch 2/25... Discriminator Loss: 2.1321... Generator Loss: 1.5419
Epoch 2/25... Discriminator Loss: 1.1574... Generator Loss: 0.7753
Epoch 2/25... Discriminator Loss: 1.1525... Generator Loss: 0.8119
Epoch 2/25... Discriminator Loss: 1.1970... Generator Loss: 0.9712
Epoch 2/25... Discriminator Loss: 1.2458... Generator Loss: 0.8639
Epoch 2/25... Discriminator Loss: 1.4542... Generator Loss: 0.6080
Epoch 2/25... Discriminator Loss: 1.3575... Generator Loss: 0.7341
Epoch 2/25... Discriminator Loss: 1.4209... Generator Loss: 0.8719
Epoch 2/25... Discriminator Loss: 1.3145... Generator Loss: 0.8467
Epoch 2/25... Discriminator Loss: 1.2878... Generator Loss: 0.7372
Epoch 2/25... Discriminator Loss: 1.3557... Generator Loss: 0.9757
Epoch 2/25... Discriminator Loss: 0.9695... Generator Loss: 1.3727
Epoch 2/25... Discriminator Loss: 1.4466... Generator Loss: 1.0197
Epoch 2/25... Discriminator Loss: 1.2351... Generator Loss: 1.0273
Epoch 2/25... Discriminator Loss: 1.0600... Generator Loss: 0.8660
Epoch 2/25... Discriminator Loss: 1.6160... Generator Loss: 0.5812
Epoch 2/25... Discriminator Loss: 1.1347... Generator Loss: 0.8720
Epoch 2/25... Discriminator Loss: 0.9639... Generator Loss: 1.1021
Epoch 2/25... Discriminator Loss: 1.5332... Generator Loss: 0.7081
Epoch 2/25... Discriminator Loss: 1.1057... Generator Loss: 0.8033
Epoch 2/25... Discriminator Loss: 1.1842... Generator Loss: 1.0290
Epoch 2/25... Discriminator Loss: 0.9352... Generator Loss: 1.0801
Epoch 2/25... Discriminator Loss: 1.2846... Generator Loss: 0.7686
Epoch 2/25... Discriminator Loss: 1.4412... Generator Loss: 0.6884
Epoch 2/25... Discriminator Loss: 1.3046... Generator Loss: 0.6293
Epoch 2/25... Discriminator Loss: 1.3081... Generator Loss: 1.6098
Epoch 2/25... Discriminator Loss: 1.1795... Generator Loss: 0.6725
Epoch 2/25... Discriminator Loss: 1.1057... Generator Loss: 1.5155
Epoch 2/25... Discriminator Loss: 1.0699... Generator Loss: 1.0670
Epoch 2/25... Discriminator Loss: 1.0882... Generator Loss: 0.8834
Epoch 2/25... Discriminator Loss: 1.2033... Generator Loss: 0.7210
Epoch 2/25... Discriminator Loss: 1.4811... Generator Loss: 0.9526
Epoch 2/25... Discriminator Loss: 1.1928... Generator Loss: 0.9804
Epoch 2/25... Discriminator Loss: 1.0864... Generator Loss: 0.8036
Epoch 2/25... Discriminator Loss: 1.1527... Generator Loss: 0.9032
Epoch 2/25... Discriminator Loss: 1.3128... Generator Loss: 0.7145
Epoch 2/25... Discriminator Loss: 1.2034... Generator Loss: 1.4495
Epoch 2/25... Discriminator Loss: 0.9888... Generator Loss: 1.0040
Epoch 2/25... Discriminator Loss: 1.2881... Generator Loss: 0.7155
Epoch 2/25... Discriminator Loss: 1.0649... Generator Loss: 0.9751
Epoch 2/25... Discriminator Loss: 1.1818... Generator Loss: 0.8797
Epoch 2/25... Discriminator Loss: 1.2327... Generator Loss: 0.8473
Epoch 2/25... Discriminator Loss: 1.1978... Generator Loss: 0.8403
Epoch 2/25... Discriminator Loss: 1.4650... Generator Loss: 1.3255
Epoch 2/25... Discriminator Loss: 1.2965... Generator Loss: 0.7922
Epoch 2/25... Discriminator Loss: 1.5487... Generator Loss: 0.6604
Epoch 2/25... Discriminator Loss: 1.0593... Generator Loss: 0.9587
Epoch 2/25... Discriminator Loss: 1.1810... Generator Loss: 0.7583
Epoch 2/25... Discriminator Loss: 1.4250... Generator Loss: 0.4571
Epoch 2/25... Discriminator Loss: 1.1581... Generator Loss: 0.8125
Epoch 3/25... Discriminator Loss: 0.9002... Generator Loss: 1.4467
Epoch 3/25... Discriminator Loss: 1.2030... Generator Loss: 0.8923
Epoch 3/25... Discriminator Loss: 1.4733... Generator Loss: 0.6311
Epoch 3/25... Discriminator Loss: 1.3651... Generator Loss: 0.6315
Epoch 3/25... Discriminator Loss: 1.2063... Generator Loss: 0.8419
Epoch 3/25... Discriminator Loss: 1.1665... Generator Loss: 0.8407
Epoch 3/25... Discriminator Loss: 1.0069... Generator Loss: 0.9821

In [ ]:
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()

In [ ]:
_ = view_samples(-1, samples, 5, 10, figsize=(10,5))

In [ ]:


In [ ]:


In [ ]:


In [ ]: