//  [python]
def add_train(self, aux_loss_list=None, optimizer='adam',
                learning_rate=0.01, max_grad_norm=5.0,
                decay_after_n_steps=1000, decay_every_n_steps=1000,
                lr_decay_factor=1.0, avg_loss_by_n=False, debug=False,
                l2_coeff=0.0,
                adam_beta1=0.9,
                name='Training'):
    "Construct part of the graph that controlls training (SGD optimization)."
    self.node_dict['max_batch_size'] = tf.placeholder(tf.int32, None)
    with tf.variable_scope(name):
      all_summaries = []
      batch_size = tf.cast(self.node_dict['max_batch_size'],
                           dtype=tf.float32)
      loss = self.node_dict['loss'] / batch_size
      
      all_summaries.append(
        tf.summary.scalar(self.vs_name + '/' + 'loss', loss))
      total_loss = loss

      if aux_loss_list is not None:
        for loss_name, w in aux_loss_list:
          if w > 0.0:
            aux_loss = self.node_dict[loss_name]
            total_loss += aux_loss * w / batch_size
            aux_loss_summary = tf.summary.scalar(
              self.vs_name + '/' + loss_name, aux_loss)
            all_summaries.append(aux_loss_summary)

      if debug:
        total_loss= tf.Print(
          total_loss, [self.node_dict['sequence_loss']], message='seq_loss:', summarize=10000)

        total_loss= tf.Print(
          total_loss, [self.node_dict['weights'].tensor], message='weights:', summarize=10000)

        total_loss= tf.Print(
          total_loss, [self.node_dict['targets'].tensor], message='targets:', summarize=10000)

        total_loss= tf.Print(
          total_loss, [self.node_dict['probs'].tensor], message='probs:', summarize=10000)

        total_loss= tf.Print(
          total_loss, [self.node_dict['step_loss'].tensor],
          message='step_loss:', summarize=10000)

        total_loss= tf.Print(
          total_loss, [self.node_dict['logits'].tensor], message='logits:', summarize=10000)
      
      total_loss_summary = tf.summary.scalar(
        self.vs_name + '/' + 'total_loss', total_loss)
      all_summaries.append(total_loss_summary)
      
      lr = tf.Variable(
        float(learning_rate), trainable=False)

      new_lr = tf.placeholder(dtype=tf.float32, shape=None, name='new_lr')
      update_lr = lr.assign(new_lr)

      global_step = tf.Variable(0, trainable=False)
      decay_step = tf.maximum(
        0, global_step - decay_after_n_steps)
      decay_exponent = (tf.cast(decay_step, tf.float32) /
                        tf.cast(decay_every_n_steps, tf.float32))
      decay = lr_decay_factor ** decay_exponent
      decayed_lr = lr * decay

      lr_summary = tf.summary.scalar(self.vs_name + '/' + 'learning_rate', decayed_lr)
      all_summaries.append(lr_summary)
      
      params = tf.get_collection(
        tf.GraphKeys.TRAINABLE_VARIABLES, scope=self.vs_name)
      n_params = 0
      tf.logging.info('trainable parameters:')
      for tv in params:
        n_tv_params = np.product(tv.get_shape().as_list())
        n_params += n_tv_params
        tf.logging.info('{}: {}'.format(tv.name, n_tv_params))
        if 'weights' in tv.name or 'kernel' in tv.name:
          total_loss += tf.reduce_sum(tf.nn.l2_loss(tv)) * l2_coeff
      tf.logging.info('total number of trainable parameters {}'.format(n_params))

      if optimizer == 'adam':
        tf.logging.info('adam beta1: {}'.format(adam_beta1))
        opt = OPTIMIZER_DICT[optimizer](decayed_lr, beta1=adam_beta1)
      else:
        opt = OPTIMIZER_DICT[optimizer](decayed_lr)

      gradients = tf.gradients(total_loss, params)
      clipped_gradients, grad_norm = tf.clip_by_global_norm(
        gradients, max_grad_norm)
      update = opt.apply_gradients(
        zip(clipped_gradients, params), global_step=global_step)
            
      grad_norm_summary = tf.summary.scalar(
        self.vs_name + '/' + 'grad_norm', grad_norm)
      all_summaries.append(grad_norm_summary)

      if debug:
        _, clipped_grad_norm = tf.clip_by_global_norm(
          clipped_gradients, max_grad_norm)
        clipped_grad_norm_summary = tf.summary.scalar(
          self.vs_name + '/' + 'clipped_grad_norm', clipped_grad_norm)
        n_summary = tf.summary.scalar(
          self.vs_name + '/' + 'n', self.node_dict['n'])
        seq_loss_summary = tf.summary.histogram(
          self.vs_name + '/' + 'seq_loss', self.node_dict['sequence_loss'])
        step_loss_summary = tf.summary.histogram(
          self.vs_name + '/' + 'step_loss', self.node_dict['step_loss'].tensor)
        weights_summary = tf.summary.histogram(
          self.vs_name + '/' + 'weights', self.node_dict['weights'].tensor)
        all_summaries += [
          clipped_grad_norm_summary, n_summary,
          step_loss_summary, seq_loss_summary, weights_summary]

      batch_size_summary = tf.summary.scalar(
        self.vs_name + '/' + 'batch_size', self.node_dict['batch_size'])
      all_summaries.append(batch_size_summary)

      if 'ent_reg' in self.node_dict:
        if avg_loss_by_n:
          ent_reg = (self.node_dict['ent_reg'] /
                     tf.cast(self.node_dict['n'],
                             dtype=tf.float32))
        else:
          ent_reg = self.node_dict['ent_reg'] / batch_size
        ent_reg_summary = tf.summary.scalar(
          self.vs_name + '/' + 'polic_entropy',
          (self.node_dict['ent_reg'] /
           tf.cast(self.node_dict['n'], tf.float32)))
        ent_reg_ppl_summary = tf.summary.scalar(
          self.vs_name + '/' + 'policy_entropy_ppl',
          tf.exp(self.node_dict['ent_reg'] /
                 tf.cast(self.node_dict['n'], tf.float32)))
        all_summaries.append(ent_reg_summary)
        all_summaries.append(ent_reg_ppl_summary)

    for s in self.node_dict['summaries']:
      all_summaries.append(s)

    merged = tf.summary.merge(inputs=all_summaries)
    self.node_dict.update(
      train=update, global_step=global_step,
      summaries=merged,
      update_lr=update_lr, new_lr=new_lr)