Introduction to GANs

Bhavesh Goyal
Analytics Vidhya
Published in
5 min readAug 15, 2020

TABLE OF CONTENTS:

  1. INTRODUCTION
  2. HISTORY OF GANs
  3. INTUITIVE EXPLANATION OF GANs
  4. TRAINING GANs
  5. GAN TRAINING PROCESS
  6. GAN BLOCK DIAGRAM
  7. KERAS IMPLEMENTATION OF GAN ON MNIST DATASET

INTRODUCTION

Generative Adversarial Networks also commonly referred to as GANs are used to generate images without very little or no input. GANs allow us to generate images created by our Neural Networks, completely removing a human (yes you) out of the loop. Before we dive into the theory, I like showing you the abilities of GANs to build your excitement. Turn Horses into Zebras (vice versa).

HISTORY OF GANs

Generative adversarial networks (GANs) was introduced by Ian Goodfellow (the GANFather of GANs) et al. in 2014, in his paper appropriately titled “Generative Adversarial Networks”. It was proposed as an alternative to Variational Auto Encoders (VAEs) which learn the latent spaces of images, to generate synthetic images. It’s aimed to create realistic artificial images that could be almost indistinguishable from real ones.

INTUITIVE EXPLANATION OF GAN

Imagine there’s an ambitious young criminal who wants to counterfeit money and sell to a mobster who specializes in handling counterfeit money. At first, the young counterfeiter is not good and our expert mobster tells him, he’s money is way off from looking real. Slowly he gets better and makes a good ‘copy’ every so often. The mobster tells him when it’s good. After some time, both the forger (our counterfeiter) and expert mobster get better at their jobs and now they have created almost real looking but fake money.

The Generator & Discriminator Networks:

● The purpose of the Generator Network is to take a random image initialization and decode it into a synthetic image.
● The purpose of the Discriminator Network is to take this input and predict whether this image came from a real dataset or is synthetic.

●As we just saw, this is effectively what GANs are, two antagonistic networks that are contesting against each other. The two components are called:

  1. Generator Network — in our example this was the young criminal creating counterfeit money.
  2. Discriminator Network — the mobster in our example.

TRAINING GANs

● Training GANs is notoriously difficult. In CNN’s we used gradient descent to change our weights to reduce our loss.

● However, in a GANs, every weight change changes the entire balance of our dynamic system.

● In GAN’s we are not seeking to minimize loss, but finding an equilibrium between our two opposing Networks.

THE GAN TRAINING PROCESS

1. Input randomly generates noisy images into our Generator Network to generate a sample image.

2. We take some sample images from our real data and mix it with some of our generated images.

3. Input these mixed images to our Discriminator who will then be trained on this mixed set and will update it’s weights accordingly.

4. We then make some more fake images and input them into the Discriminator but we label all as real. This is done to train the Generator. We’ve frozen the weights of the discriminator at this stage (Discriminator learning stops), and we use the feedback from the discriminator to now update the weights of the generator. This is how we teach both our Generator (to make better synthetic images) and Discriminator to get better at spotting fakes.

GAN Block Diagram

GAN Block Diagram

For this article, we will be generating handwritten numbers using the MNIST dataset. The architecture for this GAN is :

KERAS IMPLEMENTATION OF GAN ON MNIST DATASET

The entire code for the project can be found here.

First, we load all the necessary libraries

import os
os.environ["KERAS_BACKEND"] = "tensorflow"
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt

from keras.layers import Input
from keras.models import Model, Sequential
from keras.layers.core import Reshape, Dense, Dropout, Flatten
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Convolution2D, UpSampling2D
from keras.layers.normalization import BatchNormalization
from keras.datasets import mnist
from keras.optimizers import Adam
from keras import backend as K
from keras import initializers

K.set_image_dim_ordering('th')

# Deterministic output.
# Tired of seeing the same results every time? Remove the line below.
np.random.seed(1000)

# The results are a little better when the dimensionality of the random vector is only 10.
# The dimensionality has been left at 100 for consistency with other GAN implementations.
randomDim = 100

Now we load our dataset. For this blog MNIST dataset is being used, so no dataset needs to be downloaded separately.

(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = (X_train.astype(np.float32) - 127.5)/127.5
X_train = X_train.reshape(60000, 784)

Next, we define the architecture of our generator and discriminator

# Optimizer
adam = Adam(lr=0.0002, beta_1=0.5)
#generator
generator = Sequential()
generator.add(Dense(256, input_dim=randomDim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
generator.add(LeakyReLU(0.2))
generator.add(Dense(512))
generator.add(LeakyReLU(0.2))
generator.add(Dense(1024))
generator.add(LeakyReLU(0.2))
generator.add(Dense(784, activation='tanh'))
generator.compile(loss='binary_crossentropy', optimizer=adam)
#discriminator
discriminator = Sequential()
discriminator.add(Dense(1024, input_dim=784, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(512))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(256))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer=adam)

Now we combine our generator and discriminator to train simultaneously.

# Combined network
discriminator.trainable = False
ganInput = Input(shape=(randomDim,))
x = generator(ganInput)
ganOutput = discriminator(x)
gan = Model(inputs=ganInput, outputs=ganOutput)
gan.compile(loss='binary_crossentropy', optimizer=adam)

dLosses = []
gLosses = []

Three functions to plot and save the results after every 20 epochs and save the model.

# Plot the loss from each batch
def plotLoss(epoch):
plt.figure(figsize=(10, 8))
plt.plot(dLosses, label='Discriminitive loss')
plt.plot(gLosses, label='Generative loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.savefig('images/gan_loss_epoch_%d.png' % epoch)

# Create a wall of generated MNIST images
def plotGeneratedImages(epoch, examples=100, dim=(10, 10), figsize=(10, 10)):
noise = np.random.normal(0, 1, size=[examples, randomDim])
generatedImages = generator.predict(noise)
generatedImages = generatedImages.reshape(examples, 28, 28)

plt.figure(figsize=figsize)
for i in range(generatedImages.shape[0]):
plt.subplot(dim[0], dim[1], i+1)
plt.imshow(generatedImages[i], interpolation='nearest', cmap='gray_r')
plt.axis('off')
plt.tight_layout()
plt.savefig('images/gan_generated_image_epoch_%d.png' % epoch)

# Save the generator and discriminator networks (and weights) for later use
def saveModels(epoch):
generator.save('models/gan_generator_epoch_%d.h5' % epoch)
discriminator.save('models/gan_discriminator_epoch_%d.h5' % epoch)

The train function

def train(epochs=1, batchSize=128):
batchCount = X_train.shape[0] / batchSize
print 'Epochs:', epochs
print 'Batch size:', batchSize
print 'Batches per epoch:', batchCount

for e in xrange(1, epochs+1):
print '-'*15, 'Epoch %d' % e, '-'*15
for _ in tqdm(xrange(batchCount)):
# Get a random set of input noise and images
noise = np.random.normal(0, 1, size=[batchSize, randomDim])
imageBatch = X_train[np.random.randint(0, X_train.shape[0], size=batchSize)]

# Generate fake MNIST images
generatedImages = generator.predict(noise)
# print np.shape(imageBatch), np.shape(generatedImages)
X = np.concatenate([imageBatch, generatedImages])

# Labels for generated and real data
yDis = np.zeros(2*batchSize)
# One-sided label smoothing
yDis[:batchSize] = 0.9

# Train discriminator
discriminator.trainable = True
dloss = discriminator.train_on_batch(X, yDis)

# Train generator
noise = np.random.normal(0, 1, size=[batchSize, randomDim])
yGen = np.ones(batchSize)
discriminator.trainable = False
gloss = gan.train_on_batch(noise, yGen)

# Store loss of most recent batch from this epoch
dLosses.append(dloss)
gLosses.append(gloss)

if e == 1 or e % 20 == 0:
plotGeneratedImages(e)
saveModels(e)

# Plot losses from every epoch
plotLoss(e)

train(200, 128)

To stay connected follow me here.

READ MY PREVIOUS BLOG: UNDERSTANDING U-Net from here.

--

--