{"id":2313,"date":"2019-06-30T19:00:52","date_gmt":"2019-06-30T19:00:52","guid":{"rendered":"https:\/\/www.aiproblog.com\/index.php\/2019\/06\/30\/how-to-develop-a-gan-for-generating-small-color-photographs-of-objects\/"},"modified":"2019-06-30T19:00:52","modified_gmt":"2019-06-30T19:00:52","slug":"how-to-develop-a-gan-for-generating-small-color-photographs-of-objects","status":"publish","type":"post","link":"https:\/\/www.aiproblog.com\/index.php\/2019\/06\/30\/how-to-develop-a-gan-for-generating-small-color-photographs-of-objects\/","title":{"rendered":"How to Develop a GAN for Generating Small Color Photographs of Objects"},"content":{"rendered":"<p>Author: Jason Brownlee<\/p>\n<div>\n<p>Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images.<\/p>\n<p>Developing a GAN for generating images requires both a discriminator convolutional neural network model for classifying whether a given image is real or generated and a generator model that uses inverse convolutional layers to transform an input to a full two-dimensional image of pixel values.<\/p>\n<p>It can be challenging to understand both how GANs work and how deep convolutional neural network models can be trained in a GAN architecture for image generation. A good starting point for beginners is to practice developing and using GANs on standard image datasets used in the field of computer vision, such as the CIFAR small object photograph dataset. Using small and well-understood datasets means that smaller models can be developed and trained quickly, allowing focus to be put on the model architecture and image generation process itself.<\/p>\n<p>In this tutorial, you will discover how to develop a generative adversarial network with deep convolutional networks for generating small photographs of objects.<\/p>\n<p>After completing this tutorial, you will know:<\/p>\n<ul>\n<li>How to define and train the standalone discriminator model for learning the difference between real and fake images.<\/li>\n<li>How to define the standalone generator model and train the composite generator and discriminator model.<\/li>\n<li>How to evaluate the performance of the GAN and use the final standalone generator model to generate new images.<\/li>\n<\/ul>\n<p>Let\u2019s get started.<\/p>\n<div id=\"attachment_8133\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8133\" class=\"wp-image-8133 size-large\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/07\/How-to-Develop-a-Generative-Adversarial-Network-for-a-CIFAR-10-Small-Object-Photographs-From-Scratch-1024x768.jpg\" alt=\"How to Develop a Generative Adversarial Network for a CIFAR-10 Small Object Photographs From Scratch\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/07\/How-to-Develop-a-Generative-Adversarial-Network-for-a-CIFAR-10-Small-Object-Photographs-From-Scratch.jpg 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/07\/How-to-Develop-a-Generative-Adversarial-Network-for-a-CIFAR-10-Small-Object-Photographs-From-Scratch-300x225.jpg 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/07\/How-to-Develop-a-Generative-Adversarial-Network-for-a-CIFAR-10-Small-Object-Photographs-From-Scratch-768x576.jpg 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8133\" class=\"wp-caption-text\">How to Develop a Generative Adversarial Network for a CIFAR-10 Small Object Photographs From Scratch<br \/>Photo by <a href=\"https:\/\/www.flickr.com\/photos\/141823386@N02\/32200477897\">hiGorgeous<\/a>, some rights reserved.<\/p>\n<\/div>\n<h2>Tutorial Overview<\/h2>\n<p>This tutorial is divided into seven parts; they are:<\/p>\n<ol>\n<li>CIFAR-10 Small Object Photograph Dataset<\/li>\n<li>How to Define and Train the Discriminator Model<\/li>\n<li>How to Define and Use the Generator Model<\/li>\n<li>How to Train the Generator Model<\/li>\n<li>How to Evaluate GAN Model Performance<\/li>\n<li>Complete Example of GAN for CIFAR-10<\/li>\n<li>How to Use the Final Generator Model to Generate Images<\/li>\n<\/ol>\n<h2>CIFAR-10 Small Object Photograph Dataset<\/h2>\n<p>CIFAR is an acronym that stands for the Canadian Institute For Advanced Research and the <a href=\"https:\/\/en.wikipedia.org\/wiki\/CIFAR-10\">CIFAR-10 dataset<\/a> was developed along with the CIFAR-100 dataset (covered in the next section) by researchers at the <a href=\"https:\/\/www.cs.toronto.edu\/~kriz\/cifar.html\">CIFAR institute<\/a>.<\/p>\n<p>The dataset is comprised of 60,000 32\u00d732 pixel color photographs of objects from 10 classes, such as frogs, birds, cats, ships, airplanes, etc.<\/p>\n<p>These are very small images, much smaller than a typical photograph, and the dataset is intended for computer vision research.<\/p>\n<p>Keras provides access to the CIFAR10 dataset via the <a href=\"https:\/\/keras.io\/datasets\/#cifar10-small-image-classification\">cifar10.load_dataset() function<\/a>. It returns two tuples, one with the input and output elements for the standard training dataset, and another with the input and output elements for the standard test dataset.<\/p>\n<p>The example below loads the dataset and summarizes the shape of the loaded dataset.<\/p>\n<p>Note: the first time you load the dataset, Keras will automatically download a compressed version of the images and save them under your home directory in <em>~\/.keras\/datasets\/<\/em>. The download is fast as the dataset is only about 163 megabytes in its compressed form.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading the cifar10 dataset\r\nfrom keras.datasets.cifar10 import load_data\r\n# load the images into memory\r\n(trainX, trainy), (testX, testy) = load_data()\r\n# summarize the shape of the dataset\r\nprint('Train', trainX.shape, trainy.shape)\r\nprint('Test', testX.shape, testy.shape)<\/pre>\n<p>Running the example loads the dataset and prints the shape of the input and output components of the train and test splits of images.<\/p>\n<p>We can see that there are 50K examples in the training set and 10K in the test set and that each image is a square of 32 by 32 pixels.<\/p>\n<pre class=\"crayon-plain-tag\">Train (50000, 32, 32, 3) (50000, 1)\r\nTest (10000, 32, 32, 3) (10000, 1)<\/pre>\n<p>The images are color with the object centered in the middle of the frame.<\/p>\n<p>We can plot some of the images from the training dataset with the matplotlib library using the <a href=\"https:\/\/matplotlib.org\/api\/_as_gen\/matplotlib.pyplot.imshow.html\">imshow() function<\/a>.<\/p>\n<pre class=\"crayon-plain-tag\"># plot raw pixel data\r\npyplot.imshow(trainX[i])<\/pre>\n<p>The example below plots the first 49 images from the training dataset in a 7 by 7 square.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading and plotting the cifar10 dataset\r\nfrom keras.datasets.cifar10 import load_data\r\nfrom matplotlib import pyplot\r\n# load the images into memory\r\n(trainX, trainy), (testX, testy) = load_data()\r\n# plot images from the training dataset\r\nfor i in range(49):\r\n\t# define subplot\r\n\tpyplot.subplot(7, 7, 1 + i)\r\n\t# turn off axis\r\n\tpyplot.axis('off')\r\n\t# plot raw pixel data\r\n\tpyplot.imshow(trainX[i])\r\npyplot.show()<\/pre>\n<p>Running the example creates a figure with a plot of 49 images from the CIFAR10 training dataset, arranged in a 7\u00d77 square.<\/p>\n<p>In the plot, you can see small photographs of planes, trucks, horses, cars, frogs, and so on.<\/p>\n<div id=\"attachment_8122\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8122\" class=\"size-large wp-image-8122\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-First-49-Small-Object-Photographs-From-the-CIFAR10-Dataset-1024x768.png\" alt=\"Plot of the First 49 Small Object Photographs From the CIFAR10 Dataset.\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-First-49-Small-Object-Photographs-From-the-CIFAR10-Dataset-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-First-49-Small-Object-Photographs-From-the-CIFAR10-Dataset-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-First-49-Small-Object-Photographs-From-the-CIFAR10-Dataset-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-First-49-Small-Object-Photographs-From-the-CIFAR10-Dataset.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8122\" class=\"wp-caption-text\">Plot of the First 49 Small Object Photographs From the CIFAR10 Dataset.<\/p>\n<\/div>\n<p>We will use the images in the training dataset as the basis for training a Generative Adversarial Network.<\/p>\n<p>Specifically, the generator model will learn how to generate new plausible photographs of objects using a discriminator that will try and distinguish between real images from the CIFAR10 training dataset and new images output by the generator model.<\/p>\n<p>This is a non-trivial problem that requires modest generator and discriminator models that are probably most effectively trained on GPU hardware.<\/p>\n<p>For help using cheap Amazon EC2 instances to train deep learning models, see the post:<\/p>\n<ul>\n<li><a href=\"https:\/\/machinelearningmastery.com\/develop-evaluate-large-deep-learning-models-keras-amazon-web-services\/\">How to Setup Amazon AWS EC2 GPUs to Train Keras Deep Learning Models (step-by-step)<\/a><\/li>\n<\/ul>\n<h2>How to Define and Train the Discriminator Model<\/h2>\n<p>The first step is to define the discriminator model.<\/p>\n<p>The model must take a sample image from our dataset as input and output a classification prediction as to whether the sample is real or fake. This is a binary classification problem.<\/p>\n<ul>\n<li><strong>Inputs<\/strong>: Image with three color channel and 32\u00d732 pixels in size.<\/li>\n<li><strong>Outputs<\/strong>: Binary classification, likelihood the sample is real (or fake).<\/li>\n<\/ul>\n<p>The discriminator model has a normal convolutional layer followed by three convolutional layers using a stride of 2\u00d72 to downsample the input image. The model has no <a href=\"https:\/\/machinelearningmastery.com\/pooling-layers-for-convolutional-neural-networks\/\">pooling layers<\/a> and a single node in the output layer with the sigmoid activation function to predict whether the input sample is real or fake. The model is trained to minimize the <a href=\"https:\/\/machinelearningmastery.com\/how-to-choose-loss-functions-when-training-deep-learning-neural-networks\/\">binary cross entropy loss function<\/a>, appropriate for binary classification.<\/p>\n<p>We will use some best practices in defining the discriminator model, such as the use of LeakyReLU instead of <a href=\"https:\/\/machinelearningmastery.com\/rectified-linear-activation-function-for-deep-learning-neural-networks\/\">ReLU<\/a>, using Dropout, and using the <a href=\"https:\/\/machinelearningmastery.com\/adam-optimization-algorithm-for-deep-learning\/\">Adam version of stochastic gradient descent<\/a> with a <a href=\"https:\/\/machinelearningmastery.com\/learning-rate-for-deep-learning-neural-networks\/\">learning rate of 0.0002<\/a> and a momentum of 0.5.<\/p>\n<p>The <em>define_discriminator()<\/em> function below defines the discriminator model and parametrizes the size of the input image.<\/p>\n<pre class=\"crayon-plain-tag\"># define the standalone discriminator model\r\ndef define_discriminator(in_shape=(32,32,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(256, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model<\/pre>\n<p>We can use this function to define the discriminator model and summarize it.<\/p>\n<p>The complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of defining the discriminator model\r\nfrom keras.models import Sequential\r\nfrom keras.optimizers import Adam\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Flatten\r\nfrom keras.layers import Dropout\r\nfrom keras.layers import LeakyReLU\r\nfrom keras.utils.vis_utils import plot_model\r\n\r\n# define the standalone discriminator model\r\ndef define_discriminator(in_shape=(32,32,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(256, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model\r\n\r\n# define model\r\nmodel = define_discriminator()\r\n# summarize the model\r\nmodel.summary()\r\n# plot the model\r\nplot_model(model, to_file='discriminator_plot.png', show_shapes=True, show_layer_names=True)<\/pre>\n<p>Running the example first summarizes the model architecture, showing the input and output from each layer.<\/p>\n<p>We can see that the aggressive 2\u00d72 stride acts to down-sample the input image, first from 32\u00d732 to 16\u00d716, then to 8\u00d78 and more before the model makes an output prediction.<\/p>\n<p>This pattern is by design as we do not use pooling layers and use the large stride to achieve a similar downsampling effect. We will see a similar pattern, but in reverse in the generator model in the next section.<\/p>\n<pre class=\"crayon-plain-tag\">_________________________________________________________________\r\nLayer (type)                 Output Shape              Param #\r\n=================================================================\r\nconv2d_1 (Conv2D)            (None, 32, 32, 64)        1792\r\n_________________________________________________________________\r\nleaky_re_lu_1 (LeakyReLU)    (None, 32, 32, 64)        0\r\n_________________________________________________________________\r\nconv2d_2 (Conv2D)            (None, 16, 16, 128)       73856\r\n_________________________________________________________________\r\nleaky_re_lu_2 (LeakyReLU)    (None, 16, 16, 128)       0\r\n_________________________________________________________________\r\nconv2d_3 (Conv2D)            (None, 8, 8, 128)         147584\r\n_________________________________________________________________\r\nleaky_re_lu_3 (LeakyReLU)    (None, 8, 8, 128)         0\r\n_________________________________________________________________\r\nconv2d_4 (Conv2D)            (None, 4, 4, 256)         295168\r\n_________________________________________________________________\r\nleaky_re_lu_4 (LeakyReLU)    (None, 4, 4, 256)         0\r\n_________________________________________________________________\r\nflatten_1 (Flatten)          (None, 4096)              0\r\n_________________________________________________________________\r\ndropout_1 (Dropout)          (None, 4096)              0\r\n_________________________________________________________________\r\ndense_1 (Dense)              (None, 1)                 4097\r\n=================================================================\r\nTotal params: 522,497\r\nTrainable params: 522,497\r\nNon-trainable params: 0\r\n_________________________________________________________________<\/pre>\n<p>A plot of the model is also created and we can see that the model expects two inputs and will predict a single output.<\/p>\n<p><strong>Note<\/strong>: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement\u00a0and the call to the <em>plot_model()<\/em> function.<\/p>\n<div id=\"attachment_8123\" style=\"width: 398px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8123\" class=\"size-large wp-image-8123\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Discriminator-Model-in-the-CIFAR10-Generative-Adversarial-Network-388x1024.png\" alt=\"Plot of the Discriminator Model in the CIFAR10 Generative Adversarial Network\" width=\"388\" height=\"1024\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Discriminator-Model-in-the-CIFAR10-Generative-Adversarial-Network-388x1024.png 388w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Discriminator-Model-in-the-CIFAR10-Generative-Adversarial-Network-114x300.png 114w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Discriminator-Model-in-the-CIFAR10-Generative-Adversarial-Network.png 484w\" sizes=\"(max-width: 388px) 100vw, 388px\"><\/p>\n<p id=\"caption-attachment-8123\" class=\"wp-caption-text\">Plot of the Discriminator Model in the CIFAR10 Generative Adversarial Network<\/p>\n<\/div>\n<p>We could start training this model now with real examples with a class label of one and randomly generate samples with a class label of zero.<\/p>\n<p>The development of these elements will be useful later, and it helps to see that the discriminator is just a normal neural network model for binary classification.<\/p>\n<p>First, we need a function to load and prepare the dataset of real images.<\/p>\n<p>We will use the <em>cifar.load_data()<\/em> function to load the CIFAR-10 dataset and just use the input part of the training dataset as the real images.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n# load cifar10 dataset\r\n(trainX, _), (_, _) = load_data()<\/pre>\n<p>We must <a href=\"https:\/\/machinelearningmastery.com\/how-to-manually-scale-image-pixel-data-for-deep-learning\/\">scale the pixel values<\/a> from the range of unsigned integers in [0,255] to the normalized range of [-1,1].<\/p>\n<p>The generator model will generate images with pixel values in the range [-1,1] as it will use the tanh activation function, a best practice.<\/p>\n<p>It is also a good practice for the real images to be scaled to the same range.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n# convert from unsigned ints to floats\r\nX = trainX.astype('float32')\r\n# scale from [0,255] to [-1,1]\r\nX = (X - 127.5) \/ 127.5<\/pre>\n<p>The <em>load_real_samples()<\/em> function below implements the loading and scaling of real CIFAR-10 photographs.<\/p>\n<pre class=\"crayon-plain-tag\"># load and prepare cifar10 training images\r\ndef load_real_samples():\r\n\t# load cifar10 dataset\r\n\t(trainX, _), (_, _) = load_data()\r\n\t# convert from unsigned ints to floats\r\n\tX = trainX.astype('float32')\r\n\t# scale from [0,255] to [-1,1]\r\n\tX = (X - 127.5) \/ 127.5\r\n\treturn X<\/pre>\n<p>The model will be updated in batches, specifically with a collection of real samples and a collection of generated samples. On training, an epoch is defined as one pass through the entire training dataset.<\/p>\n<p>We could systematically enumerate all samples in the training dataset, and that is a good approach, but good training via stochastic gradient descent requires that the training dataset be shuffled prior to each epoch. A simpler approach is to select random samples of images from the training dataset.<\/p>\n<p>The <em>generate_real_samples()<\/em> function below will take the training dataset as an argument and will select a random subsample of images; it will also return class labels for the sample, specifically a class label of 1, to indicate real images.<\/p>\n<pre class=\"crayon-plain-tag\"># select real samples\r\ndef generate_real_samples(dataset, n_samples):\r\n\t# choose random instances\r\n\tix = randint(0, dataset.shape[0], n_samples)\r\n\t# retrieve selected images\r\n\tX = dataset[ix]\r\n\t# generate 'real' class labels (1)\r\n\ty = ones((n_samples, 1))\r\n\treturn X, y<\/pre>\n<p>Now, we need a source of fake images.<\/p>\n<p>We don\u2019t have a generator model yet, so instead, we can generate images comprised of random pixel values, specifically random pixel values in the range [0,1], then scaled to the range [-1, 1] like our scaled real images.<\/p>\n<p>The <em>generate_fake_samples()<\/em> function below implements this behavior and generates images of random pixel values and their associated class label of 0, for fake.<\/p>\n<pre class=\"crayon-plain-tag\"># generate n fake samples with class labels\r\ndef generate_fake_samples(n_samples):\r\n\t# generate uniform random numbers in [0,1]\r\n\tX = rand(32 * 32 * 3 * n_samples)\r\n\t# update to have the range [-1, 1]\r\n\tX = -1 + X * 2\r\n\t# reshape into a batch of color images\r\n\tX = X.reshape((n_samples, 32, 32, 3))\r\n\t# generate 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y<\/pre>\n<p>Finally, we need to train the discriminator model.<\/p>\n<p>This involves repeatedly retrieving samples of real images and samples of generated images and updating the model for a fixed number of iterations.<\/p>\n<p>We will ignore the idea of epochs for now (e.g. complete passes through the training dataset) and fit the discriminator model for a fixed number of batches. The model will learn to discriminate between real and fake (randomly generated) images rapidly, therefore not many batches will be required before it learns to discriminate perfectly.<\/p>\n<p>The <em>train_discriminator()<\/em> function implements this, using a batch size of 128 images, where 64 are real and 64 are fake each iteration.<\/p>\n<p>We update the discriminator separately for real and fake examples so that we can calculate the accuracy of the model on each sample prior to the update. This gives insight into how the discriminator model is performing over time.<\/p>\n<pre class=\"crayon-plain-tag\"># train the discriminator model\r\ndef train_discriminator(model, dataset, n_iter=20, n_batch=128):\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_iter):\r\n\t\t# get randomly selected 'real' samples\r\n\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t# update discriminator on real samples\r\n\t\t_, real_acc = model.train_on_batch(X_real, y_real)\r\n\t\t# generate 'fake' examples\r\n\t\tX_fake, y_fake = generate_fake_samples(half_batch)\r\n\t\t# update discriminator on fake samples\r\n\t\t_, fake_acc = model.train_on_batch(X_fake, y_fake)\r\n\t\t# summarize performance\r\n\t\tprint('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100))<\/pre>\n<p>Tying all of this together, the complete example of training an instance of the discriminator model on real and randomly generated (fake) images is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of training the discriminator model on real and random cifar10 images\r\nfrom numpy import expand_dims\r\nfrom numpy import ones\r\nfrom numpy import zeros\r\nfrom numpy.random import rand\r\nfrom numpy.random import randint\r\nfrom keras.datasets.cifar10 import load_data\r\nfrom keras.optimizers import Adam\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Flatten\r\nfrom keras.layers import Dropout\r\nfrom keras.layers import LeakyReLU\r\n\r\n# define the standalone discriminator model\r\ndef define_discriminator(in_shape=(32,32,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(256, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model\r\n\r\n# load and prepare cifar10 training images\r\ndef load_real_samples():\r\n\t# load cifar10 dataset\r\n\t(trainX, _), (_, _) = load_data()\r\n\t# convert from unsigned ints to floats\r\n\tX = trainX.astype('float32')\r\n\t# scale from [0,255] to [-1,1]\r\n\tX = (X - 127.5) \/ 127.5\r\n\treturn X\r\n\r\n# select real samples\r\ndef generate_real_samples(dataset, n_samples):\r\n\t# choose random instances\r\n\tix = randint(0, dataset.shape[0], n_samples)\r\n\t# retrieve selected images\r\n\tX = dataset[ix]\r\n\t# generate 'real' class labels (1)\r\n\ty = ones((n_samples, 1))\r\n\treturn X, y\r\n\r\n# generate n fake samples with class labels\r\ndef generate_fake_samples(n_samples):\r\n\t# generate uniform random numbers in [0,1]\r\n\tX = rand(32 * 32 * 3 * n_samples)\r\n\t# update to have the range [-1, 1]\r\n\tX = -1 + X * 2\r\n\t# reshape into a batch of color images\r\n\tX = X.reshape((n_samples, 32, 32, 3))\r\n\t# generate 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y\r\n\r\n# train the discriminator model\r\ndef train_discriminator(model, dataset, n_iter=20, n_batch=128):\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_iter):\r\n\t\t# get randomly selected 'real' samples\r\n\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t# update discriminator on real samples\r\n\t\t_, real_acc = model.train_on_batch(X_real, y_real)\r\n\t\t# generate 'fake' examples\r\n\t\tX_fake, y_fake = generate_fake_samples(half_batch)\r\n\t\t# update discriminator on fake samples\r\n\t\t_, fake_acc = model.train_on_batch(X_fake, y_fake)\r\n\t\t# summarize performance\r\n\t\tprint('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100))\r\n\r\n# define the discriminator model\r\nmodel = define_discriminator()\r\n# load image data\r\ndataset = load_real_samples()\r\n# fit the model\r\ntrain_discriminator(model, dataset)<\/pre>\n<p>Running the example first defines the model, loads the CIFAR-10 dataset, then trains the discriminator model.<\/p>\n<p><strong>Note<\/strong>: your specific results may vary given the stochastic nature of the learning algorithm. Consider running the example a few times.<\/p>\n<p>In this case, the discriminator model learns to tell the difference between real and randomly generated CIFAR-10 images very quickly, in about 20 batches.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n>16 real=100% fake=100%\r\n>17 real=100% fake=100%\r\n>18 real=98% fake=100%\r\n>19 real=100% fake=100%\r\n>20 real=100% fake=100%<\/pre>\n<p>Now that we know how to define and train the discriminator model, we need to look at developing the generator model.<\/p>\n<h2>How to Define and Use the Generator Model<\/h2>\n<p>The generator model is responsible for creating new, fake, but plausible small photographs of objects.<\/p>\n<p>It does this by taking a point from the latent space as input and outputting a square color image.<\/p>\n<p>The latent space is an arbitrarily defined vector space of <a href=\"https:\/\/machinelearningmastery.com\/statistical-data-distributions\/\">Gaussian-distributed values<\/a>, e.g. 100 dimensions. It has no meaning, but by drawing points from this space randomly and providing them to the generator model during training, the generator model will assign meaning to the latent points and, in turn, the latent space, until, at the end of training, the latent vector space represents a compressed representation of the output space, CIFAR-10 images, that only the generator knows how to turn into plausible CIFAR-10 images.<\/p>\n<ul>\n<li><strong>Inputs<\/strong>: Point in latent space, e.g. a 100-element vector of Gaussian random numbers.<\/li>\n<li><strong>Outputs<\/strong>: Two-dimensional square color image (3 channels) of 32 x 32 pixels with pixel values in [-1,1].<\/li>\n<\/ul>\n<p><strong>Note<\/strong>: we don\u2019t have to use a 100 element vector as input; it is a round number and widely used, but I would expect that 10, 50, or 500 would work just as well.<\/p>\n<p>Developing a generator model requires that we transform a vector from the latent space with, 100 dimensions to a 2D array with 32 x 32 x 3, or 3,072 values.<\/p>\n<p>There are a number of ways to achieve this, but there is one approach that has proven effective on deep convolutional generative adversarial networks. It involves two main elements.<\/p>\n<p>The first is a Dense layer as the first hidden layer that has enough nodes to represent a low-resolution version of the output image. Specifically, an image half the size (one quarter the area) of the output image would be 16x16x3, or 768 nodes, and an image one quarter the size (one eighth the area) would be 8 x 8 x 3, or 192 nodes.<\/p>\n<p>With some experimentation, I have found that a smaller low-resolution version of the image works better. Therefore, we will use 4 x 4 x 3, or 48 nodes.<\/p>\n<p>We don\u2019t just want one low-resolution version of the image; we want many parallel versions or interpretations of the input. This is a <a href=\"https:\/\/machinelearningmastery.com\/review-of-architectural-innovations-for-convolutional-neural-networks-for-image-classification\/\">pattern in convolutional neural networks<\/a> where we have many parallel filters resulting in multiple parallel activation maps, called feature maps, with different interpretations of the input. We want the same thing in reverse: many parallel versions of our output with different learned features that can be collapsed in the output layer into a final image. The model needs space to invent, create, or generate.<\/p>\n<p>Therefore, the first hidden layer, the Dense, needs enough nodes for multiple versions of our output image, such as 256.<\/p>\n<pre class=\"crayon-plain-tag\"># foundation for 4x4 image\r\nn_nodes = 256 * 4 * 4\r\nmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\nmodel.add(LeakyReLU(alpha=0.2))<\/pre>\n<p>The activations from these nodes can then be reshaped into something image-like to pass into a convolutional layer, such as 256 different 4 x 4 feature maps.<\/p>\n<pre class=\"crayon-plain-tag\">model.add(Reshape((4, 4, 256)))<\/pre>\n<p>The next major architectural innovation involves upsampling the low-resolution image to a higher resolution version of the image.<\/p>\n<p>There are two common ways to do this upsampling process, sometimes called deconvolution.<\/p>\n<p>One way is to use an <em>UpSampling2D<\/em> layer (like a reverse <a href=\"https:\/\/machinelearningmastery.com\/pooling-layers-for-convolutional-neural-networks\/\">pooling layer<\/a>) followed by a normal <em>Conv2D<\/em> layer. The other and perhaps more modern way is to combine these two operations into a single layer, called a <em>Conv2DTranspose<\/em>. We will use this latter approach for our generator.<\/p>\n<p>The <em>Conv2DTranspose<\/em> layer can be configured with a <a href=\"https:\/\/machinelearningmastery.com\/padding-and-stride-for-convolutional-neural-networks\/\">stride of (2\u00d72)<\/a> that will quadruple the area of the input feature maps (double their width and height dimensions). It is also good practice to use a kernel size that is a factor of the stride (e.g. double) to <a href=\"https:\/\/distill.pub\/2016\/deconv-checkerboard\/\">avoid a checkerboard pattern<\/a> that can sometimes be observed when upsampling.<\/p>\n<pre class=\"crayon-plain-tag\"># upsample to 8x8\r\nmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\nmodel.add(LeakyReLU(alpha=0.2))<\/pre>\n<p>This can be repeated two more times to arrive at our required 32 x 32 output image.<\/p>\n<p>Again, we will use the LeakyReLU with a default slope of 0.2, reported as a best practice when training GAN models.<\/p>\n<p>The output layer of the model is a Conv2D with three filters for the three required channels and a kernel size of 3\u00d73 and \u2018<em>same<\/em>\u2018 padding, designed to create a single feature map and preserve its dimensions at 32 x 32 x 3 pixels. A tanh activation is used to ensure output values are in the desired range of [-1,1], a current best practice.<\/p>\n<p>The <em>define_generator()<\/em> function below implements this and defines the generator model.<\/p>\n<p><strong>Note<\/strong>: the generator model is not compiled and does not specify a loss function or optimization algorithm. This is because the generator is not trained directly. We will learn more about this in the next section.<\/p>\n<pre class=\"crayon-plain-tag\"># define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 4x4 image\r\n\tn_nodes = 256 * 4 * 4\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((4, 4, 256)))\r\n\t# upsample to 8x8\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 16x16\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 32x32\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer\r\n\tmodel.add(Conv2D(3, (3,3), activation='tanh', padding='same'))\r\n\treturn model<\/pre>\n<p>We can summarize the model to help better understand the input and output shapes.<\/p>\n<p>The complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of defining the generator model\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Reshape\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Conv2DTranspose\r\nfrom keras.layers import LeakyReLU\r\nfrom keras.utils.vis_utils import plot_model\r\n\r\n# define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 4x4 image\r\n\tn_nodes = 256 * 4 * 4\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((4, 4, 256)))\r\n\t# upsample to 8x8\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 16x16\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 32x32\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer\r\n\tmodel.add(Conv2D(3, (3,3), activation='tanh', padding='same'))\r\n\treturn model\r\n\r\n# define the size of the latent space\r\nlatent_dim = 100\r\n# define the generator model\r\nmodel = define_generator(latent_dim)\r\n# summarize the model\r\nmodel.summary()\r\n# plot the model\r\nplot_model(model, to_file='generator_plot.png', show_shapes=True, show_layer_names=True)<\/pre>\n<p>Running the example summarizes the layers of the model and their output shape.<\/p>\n<p>We can see that, as designed, the first hidden layer has 4,096 parameters or 256 x 4 x 4, the activations of which are reshaped into 256 4 x 4 feature maps. The feature maps are then upscaled via the three <em>Conv2DTranspose<\/em> layers to the desired output shape of 32 x 32, until the output layer where three filter maps (channels) are created.<\/p>\n<pre class=\"crayon-plain-tag\">_________________________________________________________________\r\nLayer (type)                 Output Shape              Param #\r\n=================================================================\r\ndense_1 (Dense)              (None, 4096)              413696\r\n_________________________________________________________________\r\nleaky_re_lu_1 (LeakyReLU)    (None, 4096)              0\r\n_________________________________________________________________\r\nreshape_1 (Reshape)          (None, 4, 4, 256)         0\r\n_________________________________________________________________\r\nconv2d_transpose_1 (Conv2DTr (None, 8, 8, 128)         524416\r\n_________________________________________________________________\r\nleaky_re_lu_2 (LeakyReLU)    (None, 8, 8, 128)         0\r\n_________________________________________________________________\r\nconv2d_transpose_2 (Conv2DTr (None, 16, 16, 128)       262272\r\n_________________________________________________________________\r\nleaky_re_lu_3 (LeakyReLU)    (None, 16, 16, 128)       0\r\n_________________________________________________________________\r\nconv2d_transpose_3 (Conv2DTr (None, 32, 32, 128)       262272\r\n_________________________________________________________________\r\nleaky_re_lu_4 (LeakyReLU)    (None, 32, 32, 128)       0\r\n_________________________________________________________________\r\nconv2d_1 (Conv2D)            (None, 32, 32, 3)         3459\r\n=================================================================\r\nTotal params: 1,466,115\r\nTrainable params: 1,466,115\r\nNon-trainable params: 0\r\n_________________________________________________________________<\/pre>\n<p>A plot of the model is also created and we can see that the model expects a 100-element point from the latent space as input and will predict a two-element vector as output.<\/p>\n<p><strong>Note<\/strong>: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement and the call to the <em>plot_model()<\/em> function.<\/p>\n<div id=\"attachment_8124\" style=\"width: 518px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8124\" class=\"size-large wp-image-8124\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Generator-Model-in-the-CIFAR-10-Generative-Adversarial-Network-508x1024.png\" alt=\"Plot of the Generator Model in the CIFAR-10 Generative Adversarial Network\" width=\"508\" height=\"1024\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Generator-Model-in-the-CIFAR-10-Generative-Adversarial-Network-508x1024.png 508w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Generator-Model-in-the-CIFAR-10-Generative-Adversarial-Network-149x300.png 149w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Generator-Model-in-the-CIFAR-10-Generative-Adversarial-Network.png 579w\" sizes=\"(max-width: 508px) 100vw, 508px\"><\/p>\n<p id=\"caption-attachment-8124\" class=\"wp-caption-text\">Plot of the Generator Model in the CIFAR-10 Generative Adversarial Network<\/p>\n<\/div>\n<p>This model cannot do much at the moment.<\/p>\n<p>Nevertheless, we can demonstrate how to use it to generate samples. This is a helpful demonstration to understand the generator as just another model, and some of these elements will be useful later.<\/p>\n<p>The first step is to generate new points in the latent space. We can achieve this by calling the <a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/generated\/numpy.random.randn.html\">randn() NumPy function<\/a> for generating <a href=\"https:\/\/machinelearningmastery.com\/how-to-generate-random-numbers-in-python\/\">arrays of random numbers<\/a> drawn from a standard Gaussian.<\/p>\n<p>The array of random numbers can then be reshaped into samples, that is n rows with 100 elements per row. The <em>generate_latent_points()<\/em> function below implements this and generates the desired number of points in the latent space that can be used as input to the generator model.<\/p>\n<pre class=\"crayon-plain-tag\"># generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input<\/pre>\n<p>Next, we can use the generated points as input to the generator model to generate new samples, then plot the samples.<\/p>\n<p>We can update the <em>generate_fake_samples()<\/em> function from the previous section to take the generator model as an argument and use it to generate the desired number of samples by first calling the <em>generate_latent_points()<\/em> function to generate the required number of points in latent space as input to the model.<\/p>\n<p>The updated <em>generate_fake_samples()<\/em> function is listed below and returns both the generated samples and the associated class labels.<\/p>\n<pre class=\"crayon-plain-tag\"># use the generator to generate n fake examples, with class labels\r\ndef generate_fake_samples(g_model, latent_dim, n_samples):\r\n\t# generate points in latent space\r\n\tx_input = generate_latent_points(latent_dim, n_samples)\r\n\t# predict outputs\r\n\tX = g_model.predict(x_input)\r\n\t# create 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y<\/pre>\n<p>We can then plot the generated samples as we did the real CIFAR-10 examples in the first section by calling the imshow() function.<\/p>\n<p>The complete example of generating new CIFAR-10 images with the untrained generator model is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of defining and using the generator model\r\nfrom numpy import zeros\r\nfrom numpy.random import randn\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Reshape\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Conv2DTranspose\r\nfrom keras.layers import LeakyReLU\r\nfrom matplotlib import pyplot\r\n\r\n# define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 4x4 image\r\n\tn_nodes = 256 * 4 * 4\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((4, 4, 256)))\r\n\t# upsample to 8x8\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 16x16\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 32x32\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer\r\n\tmodel.add(Conv2D(3, (3,3), activation='tanh', padding='same'))\r\n\treturn model\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input\r\n\r\n# use the generator to generate n fake examples, with class labels\r\ndef generate_fake_samples(g_model, latent_dim, n_samples):\r\n\t# generate points in latent space\r\n\tx_input = generate_latent_points(latent_dim, n_samples)\r\n\t# predict outputs\r\n\tX = g_model.predict(x_input)\r\n\t# create 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y\r\n\r\n# size of the latent space\r\nlatent_dim = 100\r\n# define the discriminator model\r\nmodel = define_generator(latent_dim)\r\n# generate samples\r\nn_samples = 49\r\nX, _ = generate_fake_samples(model, latent_dim, n_samples)\r\n# scale pixel values from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# plot the generated samples\r\nfor i in range(n_samples):\r\n\t# define subplot\r\n\tpyplot.subplot(7, 7, 1 + i)\r\n\t# turn off axis labels\r\n\tpyplot.axis('off')\r\n\t# plot single image\r\n\tpyplot.imshow(X[i])\r\n# show the figure\r\npyplot.show()<\/pre>\n<p>Running the example generates 49 examples of fake CIFAR-10 images and visualizes them on a single plot of 7 by 7 images.<\/p>\n<p>As the model is not trained, the generated images are completely random pixel values in [-1, 1], rescaled to [0, 1]. As we might expect, the images look like a mess of gray.<\/p>\n<div id=\"attachment_8125\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8125\" class=\"size-large wp-image-8125\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-49-CIFAR-10-Images-Output-by-the-Untrained-Generator-Model-1024x768.png\" alt=\"Example of 49 CIFAR-10 Images Output by the Untrained Generator Model\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-49-CIFAR-10-Images-Output-by-the-Untrained-Generator-Model-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-49-CIFAR-10-Images-Output-by-the-Untrained-Generator-Model-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-49-CIFAR-10-Images-Output-by-the-Untrained-Generator-Model-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-49-CIFAR-10-Images-Output-by-the-Untrained-Generator-Model.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8125\" class=\"wp-caption-text\">Example of 49 CIFAR-10 Images Output by the Untrained Generator Model<\/p>\n<\/div>\n<p>Now that we know how to define and use the generator model, the next step is to train the model.<\/p>\n<h2>How to Train the Generator Model<\/h2>\n<p>The weights in the generator model are updated based on the performance of the discriminator model.<\/p>\n<p>When the discriminator is good at detecting fake samples, the generator is updated more, and when the discriminator model is relatively poor or confused when detecting fake samples, the generator model is updated less.<\/p>\n<p>This defines the zero-sum or adversarial relationship between these two models.<\/p>\n<p>There may be many ways to implement this using the Keras API, but perhaps the simplest approach is to create a new model that combines the generator and discriminator models.<\/p>\n<p>Specifically, a new GAN model can be defined that stacks the generator and discriminator such that the generator receives as input random points in the latent space and generates samples that are fed into the discriminator model directly, classified, and the output of this larger model can be used to update the model weights of the generator.<\/p>\n<p>To be clear, we are not talking about a new third model, just a new logical model that uses the already-defined layers and weights from the standalone generator and discriminator models.<\/p>\n<p>Only the discriminator is concerned with distinguishing between real and fake examples, therefore the discriminator model can be trained in a standalone manner on examples of each, as we did in the section on the discriminator model above.<\/p>\n<p>The generator model is only concerned with the discriminator\u2019s performance on fake examples. Therefore, we will mark all of the layers in the discriminator as not trainable when it is part of the GAN model so that they can not be updated and overtrained on fake examples.<\/p>\n<p>When training the generator via this logical GAN model, there is one more important change. We want the discriminator to think that the samples output by the generator are real, not fake. Therefore, when the generator is trained as part of the GAN model, we will mark the generated samples as real (class 1).<\/p>\n<p><strong>Why would we want to do this? <\/strong><\/p>\n<p>We can imagine that the discriminator will then classify the generated samples as not real (class 0) or a low probability of being real (0.3 or 0.5). The backpropagation process used to update the model weights will see this as a large error and will update the model weights (i.e. only the weights in the generator) to correct for this error, in turn making the generator better at generating good fake samples.<\/p>\n<p>Let\u2019s make this concrete.<\/p>\n<ul>\n<li><strong>Inputs<\/strong>: Point in latent space, e.g. a 100-element vector of Gaussian random numbers.<\/li>\n<li><strong>Outputs<\/strong>: Binary classification, likelihood the sample is real (or fake).<\/li>\n<\/ul>\n<p>The <em>define_gan()<\/em> function below takes as arguments the already-defined generator and discriminator models and creates the new, logical third model subsuming these two models. The weights in the discriminator are marked as not trainable, which only affects the weights as seen by the GAN model and not the standalone discriminator model.<\/p>\n<p>The GAN model then uses the same binary cross entropy loss function as the discriminator and the efficient <a href=\"https:\/\/machinelearningmastery.com\/adam-optimization-algorithm-for-deep-learning\/\">Adam version of stochastic gradient descent<\/a> with the learning rate of 0.0002 and momentum of 0.5, recommended when training deep convolutional GANs.<\/p>\n<pre class=\"crayon-plain-tag\"># define the combined generator and discriminator model, for updating the generator\r\ndef define_gan(g_model, d_model):\r\n\t# make weights in the discriminator not trainable\r\n\td_model.trainable = False\r\n\t# connect them\r\n\tmodel = Sequential()\r\n\t# add generator\r\n\tmodel.add(g_model)\r\n\t# add the discriminator\r\n\tmodel.add(d_model)\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\r\n\treturn model<\/pre>\n<p>Making the discriminator not trainable is a clever trick in the Keras API.<\/p>\n<p>The trainable property impacts the model after it is compiled. The discriminator model was compiled with trainable layers, therefore the model weights in those layers will be updated when the standalone model is updated via calls to the <em>train_on_batch()<\/em> function.<\/p>\n<p>The discriminator model was then marked as not trainable, added to the GAN model, and compiled. In this model, the model weights of the discriminator model are not trainable and cannot be changed when the GAN model is updated via calls to the <em>train_on_batch()<\/em> function. This change in the trainable property does not impact the training of the standalone discriminator model.<\/p>\n<p>This behavior is described in the Keras API documentation here:<\/p>\n<ul>\n<li><a href=\"https:\/\/keras.io\/getting-started\/faq\/#how-can-i-freeze-keras-layers\">How can I \u201cfreeze\u201d Keras layers?<\/a><\/li>\n<\/ul>\n<p>The complete example of creating the discriminator, generator and composite model is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># demonstrate creating the three models in the gan\r\nfrom keras.optimizers import Adam\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Reshape\r\nfrom keras.layers import Flatten\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Conv2DTranspose\r\nfrom keras.layers import LeakyReLU\r\nfrom keras.layers import Dropout\r\nfrom keras.utils.vis_utils import plot_model\r\n\r\n# define the standalone discriminator model\r\ndef define_discriminator(in_shape=(32,32,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(256, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model\r\n\r\n# define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 4x4 image\r\n\tn_nodes = 256 * 4 * 4\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((4, 4, 256)))\r\n\t# upsample to 8x8\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 16x16\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 32x32\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer\r\n\tmodel.add(Conv2D(3, (3,3), activation='tanh', padding='same'))\r\n\treturn model\r\n\r\n# define the combined generator and discriminator model, for updating the generator\r\ndef define_gan(g_model, d_model):\r\n\t# make weights in the discriminator not trainable\r\n\td_model.trainable = False\r\n\t# connect them\r\n\tmodel = Sequential()\r\n\t# add generator\r\n\tmodel.add(g_model)\r\n\t# add the discriminator\r\n\tmodel.add(d_model)\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\r\n\treturn model\r\n\r\n# size of the latent space\r\nlatent_dim = 100\r\n# create the discriminator\r\nd_model = define_discriminator()\r\n# create the generator\r\ng_model = define_generator(latent_dim)\r\n# create the gan\r\ngan_model = define_gan(g_model, d_model)\r\n# summarize gan model\r\ngan_model.summary()\r\n# plot gan model\r\nplot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True)<\/pre>\n<p>Running the example first creates a summary of the composite model, which is pretty uninteresting.<\/p>\n<p>We can see that the model expects CIFAR-10 images as input and predict a single value as output.<\/p>\n<pre class=\"crayon-plain-tag\">_________________________________________________________________\r\nLayer (type)                 Output Shape              Param #\r\n=================================================================\r\nsequential_2 (Sequential)    (None, 32, 32, 3)         1466115\r\n_________________________________________________________________\r\nsequential_1 (Sequential)    (None, 1)                 522497\r\n=================================================================\r\nTotal params: 1,988,612\r\nTrainable params: 1,466,115\r\nNon-trainable params: 522,497\r\n_________________________________________________________________<\/pre>\n<p>A plot of the model is also created and we can see that the model expects a 100-element point in latent space as input and will predict a single output classification label.<\/p>\n<p><strong>Note<\/strong>: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement and the call to the <em>plot_model()<\/em> function.<\/p>\n<div id=\"attachment_8126\" style=\"width: 450px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8126\" class=\"size-full wp-image-8126\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Composite-Generator-and-Discriminator-Model-in-the-CIFAR-10-Generative-Adversarial-Network.png\" alt=\"Plot of the Composite Generator and Discriminator Model in the CIFAR-10 Generative Adversarial Network\" width=\"440\" height=\"281\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Composite-Generator-and-Discriminator-Model-in-the-CIFAR-10-Generative-Adversarial-Network.png 440w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Composite-Generator-and-Discriminator-Model-in-the-CIFAR-10-Generative-Adversarial-Network-300x192.png 300w\" sizes=\"(max-width: 440px) 100vw, 440px\"><\/p>\n<p id=\"caption-attachment-8126\" class=\"wp-caption-text\">Plot of the Composite Generator and Discriminator Model in the CIFAR-10 Generative Adversarial Network<\/p>\n<\/div>\n<p>Training the composite model involves generating a batch worth of points in the latent space via the <em>generate_latent_points()<\/em> function in the previous section, and class=1 labels and calling the <em>train_on_batch()<\/em> function.<\/p>\n<p>The <em>train_gan()<\/em> function below demonstrates this, although it is pretty simple as only the generator will be updated each epoch, leaving the discriminator with default model weights.<\/p>\n<pre class=\"crayon-plain-tag\"># train the composite model\r\ndef train_gan(gan_model, latent_dim, n_epochs=200, n_batch=128):\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t\t# prepare points in latent space as input for the generator\r\n\t\tx_gan = generate_latent_points(latent_dim, n_batch)\r\n\t\t# create inverted labels for the fake samples\r\n\t\ty_gan = ones((n_batch, 1))\r\n\t\t# update the generator via the discriminator's error\r\n\t\tgan_model.train_on_batch(x_gan, y_gan)<\/pre>\n<p>Instead, what is required is that we first update the discriminator model with real and fake samples, then update the generator via the composite model.<\/p>\n<p>This requires combining elements from the <em>train_discriminator()<\/em> function defined in the discriminator section above and the <em>train_gan()<\/em> function defined above. It also requires that we enumerate over both epochs and batches within in an epoch.<\/p>\n<p>The complete train function for updating the discriminator model and the generator (via the composite model) is listed below.<\/p>\n<p>There are a few things to note in this model training function.<\/p>\n<p>First, the number of batches within an epoch is defined by how many times the batch size divides into the training dataset. We have a dataset size of 50K samples, so with rounding down, there are 390 batches per epoch.<\/p>\n<p>The discriminator model is updated twice per batch, once with real samples and once with fake samples, which is a best practice as opposed to combining the samples and performing a single update.<\/p>\n<p>Finally, we report the loss each batch. It is critical to keep an eye on the loss over batches. The reason for this is that a crash in the discriminator loss indicates that the generator model has started generating rubbish examples that the discriminator can easily discriminate.<\/p>\n<p>Monitor the discriminator loss and expect it to hover around 0.5 to 0.8 per batch. The generator loss is less critical and may hover between 0.5 and 2 or higher. A clever programmer might even attempt to detect the crashing loss of the discriminator, halt, and then restart the training process.<\/p>\n<pre class=\"crayon-plain-tag\"># train the generator and discriminator\r\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128):\r\n\tbat_per_epo = int(dataset.shape[0] \/ n_batch)\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t\t# enumerate batches over the training set\r\n\t\tfor j in range(bat_per_epo):\r\n\t\t\t# get randomly selected 'real' samples\r\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss1, _ = d_model.train_on_batch(X_real, y_real)\r\n\t\t\t# generate 'fake' examples\r\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss2, _ = d_model.train_on_batch(X_fake, y_fake)\r\n\t\t\t# prepare points in latent space as input for the generator\r\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\r\n\t\t\t# create inverted labels for the fake samples\r\n\t\t\ty_gan = ones((n_batch, 1))\r\n\t\t\t# update the generator via the discriminator's error\r\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\r\n\t\t\t# summarize loss on this batch\r\n\t\t\tprint('>%d, %d\/%d, d1=%.3f, d2=%.3f g=%.3f' %\r\n\t\t\t\t(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))<\/pre>\n<p>We almost have everything we need to develop a GAN for the CIFAR-10 photographs of objects dataset.<\/p>\n<p>One remaining aspect is the evaluation of the model.<\/p>\n<h2>How to Evaluate GAN Model Performance<\/h2>\n<p>Generally, there are no objective ways to evaluate the performance of a GAN model.<\/p>\n<p>We cannot calculate this objective error score for generated images.<\/p>\n<p>Instead, images must be subjectively evaluated for quality by a human operator. This means that we cannot know when to stop training without looking at examples of generated images. In turn, the adversarial nature of the training process means that the generator is changing after every batch, meaning that once \u201c<em>good enough<\/em>\u201d images can be generated, the subjective quality of the images may then begin to vary, improve, or even degrade with subsequent updates.<\/p>\n<p>There are three ways to handle this complex training situation.<\/p>\n<ol>\n<li>Periodically evaluate the classification accuracy of the discriminator on real and fake images.<\/li>\n<li>Periodically generate many images and save them to file for subjective review.<\/li>\n<li>Periodically save the generator model.<\/li>\n<\/ol>\n<p>All three of these actions can be performed at the same time for a given training epoch, such as every 10 training epochs. The result will be a saved generator model for which we have a way of subjectively assessing the quality of its output and objectively knowing how well the discriminator was fooled at the time the model was saved.<\/p>\n<p>Training the GAN over many epochs, such as hundreds or thousands of epochs, will result in many snapshots of the model that can be inspected, and from which specific outputs and models can be cherry-picked for later use.<\/p>\n<p>First, we can define a function called <em>summarize_performance()<\/em> that will summarize the performance of the discriminator model. It does this by retrieving a sample of real CIFAR-10 images, as well as generating the same number of fake CIFAR-10 images with the generator model, then evaluating the classification accuracy of the discriminator model on each sample, and reporting these scores.<\/p>\n<pre class=\"crayon-plain-tag\"># evaluate the discriminator, plot generated images, save generator model\r\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150):\r\n\t# prepare real samples\r\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\r\n\t# evaluate discriminator on real examples\r\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\r\n\t# prepare fake examples\r\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\r\n\t# evaluate discriminator on fake examples\r\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\r\n\t# summarize discriminator performance\r\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))<\/pre>\n<p>This function can be called from the <em>train()<\/em> function based on the current epoch number, such as every 10 epochs.<\/p>\n<pre class=\"crayon-plain-tag\"># train the generator and discriminator\r\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128):\r\n\tbat_per_epo = int(dataset.shape[0] \/ n_batch)\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t...\r\n\t# evaluate the model performance, sometimes\r\n\tif (i+1) % 10 == 0:\r\n\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)<\/pre>\n<p>Next, we can update the <em>summarize_performance()<\/em> function to both save the model and to create and save a plot generated examples.<\/p>\n<p>The generator model can be saved by calling the <em>save()<\/em> function on the generator model and providing a unique filename based on the training epoch number.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n# save the generator model tile file\r\nfilename = 'generator_model_%03d.h5' % (epoch+1)\r\ng_model.save(filename)<\/pre>\n<p>We can develop a function to create a plot of the generated samples.<\/p>\n<p>As we are evaluating the discriminator on 100 generated CIFAR-10 images, we can plot about half, or 49, as a 7 by 7 grid. The <em>save_plot()<\/em> function below implements this, again saving the resulting plot with a unique filename based on the epoch number.<\/p>\n<pre class=\"crayon-plain-tag\"># create and save a plot of generated images\r\ndef save_plot(examples, epoch, n=7):\r\n\t# scale from [-1,1] to [0,1]\r\n\texamples = (examples + 1) \/ 2.0\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i])\r\n\t# save plot to file\r\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\r\n\tpyplot.savefig(filename)\r\n\tpyplot.close()<\/pre>\n<p>The updated <em>summarize_performance()<\/em> function with these additions is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># evaluate the discriminator, plot generated images, save generator model\r\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150):\r\n\t# prepare real samples\r\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\r\n\t# evaluate discriminator on real examples\r\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\r\n\t# prepare fake examples\r\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\r\n\t# evaluate discriminator on fake examples\r\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\r\n\t# summarize discriminator performance\r\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\r\n\t# save plot\r\n\tsave_plot(x_fake, epoch)\r\n\t# save the generator model tile file\r\n\tfilename = 'generator_model_%03d.h5' % (epoch+1)\r\n\tg_model.save(filename)<\/pre>\n<\/p>\n<h2>Complete Example of GAN for CIFAR-10<\/h2>\n<p>We now have everything we need to train and evaluate a GAN on the CIFAR-10 photographs of small objects dataset.<\/p>\n<p>The complete example is listed below.<\/p>\n<p><strong>Note<\/strong>: this example can run on a CPU but may take a number of hours. The example can run on a GPU, such as the Amazon EC2 p3 instances, and will complete in a few minutes.<\/p>\n<p>For help on setting up an AWS EC2 instance to run this code, see the tutorial:<\/p>\n<ul>\n<li><a href=\"https:\/\/machinelearningmastery.com\/develop-evaluate-large-deep-learning-models-keras-amazon-web-services\/\">How to Setup Amazon AWS EC2 GPUs to Train Keras Deep Learning Models (step-by-step)<\/a><\/li>\n<\/ul>\n<pre class=\"crayon-plain-tag\"># example of a dcgan on cifar10\r\nfrom numpy import expand_dims\r\nfrom numpy import zeros\r\nfrom numpy import ones\r\nfrom numpy import vstack\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom keras.datasets.cifar10 import load_data\r\nfrom keras.optimizers import Adam\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Reshape\r\nfrom keras.layers import Flatten\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Conv2DTranspose\r\nfrom keras.layers import LeakyReLU\r\nfrom keras.layers import Dropout\r\nfrom matplotlib import pyplot\r\n\r\n# define the standalone discriminator model\r\ndef define_discriminator(in_shape=(32,32,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample\r\n\tmodel.add(Conv2D(256, (3,3), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model\r\n\r\n# define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 4x4 image\r\n\tn_nodes = 256 * 4 * 4\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((4, 4, 256)))\r\n\t# upsample to 8x8\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 16x16\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 32x32\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer\r\n\tmodel.add(Conv2D(3, (3,3), activation='tanh', padding='same'))\r\n\treturn model\r\n\r\n# define the combined generator and discriminator model, for updating the generator\r\ndef define_gan(g_model, d_model):\r\n\t# make weights in the discriminator not trainable\r\n\td_model.trainable = False\r\n\t# connect them\r\n\tmodel = Sequential()\r\n\t# add generator\r\n\tmodel.add(g_model)\r\n\t# add the discriminator\r\n\tmodel.add(d_model)\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\r\n\treturn model\r\n\r\n# load and prepare cifar10 training images\r\ndef load_real_samples():\r\n\t# load cifar10 dataset\r\n\t(trainX, _), (_, _) = load_data()\r\n\t# convert from unsigned ints to floats\r\n\tX = trainX.astype('float32')\r\n\t# scale from [0,255] to [-1,1]\r\n\tX = (X - 127.5) \/ 127.5\r\n\treturn X\r\n\r\n# select real samples\r\ndef generate_real_samples(dataset, n_samples):\r\n\t# choose random instances\r\n\tix = randint(0, dataset.shape[0], n_samples)\r\n\t# retrieve selected images\r\n\tX = dataset[ix]\r\n\t# generate 'real' class labels (1)\r\n\ty = ones((n_samples, 1))\r\n\treturn X, y\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input\r\n\r\n# use the generator to generate n fake examples, with class labels\r\ndef generate_fake_samples(g_model, latent_dim, n_samples):\r\n\t# generate points in latent space\r\n\tx_input = generate_latent_points(latent_dim, n_samples)\r\n\t# predict outputs\r\n\tX = g_model.predict(x_input)\r\n\t# create 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y\r\n\r\n# create and save a plot of generated images\r\ndef save_plot(examples, epoch, n=7):\r\n\t# scale from [-1,1] to [0,1]\r\n\texamples = (examples + 1) \/ 2.0\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i])\r\n\t# save plot to file\r\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\r\n\tpyplot.savefig(filename)\r\n\tpyplot.close()\r\n\r\n# evaluate the discriminator, plot generated images, save generator model\r\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150):\r\n\t# prepare real samples\r\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\r\n\t# evaluate discriminator on real examples\r\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\r\n\t# prepare fake examples\r\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\r\n\t# evaluate discriminator on fake examples\r\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\r\n\t# summarize discriminator performance\r\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\r\n\t# save plot\r\n\tsave_plot(x_fake, epoch)\r\n\t# save the generator model tile file\r\n\tfilename = 'generator_model_%03d.h5' % (epoch+1)\r\n\tg_model.save(filename)\r\n\r\n# train the generator and discriminator\r\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128):\r\n\tbat_per_epo = int(dataset.shape[0] \/ n_batch)\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t\t# enumerate batches over the training set\r\n\t\tfor j in range(bat_per_epo):\r\n\t\t\t# get randomly selected 'real' samples\r\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss1, _ = d_model.train_on_batch(X_real, y_real)\r\n\t\t\t# generate 'fake' examples\r\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss2, _ = d_model.train_on_batch(X_fake, y_fake)\r\n\t\t\t# prepare points in latent space as input for the generator\r\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\r\n\t\t\t# create inverted labels for the fake samples\r\n\t\t\ty_gan = ones((n_batch, 1))\r\n\t\t\t# update the generator via the discriminator's error\r\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\r\n\t\t\t# summarize loss on this batch\r\n\t\t\tprint('>%d, %d\/%d, d1=%.3f, d2=%.3f g=%.3f' %\r\n\t\t\t\t(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))\r\n\t\t# evaluate the model performance, sometimes\r\n\t\tif (i+1) % 10 == 0:\r\n\t\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)\r\n\r\n# size of the latent space\r\nlatent_dim = 100\r\n# create the discriminator\r\nd_model = define_discriminator()\r\n# create the generator\r\ng_model = define_generator(latent_dim)\r\n# create the gan\r\ngan_model = define_gan(g_model, d_model)\r\n# load image data\r\ndataset = load_real_samples()\r\n# train model\r\ntrain(g_model, d_model, gan_model, dataset, latent_dim)<\/pre>\n<p>The chosen configuration results in the stable training of both the generative and discriminative model.<\/p>\n<p>The model performance is reported every batch, including the loss of both the discriminative (<em>d<\/em>) and generative (<em>g<\/em>) models.<\/p>\n<p><strong>Note<\/strong>: your specific results may vary given the stochastic nature of the training algorithm. Try running the example a few times.<\/p>\n<p>In this case, the loss remains stable over the course of training. The discriminator loss on the real and generated examples sits around 0.5, whereas the loss for the generator trained via the discriminator sits around 1.5 for much of the training process.<\/p>\n<pre class=\"crayon-plain-tag\">>1, 1\/390, d1=0.720, d2=0.695 g=0.692\r\n>1, 2\/390, d1=0.658, d2=0.697 g=0.691\r\n>1, 3\/390, d1=0.604, d2=0.700 g=0.687\r\n>1, 4\/390, d1=0.522, d2=0.709 g=0.680\r\n>1, 5\/390, d1=0.417, d2=0.731 g=0.662\r\n...\r\n>200, 386\/390, d1=0.499, d2=0.401 g=1.565\r\n>200, 387\/390, d1=0.459, d2=0.623 g=1.481\r\n>200, 388\/390, d1=0.588, d2=0.556 g=1.700\r\n>200, 389\/390, d1=0.579, d2=0.288 g=1.555\r\n>200, 390\/390, d1=0.620, d2=0.453 g=1.466<\/pre>\n<p>The generator is evaluated every 10 epochs, resulting in 20 evaluations, 20 plots of generated images, and 20 saved models.<\/p>\n<p>In this case, we can see that the accuracy fluctuates over training. When viewing the discriminator model\u2019s accuracy score in concert with generated images, we can see that the accuracy on fake examples does not correlate well with the subjective quality of images, but the accuracy for real examples may.<\/p>\n<p>It is a crude and possibly unreliable metric of GAN performance, along with loss.<\/p>\n<pre class=\"crayon-plain-tag\">>Accuracy real: 55%, fake: 89%\r\n>Accuracy real: 50%, fake: 75%\r\n>Accuracy real: 49%, fake: 86%\r\n>Accuracy real: 60%, fake: 79%\r\n>Accuracy real: 49%, fake: 87%\r\n...<\/pre>\n<p>More training, beyond some point, does not mean better quality generated images.<\/p>\n<p>In this case, the results after 10 epochs are low quality, although we can see some difference between background and foreground with a blog in the middle of each image.<\/p>\n<div id=\"attachment_8127\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8127\" class=\"size-full wp-image-8127\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-10-Epochs.png\" alt=\"Plot of 49 GAN Generated CIFAR-10 Photographs After 10 Epochs\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-10-Epochs.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-10-Epochs-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8127\" class=\"wp-caption-text\">Plot of 49 GAN Generated CIFAR-10 Photographs After 10 Epochs<\/p>\n<\/div>\n<p>After 90 or 100 epochs, we are starting to see plausible photographs with blobs that look like birds, dogs, cats, and horses.<\/p>\n<p>The objects are familiar and CIFAR-10-like, but many of them are not clearly one of the 10 specified classes.<\/p>\n<div id=\"attachment_8128\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8128\" class=\"size-full wp-image-8128\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-90-Epochs.png\" alt=\"Plot of 49 GAN Generated CIFAR-10 Photographs After 90 Epochs\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-90-Epochs.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-90-Epochs-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8128\" class=\"wp-caption-text\">Plot of 49 GAN Generated CIFAR-10 Photographs After 90 Epochs<\/p>\n<\/div>\n<div id=\"attachment_8129\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8129\" class=\"size-full wp-image-8129\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-100-Epochs.png\" alt=\"Plot of 49 GAN Generated CIFAR-10 Photographs After 100 Epochs\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-100-Epochs.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-100-Epochs-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8129\" class=\"wp-caption-text\">Plot of 49 GAN Generated CIFAR-10 Photographs After 100 Epochs<\/p>\n<\/div>\n<p>The model remains stable over the next 100 epochs, with little major improvement in the generated images.<\/p>\n<p>The small photos remain vaguely CIFAR-10 like and focused on animals like dogs, cats, and birds.<\/p>\n<div id=\"attachment_8130\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8130\" class=\"size-full wp-image-8130\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-200-Epochs.png\" alt=\"Plot of 49 GAN Generated CIFAR-10 Photographs After 200 Epochs\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-200-Epochs.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-49-GAN-Generated-CIFAR-10-Photographs-After-200-Epochs-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8130\" class=\"wp-caption-text\">Plot of 49 GAN Generated CIFAR-10 Photographs After 200 Epochs<\/p>\n<\/div>\n<h2>How to Use the Final Generator Model to Generate Images<\/h2>\n<p>Once a final generator model is selected, it can be used in a standalone manner for your application.<\/p>\n<p>This involves first loading the model from file, then using it to generate images. The generation of each image requires a point in the latent space as input.<\/p>\n<p>The complete example of loading the saved model and generating images is listed below. In this case, we will use the model saved after 200 training epochs, but the model saved after 100 epochs would work just as well.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading the generator model and generating images\r\nfrom keras.models import load_model\r\nfrom numpy.random import randn\r\nfrom matplotlib import pyplot\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input\r\n\r\n# create and save a plot of generated images\r\ndef save_plot(examples, n):\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_200.h5')\r\n# generate images\r\nlatent_points = generate_latent_points(100, 100)\r\n# generate images\r\nX = model.predict(latent_points)\r\n# scale from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# plot the result\r\nsave_plot(X, 10)<\/pre>\n<p>Running the example first loads the model, samples 100 random points in the latent space, generates 100 images, then plots the results as a single image.<\/p>\n<p>We can see that most of the images are plausible, or plausible pieces of small objects.<\/p>\n<p>I can see dogs, cats, horses, birds, frogs, and perhaps planes.<\/p>\n<div id=\"attachment_8131\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8131\" class=\"size-large wp-image-8131\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-100-GAN-Generated-CIFAR-10-Small-Object-Photographs-1024x768.png\" alt=\"Example of 100 GAN Generated CIFAR-10 Small Object Photographs\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-100-GAN-Generated-CIFAR-10-Small-Object-Photographs-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-100-GAN-Generated-CIFAR-10-Small-Object-Photographs-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-100-GAN-Generated-CIFAR-10-Small-Object-Photographs-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-100-GAN-Generated-CIFAR-10-Small-Object-Photographs.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8131\" class=\"wp-caption-text\">Example of 100 GAN Generated CIFAR-10 Small Object Photographs<\/p>\n<\/div>\n<p>The latent space now defines a compressed representation of CIFAR-10 photos.<\/p>\n<p>You can experiment with generating different points in this space and see what types of images they generate.<\/p>\n<p>The example below generates a single handwritten digit using a vector of all 0.75 values.<\/p>\n<pre class=\"crayon-plain-tag\"># example of generating an image for a specific point in the latent space\r\nfrom keras.models import load_model\r\nfrom numpy import asarray\r\nfrom matplotlib import pyplot\r\n# load model\r\nmodel = load_model('generator_model_200.h5')\r\n# all 0s\r\nvector = asarray([[0.75 for _ in range(100)]])\r\n# generate image\r\nX = model.predict(vector)\r\n# scale from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# plot the result\r\npyplot.imshow(X[0, :, :])\r\npyplot.show()<\/pre>\n<p>Your specific results may vary given the stochastic nature of the model and the learning algorithm.<\/p>\n<p>In this case, a vector of all 0.75 results in a deer or perhaps deer-horse-looking animal in a green field.<\/p>\n<div id=\"attachment_8132\" style=\"width: 310px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8132\" class=\"wp-image-8132 size-medium\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-a-GAN-Generated-CIFAR-Small-Object-Photo-for-A-Specific-Point-in-the-Latent-Space-300x225.png\" alt=\"Example of a GAN Generated CIFAR Small Object Photo for a Specific Point in the Latent Space\" width=\"300\" height=\"225\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-a-GAN-Generated-CIFAR-Small-Object-Photo-for-A-Specific-Point-in-the-Latent-Space-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-a-GAN-Generated-CIFAR-Small-Object-Photo-for-A-Specific-Point-in-the-Latent-Space-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-a-GAN-Generated-CIFAR-Small-Object-Photo-for-A-Specific-Point-in-the-Latent-Space-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-a-GAN-Generated-CIFAR-Small-Object-Photo-for-A-Specific-Point-in-the-Latent-Space.png 1280w\" sizes=\"(max-width: 300px) 100vw, 300px\"><\/p>\n<p id=\"caption-attachment-8132\" class=\"wp-caption-text\">Example of a GAN Generated CIFAR Small Object Photo for a Specific Point in the Latent Space<\/p>\n<\/div>\n<h2>Extensions<\/h2>\n<p>This section lists some ideas for extending the tutorial that you may wish to explore.<\/p>\n<ul>\n<li><strong>Change Latent Space<\/strong>. Update the example to use a larger or smaller latent space and compare the quality of the results and speed of training.<\/li>\n<li><strong>Batch Normalization<\/strong>. Update the discriminator and\/or the generator to make use of batch normalization, recommended for DCGAN models.<\/li>\n<li><strong>Label Smoothing<\/strong>. Update the example to use one-sided label smoothing when training the discriminator, specifically change the target label of real examples from 1.0 to 0.9 and add random noise, then review the effects on image quality and speed of training.<\/li>\n<li><strong>Model Configuration<\/strong>. Update the model configuration to use deeper or more shallow discriminator and\/or generator models, perhaps experiment with the UpSampling2D layers in the generator.<\/li>\n<\/ul>\n<p>If you explore any of these extensions, I\u2019d love to know.<br \/>\nPost your findings in the comments below.<\/p>\n<h2>Further Reading<\/h2>\n<p>This section provides more resources on the topic if you are looking to go deeper.<\/p>\n<h3>Posts<\/h3>\n<ul>\n<li>Chapter 20. Deep Generative Models, <a href=\"https:\/\/amzn.to\/2YuwVjL\">Deep Learning<\/a>, 2016.<\/li>\n<li>Chapter 8. Generative Deep Learning, <a href=\"https:\/\/amzn.to\/2U2bHuP\">Deep Learning with Python<\/a>, 2017.<\/li>\n<\/ul>\n<h3>Papers<\/h3>\n<ul>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1406.2661\">Generative Adversarial Networks<\/a>, 2014.<\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1701.00160\">Tutorial: Generative Adversarial Networks, NIPS<\/a>, 2016.<\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1511.06434\">Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks<\/a>, 2015.<\/li>\n<\/ul>\n<h3>API<\/h3>\n<ul>\n<li><a href=\"https:\/\/keras.io\/datasets\/\">Keras Datasets API.<\/a><\/li>\n<li><a href=\"https:\/\/keras.io\/models\/sequential\/\">Keras Sequential Model API<\/a><\/li>\n<li><a href=\"https:\/\/keras.io\/layers\/convolutional\/\">Keras Convolutional Layers API<\/a><\/li>\n<li><a href=\"https:\/\/keras.io\/getting-started\/faq\/#how-can-i-freeze-keras-layers\">How can I \u201cfreeze\u201d Keras layers?<\/a><\/li>\n<li><a href=\"https:\/\/matplotlib.org\/api\/\">MatplotLib API<\/a><\/li>\n<li><a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/routines.random.html\">NumPy Random sampling (numpy.random) API<\/a><\/li>\n<li><a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/routines.array-manipulation.html\">NumPy Array manipulation routines<\/a><\/li>\n<\/ul>\n<h3>Articles<\/h3>\n<ul>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/CIFAR-10\">CIFAR-10, Wikipedia<\/a>.<\/li>\n<li><a href=\"https:\/\/www.cs.toronto.edu\/~kriz\/cifar.html\">The CIFAR-10 dataset and CIFAR-100 datasets<\/a>.<\/li>\n<li><a href=\"https:\/\/medium.com\/@stepanulyanin\/dcgan-adventures-with-cifar10-905fb0a24d21\">DCGAN Adventures with Cifar10, 2018<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/4thgen\/DCGAN-CIFAR10\">DCGAN-CIFAR10 Project, A implementation of DCGAN for CIFAR10 image<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/tensorflow\/tensorflow\/tree\/master\/tensorflow\/contrib\/gan\">TensorFlow-GAN (TF-GAN) Project<\/a>.<\/li>\n<\/ul>\n<h2>Summary<\/h2>\n<p>In this tutorial, you discovered how to develop a generative adversarial network with deep convolutional networks for generating small photographs of objects.<\/p>\n<p>Specifically, you learned:<\/p>\n<ul>\n<li>How to define and train the standalone discriminator model for learning the difference between real and fake images.<\/li>\n<li>How to define the standalone generator model and train the composite generator and discriminator model.<\/li>\n<li>How to evaluate the performance of the GAN and use the final standalone generator model to generate new images.<\/li>\n<\/ul>\n<p>Do you have any questions?<br \/>\nAsk your questions in the comments below and I will do my best to answer.<\/p>\n<p>The post <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/how-to-develop-a-generative-adversarial-network-for-a-cifar-10-small-object-photographs-from-scratch\/\">How to Develop a GAN for Generating Small Color Photographs of Objects<\/a> appeared first on <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/\">Machine Learning Mastery<\/a>.<\/p>\n<\/div>\n<p><a href=\"https:\/\/machinelearningmastery.com\/how-to-develop-a-generative-adversarial-network-for-a-cifar-10-small-object-photographs-from-scratch\/\">Go to Source<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Author: Jason Brownlee Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images. Developing [&hellip;] <span class=\"read-more-link\"><a class=\"read-more\" href=\"https:\/\/www.aiproblog.com\/index.php\/2019\/06\/30\/how-to-develop-a-gan-for-generating-small-color-photographs-of-objects\/\">Read More<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":2314,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"footnotes":""},"categories":[24],"tags":[],"_links":{"self":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/2313"}],"collection":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/comments?post=2313"}],"version-history":[{"count":0,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/2313\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media\/2314"}],"wp:attachment":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media?parent=2313"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/categories?post=2313"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/tags?post=2313"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}