{
  "source_file": "0930",
  "source_platform": "技术平台",
  "author": "Jason Brownlee / Machine Learning Mastery",
  "title_listed": "How to Develop a GAN for Generating MNIST Handwritten Digits",
  "keywords": "GAN,生成对抗,教程",
  "domain": "机器学习",
  "content_type": "教程",
  "is_paid_flag": "否",
  "url": "https://machinelearningmastery.com/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/",
  "publish_date": "2020-01",
  "corpus_stage": "预训练/SFT",
  "notes": "GAN生成对抗网络教程",
  "id": "065e3ea689a7",
  "collected_at": "2026-10-02T17:55:35",
  "title": "How to Develop a GAN for Generating MNIST Handwritten Digits - MachineLearningMastery.com",
  "body_markdown": "How to Develop a GAN for Generating MNIST Handwritten Digits\nBy\nJason Brownlee\non\nSeptember 1, 2020\nin\nGenerative Adversarial Networks\n115\nShare\nPost\nShare\nGenerative Adversarial Networks\n, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images.\nDeveloping 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.\nIt 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 MNIST handwritten digit dataset. Using small and well-understood datasets means that smaller models can be developed and trained quickly, allowing the focus to be put on the model architecture and image generation process itself.\nIn this tutorial, you will discover how to develop a generative adversarial network with deep convolutional networks for generating handwritten digits.\nAfter completing this tutorial, you will know:\nHow to define and train the standalone discriminator model for learning the difference between real and fake images.\nHow to define the standalone generator model and train the composite generator and discriminator model.\nHow to evaluate the performance of the GAN and use the final standalone generator model to generate new images.\nKick-start your project\nwith my new book\nGenerative Adversarial Networks with Python\n, including\nstep-by-step tutorials\nand the\nPython source code\nfiles for all examples.\nLet’s get started.\nHow to Develop a Generative Adversarial Network for an MNIST Handwritten Digits From Scratch in Keras\nPhoto by\njcookfisher\n, some rights reserved.\nTutorial Overview\nThis tutorial is divided into seven parts; they are:\nMNIST Handwritten Digit Dataset\nHow to Define and Train the Discriminator Model\nHow to Define and Use the Generator Model\nHow to Train the Generator Model\nHow to Evaluate GAN Model Performance\nComplete Example of GAN for MNIST\nHow to Use the Final Generator Model to Generate Images\nMNIST Handwritten Digit Dataset\nThe\nMNIST dataset\nis an acronym that stands for the Modified National Institute of Standards and Technology dataset.\nIt is a dataset of 70,000 small square 28×28 pixel grayscale images of handwritten single digits between 0 and 9.\nThe task is to classify a given image of a handwritten digit into one of 10 classes representing integer values from 0 to 9, inclusively.\nKeras provides access to the MNIST dataset via the\nmnist.load_dataset() function\n. 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.\nThe example below loads the dataset and summarizes the shape of the loaded dataset.\nNote\n: 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\n~/.keras/datasets/\n. The download is fast as the dataset is only about eleven megabytes in its compressed form.\n# example of loading the mnist dataset\nfrom keras.datasets.mnist import load_data\n# load the images into memory\n(trainX, trainy), (testX, testy) = load_data()\n# summarize the shape of the dataset\nprint('Train', trainX.shape, trainy.shape)\nprint('Test', testX.shape, testy.shape)\n1\n2\n3\n4\n5\n6\n7\n# example of loading the mnist dataset\nfrom\nkeras\n.\ndatasets\n.\nmnist\nimport\nload\n_\ndata\n# load the images into memory\n(\ntrainX\n,\ntrainy\n)\n,\n(\ntestX\n,\ntesty\n)\n=\nload_data\n(\n)\n# summarize the shape of the dataset\nprint\n(\n'Train'\n,\ntrainX\n.\nshape\n,\ntrainy\n.\nshape\n)\nprint\n(\n'Test'\n,\ntestX\n.\nshape\n,\ntesty\n.\nshape\n)\nRunning the example loads the dataset and prints the shape of the input and output components of the train and test splits of images.\nWe can see that there are 60K examples in the training set and 10K in the test set and that each image is a square of 28 by 28 pixels.\nTrain (60000, 28, 28) (60000,)\nTest (10000, 28, 28) (10000,)\n1\n2\nTrain (60000, 28, 28) (60000,)\nTest (10000, 28, 28) (10000,)\nThe images are grayscale with a black background (0 pixel value) and the handwritten digits in white (pixel values near 255). This means if the images were plotted, they would be mostly black with a white digit in the middle.\nWe can plot some of the images from the training dataset using the matplotlib library using the\nimshow() function\nand specify the color map via the ‘\ncmap\n‘ argument as ‘\ngray\n‘ to show the pixel values correctly.\n# plot raw pixel data\npyplot.imshow(trainX[i], cmap='gray')\n1\n2\n# plot raw pixel data\npyplot\n.\nimshow\n(\ntrainX\n[\ni\n]\n,\ncmap\n=\n'gray'\n)\nAlternately, the images are easier to review when we reverse the colors and plot the background as white and the handwritten digits in black.\nThey are easier to view as most of the image is now white with the area of interest in black. This can be achieved using a reverse grayscale color map, as follows:\n# plot raw pixel data\npyplot.imshow(trainX[i], cmap='gray_r')\n1\n2\n# plot raw pixel data\npyplot\n.\nimshow\n(\ntrainX\n[\ni\n]\n,\ncmap\n=\n'gray_r'\n)\nThe example below plots the first 25 images from the training dataset in a 5 by 5 square.\n# example of loading the mnist dataset\nfrom keras.datasets.mnist import load_data\nfrom matplotlib import pyplot\n# load the images into memory\n(trainX, trainy), (testX, testy) = load_data()\n# plot images from the training dataset\nfor i in range(25):\n\t# define subplot\n\tpyplot.subplot(5, 5, 1 + i)\n\t# turn off axis\n\tpyplot.axis('off')\n\t# plot raw pixel data\n\tpyplot.imshow(trainX[i], cmap='gray_r')\npyplot.show()\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n# example of loading the mnist dataset\nfrom\nkeras\n.\ndatasets\n.\nmnist\nimport\nload_data\nfrom\nmatplotlib\nimport\npyplot\n# load the images into memory\n(\ntrainX\n,\ntrainy\n)\n,\n(\ntestX\n,\ntesty\n)\n=\nload_data\n(\n)\n# plot images from the training dataset\nfor\ni\nin\nrange\n(\n25\n)\n:\n# define subplot\npyplot\n.\nsubplot\n(\n5\n,\n5\n,\n1\n+\ni\n)\n# turn off axis\npyplot\n.\naxis\n(\n'off'\n)\n# plot raw pixel data\npyplot\n.\nimshow\n(\ntrainX\n[\ni\n]\n,\ncmap\n=\n'gray_r'\n)\npyplot\n.\nshow\n(\n)\nRunning the example creates a plot of 25 images from the MNIST training dataset, arranged in a 5×5 square.\nPlot of the First 25 Handwritten Digits From the MNIST Dataset.\nWe will use the images in the training dataset as the basis for training a Generative Adversarial Network.\nSpecifically, the generator model will learn how to generate new plausible handwritten digits between 0 and 9, using a discriminator that will try to distinguish between real images from the MNIST training dataset and new images output by the generator model.\nThis is a relatively simple problem that does not require a sophisticated generator or discriminator model, although it does require the generation of a grayscale output image.\nWant to Develop GANs from Scratch?\nTake my free 7-day email crash course now (with sample code).\nClick to sign-up and also get a free PDF Ebook version of the course.\nDownload Your FREE Mini-Course\nHow to Define and Train the Discriminator Model\nThe first step is to define the discriminator model.\nThe 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.\nThis is a binary classification problem:\nInputs\n: Image with one channel and 28×28 pixels in size.\nOutputs\n: Binary classification, likelihood the sample is real (or fake).\nThe discriminator model has two\nconvolutional layers\nwith 64 filters each, a small kernel size of 3, and larger than normal stride of 2. The model has no pooling layers 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\nbinary cross entropy loss function\n, appropriate for binary classification.\nWe will use some best practices in defining the discriminator model, such as the use of LeakyReLU instead of\nReLU\n, using\nDropout\n, and using the\nAdam version of stochastic gradient descent\nwith a learning rate of 0.0002 and a momentum of 0.5.\nThe function\ndefine_discriminator()\nbelow defines the discriminator model and parametrizes the size of the input image.\n# define the standalone discriminator model\ndef define_discriminator(in_shape=(28,28,1)):\n\tmodel = Sequential()\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Flatten())\n\tmodel.add(Dense(1, activation='sigmoid'))\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\n\treturn model\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n# define the standalone discriminator model\ndef\ndefine_discriminator\n(\nin_shape\n=\n(\n28\n,\n28\n,\n1\n)\n)\n:\nmodel\n=\nSequential\n(\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n,\ninput_shape\n=\nin_shape\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nFlatten\n(\n)\n)\nmodel\n.\nadd\n(\nDense\n(\n1\n,\nactivation\n=\n'sigmoid'\n)\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n,\nmetrics\n=\n[\n'accuracy'\n]\n)\nreturn\nmodel\nWe can use this function to define the discriminator model and summarize it.\nThe complete example is listed below.\n# example of defining the discriminator model\nfrom keras.models import Sequential\nfrom keras.optimizers import Adam\nfrom keras.layers import Dense\nfrom keras.layers import Conv2D\nfrom keras.layers import Flatten\nfrom keras.layers import Dropout\nfrom keras.layers import LeakyReLU\nfrom keras.utils.vis_utils import plot_model\n\n# define the standalone discriminator model\ndef define_discriminator(in_shape=(28,28,1)):\n\tmodel = Sequential()\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Flatten())\n\tmodel.add(Dense(1, activation='sigmoid'))\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\n\treturn model\n\n# define model\nmodel = define_discriminator()\n# summarize the model\nmodel.summary()\n# plot the model\nplot_model(model, to_file='discriminator_plot.png', show_shapes=True, show_layer_names=True)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n# example of defining the discriminator model\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\noptimizers\nimport\nAdam\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nFlatten\nfrom\nkeras\n.\nlayers\nimport\nDropout\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\nfrom\nkeras\n.\nutils\n.\nvis_utils\nimport\nplot\n_\nmodel\n# define the standalone discriminator model\ndef\ndefine_discriminator\n(\nin_shape\n=\n(\n28\n,\n28\n,\n1\n)\n)\n:\nmodel\n=\nSequential\n(\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n,\ninput_shape\n=\nin_shape\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nFlatten\n(\n)\n)\nmodel\n.\nadd\n(\nDense\n(\n1\n,\nactivation\n=\n'sigmoid'\n)\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n,\nmetrics\n=\n[\n'accuracy'\n]\n)\nreturn\nmodel\n# define model\nmodel\n=\ndefine_discriminator\n(\n)\n# summarize the model\nmodel\n.\nsummary\n(\n)\n# plot the model\nplot_model\n(\nmodel\n,\nto_file\n=\n'discriminator_plot.png'\n,\nshow_shapes\n=\nTrue\n,\nshow_layer_names\n=\nTrue\n)\nRunning the example first summarizes the model architecture, showing the input and output from each layer.\nWe can see that the aggressive\n2×2 stride acts to down-sample the input image\n, first from 28×28 to 14×14, then to 7×7, before the model makes an output prediction.\nThis pattern is by design as we do not use pooling layers and use the large stride as achieve a similar downsampling effect. We will see a similar pattern, but in reverse, in the generator model in the next section.\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\nconv2d_1 (Conv2D)            (None, 14, 14, 64)        640\n_________________________________________________________________\nleaky_re_lu_1 (LeakyReLU)    (None, 14, 14, 64)        0\n_________________________________________________________________\ndropout_1 (Dropout)          (None, 14, 14, 64)        0\n_________________________________________________________________\nconv2d_2 (Conv2D)            (None, 7, 7, 64)          36928\n_________________________________________________________________\nleaky_re_lu_2 (LeakyReLU)    (None, 7, 7, 64)          0\n_________________________________________________________________\ndropout_2 (Dropout)          (None, 7, 7, 64)          0\n_________________________________________________________________\nflatten_1 (Flatten)          (None, 3136)              0\n_________________________________________________________________\ndense_1 (Dense)              (None, 1)                 3137\n=================================================================\nTotal params: 40,705\nTrainable params: 40,705\nNon-trainable params: 0\n_________________________________________________________________\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\nconv2d_1 (Conv2D)            (None, 14, 14, 64)        640\n_________________________________________________________________\nleaky_re_lu_1 (LeakyReLU)    (None, 14, 14, 64)        0\n_________________________________________________________________\ndropout_1 (Dropout)          (None, 14, 14, 64)        0\n_________________________________________________________________\nconv2d_2 (Conv2D)            (None, 7, 7, 64)          36928\n_________________________________________________________________\nleaky_re_lu_2 (LeakyReLU)    (None, 7, 7, 64)          0\n_________________________________________________________________\ndropout_2 (Dropout)          (None, 7, 7, 64)          0\n_________________________________________________________________\nflatten_1 (Flatten)          (None, 3136)              0\n_________________________________________________________________\ndense_1 (Dense)              (None, 1)                 3137\n=================================================================\nTotal params: 40,705\nTrainable params: 40,705\nNon-trainable params: 0\n_________________________________________________________________\nA plot of the model is also created and we can see that the model expects two inputs and will predict a single output.\nNote\n: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement for the\nplot_model\nfunction and the call to the\nplot_model()\nfunction.\nPlot of the Discriminator Model in the MNIST GAN\nWe could start training this model now with real examples with a class label of one, and randomly generated samples with a class label of zero.\nThe 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.\nFirst, we need a function to load and prepare the dataset of real images.\nWe will use the\nmnist.load_data()\nfunction to load the MNIST dataset and just use the input part of the training dataset as the real images.\n# load mnist dataset\n(trainX, _), (_, _) = load_data()\n1\n2\n# load mnist dataset\n(\ntrainX\n,\n_\n)\n,\n(\n_\n,\n_\n)\n=\nload_data\n(\n)\nThe images are 2D arrays of pixels and convolutional neural networks expect 3D arrays of images as input, where each image has one or more channels.\nWe must update the images to have an additional dimension for the grayscale channel. We can do this using the\nexpand_dims() NumPy function\nand specify the final dimension for the channels-last image format.\n# expand to 3d, e.g. add channels dimension\nX = expand_dims(trainX, axis=-1)\n1\n2\n# expand to 3d, e.g. add channels dimension\nX\n=\nexpand_dims\n(\ntrainX\n,\naxis\n=\n-\n1\n)\nFinally, we must\nscale the pixel values\nfrom the range of unsigned integers in [0,255] to the normalized range of [0,1].\n# convert from unsigned ints to floats\nX = X.astype('float32')\n# scale from [0,255] to [0,1]\nX = X / 255.0\n1\n2\n3\n4\n# convert from unsigned ints to floats\nX\n=\nX\n.\nastype\n(\n'float32'\n)\n# scale from [0,255] to [0,1]\nX\n=\nX\n/\n255.0\nThe\nload_real_samples()\nfunction below implements this.\n# load and prepare mnist training images\ndef load_real_samples():\n\t# load mnist dataset\n\t(trainX, _), (_, _) = load_data()\n\t# expand to 3d, e.g. add channels dimension\n\tX = expand_dims(trainX, axis=-1)\n\t# convert from unsigned ints to floats\n\tX = X.astype('float32')\n\t# scale from [0,255] to [0,1]\n\tX = X / 255.0\n\treturn X\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n# load and prepare mnist training images\ndef\nload_real_samples\n(\n)\n:\n# load mnist dataset\n(\ntrainX\n,\n_\n)\n,\n(\n_\n,\n_\n)\n=\nload_data\n(\n)\n# expand to 3d, e.g. add channels dimension\nX\n=\nexpand_dims\n(\ntrainX\n,\naxis\n=\n-\n1\n)\n# convert from unsigned ints to floats\nX\n=\nX\n.\nastype\n(\n'float32'\n)\n# scale from [0,255] to [0,1]\nX\n=\nX\n/\n255.0\nreturn\nX\nThe model will be updated in batches, specifically with a collection of real samples and a collection of generated samples. On training, epoch is defined as one pass through the entire training dataset.\nWe 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.\nThe\ngenerate_real_samples()\nfunction 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.\n# select real samples\ndef generate_real_samples(dataset, n_samples):\n\t# choose random instances\n\tix = randint(0, dataset.shape[0], n_samples)\n\t# retrieve selected images\n\tX = dataset[ix]\n\t# generate 'real' class labels (1)\n\ty = ones((n_samples, 1))\n\treturn X, y\n1\n2\n3\n4\n5\n6\n7\n8\n9\n# select real samples\ndef\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n:\n# choose random instances\nix\n=\nrandint\n(\n0\n,\ndataset\n.\nshape\n[\n0\n]\n,\nn_samples\n)\n# retrieve selected images\nX\n=\ndataset\n[\nix\n]\n# generate 'real' class labels (1)\ny\n=\nones\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\nNow, we need a source of fake images.\nWe don’t have a generator model yet, so instead, we can generate images comprised of random pixel values, specifically\nrandom pixel values\nin the range [0,1] like our scaled real images.\nThe\ngenerate_fake_samples()\nfunction below implements this behavior and generates images of random pixel values and their associated class label of 0, for fake.\n# generate n fake samples with class labels\ndef generate_fake_samples(n_samples):\n\t# generate uniform random numbers in [0,1]\n\tX = rand(28 * 28 * n_samples)\n\t# reshape into a batch of grayscale images\n\tX = X.reshape((n_samples, 28, 28, 1))\n\t# generate 'fake' class labels (0)\n\ty = zeros((n_samples, 1))\n\treturn X, y\n1\n2\n3\n4\n5\n6\n7\n8\n9\n# generate n fake samples with class labels\ndef\ngenerate_fake_samples\n(\nn_samples\n)\n:\n# generate uniform random numbers in [0,1]\nX\n=\nrand\n(\n28\n*\n28\n*\nn_samples\n)\n# reshape into a batch of grayscale images\nX\n=\nX\n.\nreshape\n(\n(\nn_samples\n,\n28\n,\n28\n,\n1\n)\n)\n# generate 'fake' class labels (0)\ny\n=\nzeros\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\nFinally, we need to train the discriminator model.\nThis involves repeatedly retrieving samples of real images and samples of generated images and updating the model for a fixed number of iterations.\nWe 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.\nThe\ntrain_discriminator()\nfunction implements this, using a batch size of 256 images where 128 are real and 128 are fake each iteration.\nWe 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.\n# train the discriminator model\ndef train_discriminator(model, dataset, n_iter=100, n_batch=256):\n\thalf_batch = int(n_batch / 2)\n\t# manually enumerate epochs\n\tfor i in range(n_iter):\n\t\t# get randomly selected 'real' samples\n\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\n\t\t# update discriminator on real samples\n\t\t_, real_acc = model.train_on_batch(X_real, y_real)\n\t\t# generate 'fake' examples\n\t\tX_fake, y_fake = generate_fake_samples(half_batch)\n\t\t# update discriminator on fake samples\n\t\t_, fake_acc = model.train_on_batch(X_fake, y_fake)\n\t\t# summarize performance\n\t\tprint('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100))\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n# train the discriminator model\ndef\ntrain_discriminator\n(\nmodel\n,\ndataset\n,\nn_iter\n=\n100\n,\nn_batch\n=\n256\n)\n:\nhalf_batch\n=\nint\n(\nn_batch\n/\n2\n)\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_iter\n)\n:\n# get randomly selected 'real' samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nhalf_batch\n)\n# update discriminator on real samples\n_\n,\nreal_acc\n=\nmodel\n.\ntrain_on_batch\n(\nX_real\n,\ny_real\n)\n# generate 'fake' examples\nX_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\nhalf_batch\n)\n# update discriminator on fake samples\n_\n,\nfake_acc\n=\nmodel\n.\ntrain_on_batch\n(\nX_fake\n,\ny_fake\n)\n# summarize performance\nprint\n(\n'>%d real=%.0f%% fake=%.0f%%'\n%\n(\ni\n+\n1\n,\nreal_acc*\n100\n,\nfake_acc*\n100\n)\n)\nTying 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.\n# example of training the discriminator model on real and random mnist images\nfrom numpy import expand_dims\nfrom numpy import ones\nfrom numpy import zeros\nfrom numpy.random import rand\nfrom numpy.random import randint\nfrom keras.datasets.mnist import load_data\nfrom keras.optimizers import Adam\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.layers import Conv2D\nfrom keras.layers import Flatten\nfrom keras.layers import Dropout\nfrom keras.layers import LeakyReLU\n\n# define the standalone discriminator model\ndef define_discriminator(in_shape=(28,28,1)):\n\tmodel = Sequential()\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Flatten())\n\tmodel.add(Dense(1, activation='sigmoid'))\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\n\treturn model\n\n# load and prepare mnist training images\ndef load_real_samples():\n\t# load mnist dataset\n\t(trainX, _), (_, _) = load_data()\n\t# expand to 3d, e.g. add channels dimension\n\tX = expand_dims(trainX, axis=-1)\n\t# convert from unsigned ints to floats\n\tX = X.astype('float32')\n\t# scale from [0,255] to [0,1]\n\tX = X / 255.0\n\treturn X\n\n# select real samples\ndef generate_real_samples(dataset, n_samples):\n\t# choose random instances\n\tix = randint(0, dataset.shape[0], n_samples)\n\t# retrieve selected images\n\tX = dataset[ix]\n\t# generate 'real' class labels (1)\n\ty = ones((n_samples, 1))\n\treturn X, y\n\n# generate n fake samples with class labels\ndef generate_fake_samples(n_samples):\n\t# generate uniform random numbers in [0,1]\n\tX = rand(28 * 28 * n_samples)\n\t# reshape into a batch of grayscale images\n\tX = X.reshape((n_samples, 28, 28, 1))\n\t# generate 'fake' class labels (0)\n\ty = zeros((n_samples, 1))\n\treturn X, y\n\n# train the discriminator model\ndef train_discriminator(model, dataset, n_iter=100, n_batch=256):\n\thalf_batch = int(n_batch / 2)\n\t# manually enumerate epochs\n\tfor i in range(n_iter):\n\t\t# get randomly selected 'real' samples\n\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\n\t\t# update discriminator on real samples\n\t\t_, real_acc = model.train_on_batch(X_real, y_real)\n\t\t# generate 'fake' examples\n\t\tX_fake, y_fake = generate_fake_samples(half_batch)\n\t\t# update discriminator on fake samples\n\t\t_, fake_acc = model.train_on_batch(X_fake, y_fake)\n\t\t# summarize performance\n\t\tprint('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100))\n\n# define the discriminator model\nmodel = define_discriminator()\n# load image data\ndataset = load_real_samples()\n# fit the model\ntrain_discriminator(model, dataset)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n# example of training the discriminator model on real and random mnist images\nfrom\nnumpy\nimport\nexpand_dims\nfrom\nnumpy\nimport\nones\nfrom\nnumpy\nimport\nzeros\nfrom\nnumpy\n.\nrandom\nimport\nrand\nfrom\nnumpy\n.\nrandom\nimport\nrandint\nfrom\nkeras\n.\ndatasets\n.\nmnist\nimport\nload_data\nfrom\nkeras\n.\noptimizers\nimport\nAdam\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nFlatten\nfrom\nkeras\n.\nlayers\nimport\nDropout\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\n# define the standalone discriminator model\ndef\ndefine_discriminator\n(\nin_shape\n=\n(\n28\n,\n28\n,\n1\n)\n)\n:\nmodel\n=\nSequential\n(\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n,\ninput_shape\n=\nin_shape\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nFlatten\n(\n)\n)\nmodel\n.\nadd\n(\nDense\n(\n1\n,\nactivation\n=\n'sigmoid'\n)\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n,\nmetrics\n=\n[\n'accuracy'\n]\n)\nreturn\nmodel\n# load and prepare mnist training images\ndef\nload_real_samples\n(\n)\n:\n# load mnist dataset\n(\ntrainX\n,\n_\n)\n,\n(\n_\n,\n_\n)\n=\nload_data\n(\n)\n# expand to 3d, e.g. add channels dimension\nX\n=\nexpand_dims\n(\ntrainX\n,\naxis\n=\n-\n1\n)\n# convert from unsigned ints to floats\nX\n=\nX\n.\nastype\n(\n'float32'\n)\n# scale from [0,255] to [0,1]\nX\n=\nX\n/\n255.0\nreturn\nX\n# select real samples\ndef\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n:\n# choose random instances\nix\n=\nrandint\n(\n0\n,\ndataset\n.\nshape\n[\n0\n]\n,\nn_samples\n)\n# retrieve selected images\nX\n=\ndataset\n[\nix\n]\n# generate 'real' class labels (1)\ny\n=\nones\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\n# generate n fake samples with class labels\ndef\ngenerate_fake_samples\n(\nn_samples\n)\n:\n# generate uniform random numbers in [0,1]\nX\n=\nrand\n(\n28\n*\n28\n*\nn_samples\n)\n# reshape into a batch of grayscale images\nX\n=\nX\n.\nreshape\n(\n(\nn_samples\n,\n28\n,\n28\n,\n1\n)\n)\n# generate 'fake' class labels (0)\ny\n=\nzeros\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\n# train the discriminator model\ndef\ntrain_discriminator\n(\nmodel\n,\ndataset\n,\nn_iter\n=\n100\n,\nn_batch\n=\n256\n)\n:\nhalf_batch\n=\nint\n(\nn_batch\n/\n2\n)\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_iter\n)\n:\n# get randomly selected 'real' samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nhalf_batch\n)\n# update discriminator on real samples\n_\n,\nreal_acc\n=\nmodel\n.\ntrain_on_batch\n(\nX_real\n,\ny_real\n)\n# generate 'fake' examples\nX_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\nhalf_batch\n)\n# update discriminator on fake samples\n_\n,\nfake_acc\n=\nmodel\n.\ntrain_on_batch\n(\nX_fake\n,\ny_fake\n)\n# summarize performance\nprint\n(\n'>%d real=%.0f%% fake=%.0f%%'\n%\n(\ni\n+\n1\n,\nreal_acc*\n100\n,\nfake_acc*\n100\n)\n)\n# define the discriminator model\nmodel\n=\ndefine_discriminator\n(\n)\n# load image data\ndataset\n=\nload_real_samples\n(\n)\n# fit the model\ntrain_discriminator\n(\nmodel\n,\ndataset\n)\nRunning the example first defines the model, loads the MNIST dataset, then trains the discriminator model.\nNote\n: Your\nresults may vary\ngiven the stochastic nature of the algorithm or evaluation procedure, or differences in numerical precision. Consider running the example a few times and compare the average outcome.\nIn this case, the discriminator model learns to tell the difference between real and randomly generated MNIST images very quickly, in about 50 batches.\n...\n>96 real=100% fake=100%\n>97 real=100% fake=100%\n>98 real=100% fake=100%\n>99 real=100% fake=100%\n>100 real=100% fake=100%\n1\n2\n3\n4\n5\n6\n...\n>96 real=100% fake=100%\n>97 real=100% fake=100%\n>98 real=100% fake=100%\n>99 real=100% fake=100%\n>100 real=100% fake=100%\nNow that we know how to define and train the discriminator model, we need to look at developing the generator model.\nHow to Define and Use the Generator Model\nThe generator model is responsible for creating new, fake but plausible images of handwritten digits.\nIt does this by taking a point from the latent space as input and outputting a square grayscale image.\nThe latent space is an arbitrarily defined vector space of\nGaussian-distributed values\n, 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, MNIST images, that only the generator knows how to turn into plausible MNIST images.\nInputs\n: Point in latent space, e.g. a 100 element vector of Gaussian random numbers.\nOutputs\n: Two-dimensional square grayscale image of 28×28 pixels with pixel values in [0,1].\nNote: we don’t 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.\nDeveloping a generator model requires that we transform a vector from the latent space with, 100 dimensions to a 2D array with 28×28 or 784 values.\nThere are a number of ways to achieve this but there is one approach that has proven effective at deep convolutional generative adversarial networks. It involves two main elements.\nThe 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 14×14 or 196 nodes, and an image one quarter the size (one eighth the area) would be 7×7 or 49 nodes.\nWe don’t just want one low-resolution version of the image; we want many parallel versions or interpretations of the input. This is a\npattern in convolutional neural networks\nwhere 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.\nTherefore, the first hidden layer, the Dense, needs enough nodes for multiple low-resolution versions of our output image, such as 128.\n# foundation for 7x7 image\nmodel.add(Dense(128 * 7 * 7, input_dim=100))\n1\n2\n# foundation for 7x7 image\nmodel\n.\nadd\n(\nDense\n(\n128\n*\n7\n*\n7\n,\ninput_dim\n=\n100\n)\n)\nThe activations from these nodes can then be reshaped into something image-like to pass into a convolutional layer, such as 128 different 7×7 feature maps.\nmodel.add(Reshape((7, 7, 128)))\n1\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\nThe next major architectural innovation involves upsampling the low-resolution image to a higher resolution version of the image.\nThere are two common ways to do this upsampling process, sometimes called deconvolution.\nOne way is to use an\nUpSampling2D\nlayer (like a reverse pooling layer) followed by a normal\nConv2D\nlayer. The other and perhaps more modern way is to combine these two operations into a single layer, called a\nConv2DTranspose\n. We will use this latter approach for our generator.\nThe\nConv2DTranspose\nlayer can be configured with a stride of (2×2) 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\navoid a checkerboard pattern\nthat can be observed when upsampling.\n# upsample to 14x14\nmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n1\n2\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nThis can be repeated to arrive at our 28×28 output image.\nAgain, we will use the LeakyReLU with a default slope of 0.2, reported as a best practice when training GAN models.\nThe output layer of the model is a\nConv2D\nwith one filter and a kernel size of 7×7 and\n‘same’ padding\n, designed to create a single feature map and preserve its dimensions at 28×28 pixels. A sigmoid activation is used to ensure output values are in the desired range of [0,1].\nThe\ndefine_generator()\nfunction below implements this and defines the generator model.\nNote\n: 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.\n# define the standalone generator model\ndef define_generator(latent_dim):\n\tmodel = Sequential()\n\t# foundation for 7x7 image\n\tn_nodes = 128 * 7 * 7\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Reshape((7, 7, 128)))\n\t# upsample to 14x14\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\t# upsample to 28x28\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))\n\treturn model\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n# define the standalone generator model\ndef\ndefine_generator\n(\nlatent_dim\n)\n:\nmodel\n=\nSequential\n(\n)\n# foundation for 7x7 image\nn_nodes\n=\n128\n*\n7\n*\n7\nmodel\n.\nadd\n(\nDense\n(\nn_nodes\n,\ninput_dim\n=\nlatent_dim\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\n# upsample to 28x28\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n1\n,\n(\n7\n,\n7\n)\n,\nactivation\n=\n'sigmoid'\n,\npadding\n=\n'same'\n)\n)\nreturn\nmodel\nWe can summarize the model to help better understand the input and output shapes.\nThe complete example is listed below.\n# example of defining the generator model\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.layers import Reshape\nfrom keras.layers import Conv2D\nfrom keras.layers import Conv2DTranspose\nfrom keras.layers import LeakyReLU\nfrom keras.utils.vis_utils import plot_model\n\n# define the standalone generator model\ndef define_generator(latent_dim):\n\tmodel = Sequential()\n\t# foundation for 7x7 image\n\tn_nodes = 128 * 7 * 7\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Reshape((7, 7, 128)))\n\t# upsample to 14x14\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\t# upsample to 28x28\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))\n\treturn model\n\n# define the size of the latent space\nlatent_dim = 100\n# define the generator model\nmodel = define_generator(latent_dim)\n# summarize the model\nmodel.summary()\n# plot the model\nplot_model(model, to_file='generator_plot.png', show_shapes=True, show_layer_names=True)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n# example of defining the generator model\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nReshape\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nConv2DTranspose\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\nfrom\nkeras\n.\nutils\n.\nvis_utils\nimport\nplot\n_\nmodel\n# define the standalone generator model\ndef\ndefine_generator\n(\nlatent_dim\n)\n:\nmodel\n=\nSequential\n(\n)\n# foundation for 7x7 image\nn_nodes\n=\n128\n*\n7\n*\n7\nmodel\n.\nadd\n(\nDense\n(\nn_nodes\n,\ninput_dim\n=\nlatent_dim\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\n# upsample to 28x28\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n1\n,\n(\n7\n,\n7\n)\n,\nactivation\n=\n'sigmoid'\n,\npadding\n=\n'same'\n)\n)\nreturn\nmodel\n# define the size of the latent space\nlatent_dim\n=\n100\n# define the generator model\nmodel\n=\ndefine_generator\n(\nlatent_dim\n)\n# summarize the model\nmodel\n.\nsummary\n(\n)\n# plot the model\nplot_model\n(\nmodel\n,\nto_file\n=\n'generator_plot.png'\n,\nshow_shapes\n=\nTrue\n,\nshow_layer_names\n=\nTrue\n)\nRunning the example summarizes the layers of the model and their output shape.\nWe can see that, as designed, the first hidden layer has 6,272 parameters or 128 * 7 * 7, the activations of which are reshaped into 128 7×7 feature maps. The feature maps are then upscaled via the two\nConv2DTranspose\nlayers to the desired output shape of 28×28, until the output layer, where a single activation map is output.\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\ndense_1 (Dense)              (None, 6272)              633472\n_________________________________________________________________\nleaky_re_lu_1 (LeakyReLU)    (None, 6272)              0\n_________________________________________________________________\nreshape_1 (Reshape)          (None, 7, 7, 128)         0\n_________________________________________________________________\nconv2d_transpose_1 (Conv2DTr (None, 14, 14, 128)       262272\n_________________________________________________________________\nleaky_re_lu_2 (LeakyReLU)    (None, 14, 14, 128)       0\n_________________________________________________________________\nconv2d_transpose_2 (Conv2DTr (None, 28, 28, 128)       262272\n_________________________________________________________________\nleaky_re_lu_3 (LeakyReLU)    (None, 28, 28, 128)       0\n_________________________________________________________________\nconv2d_1 (Conv2D)            (None, 28, 28, 1)         6273\n=================================================================\nTotal params: 1,164,289\nTrainable params: 1,164,289\nNon-trainable params: 0\n_________________________________________________________________\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\ndense_1 (Dense)              (None, 6272)              633472\n_________________________________________________________________\nleaky_re_lu_1 (LeakyReLU)    (None, 6272)              0\n_________________________________________________________________\nreshape_1 (Reshape)          (None, 7, 7, 128)         0\n_________________________________________________________________\nconv2d_transpose_1 (Conv2DTr (None, 14, 14, 128)       262272\n_________________________________________________________________\nleaky_re_lu_2 (LeakyReLU)    (None, 14, 14, 128)       0\n_________________________________________________________________\nconv2d_transpose_2 (Conv2DTr (None, 28, 28, 128)       262272\n_________________________________________________________________\nleaky_re_lu_3 (LeakyReLU)    (None, 28, 28, 128)       0\n_________________________________________________________________\nconv2d_1 (Conv2D)            (None, 28, 28, 1)         6273\n=================================================================\nTotal params: 1,164,289\nTrainable params: 1,164,289\nNon-trainable params: 0\n_________________________________________________________________\nA 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 generate an image as output.\nNote\n: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement for the plot_model function and the call to the plot_model function.\nPlot of the Generator Model in the MNIST GAN\nThis model cannot do much at the moment.\nNevertheless, 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.\nThe first step is to generate new points in the latent space. We can achieve this by calling the\nrandn() NumPy function\nfor generating arrays of\nrandom numbers drawn from a standard Gaussian\n.\nThe array of random numbers can then be reshaped into samples, that is n rows with 100 elements per row. The\ngenerate_latent_points()\nfunction below implements this and generates the desired number of points in the latent space that can be used as input to the generator model.\n# generate points in latent space as input for the generator\ndef generate_latent_points(latent_dim, n_samples):\n\t# generate points in the latent space\n\tx_input = randn(latent_dim * n_samples)\n\t# reshape into a batch of inputs for the network\n\tx_input = x_input.reshape(n_samples, latent_dim)\n\treturn x_input\n1\n2\n3\n4\n5\n6\n7\n# generate points in latent space as input for the generator\ndef\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in the latent space\nx_input\n=\nrandn\n(\nlatent_dim *\nn_samples\n)\n# reshape into a batch of inputs for the network\nx_input\n=\nx_input\n.\nreshape\n(\nn_samples\n,\nlatent_dim\n)\nreturn\nx_input\nNext, we can use the generated points as input to the generator model to generate new samples, then plot the samples.\nWe can update the\ngenerate_fake_samples()\nfunction 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\ngenerate_latent_points()\nfunction to generate the required number of points in latent space as input to the model.\nThe updated\ngenerate_fake_samples()\nfunction is listed below and returns both the generated samples and the associated class labels.\n# use the generator to generate n fake examples, with class labels\ndef generate_fake_samples(g_model, latent_dim, n_samples):\n\t# generate points in latent space\n\tx_input = generate_latent_points(latent_dim, n_samples)\n\t# predict outputs\n\tX = g_model.predict(x_input)\n\t# create 'fake' class labels (0)\n\ty = zeros((n_samples, 1))\n\treturn X, y\n1\n2\n3\n4\n5\n6\n7\n8\n9\n# use the generator to generate n fake examples, with class labels\ndef\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in latent space\nx_input\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n# predict outputs\nX\n=\ng_model\n.\npredict\n(\nx_input\n)\n# create 'fake' class labels (0)\ny\n=\nzeros\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\nWe can then plot the generated samples as we did the real MNIST examples in the first section by calling the imshow() function with the reversed grayscale color map.\nThe complete example of generating new MNIST images with the untrained generator model is listed below.\n# example of defining and using the generator model\nfrom numpy import zeros\nfrom numpy.random import randn\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.layers import Reshape\nfrom keras.layers import Conv2D\nfrom keras.layers import Conv2DTranspose\nfrom keras.layers import LeakyReLU\nfrom matplotlib import pyplot\n\n# define the standalone generator model\ndef define_generator(latent_dim):\n\tmodel = Sequential()\n\t# foundation for 7x7 image\n\tn_nodes = 128 * 7 * 7\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Reshape((7, 7, 128)))\n\t# upsample to 14x14\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\t# upsample to 28x28\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))\n\treturn model\n\n# generate points in latent space as input for the generator\ndef generate_latent_points(latent_dim, n_samples):\n\t# generate points in the latent space\n\tx_input = randn(latent_dim * n_samples)\n\t# reshape into a batch of inputs for the network\n\tx_input = x_input.reshape(n_samples, latent_dim)\n\treturn x_input\n\n# use the generator to generate n fake examples, with class labels\ndef generate_fake_samples(g_model, latent_dim, n_samples):\n\t# generate points in latent space\n\tx_input = generate_latent_points(latent_dim, n_samples)\n\t# predict outputs\n\tX = g_model.predict(x_input)\n\t# create 'fake' class labels (0)\n\ty = zeros((n_samples, 1))\n\treturn X, y\n\n# size of the latent space\nlatent_dim = 100\n# define the discriminator model\nmodel = define_generator(latent_dim)\n# generate samples\nn_samples = 25\nX, _ = generate_fake_samples(model, latent_dim, n_samples)\n# plot the generated samples\nfor i in range(n_samples):\n\t# define subplot\n\tpyplot.subplot(5, 5, 1 + i)\n\t# turn off axis labels\n\tpyplot.axis('off')\n\t# plot single image\n\tpyplot.imshow(X[i, :, :, 0], cmap='gray_r')\n# show the figure\npyplot.show()\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n# example of defining and using the generator model\nfrom\nnumpy\nimport\nzeros\nfrom\nnumpy\n.\nrandom\nimport\nrandn\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nReshape\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nConv2DTranspose\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\nfrom\nmatplotlib\nimport\npyplot\n# define the standalone generator model\ndef\ndefine_generator\n(\nlatent_dim\n)\n:\nmodel\n=\nSequential\n(\n)\n# foundation for 7x7 image\nn_nodes\n=\n128\n*\n7\n*\n7\nmodel\n.\nadd\n(\nDense\n(\nn_nodes\n,\ninput_dim\n=\nlatent_dim\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\n# upsample to 28x28\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n1\n,\n(\n7\n,\n7\n)\n,\nactivation\n=\n'sigmoid'\n,\npadding\n=\n'same'\n)\n)\nreturn\nmodel\n# generate points in latent space as input for the generator\ndef\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in the latent space\nx_input\n=\nrandn\n(\nlatent_dim *\nn_samples\n)\n# reshape into a batch of inputs for the network\nx_input\n=\nx_input\n.\nreshape\n(\nn_samples\n,\nlatent_dim\n)\nreturn\nx\n_\ninput\n# use the generator to generate n fake examples, with class labels\ndef\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in latent space\nx_input\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n# predict outputs\nX\n=\ng_model\n.\npredict\n(\nx_input\n)\n# create 'fake' class labels (0)\ny\n=\nzeros\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\n# size of the latent space\nlatent_dim\n=\n100\n# define the discriminator model\nmodel\n=\ndefine_generator\n(\nlatent_dim\n)\n# generate samples\nn_samples\n=\n25\nX\n,\n_\n=\ngenerate_fake_samples\n(\nmodel\n,\nlatent_dim\n,\nn_samples\n)\n# plot the generated samples\nfor\ni\nin\nrange\n(\nn_samples\n)\n:\n# define subplot\npyplot\n.\nsubplot\n(\n5\n,\n5\n,\n1\n+\ni\n)\n# turn off axis labels\npyplot\n.\naxis\n(\n'off'\n)\n# plot single image\npyplot\n.\nimshow\n(\nX\n[\ni\n,\n:\n,\n:\n,\n0\n]\n,\ncmap\n=\n'gray_r'\n)\n# show the figure\npyplot\n.\nshow\n(\n)\nRunning the example generates 25 examples of fake MNIST images and visualizes them on a single plot of 5 by 5 images.\nAs the model is not trained, the generated images are completely random pixel values in [0, 1].\nExample of 25 MNIST Images Output by the Untrained Generator Model\nNow that we know how to define and use the generator model, the next step is to train the model.\nHow to Train the Generator Model\nThe weights in the generator model are updated based on the performance of the discriminator model.\nWhen 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.\nThis defines the zero-sum or adversarial relationship between these two models.\nThere 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.\nSpecifically, 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.\nTo 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.\nOnly 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.\nThe generator model is only concerned with the discriminator’s 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.\nWhen 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).\nWhy would we want to do this?\nWe 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.\nLet’s make this concrete.\nInputs\n: Point in latent space, e.g. a 100 element vector of Gaussian random numbers.\nOutputs\n: Binary classification, likelihood the sample is real (or fake).\nThe\ndefine_gan()\nfunction 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.\nThe GAN model then uses the same binary cross entropy loss function as the discriminator and the efficient Adam version of stochastic gradient descent with the learning rate of 0.0002 and momentum 0.5, recommended when training deep convolutional GANs.\n# define the combined generator and discriminator model, for updating the generator\ndef define_gan(g_model, d_model):\n\t# make weights in the discriminator not trainable\n\td_model.trainable = False\n\t# connect them\n\tmodel = Sequential()\n\t# add generator\n\tmodel.add(g_model)\n\t# add the discriminator\n\tmodel.add(d_model)\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\n\treturn model\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n# define the combined generator and discriminator model, for updating the generator\ndef\ndefine_gan\n(\ng_model\n,\nd_model\n)\n:\n# make weights in the discriminator not trainable\nd_model\n.\ntrainable\n=\nFalse\n# connect them\nmodel\n=\nSequential\n(\n)\n# add generator\nmodel\n.\nadd\n(\ng_model\n)\n# add the discriminator\nmodel\n.\nadd\n(\nd_model\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n)\nreturn\nmodel\nMaking the discriminator not trainable is a clever trick in the Keras API.\nThe 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\ntrain_on_batch()\nfunction.\nThe 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\ntrain_on_batch()\nfunction. This change in the trainable property does not impact the training of standalone discriminator model.\nThis behavior is described in the Keras API documentation here:\nHow can I “freeze” Keras layers?\nThe complete example of creating the discriminator, generator, and composite model is listed below.\n# demonstrate creating the three models in the gan\nfrom keras.optimizers import Adam\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.layers import Reshape\nfrom keras.layers import Flatten\nfrom keras.layers import Conv2D\nfrom keras.layers import Conv2DTranspose\nfrom keras.layers import LeakyReLU\nfrom keras.layers import Dropout\nfrom keras.utils.vis_utils import plot_model\n\n# define the standalone discriminator model\ndef define_discriminator(in_shape=(28,28,1)):\n\tmodel = Sequential()\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Flatten())\n\tmodel.add(Dense(1, activation='sigmoid'))\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\n\treturn model\n\n# define the standalone generator model\ndef define_generator(latent_dim):\n\tmodel = Sequential()\n\t# foundation for 7x7 image\n\tn_nodes = 128 * 7 * 7\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Reshape((7, 7, 128)))\n\t# upsample to 14x14\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\t# upsample to 28x28\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))\n\treturn model\n\n# define the combined generator and discriminator model, for updating the generator\ndef define_gan(g_model, d_model):\n\t# make weights in the discriminator not trainable\n\td_model.trainable = False\n\t# connect them\n\tmodel = Sequential()\n\t# add generator\n\tmodel.add(g_model)\n\t# add the discriminator\n\tmodel.add(d_model)\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\n\treturn model\n\n# size of the latent space\nlatent_dim = 100\n# create the discriminator\nd_model = define_discriminator()\n# create the generator\ng_model = define_generator(latent_dim)\n# create the gan\ngan_model = define_gan(g_model, d_model)\n# summarize gan model\ngan_model.summary()\n# plot gan model\nplot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n# demonstrate creating the three models in the gan\nfrom\nkeras\n.\noptimizers\nimport\nAdam\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nReshape\nfrom\nkeras\n.\nlayers\nimport\nFlatten\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nConv2DTranspose\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\nfrom\nkeras\n.\nlayers\nimport\nDropout\nfrom\nkeras\n.\nutils\n.\nvis_utils\nimport\nplot\n_\nmodel\n# define the standalone discriminator model\ndef\ndefine_discriminator\n(\nin_shape\n=\n(\n28\n,\n28\n,\n1\n)\n)\n:\nmodel\n=\nSequential\n(\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n,\ninput_shape\n=\nin_shape\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nFlatten\n(\n)\n)\nmodel\n.\nadd\n(\nDense\n(\n1\n,\nactivation\n=\n'sigmoid'\n)\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n,\nmetrics\n=\n[\n'accuracy'\n]\n)\nreturn\nmodel\n# define the standalone generator model\ndef\ndefine_generator\n(\nlatent_dim\n)\n:\nmodel\n=\nSequential\n(\n)\n# foundation for 7x7 image\nn_nodes\n=\n128\n*\n7\n*\n7\nmodel\n.\nadd\n(\nDense\n(\nn_nodes\n,\ninput_dim\n=\nlatent_dim\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\n# upsample to 28x28\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n1\n,\n(\n7\n,\n7\n)\n,\nactivation\n=\n'sigmoid'\n,\npadding\n=\n'same'\n)\n)\nreturn\nmodel\n# define the combined generator and discriminator model, for updating the generator\ndef\ndefine_gan\n(\ng_model\n,\nd_model\n)\n:\n# make weights in the discriminator not trainable\nd_model\n.\ntrainable\n=\nFalse\n# connect them\nmodel\n=\nSequential\n(\n)\n# add generator\nmodel\n.\nadd\n(\ng_model\n)\n# add the discriminator\nmodel\n.\nadd\n(\nd_model\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n)\nreturn\nmodel\n# size of the latent space\nlatent_dim\n=\n100\n# create the discriminator\nd_model\n=\ndefine_discriminator\n(\n)\n# create the generator\ng_model\n=\ndefine_generator\n(\nlatent_dim\n)\n# create the gan\ngan_model\n=\ndefine_gan\n(\ng_model\n,\nd_model\n)\n# summarize gan model\ngan_model\n.\nsummary\n(\n)\n# plot gan model\nplot_model\n(\ngan_model\n,\nto_file\n=\n'gan_plot.png'\n,\nshow_shapes\n=\nTrue\n,\nshow_layer_names\n=\nTrue\n)\nRunning the example first creates a summary of the composite model.\nWe can see that the model expects MNIST images as input and predict a single value as output.\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\nsequential_2 (Sequential)    (None, 28, 28, 1)         1164289\n_________________________________________________________________\nsequential_1 (Sequential)    (None, 1)                 40705\n=================================================================\nTotal params: 1,204,994\nTrainable params: 1,164,289\nNon-trainable params: 40,705\n_________________________________________________________________\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #\n=================================================================\nsequential_2 (Sequential)    (None, 28, 28, 1)         1164289\n_________________________________________________________________\nsequential_1 (Sequential)    (None, 1)                 40705\n=================================================================\nTotal params: 1,204,994\nTrainable params: 1,164,289\nNon-trainable params: 40,705\n_________________________________________________________________\nA 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.\nNote\n: creating this plot assumes that the pydot and graphviz libraries are installed. If this is a problem, you can comment out the import statement for the\nplot_model\nfunction and the call to the\nplot_model()\nfunction.\nPlot of the Composite Generator and Discriminator Model in the MNIST GAN\nTraining the composite model involves generating a batch worth of points in the latent space via the\ngenerate_latent_points()\nfunction in the previous section, and\nclass=1\nlabels and calling the\ntrain_on_batch()\nfunction.\nThe\ntrain_gan()\nfunction below demonstrates this, although is pretty simple as only the generator will be updated each epoch, leaving the discriminator with default model weights.\n# train the composite model\ndef train_gan(gan_model, latent_dim, n_epochs=100, n_batch=256):\n\t# manually enumerate epochs\n\tfor i in range(n_epochs):\n\t\t# prepare points in latent space as input for the generator\n\t\tx_gan = generate_latent_points(latent_dim, n_batch)\n\t\t# create inverted labels for the fake samples\n\t\ty_gan = ones((n_batch, 1))\n\t\t# update the generator via the discriminator's error\n\t\tgan_model.train_on_batch(x_gan, y_gan)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n# train the composite model\ndef\ntrain_gan\n(\ngan_model\n,\nlatent_dim\n,\nn_epochs\n=\n100\n,\nn_batch\n=\n256\n)\n:\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_epochs\n)\n:\n# prepare points in latent space as input for the generator\nx_gan\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_batch\n)\n# create inverted labels for the fake samples\ny_gan\n=\nones\n(\n(\nn_batch\n,\n1\n)\n)\n# update the generator via the discriminator's error\ngan_model\n.\ntrain_on_batch\n(\nx_gan\n,\ny_gan\n)\nInstead, what is required is that we first update the discriminator model with real and fake samples, then update the generator via the composite model.\nThis requires combining elements from the\ntrain_discriminator()\nfunction defined in the discriminator section above and the\ntrain_gan()\nfunction defined above. It also requires that we enumerate over both epochs and batches within in an epoch.\nThe complete train function for updating the discriminator model and the generator (via the composite model) is listed below.\nThere are a few things to note in this model training function.\nFirst, 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 60K samples, so with rounding down, there are 234 batches per epoch.\nThe discriminator model is updated once per batch by combining one half a batch of fake and real examples into a single batch via the\nvstack() NumPy function\n. You could update the discriminator with each half batch separately (recommended for more complex datasets) but combining the samples into a single batch will be faster over a long run, especially when training on GPU hardware.\nFinally, 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.\nMonitor the discriminator loss and expect it to hover around 0.5 to 0.8 per batch on this dataset. The generator loss is less critical and may hover between 0.5 and 2 or higher on this dataset. A clever programmer might even attempt to detect the crashing loss of the discriminator, halt, and then restart the training process.\n# train the generator and discriminator\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=256):\n\tbat_per_epo = int(dataset.shape[0] / n_batch)\n\thalf_batch = int(n_batch / 2)\n\t# manually enumerate epochs\n\tfor i in range(n_epochs):\n\t\t# enumerate batches over the training set\n\t\tfor j in range(bat_per_epo):\n\t\t\t# get randomly selected 'real' samples\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\n\t\t\t# generate 'fake' examples\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\n\t\t\t# create training set for the discriminator\n\t\t\tX, y = vstack((X_real, X_fake)), vstack((y_real, y_fake))\n\t\t\t# update discriminator model weights\n\t\t\td_loss, _ = d_model.train_on_batch(X, y)\n\t\t\t# prepare points in latent space as input for the generator\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\n\t\t\t# create inverted labels for the fake samples\n\t\t\ty_gan = ones((n_batch, 1))\n\t\t\t# update the generator via the discriminator's error\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\n\t\t\t# summarize loss on this batch\n\t\t\tprint('>%d, %d/%d, d=%.3f, g=%.3f' % (i+1, j+1, bat_per_epo, d_loss, g_loss))\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n# train the generator and discriminator\ndef\ntrain\n(\ng_model\n,\nd_model\n,\ngan_model\n,\ndataset\n,\nlatent_dim\n,\nn_epochs\n=\n100\n,\nn_batch\n=\n256\n)\n:\nbat_per_epo\n=\nint\n(\ndataset\n.\nshape\n[\n0\n]\n/\nn_batch\n)\nhalf_batch\n=\nint\n(\nn_batch\n/\n2\n)\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_epochs\n)\n:\n# enumerate batches over the training set\nfor\nj\nin\nrange\n(\nbat_per_epo\n)\n:\n# get randomly selected 'real' samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nhalf_batch\n)\n# generate 'fake' examples\nX_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nhalf_batch\n)\n# create training set for the discriminator\nX\n,\ny\n=\nvstack\n(\n(\nX_real\n,\nX_fake\n)\n)\n,\nvstack\n(\n(\ny_real\n,\ny_fake\n)\n)\n# update discriminator model weights\nd_loss\n,\n_\n=\nd_model\n.\ntrain_on_batch\n(\nX\n,\ny\n)\n# prepare points in latent space as input for the generator\nX_gan\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_batch\n)\n# create inverted labels for the fake samples\ny_gan\n=\nones\n(\n(\nn_batch\n,\n1\n)\n)\n# update the generator via the discriminator's error\ng_loss\n=\ngan_model\n.\ntrain_on_batch\n(\nX_gan\n,\ny_gan\n)\n# summarize loss on this batch\nprint\n(\n'>%d, %d/%d, d=%.3f, g=%.3f'\n%\n(\ni\n+\n1\n,\nj\n+\n1\n,\nbat_per_epo\n,\nd_loss\n,\ng_loss\n)\n)\nWe almost have everything we need to develop a GAN for the MNIST handwritten digits dataset.\nOne remaining aspect is the evaluation of the model.\nHow to Evaluate GAN Model Performance\nGenerally, there are no objective ways to evaluate the performance of a GAN model.\nWe cannot calculate this objective error score for generated images. It might be possible in the case of MNIST images because the images are so well constrained, but in general, it is not possible (yet).\nInstead, 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 “\ngood enough\n” images can be generated, the subjective quality of the images may then begin to vary, improve, or even degrade with subsequent updates.\nThere are three ways to handle this complex training situation.\nPeriodically evaluate the classification accuracy of the discriminator on real and fake images.\nPeriodically generate many images and save them to file for subjective review.\nPeriodically save the generator model.\nAll three of these actions can be performed at the same time for a given training epoch, such as every five or 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.\nTraining 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.\nFirst, we can define a function called\nsummarize_performance()\nfunction that will summarize the performance of the discriminator model. It does this by retrieving a sample of real MNIST images, as well as generating the same number of fake MNIST images with the generator model, then evaluating the classification accuracy of the discriminator model on each sample and reporting these scores.\n# evaluate the discriminator, plot generated images, save generator model\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):\n\t# prepare real samples\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\n\t# evaluate discriminator on real examples\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\n\t# prepare fake examples\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\n\t# evaluate discriminator on fake examples\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\n\t# summarize discriminator performance\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n# evaluate the discriminator, plot generated images, save generator model\ndef\nsummarize_performance\n(\nepoch\n,\ng_model\n,\nd_model\n,\ndataset\n,\nlatent_dim\n,\nn_samples\n=\n100\n)\n:\n# prepare real samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n# evaluate discriminator on real examples\n_\n,\nacc_real\n=\nd_model\n.\nevaluate\n(\nX_real\n,\ny_real\n,\nverbose\n=\n0\n)\n# prepare fake examples\nx_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n# evaluate discriminator on fake examples\n_\n,\nacc_fake\n=\nd_model\n.\nevaluate\n(\nx_fake\n,\ny_fake\n,\nverbose\n=\n0\n)\n# summarize discriminator performance\nprint\n(\n'>Accuracy real: %.0f%%, fake: %.0f%%'\n%\n(\nacc_real*\n100\n,\nacc_fake*\n100\n)\n)\nThis function can be called from the\ntrain()\nfunction based on the current epoch number, such as every 10 epochs.\n# train the generator and discriminator\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=256):\n\tbat_per_epo = int(dataset.shape[0] / n_batch)\n\thalf_batch = int(n_batch / 2)\n\t# manually enumerate epochs\n\tfor i in range(n_epochs):\n\t...\n\t# evaluate the model performance, sometimes\n\tif (i+1) % 10 == 0:\n\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n# train the generator and discriminator\ndef\ntrain\n(\ng_model\n,\nd_model\n,\ngan_model\n,\ndataset\n,\nlatent_dim\n,\nn_epochs\n=\n100\n,\nn_batch\n=\n256\n)\n:\nbat_per_epo\n=\nint\n(\ndataset\n.\nshape\n[\n0\n]\n/\nn_batch\n)\nhalf_batch\n=\nint\n(\nn_batch\n/\n2\n)\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_epochs\n)\n:\n.\n.\n.\n# evaluate the model performance, sometimes\nif\n(\ni\n+\n1\n)\n%\n10\n==\n0\n:\nsummarize_performance\n(\ni\n,\ng_model\n,\nd_model\n,\ndataset\n,\nlatent_dim\n)\nNext, we can update the\nsummarize_performance()\nfunction to both save the model and to create and save a plot generated examples.\nThe generator model can be saved by calling the\nsave()\nfunction on the generator model and providing a unique filename based on the training epoch number.\n...\n# save the generator model tile file\nfilename = 'generator_model_%03d.h5' % (epoch + 1)\ng_model.save(filename)\n1\n2\n3\n4\n.\n.\n.\n# save the generator model tile file\nfilename\n=\n'generator_model_%03d.h5'\n%\n(\nepoch\n+\n1\n)\ng_model\n.\nsave\n(\nfilename\n)\nWe can develop a function to create a plot of the generated samples.\nAs we are evaluating the discriminator on 100 generated MNIST images, we can plot all 100 images as a 10 by 10 grid. The\nsave_plot()\nfunction below implements this, again saving the resulting plot with a unique filename based on the epoch number.\n# create and save a plot of generated images (reversed grayscale)\ndef save_plot(examples, epoch, n=10):\n\t# plot images\n\tfor i in range(n * n):\n\t\t# define subplot\n\t\tpyplot.subplot(n, n, 1 + i)\n\t\t# turn off axis\n\t\tpyplot.axis('off')\n\t\t# plot raw pixel data\n\t\tpyplot.imshow(examples[i, :, :, 0], cmap='gray_r')\n\t# save plot to file\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\n\tpyplot.savefig(filename)\n\tpyplot.close()\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n# create and save a plot of generated images (reversed grayscale)\ndef\nsave_plot\n(\nexamples\n,\nepoch\n,\nn\n=\n10\n)\n:\n# plot images\nfor\ni\nin\nrange\n(\nn *\nn\n)\n:\n# define subplot\npyplot\n.\nsubplot\n(\nn\n,\nn\n,\n1\n+\ni\n)\n# turn off axis\npyplot\n.\naxis\n(\n'off'\n)\n# plot raw pixel data\npyplot\n.\nimshow\n(\nexamples\n[\ni\n,\n:\n,\n:\n,\n0\n]\n,\ncmap\n=\n'gray_r'\n)\n# save plot to file\nfilename\n=\n'generated_plot_e%03d.png'\n%\n(\nepoch\n+\n1\n)\npyplot\n.\nsavefig\n(\nfilename\n)\npyplot\n.\nclose\n(\n)\nThe updated\nsummarize_performance()\nfunction with these additions is listed below.\n# evaluate the discriminator, plot generated images, save generator model\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):\n\t# prepare real samples\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\n\t# evaluate discriminator on real examples\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\n\t# prepare fake examples\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\n\t# evaluate discriminator on fake examples\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\n\t# summarize discriminator performance\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\n\t# save plot\n\tsave_plot(x_fake, epoch)\n\t# save the generator model tile file\n\tfilename = 'generator_model_%03d.h5' % (epoch + 1)\n\tg_model.save(filename)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n# evaluate the discriminator, plot generated images, save generator model\ndef\nsummarize_performance\n(\nepoch\n,\ng_model\n,\nd_model\n,\ndataset\n,\nlatent_dim\n,\nn_samples\n=\n100\n)\n:\n# prepare real samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n# evaluate discriminator on real examples\n_\n,\nacc_real\n=\nd_model\n.\nevaluate\n(\nX_real\n,\ny_real\n,\nverbose\n=\n0\n)\n# prepare fake examples\nx_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n# evaluate discriminator on fake examples\n_\n,\nacc_fake\n=\nd_model\n.\nevaluate\n(\nx_fake\n,\ny_fake\n,\nverbose\n=\n0\n)\n# summarize discriminator performance\nprint\n(\n'>Accuracy real: %.0f%%, fake: %.0f%%'\n%\n(\nacc_real*\n100\n,\nacc_fake*\n100\n)\n)\n# save plot\nsave_plot\n(\nx_fake\n,\nepoch\n)\n# save the generator model tile file\nfilename\n=\n'generator_model_%03d.h5'\n%\n(\nepoch\n+\n1\n)\ng_model\n.\nsave\n(\nfilename\n)\nComplete Example of GAN for MNIST\nWe now have everything we need to train and evaluate a GAN on the MNIST handwritten digit dataset.\nThe complete example is listed below.\nNote: 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.\nFor help on setting up an AWS EC2 instance to run this code, see the tutorial:\nHow to Setup Amazon AWS EC2 GPUs to Train Keras Deep Learning Models (step-by-step)\n# example of training a gan on mnist\nfrom numpy import expand_dims\nfrom numpy import zeros\nfrom numpy import ones\nfrom numpy import vstack\nfrom numpy.random import randn\nfrom numpy.random import randint\nfrom keras.datasets.mnist import load_data\nfrom keras.optimizers import Adam\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.layers import Reshape\nfrom keras.layers import Flatten\nfrom keras.layers import Conv2D\nfrom keras.layers import Conv2DTranspose\nfrom keras.layers import LeakyReLU\nfrom keras.layers import Dropout\nfrom matplotlib import pyplot\n\n# define the standalone discriminator model\ndef define_discriminator(in_shape=(28,28,1)):\n\tmodel = Sequential()\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Dropout(0.4))\n\tmodel.add(Flatten())\n\tmodel.add(Dense(1, activation='sigmoid'))\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\n\treturn model\n\n# define the standalone generator model\ndef define_generator(latent_dim):\n\tmodel = Sequential()\n\t# foundation for 7x7 image\n\tn_nodes = 128 * 7 * 7\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Reshape((7, 7, 128)))\n\t# upsample to 14x14\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\t# upsample to 28x28\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\n\tmodel.add(LeakyReLU(alpha=0.2))\n\tmodel.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))\n\treturn model\n\n# define the combined generator and discriminator model, for updating the generator\ndef define_gan(g_model, d_model):\n\t# make weights in the discriminator not trainable\n\td_model.trainable = False\n\t# connect them\n\tmodel = Sequential()\n\t# add generator\n\tmodel.add(g_model)\n\t# add the discriminator\n\tmodel.add(d_model)\n\t# compile model\n\topt = Adam(lr=0.0002, beta_1=0.5)\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\n\treturn model\n\n# load and prepare mnist training images\ndef load_real_samples():\n\t# load mnist dataset\n\t(trainX, _), (_, _) = load_data()\n\t# expand to 3d, e.g. add channels dimension\n\tX = expand_dims(trainX, axis=-1)\n\t# convert from unsigned ints to floats\n\tX = X.astype('float32')\n\t# scale from [0,255] to [0,1]\n\tX = X / 255.0\n\treturn X\n\n# select real samples\ndef generate_real_samples(dataset, n_samples):\n\t# choose random instances\n\tix = randint(0, dataset.shape[0], n_samples)\n\t# retrieve selected images\n\tX = dataset[ix]\n\t# generate 'real' class labels (1)\n\ty = ones((n_samples, 1))\n\treturn X, y\n\n# generate points in latent space as input for the generator\ndef generate_latent_points(latent_dim, n_samples):\n\t# generate points in the latent space\n\tx_input = randn(latent_dim * n_samples)\n\t# reshape into a batch of inputs for the network\n\tx_input = x_input.reshape(n_samples, latent_dim)\n\treturn x_input\n\n# use the generator to generate n fake examples, with class labels\ndef generate_fake_samples(g_model, latent_dim, n_samples):\n\t# generate points in latent space\n\tx_input = generate_latent_points(latent_dim, n_samples)\n\t# predict outputs\n\tX = g_model.predict(x_input)\n\t# create 'fake' class labels (0)\n\ty = zeros((n_samples, 1))\n\treturn X, y\n\n# create and save a plot of generated images (reversed grayscale)\ndef save_plot(examples, epoch, n=10):\n\t# plot images\n\tfor i in range(n * n):\n\t\t# define subplot\n\t\tpyplot.subplot(n, n, 1 + i)\n\t\t# turn off axis\n\t\tpyplot.axis('off')\n\t\t# plot raw pixel data\n\t\tpyplot.imshow(examples[i, :, :, 0], cmap='gray_r')\n\t# save plot to file\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\n\tpyplot.savefig(filename)\n\tpyplot.close()\n\n# evaluate the discriminator, plot generated images, save generator model\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):\n\t# prepare real samples\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\n\t# evaluate discriminator on real examples\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\n\t# prepare fake examples\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\n\t# evaluate discriminator on fake examples\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\n\t# summarize discriminator performance\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\n\t# save plot\n\tsave_plot(x_fake, epoch)\n\t# save the generator model tile file\n\tfilename = 'generator_model_%03d.h5' % (epoch + 1)\n\tg_model.save(filename)\n\n# train the generator and discriminator\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=256):\n\tbat_per_epo = int(dataset.shape[0] / n_batch)\n\thalf_batch = int(n_batch / 2)\n\t# manually enumerate epochs\n\tfor i in range(n_epochs):\n\t\t# enumerate batches over the training set\n\t\tfor j in range(bat_per_epo):\n\t\t\t# get randomly selected 'real' samples\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\n\t\t\t# generate 'fake' examples\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\n\t\t\t# create training set for the discriminator\n\t\t\tX, y = vstack((X_real, X_fake)), vstack((y_real, y_fake))\n\t\t\t# update discriminator model weights\n\t\t\td_loss, _ = d_model.train_on_batch(X, y)\n\t\t\t# prepare points in latent space as input for the generator\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\n\t\t\t# create inverted labels for the fake samples\n\t\t\ty_gan = ones((n_batch, 1))\n\t\t\t# update the generator via the discriminator's error\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\n\t\t\t# summarize loss on this batch\n\t\t\tprint('>%d, %d/%d, d=%.3f, g=%.3f' % (i+1, j+1, bat_per_epo, d_loss, g_loss))\n\t\t# evaluate the model performance, sometimes\n\t\tif (i+1) % 10 == 0:\n\t\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)\n\n# size of the latent space\nlatent_dim = 100\n# create the discriminator\nd_model = define_discriminator()\n# create the generator\ng_model = define_generator(latent_dim)\n# create the gan\ngan_model = define_gan(g_model, d_model)\n# load image data\ndataset = load_real_samples()\n# train model\ntrain(g_model, d_model, gan_model, dataset, latent_dim)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n# example of training a gan on mnist\nfrom\nnumpy\nimport\nexpand_dims\nfrom\nnumpy\nimport\nzeros\nfrom\nnumpy\nimport\nones\nfrom\nnumpy\nimport\nvstack\nfrom\nnumpy\n.\nrandom\nimport\nrandn\nfrom\nnumpy\n.\nrandom\nimport\nrandint\nfrom\nkeras\n.\ndatasets\n.\nmnist\nimport\nload_data\nfrom\nkeras\n.\noptimizers\nimport\nAdam\nfrom\nkeras\n.\nmodels\nimport\nSequential\nfrom\nkeras\n.\nlayers\nimport\nDense\nfrom\nkeras\n.\nlayers\nimport\nReshape\nfrom\nkeras\n.\nlayers\nimport\nFlatten\nfrom\nkeras\n.\nlayers\nimport\nConv2D\nfrom\nkeras\n.\nlayers\nimport\nConv2DTranspose\nfrom\nkeras\n.\nlayers\nimport\nLeakyReLU\nfrom\nkeras\n.\nlayers\nimport\nDropout\nfrom\nmatplotlib\nimport\npyplot\n# define the standalone discriminator model\ndef\ndefine_discriminator\n(\nin_shape\n=\n(\n28\n,\n28\n,\n1\n)\n)\n:\nmodel\n=\nSequential\n(\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n,\ninput_shape\n=\nin_shape\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n64\n,\n(\n3\n,\n3\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nDropout\n(\n0.4\n)\n)\nmodel\n.\nadd\n(\nFlatten\n(\n)\n)\nmodel\n.\nadd\n(\nDense\n(\n1\n,\nactivation\n=\n'sigmoid'\n)\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n,\nmetrics\n=\n[\n'accuracy'\n]\n)\nreturn\nmodel\n# define the standalone generator model\ndef\ndefine_generator\n(\nlatent_dim\n)\n:\nmodel\n=\nSequential\n(\n)\n# foundation for 7x7 image\nn_nodes\n=\n128\n*\n7\n*\n7\nmodel\n.\nadd\n(\nDense\n(\nn_nodes\n,\ninput_dim\n=\nlatent_dim\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nReshape\n(\n(\n7\n,\n7\n,\n128\n)\n)\n)\n# upsample to 14x14\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\n# upsample to 28x28\nmodel\n.\nadd\n(\nConv2DTranspose\n(\n128\n,\n(\n4\n,\n4\n)\n,\nstrides\n=\n(\n2\n,\n2\n)\n,\npadding\n=\n'same'\n)\n)\nmodel\n.\nadd\n(\nLeakyReLU\n(\nalpha\n=\n0.2\n)\n)\nmodel\n.\nadd\n(\nConv2D\n(\n1\n,\n(\n7\n,\n7\n)\n,\nactivation\n=\n'sigmoid'\n,\npadding\n=\n'same'\n)\n)\nreturn\nmodel\n# define the combined generator and discriminator model, for updating the generator\ndef\ndefine_gan\n(\ng_model\n,\nd_model\n)\n:\n# make weights in the discriminator not trainable\nd_model\n.\ntrainable\n=\nFalse\n# connect them\nmodel\n=\nSequential\n(\n)\n# add generator\nmodel\n.\nadd\n(\ng_model\n)\n# add the discriminator\nmodel\n.\nadd\n(\nd_model\n)\n# compile model\nopt\n=\nAdam\n(\nlr\n=\n0.0002\n,\nbeta_1\n=\n0.5\n)\nmodel\n.\ncompile\n(\nloss\n=\n'binary_crossentropy'\n,\noptimizer\n=\nopt\n)\nreturn\nmodel\n# load and prepare mnist training images\ndef\nload_real_samples\n(\n)\n:\n# load mnist dataset\n(\ntrainX\n,\n_\n)\n,\n(\n_\n,\n_\n)\n=\nload_data\n(\n)\n# expand to 3d, e.g. add channels dimension\nX\n=\nexpand_dims\n(\ntrainX\n,\naxis\n=\n-\n1\n)\n# convert from unsigned ints to floats\nX\n=\nX\n.\nastype\n(\n'float32'\n)\n# scale from [0,255] to [0,1]\nX\n=\nX\n/\n255.0\nreturn\nX\n# select real samples\ndef\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n:\n# choose random instances\nix\n=\nrandint\n(\n0\n,\ndataset\n.\nshape\n[\n0\n]\n,\nn_samples\n)\n# retrieve selected images\nX\n=\ndataset\n[\nix\n]\n# generate 'real' class labels (1)\ny\n=\nones\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\n# generate points in latent space as input for the generator\ndef\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in the latent space\nx_input\n=\nrandn\n(\nlatent_dim *\nn_samples\n)\n# reshape into a batch of inputs for the network\nx_input\n=\nx_input\n.\nreshape\n(\nn_samples\n,\nlatent_dim\n)\nreturn\nx\n_\ninput\n# use the generator to generate n fake examples, with class labels\ndef\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in latent space\nx_input\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n# predict outputs\nX\n=\ng_model\n.\npredict\n(\nx_input\n)\n# create 'fake' class labels (0)\ny\n=\nzeros\n(\n(\nn_samples\n,\n1\n)\n)\nreturn\nX\n,\ny\n# create and save a plot of generated images (reversed grayscale)\ndef\nsave_plot\n(\nexamples\n,\nepoch\n,\nn\n=\n10\n)\n:\n# plot images\nfor\ni\nin\nrange\n(\nn *\nn\n)\n:\n# define subplot\npyplot\n.\nsubplot\n(\nn\n,\nn\n,\n1\n+\ni\n)\n# turn off axis\npyplot\n.\naxis\n(\n'off'\n)\n# plot raw pixel data\npyplot\n.\nimshow\n(\nexamples\n[\ni\n,\n:\n,\n:\n,\n0\n]\n,\ncmap\n=\n'gray_r'\n)\n# save plot to file\nfilename\n=\n'generated_plot_e%03d.png'\n%\n(\nepoch\n+\n1\n)\npyplot\n.\nsavefig\n(\nfilename\n)\npyplot\n.\nclose\n(\n)\n# evaluate the discriminator, plot generated images, save generator model\ndef\nsummarize_performance\n(\nepoch\n,\ng_model\n,\nd_model\n,\ndataset\n,\nlatent_dim\n,\nn_samples\n=\n100\n)\n:\n# prepare real samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nn_samples\n)\n# evaluate discriminator on real examples\n_\n,\nacc_real\n=\nd_model\n.\nevaluate\n(\nX_real\n,\ny_real\n,\nverbose\n=\n0\n)\n# prepare fake examples\nx_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nn_samples\n)\n# evaluate discriminator on fake examples\n_\n,\nacc_fake\n=\nd_model\n.\nevaluate\n(\nx_fake\n,\ny_fake\n,\nverbose\n=\n0\n)\n# summarize discriminator performance\nprint\n(\n'>Accuracy real: %.0f%%, fake: %.0f%%'\n%\n(\nacc_real*\n100\n,\nacc_fake*\n100\n)\n)\n# save plot\nsave_plot\n(\nx_fake\n,\nepoch\n)\n# save the generator model tile file\nfilename\n=\n'generator_model_%03d.h5'\n%\n(\nepoch\n+\n1\n)\ng_model\n.\nsave\n(\nfilename\n)\n# train the generator and discriminator\ndef\ntrain\n(\ng_model\n,\nd_model\n,\ngan_model\n,\ndataset\n,\nlatent_dim\n,\nn_epochs\n=\n100\n,\nn_batch\n=\n256\n)\n:\nbat_per_epo\n=\nint\n(\ndataset\n.\nshape\n[\n0\n]\n/\nn_batch\n)\nhalf_batch\n=\nint\n(\nn_batch\n/\n2\n)\n# manually enumerate epochs\nfor\ni\nin\nrange\n(\nn_epochs\n)\n:\n# enumerate batches over the training set\nfor\nj\nin\nrange\n(\nbat_per_epo\n)\n:\n# get randomly selected 'real' samples\nX_real\n,\ny_real\n=\ngenerate_real_samples\n(\ndataset\n,\nhalf_batch\n)\n# generate 'fake' examples\nX_fake\n,\ny_fake\n=\ngenerate_fake_samples\n(\ng_model\n,\nlatent_dim\n,\nhalf_batch\n)\n# create training set for the discriminator\nX\n,\ny\n=\nvstack\n(\n(\nX_real\n,\nX_fake\n)\n)\n,\nvstack\n(\n(\ny_real\n,\ny_fake\n)\n)\n# update discriminator model weights\nd_loss\n,\n_\n=\nd_model\n.\ntrain_on_batch\n(\nX\n,\ny\n)\n# prepare points in latent space as input for the generator\nX_gan\n=\ngenerate_latent_points\n(\nlatent_dim\n,\nn_batch\n)\n# create inverted labels for the fake samples\ny_gan\n=\nones\n(\n(\nn_batch\n,\n1\n)\n)\n# update the generator via the discriminator's error\ng_loss\n=\ngan_model\n.\ntrain_on_batch\n(\nX_gan\n,\ny_gan\n)\n# summarize loss on this batch\nprint\n(\n'>%d, %d/%d, d=%.3f, g=%.3f'\n%\n(\ni\n+\n1\n,\nj\n+\n1\n,\nbat_per_epo\n,\nd_loss\n,\ng_loss\n)\n)\n# evaluate the model performance, sometimes\nif\n(\ni\n+\n1\n)\n%\n10\n==\n0\n:\nsummarize_performance\n(\ni\n,\ng_model\n,\nd_model\n,\ndataset\n,\nlatent_dim\n)\n# size of the latent space\nlatent_dim\n=\n100\n# create the discriminator\nd_model\n=\ndefine_discriminator\n(\n)\n# create the generator\ng_model\n=\ndefine_generator\n(\nlatent_dim\n)\n# create the gan\ngan_model\n=\ndefine_gan\n(\ng_model\n,\nd_model\n)\n# load image data\ndataset\n=\nload_real_samples\n(\n)\n# train model\ntrain\n(\ng_model\n,\nd_model\n,\ngan_model\n,\ndataset\n,\nlatent_dim\n)\nThe chosen configuration results in the stable training of both the generative and discriminative model.\nThe model performance is reported every batch, including the loss of both the discriminative (\nd\n) and generative (\ng\n) models.\nNote\n: Your\nresults may vary\ngiven the stochastic nature of the algorithm or evaluation procedure, or differences in numerical precision. Consider running the example a few times and compare the average outcome.\nIn this case, the loss remains stable over the course of training.\n>1, 1/234, d=0.711, g=0.678\n>1, 2/234, d=0.703, g=0.698\n>1, 3/234, d=0.694, g=0.717\n>1, 4/234, d=0.684, g=0.740\n>1, 5/234, d=0.679, g=0.757\n>1, 6/234, d=0.668, g=0.777\n...\n>100, 230/234, d=0.690, g=0.710\n>100, 231/234, d=0.692, g=0.705\n>100, 232/234, d=0.698, g=0.701\n>100, 233/234, d=0.697, g=0.688\n>100, 234/234, d=0.693, g=0.698\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n>1, 1/234, d=0.711, g=0.678\n>1, 2/234, d=0.703, g=0.698\n>1, 3/234, d=0.694, g=0.717\n>1, 4/234, d=0.684, g=0.740\n>1, 5/234, d=0.679, g=0.757\n>1, 6/234, d=0.668, g=0.777\n...\n>100, 230/234, d=0.690, g=0.710\n>100, 231/234, d=0.692, g=0.705\n>100, 232/234, d=0.698, g=0.701\n>100, 233/234, d=0.697, g=0.688\n>100, 234/234, d=0.693, g=0.698\nThe generator is evaluated every 20 epochs, resulting in 10 evaluations, 10 plots of generated images, and 10 saved models.\nIn this case, we can see that the accuracy fluctuates over training. When viewing the discriminator model’s 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.\nIt is crude and possibly unreliable metric of GAN performance, along with loss.\n>Accuracy real: 51%, fake: 78%\n>Accuracy real: 30%, fake: 95%\n>Accuracy real: 75%, fake: 59%\n>Accuracy real: 98%, fake: 11%\n>Accuracy real: 27%, fake: 92%\n>Accuracy real: 21%, fake: 92%\n>Accuracy real: 29%, fake: 96%\n>Accuracy real: 4%, fake: 99%\n>Accuracy real: 18%, fake: 97%\n>Accuracy real: 28%, fake: 89%\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n>Accuracy real: 51%, fake: 78%\n>Accuracy real: 30%, fake: 95%\n>Accuracy real: 75%, fake: 59%\n>Accuracy real: 98%, fake: 11%\n>Accuracy real: 27%, fake: 92%\n>Accuracy real: 21%, fake: 92%\n>Accuracy real: 29%, fake: 96%\n>Accuracy real: 4%, fake: 99%\n>Accuracy real: 18%, fake: 97%\n>Accuracy real: 28%, fake: 89%\nMore training, beyond some point, does not mean better quality generated images.\nIn this case, the results after 10 epochs are low quality, although we can see that the generator has learned to generate centered figures in white on a back background (recall we have inverted the grayscale in the plot).\nPlot of 100 GAN Generated MNIST Figures After 10 Epochs\nAfter 20 or 30 more epochs, the model begins to generate very plausible MNIST figures, suggesting that 100 epochs are probably not required for the chosen model configurations.\nPlot of 100 GAN Generated MNIST Figures After 40 Epochs\nThe generated images after 100 epochs are not greatly different, but I believe I can detect less blocky-ness in the curves.\nPlot of 100 GAN Generated MNIST Figures After 100 Epochs\nHow to Use the Final Generator Model to Generate Images\nOnce a final generator model is selected, it can be used in a standalone manner for your application.\nThis 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.\nThe complete example of loading the saved model and generating images is listed below. In this case, we will use the model saved after 100 training epochs, but the model saved after 40 or 50 epochs would work just as well.\n# example of loading the generator model and generating images\nfrom keras.models import load_model\nfrom numpy.random import randn\nfrom matplotlib import pyplot\n\n# generate points in latent space as input for the generator\ndef generate_latent_points(latent_dim, n_samples):\n\t# generate points in the latent space\n\tx_input = randn(latent_dim * n_samples)\n\t# reshape into a batch of inputs for the network\n\tx_input = x_input.reshape(n_samples, latent_dim)\n\treturn x_input\n\n# create and save a plot of generated images (reversed grayscale)\ndef save_plot(examples, n):\n\t# plot images\n\tfor i in range(n * n):\n\t\t# define subplot\n\t\tpyplot.subplot(n, n, 1 + i)\n\t\t# turn off axis\n\t\tpyplot.axis('off')\n\t\t# plot raw pixel data\n\t\tpyplot.imshow(examples[i, :, :, 0], cmap='gray_r')\n\tpyplot.show()\n\n# load model\nmodel = load_model('generator_model_100.h5')\n# generate images\nlatent_points = generate_latent_points(100, 25)\n# generate images\nX = model.predict(latent_points)\n# plot the result\nsave_plot(X, 5)\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n# example of loading the generator model and generating images\nfrom\nkeras\n.\nmodels\nimport\nload_model\nfrom\nnumpy\n.\nrandom\nimport\nrandn\nfrom\nmatplotlib\nimport\npyplot\n# generate points in latent space as input for the generator\ndef\ngenerate_latent_points\n(\nlatent_dim\n,\nn_samples\n)\n:\n# generate points in the latent space\nx_input\n=\nrandn\n(\nlatent_dim *\nn_samples\n)\n# reshape into a batch of inputs for the network\nx_input\n=\nx_input\n.\nreshape\n(\nn_samples\n,\nlatent_dim\n)\nreturn\nx\n_\ninput\n# create and save a plot of generated images (reversed grayscale)\ndef\nsave_plot\n(\nexamples\n,\nn\n)\n:\n# plot images\nfor\ni\nin\nrange\n(\nn *\nn\n)\n:\n# define subplot\npyplot\n.\nsubplot\n(\nn\n,\nn\n,\n1\n+\ni\n)\n# turn off axis\npyplot\n.\naxis\n(\n'off'\n)\n# plot raw pixel data\npyplot\n.\nimshow\n(\nexamples\n[\ni\n,\n:\n,\n:\n,\n0\n]\n,\ncmap\n=\n'gray_r'\n)\npyplot\n.\nshow\n(\n)\n# load model\nmodel\n=\nload_model\n(\n'generator_model_100.h5'\n)\n# generate images\nlatent_points\n=\ngenerate_latent_points\n(\n100\n,\n25\n)\n# generate images\nX\n=\nmodel\n.\npredict\n(\nlatent_points\n)\n# plot the result\nsave_plot\n(\nX\n,\n5\n)\nRunning the example first loads the model, samples 25 random points in the latent space, generates 25 images, then plots the results as a single image.\nWe can see that most of the images are plausible, or plausible pieces of handwritten digits.\nExample of 25 GAN Generated MNIST Handwritten Images\nThe latent space now defines a compressed representation of MNIST handwritten digits.\nYou can experiment with generating different points in this space and see what types of numbers they generate.\nThe example below generates a single handwritten digit using a vector of all 0.0 values.\n# example of generating an image for a specific point in the latent space\nfrom keras.models import load_model\nfrom numpy import asarray\nfrom matplotlib import pyplot\n# load model\nmodel = load_model('generator_model_100.h5')\n# all 0s\nvector = asarray([[0.0 for _ in range(100)]])\n# generate image\nX = model.predict(vector)\n# plot the result\npyplot.imshow(X[0, :, :, 0], cmap='gray_r')\npyplot.show()\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n# example of generating an image for a specific point in the latent space\nfrom\nkeras\n.\nmodels\nimport\nload_model\nfrom\nnumpy\nimport\nasarray\nfrom\nmatplotlib\nimport\npyplot\n# load model\nmodel\n=\nload_model\n(\n'generator_model_100.h5'\n)\n# all 0s\nvector\n=\nasarray\n(\n[\n[\n0.0\nfor\n_\nin\nrange\n(\n100\n)\n]\n]\n)\n# generate image\nX\n=\nmodel\n.\npredict\n(\nvector\n)\n# plot the result\npyplot\n.\nimshow\n(\nX\n[\n0\n,\n:\n,\n:\n,\n0\n]\n,\ncmap\n=\n'gray_r'\n)\npyplot\n.\nshow\n(\n)\nNote\n: Your\nresults may vary\ngiven the stochastic nature of the algorithm or evaluation procedure, or differences in numerical precision. Consider running the example a few times and compare the average outcome.\nIn this case, a vector of all zeros results in a handwritten 9 or maybe an 8. You can then try navigating the space and see if you can generate a range of similar, but different handwritten digits.\nExample of a GAN Generated MNIST Handwritten Digit for a Vector of Zeros\nExtensions\nThis section lists some ideas for extending the tutorial that you may wish to explore.\nTanH Activation and Scaling\n. Update the example to use the tanh activation function in the generator and scale all pixel values to the range [-1, 1].\nChange Latent Space\n. Update the example to use a larger or smaller latent space and compare the quality of the results and speed of training.\nBatch Normalization\n. Update the discriminator and/or the generator to make use of batch normalization, recommended for DCGAN models.\nLabel Smoothing\n. 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 review the effects on image quality and speed of training.\nModel Configuration\n. Update the model configuration to use deeper or more shallow discriminator and/or generator models, perhaps experiment with the UpSampling2D layers in the generator.\nIf you explore any of these extensions, I’d love to know.\nPost your findings in the comments below.\nFurther Reading\nThis section provides more resources on the topic if you are looking to go deeper.\nBooks\nChapter 20. Deep Generative Models,\nDeep Learning\n, 2016.\nChapter 8. Generative Deep Learning,\nDeep Learning with Python\n, 2017.\nPapers\nGenerative Adversarial Networks\n, 2014.\nTutorial: Generative Adversarial Networks, NIPS\n, 2016.\nUnsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks\n, 2015\nAPI\nKeras Datasets API\nKeras Sequential Model API\nKeras Convolutional Layers API\nHow can I “freeze” Keras layers?\nMatplotLib API\nNumPy Random sampling (numpy.random) API\nNumPy Array manipulation routines\nPosts\nHow to Generate Random Numbers in Python\nArticles\nMNIST Dataset, Wikipedia\n.\nGAN-in-keras-on-mnist Project, GitHub\n.\nKeras-GAN Project, GitHub\n.\nKeras-MNIST-GAN Project, GitHub\n.\nSummary\nIn this tutorial, you discovered how to develop a generative adversarial network with deep convolutional networks for generating handwritten digits.\nSpecifically, you learned:\nHow to define and train the standalone discriminator model for learning the difference between real and fake images.\nHow to define the standalone generator model and train the composite generator and discriminator model.\nHow to evaluate the performance of the GAN and use the final standalone generator model to generate new images.\nDo you have any questions?\nAsk your questions in the comments below and I will do my best to answer.\nDevelop Generative Adversarial Networks Today!\nDevelop Your GAN Models in Minutes\n...with just a few lines of python code\nDiscover how in my new Ebook:\nGenerative Adversarial Networks with Python\nIt provides\nself-study tutorials\nand\nend-to-end projects\non:\nDCGAN\n,\nconditional GANs\n,\nimage translation\n,\nPix2Pix\n,\nCycleGAN\nand much more...\nFinally Bring GAN Models to your Vision Projects\nSkip the Academics. Just Results.\nSee What's Inside\nShare\nPost\nShare\nMore On This Topic\nHow to Develop a CNN for MNIST Handwritten Digit…\nHow to Develop an Auxiliary Classifier GAN (AC-GAN)…\nHow to Explore the GAN Latent Space When Generating Faces\nHow to Develop a Pix2Pix GAN for Image-to-Image Translation\nHow to Develop an Information Maximizing GAN…\nHow to Develop a Conditional GAN (cGAN) From Scratch\nAbout Jason Brownlee\nJason Brownlee, PhD is a machine learning specialist who teaches developers how to get results with modern machine learning methods via hands-on tutorials.\nView all posts by Jason Brownlee\n→",
  "metadata": {
    "title": "How to Develop a GAN for Generating MNIST Handwritten Digits - MachineLearningMastery.com",
    "description": null,
    "keywords": null,
    "author": "Jason Brownlee",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "How to Develop a GAN for Generating MNIST Handwritten Digits - MachineLearningMastery.com",
    "og:description": "Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images. 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…",
    "og:url": "https://machinelearningmastery.com/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/",
    "og:site_name": "MachineLearningMastery.com",
    "og:image": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-100-Epochs.png",
    "og:image:width": "640",
    "og:image:height": "480",
    "og:image:type": "image/png",
    "twitter:label1": "Written by",
    "twitter:data1": "Jason Brownlee",
    "twitter:label2": "Est. reading time",
    "twitter:data2": "48 minutes",
    "twitter:card": "summary_large_image",
    "twitter:description": "Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images. 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…",
    "twitter:title": "How to Develop a GAN for Generating MNIST Handwritten Digits - MachineLearningMastery.com",
    "article:publisher": "https://www.facebook.com/MachineLearningMastery/",
    "article:author": "https://www.facebook.com/MachineLearningMastery/",
    "article:published_time": "2019-06-27T19:00:18+00:00",
    "article:modified_time": "2020-09-01T00:34:21+00:00"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [],
  "images": [
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/06/How-to-Develop-a-Generative-Adversarial-Network-for-an-MNIST-Handwritten-Digits-From-Scratch-in-Keras.jpg",
      "bytes": 46369,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/000.jpg",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/06/How-to-Develop-a-Generative-Adversarial-Network-for-an-MNIST-Handwritten-Digits-From-Scratch-in-Keras.jpg",
      "alt": "How to Develop a Generative Adversarial Network for an MNIST Handwritten Digits From Scratch in Keras",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-First-25-Handwritten-Digits-From-the-MNIST-Dataset-1024x768.png",
      "bytes": 17397,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/002.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-First-25-Handwritten-Digits-From-the-MNIST-Dataset-1024x768.png",
      "alt": "Plot of the First 25 Handwritten Digits From the MNIST Dataset.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Discriminator-Model-in-the-MNIST-GAN.png",
      "bytes": 12889,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/006.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Discriminator-Model-in-the-MNIST-GAN.png",
      "alt": "Plot of the Discriminator Model in the MNIST GAN",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Generator-Model-in-the-MNIST-GAN.png",
      "bytes": 15410,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/008.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Generator-Model-in-the-MNIST-GAN.png",
      "alt": "Plot of the Generator Model in the MNIST GAN",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-25-MNIST-Images-Output-by-the-Untrained-Generator-Model-1024x768.png",
      "bytes": 105402,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/010.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-25-MNIST-Images-Output-by-the-Untrained-Generator-Model-1024x768.png",
      "alt": "Example of 25 MNIST Images Output by the Untrained Generator Model",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Composite-Generator-and-Discriminator-Model-in-the-MNIST-GAN.png",
      "bytes": 3578,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/014.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-the-Composite-Generator-and-Discriminator-Model-in-the-MNIST-GAN.png",
      "alt": "Plot of the Composite Generator and Discriminator Model in the MNIST GAN",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-10-Epochs.png",
      "bytes": 14880,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/016.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-10-Epochs.png",
      "alt": "Plot of 100 GAN Generated MNIST Figures After 10 Epochs",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-40-Epochs.png",
      "bytes": 14633,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/018.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-40-Epochs.png",
      "alt": "Plot of 100 GAN Generated MNIST Figures After 40 Epochs",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-100-Epochs.png",
      "bytes": 14302,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/020.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Plot-of-100-GAN-Generated-MNIST-Figures-After-100-Epochs.png",
      "alt": "Plot of 100 GAN Generated MNIST Figures After 100 Epochs",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-25-GAN-Generated-MNIST-Handwritten-Images-1024x768.png",
      "bytes": 26010,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/022.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-25-GAN-Generated-MNIST-Handwritten-Images-1024x768.png",
      "alt": "Example of 25 GAN Generated MNIST Handwritten Images",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-a-GAN-Generated-MNIST-Handwritten-Digit-for-a-Vector-of-Zeros-300x225.png",
      "bytes": 2984,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/026.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/04/Example-of-a-GAN-Generated-MNIST-Handwritten-Digit-for-a-Vector-of-Zeros-300x225.png",
      "alt": "Example of a GAN Generated MNIST Handwritten Digit for a Vector of Zeros",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2022/11/GANP-220.png",
      "bytes": 5852,
      "reused": false,
      "local_path": "attachments/065e3ea689a7/images/body-122ca4ba21a74f647b16.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2022/11/GANP-220.png",
      "alt": "Generative Adversarial Networks with Python",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/02/Plot-of-a-Subset-of-Images-from-the-MNIST-Dataset.png",
      "bytes": 12626,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/031.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/02/Plot-of-a-Subset-of-Images-from-the-MNIST-Dataset.png",
      "alt": "Plot of a Subset of Images From the MNIST Dataset",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Example-of-100-Photos-of-Sneakers-Generated-by-an-AC-GAN.png",
      "bytes": 53303,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/035.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Example-of-100-Photos-of-Sneakers-Generated-by-an-AC-GAN.png",
      "alt": "Example of 100 Photos of Sneakers Generated by an AC-GAN",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model.png",
      "bytes": 274423,
      "reused": true,
      "local_path": "attachments/065e3ea689a7/images/039.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model.png",
      "alt": "Plot of Randomly Generated Faces Using the Loaded GAN Model",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Plot-of-Satellite-to-Google-Map-Translated-Images-Using-Pix2Pix-After-100-Training-Epochs.png",
      "bytes": 41884,
      "reused": false,
      "local_path": "attachments/065e3ea689a7/images/body-a973dffa0226d2c182d5.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Plot-of-Satellite-to-Google-Map-Translated-Images-Using-Pix2Pix-After-100-Training-Epochs.png",
      "alt": "Plot of Satellite to Google Map Translated Images Using Pix2Pix After 100 Training Epochs",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/07/How-to-Develop-an-Information-Maximizing-Generative-Adversarial-Network-InfoGAN-in-Keras.jpg",
      "bytes": 31254,
      "reused": false,
      "local_path": "attachments/065e3ea689a7/images/body-eaf154b006a61c257d95.jpg",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/07/How-to-Develop-an-Information-Maximizing-Generative-Adversarial-Network-InfoGAN-in-Keras.jpg",
      "alt": "How to Develop an Information Maximizing Generative Adversarial Network (InfoGAN) in Keras",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Example-of-100-Generated-items-of-Clothing-using-an-Conditional-GAN.png",
      "bytes": 86637,
      "reused": false,
      "local_path": "attachments/065e3ea689a7/images/body-6c6c53a32f380930c2a7.png",
      "src": "https://machinelearningmastery.com/wp-content/uploads/2019/05/Example-of-100-Generated-items-of-Clothing-using-an-Conditional-GAN.png",
      "alt": "Example of 100 Generated items of Clothing using a Conditional GAN.",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://machinelearningmastery.com/author/jasonb/",
      "label": "Jason Brownlee",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/category/generative-adversarial-networks/",
      "label": "Generative Adversarial Networks",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/",
      "label": "115",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/what-are-generative-adversarial-networks-gans/",
      "label": "Generative Adversarial Networks",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/generative_adversarial_networks/",
      "label": "Generative Adversarial Networks with Python",
      "type": "a"
    },
    {
      "href": "https://www.flickr.com/photos/jcookfisher/3410004359/",
      "label": "jcookfisher",
      "type": "a"
    },
    {
      "href": "https://en.wikipedia.org/wiki/MNIST_database",
      "label": "MNIST dataset",
      "type": "a"
    },
    {
      "href": "https://keras.io/datasets/",
      "label": "mnist.load_dataset() function",
      "type": "a"
    },
    {
      "href": "https://matplotlib.org/api/_as_gen/matplotlib.pyplot.imshow.html",
      "label": "imshow() function",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/convolutional-layers-for-deep-learning-neural-networks/",
      "label": "convolutional layers",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-choose-loss-functions-when-training-deep-learning-neural-networks/",
      "label": "binary cross entropy loss function",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural-networks/",
      "label": "ReLU",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-reduce-overfitting-with-dropout-regularization-in-keras/",
      "label": "Dropout",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/",
      "label": "Adam version of stochastic gradient descent",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/padding-and-stride-for-convolutional-neural-networks/",
      "label": "2×2 stride acts to down-sample the input image",
      "type": "a"
    },
    {
      "href": "https://docs.scipy.org/doc/numpy/reference/generated/numpy.expand_dims.html",
      "label": "expand_dims() NumPy function",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-manually-scale-image-pixel-data-for-deep-learning/",
      "label": "scale the pixel values",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-generate-random-numbers-in-python/",
      "label": "random pixel values",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/different-results-each-time-in-machine-learning/",
      "label": "results may vary",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/statistical-data-distributions/",
      "label": "Gaussian-distributed values",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/review-of-architectural-innovations-for-convolutional-neural-networks-for-image-classification/",
      "label": "pattern in convolutional neural networks",
      "type": "a"
    },
    {
      "href": "https://distill.pub/2016/deconv-checkerboard/",
      "label": "avoid a checkerboard pattern",
      "type": "a"
    },
    {
      "href": "https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.randn.html",
      "label": "randn() NumPy function",
      "type": "a"
    },
    {
      "href": "https://keras.io/getting-started/faq/",
      "label": "How can I “freeze” Keras layers?",
      "type": "a"
    },
    {
      "href": "https://docs.scipy.org/doc/numpy/reference/generated/numpy.vstack.html",
      "label": "vstack() NumPy function",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/develop-evaluate-large-deep-learning-models-keras-amazon-web-services/",
      "label": "How to Setup Amazon AWS EC2 GPUs to Train Keras Deep Learning Models (step-by-step)",
      "type": "a"
    },
    {
      "href": "https://amzn.to/2YuwVjL",
      "label": "Deep Learning",
      "type": "a"
    },
    {
      "href": "https://amzn.to/2U2bHuP",
      "label": "Deep Learning with Python",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/1406.2661",
      "label": "Generative Adversarial Networks",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/1701.00160",
      "label": "Tutorial: Generative Adversarial Networks, NIPS",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/1511.06434",
      "label": "Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks",
      "type": "a"
    },
    {
      "href": "https://keras.io/models/sequential/",
      "label": "Keras Sequential Model API",
      "type": "a"
    },
    {
      "href": "https://keras.io/layers/convolutional/",
      "label": "Keras Convolutional Layers API",
      "type": "a"
    },
    {
      "href": "https://matplotlib.org/api/",
      "label": "MatplotLib API",
      "type": "a"
    },
    {
      "href": "https://docs.scipy.org/doc/numpy/reference/routines.random.html",
      "label": "NumPy Random sampling (numpy.random) API",
      "type": "a"
    },
    {
      "href": "https://docs.scipy.org/doc/numpy/reference/routines.array-manipulation.html",
      "label": "NumPy Array manipulation routines",
      "type": "a"
    },
    {
      "href": "https://github.com/kroosen/GAN-in-keras-on-mnist",
      "label": "GAN-in-keras-on-mnist Project, GitHub",
      "type": "a"
    },
    {
      "href": "https://github.com/eriklindernoren/Keras-GAN",
      "label": "Keras-GAN Project, GitHub",
      "type": "a"
    },
    {
      "href": "https://github.com/Zackory/Keras-MNIST-GAN",
      "label": "Keras-MNIST-GAN Project, GitHub",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-a-convolutional-neural-network-from-scratch-for-mnist-handwritten-digit-classification/",
      "label": "How to Develop a CNN for MNIST Handwritten Digit…",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-an-auxiliary-classifier-gan-ac-gan-from-scratch-with-keras/",
      "label": "How to Develop an Auxiliary Classifier GAN (AC-GAN)…",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network/",
      "label": "How to Explore the GAN Latent Space When Generating Faces",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-a-pix2pix-gan-for-image-to-image-translation/",
      "label": "How to Develop a Pix2Pix GAN for Image-to-Image Translation",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-an-information-maximizing-generative-adversarial-network-infogan-in-keras/",
      "label": "How to Develop an Information Maximizing GAN…",
      "type": "a"
    },
    {
      "href": "https://machinelearningmastery.com/how-to-develop-a-conditional-generative-adversarial-network-from-scratch/",
      "label": "How to Develop a Conditional GAN (cGAN) From Scratch",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:54:30Z",
  "source_url": "https://machinelearningmastery.com/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/",
  "papers": [
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/1406.2661",
      "bytes": 530482,
      "reused": false,
      "local_path": "papers/repair/6c774b14a7c22605ace6.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/1701.00160",
      "bytes": 10905503,
      "reused": false,
      "local_path": "papers/repair/3ac41a1fc8a46eed2483.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/1511.06434",
      "bytes": 7457198,
      "reused": true,
      "local_path": "papers/repair/adcbe83e3cd16cada0ea.pdf"
    }
  ],
  "actual_video_files": [],
  "body_chars": 101187,
  "expected_body_images": 18,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/065e3ea689a7/pages/source.html",
  "local_html": "attachments/065e3ea689a7/pages/article.html",
  "body_base_path": "attachments/065e3ea689a7/pages/",
  "body_markdown_path": "attachments/065e3ea689a7/pages/article.md"
}