text: # Training a GAN on MNIST Dataset
  from matplotlib import pyplot
  from keras.optimizers import Adam
  from keras.models import Sequential
  from numpy.random import randn, randint
  from keras.datasets.mnist import load_data
  from keras.layers import LeakyReLU, Dropout
  from keras.layers import Conv2D, Conv2DTranspose
  from keras.layers import Dense, Reshape, Flatten
  from numpy import ones, zeros, vstack, expand_dims
  
  """The function define the standalone discriminator model."""
  def define_discriminator(in_shape=(28,28,1)):
      model = Sequential()
      model.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))
      model.add(LeakyReLU(alpha=0.2))
      model.add(Dropout(0.4))
      model.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))
      model.add(LeakyReLU(alpha=0.2))
      model.add(Dropout(0.4))
      model.add(Flatten())
      model.add(Dense(1, activation='sigmoid'))
      # compile model
      opt = Adam(learning_rate=0.0002, beta_1=0.5)
      model.compile(optimizer=opt, loss='binary_crossentropy', metrics=['accuracy'])
      return model
  
  """The function define the standalone generator model."""
  def define_generator(latent_dim):
      model = Sequential()
      # foundation for 7x7 image
      n_nodes = 128 * 7 * 7
      model.add(Dense(n_nodes, input_dim=latent_dim))
      model.add(LeakyReLU(alpha=0.2))
      model.add(Reshape((7, 7, 128)))
      # upsample to 14x14
      model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
      model.add(LeakyReLU(alpha=0.2))
      # upsample to 28x28
      model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
      model.add(LeakyReLU(alpha=0.2))
      model.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))
      return model
  
  """The function define the combined generator and discriminator model, for updating the generator."""
  def define_gan(g_model, d_model):
      # make weights in the discriminator not trainable
      d_model.trainable = False
      # connect them
      model = Sequential()
      # add generator and discriminator
      model.add(g_model)
      model.add(d_model)
      # compile model
      opt = Adam(learning_rate=0.0002, beta_1=0.5)
      model.compile(loss='binary_crossentropy', optimizer=opt)
      return model
  
  """The function load and prepare MNIST training images."""
  def load_real_samples():
      # load mnist dataset
      (trainX, _), (_, _) = load_data()
      # expand to 3d, e.g. add channels dimension
      X = expand_dims(trainX, axis=-1)
      # convert from unsigned ints to floats
      X = X.astype('float32')
      # scale from [0,255] to [0,1]
      X = X / 255.0
      return X
  
  """The function select real samples."""
  def generate_real_samples(dataset, n_samples):
      # choose random instances
      ix = randint(0, dataset.shape[0], n_samples)
      # retrieve selected images
      X = dataset[ix]
      # generate 'real' class labels (1)
      y = ones((n_samples, 1))
      return X, y
  
  """The function generate points in latent space as input for the generator."""
  def generate_latent_points(latent_dim, n_samples):
      # generate points in the latent space
      x_input = randn(latent_dim * n_samples)
      # reshape into a batch of inputs for the network
      x_input = x_input.reshape(n_samples, latent_dim)
      return x_input
  
  """The function use the generator to generate n fake examples, with class labels."""
  def generate_fake_samples(g_model, latent_dim, n_samples):
      # generate points in latent space
      x_input = generate_latent_points(latent_dim, n_samples)
      # predict outputs
      X = g_model.predict(x_input)
      # create 'fake' class labels (0)
      y = zeros((n_samples, 1))
      return X, y
  
  """The function create and save a plot of generated images (reversed grayscale)."""
  def save_plot(examples, epoch, n=10):
      # plot images
      for i in range(n * n):
          # define subplot
          pyplot.subplot(n, n, 1 + i)
          pyplot.axis('off')
          # plot raw pixel data
          pyplot.imshow(examples[i, :, :, 0], cmap='gray_r')
      # save plot to file
      filename = 'gen{}.png'.format(epoch + 1)
      pyplot.savefig(filename, dpi=900, transparent=True)
      pyplot.close()
  
  """The function evaluate the discriminator, plot generated images, save generator model."""
  def summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):
      # prepare real samples
      X_real, y_real = generate_real_samples(dataset, n_samples)
      # evaluate discriminator on real examples
      _, acc_real = d_model.evaluate(X_real, y_real, verbose=0)
      # prepare fake examples
      x_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)
      # evaluate discriminator on fake examples
      _, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)
      # summarize discriminator performance
      title = 'Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100)
      print('>' + title)
      # save plot and the generator model tile file
      save_plot(x_fake, epoch)
      filename = 'generator_model_%03d.h5' % (epoch + 1)
      g_model.save(filename)
      return title
  
  """The function train the generator and discriminator."""
  def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=256):
      bat_per_epo = int(dataset.shape[0] / n_batch)
      half_batch = int(n_batch / 2)
      output_file = open("output.txt", "a")
  
      # manually enumerate epochs
      for i in range(n_epochs):
          # enumerate batches over the training set
          for j in range(bat_per_epo):
              # get randomly selected 'real' samples
              X_real, y_real = generate_real_samples(dataset, half_batch)
              # generate 'fake' examples
              X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
              # create training set for the discriminator
              X = vstack((X_real, X_fake))
              y = vstack((y_real, y_fake))
              # update discriminator model weights
              d_loss, _ = d_model.train_on_batch(X, y)
              # prepare points in latent space as input for the generator
              X_gan = generate_latent_points(latent_dim, n_batch)
              # create inverted labels for the fake samples
              y_gan = ones((n_batch, 1))
              # update the generator via discriminator's error
              g_loss = gan_model.train_on_batch(X_gan, y_gan)
              # summarize loss on this batch
              print('>%d, %d/%d, d=%.3f, g=%.3f'.format(i + 1, j + 1, bat_per_epo, d_loss, g_loss))
          # evaluate the model performance, sometimes
          if not((i + 1) % 10):
              output_file.write(str(i % 10) + ': ' + summarize_performance(i, g_model, d_model, dataset, latent_dim))
      output_file.close()
  
  def main():
      # size of the latent space
      latent_dim = 100
      # create the discriminator and the generator
      d_model = define_discriminator()
      g_model = define_generator(latent_dim)
      # create the gan
      gan_model = define_gan(g_model, d_model)
      # load image data
      dataset = load_real_samples()
      # train model
      train(g_model, d_model, gan_model, dataset, latent_dim)
  
  if __name__ == '__main__':
      main()
  