code: def build_graph(self, real_sample):
      real_sample = tf.expand_dims(real_sample, -1)
  
      # sample the latent code:
      zc = shapeless_placeholder(sample_prior(BATCH), 0, name="z_code")
      z_noise = shapeless_placeholder(
          tf.random_uniform([BATCH, NOISE_DIM], -1, 1),
          0,
          name="z_noise",
      )
      z = tf.concat([zc, z_noise], 1, name="z")
  
      with argscope(
          [Conv2D, Conv2DTranspose, FullyConnected],
          kernel_initializer=tf.truncated_normal_initializer(
              stddev=0.02
          ),
      ):
          with tf.variable_scope("gen"):
              fake_sample = self.generator(z)
              fake_sample_viz = tf.cast(
                  (fake_sample) * 255.0, tf.uint8, name="viz"
              )
              tf.summary.image("gen", fake_sample_viz, max_outputs=30)
  
          # may need to investigate how bn stats should be updated across two discrim
          with tf.variable_scope("discrim"):
              real_pred, _ = self.discriminator(real_sample)
              fake_pred, dist_param = self.discriminator(fake_sample)
  
      """
          Mutual information between x (i.e. zc in this case) and some
          information s (the generated samples in this case):
  
                      I(x;s) = H(x) - H(x|s)
                             = H(x) + E[\log P(x|s)]
  
          The distribution from which zc is sampled, in this case, is set to a fixed prior already.
          So the first term is a constant.
          For the second term, we can maximize its variational lower bound:
                      E_{x \sim P(x|s)}[\log Q(x|s)]
          where Q(x|s) is a proposal distribution to approximate P(x|s).
  
          Here, Q(x|s) is assumed to be a distribution which shares the form
          of P, and whose parameters are predicted by the discriminator network.
          """
      with tf.name_scope("mutual_information"):
          with tf.name_scope("prior_entropy"):
              cat, uni = get_distributions(
                  DIST_PRIOR_PARAM[:NUM_CLASS],
                  DIST_PRIOR_PARAM[NUM_CLASS:],
              )
              ents = [
                  cat.entropy(name="cat_entropy"),
                  tf.reduce_sum(uni.entropy(), name="uni_entropy"),
              ]
              entropy = tf.add_n(ents, name="total_entropy")
              # Note that the entropy of prior is a constant. The paper mentioned it but didn't use it.
  
          with tf.name_scope("conditional_entropy"):
              cond_ents = entropy_from_samples(zc, dist_param)
              cond_entropy = tf.add_n(cond_ents, name="total_entropy")
  
          MI = tf.subtract(
              entropy, cond_entropy, name="mutual_information"
          )
          summary.add_moving_summary(
              entropy, cond_entropy, MI, *cond_ents
          )
  
      # default GAN objective
      self.build_losses(real_pred, fake_pred)
  
      # subtract mutual information for latent factors (we want to maximize them)
      self.g_loss = tf.subtract(self.g_loss, MI, name="total_g_loss")
      self.d_loss = tf.subtract(self.d_loss, MI, name="total_d_loss")
  
      summary.add_moving_summary(self.g_loss, self.d_loss)
  
      # distinguish between variables of generator and discriminator updates
      self.collect_variables()
  
imports: import argparse
  import numpy
  import os
  import cv2
  import tensorflow
  from tensorpack import *
  from tensorpack.dataflow import dataset
  from tensorpack.tfutils import gradproc, optimizer, summary
  from tensorpack.tfutils.scope_utils import auto_reuse_variable_scope, under_name_scope
  from tensorpack.utils import viz
  from GAN import GANModelDesc, GANTrainer
label_desc: Use image node_id 4 for calling the Model obj's underlying member method code with example usage: obj.build_graph(real_sample) without return types