text: #   Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserve.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from six.moves import xrange  # pylint: disable=redefined-builtin
  from datetime import datetime
  import math
  import re
  import time
  
  import tensorflow.python.platform
  import tensorflow as tf
  
  FLAGS = tf.app.flags.FLAGS
  
  tf.app.flags.DEFINE_integer('batch_size', 64, """Batch size.""")
  tf.app.flags.DEFINE_integer('num_batches', 100, """Number of batches to run.""")
  tf.app.flags.DEFINE_string('data_format', 'NCHW',
                             """The data format for Convnet operations.
                             Can be either NHWC or NCHW.
                             """)
  
  tf.app.flags.DEFINE_string('train_dir', '/train_model',
                             """Directory where to write event logs """
                             """and checkpoint.""")
  tf.app.flags.DEFINE_integer('num_gpus', 4, """How many GPUs to use.""")
  tf.app.flags.DEFINE_boolean('log_device_placement', False,
                              """Whether to log device placement.""")
  
  NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
  NUM_EPOCHS_PER_DECAY = 50
  INITIAL_LEARNING_RATE = 0.1
  LEARNING_RATE_DECAY_FACTOR = 0.1
  TOWER_NAME = 'tower'
  
  
  def _conv(name, inpOp, nIn, nOut, kH, kW, dH, dW, padType, wd=0.005):
      with tf.name_scope(name) as scope:
          kernel = tf.get_variable(
              name + '_w', [kH, kW, nIn, nOut],
              initializer=tf.truncated_normal_initializer(
                  stddev=0.01, dtype=tf.float32),
              dtype=tf.float32)
  
          if wd is not None:
              weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss')
              tf.add_to_collection('losses', weight_decay)
  
          if FLAGS.data_format == 'NCHW':
              strides = [1, 1, dH, dW]
          else:
              strides = [1, dH, dW, 1]
          conv = tf.nn.conv2d(
              inpOp,
              kernel,
              strides,
              padding=padType,
              data_format=FLAGS.data_format)
  
          biases = tf.get_variable(
              name=name + '_b',
              shape=[nOut],
              initializer=tf.constant_initializer(
                  value=0.0, dtype=tf.float32),
              dtype=tf.float32)
  
          bias = tf.reshape(
              tf.nn.bias_add(
                  conv, biases, data_format=FLAGS.data_format),
              conv.get_shape())
  
          conv1 = tf.nn.relu(bias, name=scope)
          return conv1
  
  
  def _affine(name, inpOp, nIn, nOut, wd=0.005, act=True):
      with tf.name_scope(name) as scope:
          kernel = tf.get_variable(
              name + '_w', [nIn, nOut],
              initializer=tf.truncated_normal_initializer(
                  stddev=0.01, dtype=tf.float32),
              dtype=tf.float32)
  
          if wd is not None:
              weight_decay = tf.mul(tf.nn.l2_loss(kernel), wd, name='weight_loss')
              tf.add_to_collection('losses', weight_decay)
  
          biases = tf.get_variable(
              name + '_b', [nOut],
              initializer=tf.constant_initializer(
                  value=0.0, dtype=tf.float32),
              dtype=tf.float32,
              trainable=True)
  
          affine1 = tf.nn.relu_layer(inpOp, kernel, biases, name=name) if act else \
                    tf.matmul(inpOp, kernel) + biases
  
          return affine1
  
  
  def _mpool(name, inpOp, kH, kW, dH, dW):
      if FLAGS.data_format == 'NCHW':
          ksize = [1, 1, kH, kW]
          strides = [1, 1, dH, dW]
      else:
          ksize = [1, kH, kW, 1]
          strides = [1, dH, dW, 1]
      return tf.nn.max_pool(
          inpOp,
          ksize=ksize,
          strides=strides,
          padding='VALID',
          data_format=FLAGS.data_format,
          name=name)
  
  
  def _norm(name, l_input, lsize=4):
      return tf.nn.lrn(l_input,
                       lsize,
                       bias=1.0,
                       alpha=0.001 / 9.0,
                       beta=0.75,
                       name=name)
  
  
  def loss(logits, labels):
      labels = tf.cast(labels, tf.int64)
      cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
          logits, labels, name='cross_entropy_per_example')
      cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
      tf.add_to_collection('losses', cross_entropy_mean)
  
      # The total loss is defined as the cross entropy loss plus all of the weight
      # decay terms (L2 loss).
      return tf.add_n(tf.get_collection('losses'), name='total_loss')
  
  
  def get_incoming_shape(incoming):
      """ Returns the incoming data shape """
      if isinstance(incoming, tf.Tensor):
          return incoming.get_shape().as_list()
      elif type(incoming) in [np.array, list, tuple]:
          return np.shape(incoming)
      else:
          raise Exception("Invalid incoming layer.")
  
  
  def inference(images):
      conv1 = _conv('conv1', images, 3, 96, 11, 11, 4, 4, 'VALID')
      pool1 = _mpool('pool1', conv1, 3, 3, 2, 2)
      norm1 = _norm('norm1', pool1, lsize=5)
      conv2 = _conv('conv2', norm1, 96, 256, 5, 5, 1, 1, 'SAME')
      pool2 = _mpool('pool2', conv2, 3, 3, 2, 2)
      norm2 = _norm('norm2', pool2, lsize=5)
      conv3 = _conv('conv3', norm2, 256, 384, 3, 3, 1, 1, 'SAME')
      conv4 = _conv('conv4', conv3, 384, 384, 3, 3, 1, 1, 'SAME')
      conv5 = _conv('conv5', conv4, 384, 256, 3, 3, 1, 1, 'SAME')
      pool5 = _mpool('pool5', conv5, 3, 3, 2, 2)
      resh1 = tf.reshape(pool5, [-1, 256 * 6 * 6])
      affn1 = _affine('fc6', resh1, 256 * 6 * 6, 4096)
      affn2 = _affine('fc7', affn1, 4096, 4096)
      affn3 = _affine('fc8', affn2, 4096, 1000, wd=None, act=False)  # last fc
  
      return affn3
  
  
  def tower_loss(scope):
      """Calculate the total loss on a single tower running the model.
      Args:
          scope: unique prefix string identifying the tower, e.g. 'tower_0'
      Returns:
          Tensor of shape [] containing the total loss for a batch of data
      """
      image_size = 224
      if FLAGS.data_format == 'NCHW':
          image_shape = [FLAGS.batch_size, 3, image_size + 3, image_size + 3]
      else:
          image_shape = [FLAGS.batch_size, image_size + 3, image_size + 3, 3]
      images = tf.get_variable(
          'image',
          image_shape,
          initializer=tf.truncated_normal_initializer(
              stddev=0.1, dtype=tf.float32),
          dtype=tf.float32,
          trainable=False)
  
      labels = tf.get_variable(
          'label', [FLAGS.batch_size],
          initializer=tf.constant_initializer(1),
          dtype=tf.int32,
          trainable=False)
  
      # Build a Graph that computes the logits predictions from the
      # inference model.
      last_layer = inference(images)
  
      # Build the portion of the Graph calculating the losses. Note that we will
      # assemble the total_loss using a custom function below.
      _ = loss(last_layer, labels)
  
      # Assemble all of the losses for the current tower only.
      losses = tf.get_collection('losses', scope)
  
      # Calculate the total loss for the current tower.
      total_loss = tf.add_n(losses, name='total_loss')
  
      # Compute the moving average of all individual losses and the total loss.
      loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
      loss_averages_op = loss_averages.apply(losses + [total_loss])
  
      # Attach a scalar summary to all individual losses and the total loss; do the
      # same for the averaged version of the losses.
      for l in losses + [total_loss]:
          # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training
          # session. This helps the clarity of presentation on tensorboard.
          loss_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', l.op.name)
          # Name each loss as '(raw)' and name the moving average version of the loss
          # as the original loss name.
          tf.scalar_summary(loss_name + ' (raw)', l)
          tf.scalar_summary(loss_name, loss_averages.average(l))
  
      with tf.control_dependencies([loss_averages_op]):
          total_loss = tf.identity(total_loss)
      return total_loss
  
  
  def average_gradients(tower_grads):
      """Calculate the average gradient for each shared variable across all towers.
    Note that this function provides a synchronization point across all towers.
    Args:
      tower_grads: List of lists of (gradient, variable) tuples. The outer list
        is over individual gradients. The inner list is over the gradient
        calculation for each tower.
    Returns:
       List of pairs of (gradient, variable) where the gradient has been averaged
       across all towers.
    """
      average_grads = []
      for grad_and_vars in zip(*tower_grads):
          # Note that each grad_and_vars looks like the following:
          #   ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN))
          grads = []
          for g, _ in grad_and_vars:
              # Add 0 dimension to the gradients to represent the tower.
              expanded_g = tf.expand_dims(g, 0)
  
              # Append on a 'tower' dimension which we will average over below.
              grads.append(expanded_g)
  
          # Average over the 'tower' dimension.
          grad = tf.concat(0, grads)
          grad = tf.reduce_mean(grad, 0)
  
          # Keep in mind that the Variables are redundant because they are shared
          # across towers. So .. we will just return the first tower's pointer to
          # the Variable.
          v = grad_and_vars[0][1]
          grad_and_var = (grad, v)
          average_grads.append(grad_and_var)
      return average_grads
  
  
  def time_tensorflow_run(session, target):
      num_steps_burn_in = 50
      total_duration = 0.0
      total_duration_squared = 0.0
      for i in xrange(FLAGS.num_batches + num_steps_burn_in):
          start_time = time.time()
          _, loss_value = session.run(target)
          duration = time.time() - start_time
          if i > num_steps_burn_in:
              if not i % 10:
                  num_examples_per_step = FLAGS.batch_size * FLAGS.num_gpus
                  examples_per_sec = num_examples_per_step / duration
                  sec_per_batch = duration
  
                  format_str = (
                      '%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                      'sec/batch batch_size = %d)')
                  print(format_str %
                        (datetime.now(), i - num_steps_burn_in, loss_value,
                         duration, sec_per_batch, num_examples_per_step))
  
              total_duration += duration
              total_duration_squared += duration * duration
  
      mn = total_duration / FLAGS.num_batches
      vr = total_duration_squared / FLAGS.num_batches - mn * mn
      sd = math.sqrt(vr)
      print('%s: FwdBwd across %d steps, %.3f +/- %.3f sec / batch' %
            (datetime.now(), FLAGS.num_batches, mn, sd))
  
  
  def run_benchmark():
      with tf.Graph().as_default(), tf.device('/cpu:0'):
          # Create a variable to count the number of train() calls. This equals the
          # number of batches processed * FLAGS.num_gpus.
          global_step = tf.get_variable(
              'global_step', [],
              initializer=tf.constant_initializer(0),
              trainable=False)
  
          # Calculate the learning rate schedule.
          num_batches_per_epoch = (NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN /
                                   FLAGS.batch_size)
          decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)
  
          # Decay the learning rate exponentially based on the number of steps.
          lr = tf.train.exponential_decay(
              INITIAL_LEARNING_RATE,
              global_step,
              decay_steps,
              LEARNING_RATE_DECAY_FACTOR,
              staircase=True)
  
          # Create an optimizer that performs gradient descent.
          opt = tf.train.MomentumOptimizer(lr, 0.9)
  
          # Calculate the gradients for each model tower.
          tower_grads = []
          for i in xrange(FLAGS.num_gpus):
              with tf.device('/gpu:%d' % i):
                  with tf.name_scope('%s_%d' % (TOWER_NAME, i)) as scope:
                      # Calculate the loss for one tower of the model. This function
                      # constructs the entire model but shares the variables across
                      # all towers.
                      loss = tower_loss(scope)
  
                      # Reuse variables for the next tower.
                      tf.get_variable_scope().reuse_variables()
  
                      # Retain the summaries from the final tower.
                      summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope)
  
                      # Calculate the gradients for the batch of data on this tower.
                      grads = opt.compute_gradients(loss)
  
                      # Keep track of the gradients across all towers.
                      tower_grads.append(grads)
  
          # We must calculate the mean of each gradient. Note that this is the
          # synchronization point across all towers.
          grads = average_gradients(tower_grads)
  
          # Apply the gradients to adjust the shared variables.
          apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)
  
          # Group all updates to into a single train op.
          train_op = tf.group(apply_gradient_op)
  
          # Build an initialization operation.
          init = tf.initialize_all_variables()
  
          # Start running operations on the Graph. allow_soft_placement must be set to
          # True to build towers on GPU, as some of the ops do not have GPU
          # implementations.
          sess = tf.Session(config=tf.ConfigProto(
              allow_soft_placement=True,
              log_device_placement=FLAGS.log_device_placement))
          sess.run(init)
          time_tensorflow_run(sess, [train_op, loss])
  
  
  def main(_):
      run_benchmark()
  
  
  if __name__ == '__main__':
      tf.app.run()
  