class_docstr: A Generative Adversarial Network with deep fully-connected neural nets as
  Generator and Discriminator.
  
  Training Data: MNIST Handwritten Digits (28x28 images)
class_docstr_tok: ["A","Generative","Adversarial","Network","with","deep","fully-connected","neural","nets","as","Generator","and","Discriminator",".","Training","Data",":","MNIST","Handwritten","Digits","(","28x28","images",")"]
code: def train(self, n_epochs, batch_size=128, save_interval=50):
      mnist = fetch_mldata("MNIST original")
  
      X = mnist.data
      y = mnist.target
  
      # Rescale [-1, 1]
      X = (X.astype(np.float32) - 127.5) / 127.5
  
      half_batch = int(batch_size / 2)
  
      for epoch in range(n_epochs):
          # ---------------------
          #  Train Discriminator
          # ---------------------
  
          self.discriminator.set_trainable(True)
  
          # Select a random half batch of images
          idx = np.random.randint(0, X.shape[0], half_batch)
          imgs = X[idx]
  
          # Sample noise to use as generator input
          noise = np.random.normal(0, 1, (half_batch, self.latent_dim))
  
          # Generate a half batch of images
          gen_imgs = self.generator.predict(noise)
  
          # Valid = [1, 0], Fake = [0, 1]
          valid = np.concatenate(
              (np.ones((half_batch, 1)), np.zeros((half_batch, 1))),
              axis=1,
          )
          fake = np.concatenate(
              (np.zeros((half_batch, 1)), np.ones((half_batch, 1))),
              axis=1,
          )
  
          # Train the discriminator
          d_loss_real, d_acc_real = self.discriminator.train_on_batch(
              imgs, valid
          )
          d_loss_fake, d_acc_fake = self.discriminator.train_on_batch(
              gen_imgs, fake
          )
          d_loss = 0.5 * (d_loss_real + d_loss_fake)
          d_acc = 0.5 * (d_acc_real + d_acc_fake)
  
          # ---------------------
          #  Train Generator
          # ---------------------
  
          # We only want to train the generator for the combined model
          self.discriminator.set_trainable(False)
  
          # Sample noise and use as generator input
          noise = np.random.normal(0, 1, (batch_size, self.latent_dim))
  
          # The generator wants the discriminator to label the generated samples as valid
          valid = np.concatenate(
              (np.ones((batch_size, 1)), np.zeros((batch_size, 1))),
              axis=1,
          )
  
          # Train the generator
          g_loss, g_acc = self.combined.train_on_batch(noise, valid)
  
          # Display the progress
          print(
              "%d [D loss: %f, acc: %.2f%%] [G loss: %f, acc: %.2f%%]"
              % (epoch, d_loss, 100 * d_acc, g_loss, 100 * g_acc)
          )
  
          # If at save interval => save generated image samples
          if epoch % save_interval == 0:
              self.save_imgs(epoch)
  
file_path: ML-From-Scratch/mlfromscratch/unsupervised_learning/generative_adversarial_network.py
imports: from __future__ import print_function, division
  from sklearn import datasets
  import math
  import matplotlib.pyplot
  import numpy
  import progressbar
  from sklearn.datasets import fetch_mldata
  from mlfromscratch.deep_learning.optimizers import Adam
  from mlfromscratch.deep_learning.loss_functions import CrossEntropy
  from mlfromscratch.deep_learning.layers import Dense, Dropout, Flatten, Activation, Reshape, BatchNormalization
  from mlfromscratch.deep_learning import NeuralNetwork
label_desc: Use image node_id 4 for calling the GAN obj's underlying member method code with example usage: obj.train(n_epochs, batch_size, save_interval) without return types