docs: def train(self, early_stopping_steps: int = 10, min_epochs: int = 30,
                max_valid_ler: float = 1.0, max_train_ler: float = 0.3,
                max_epochs: int = 100, restore_model_path: Optional[str]=None,
                epoch_callback: Optional[Callable[[Dict], None]]=None) -> None:
          """ Train the model.
  
              min_epochs: minimum number of epochs to run training for.
              max_epochs: maximum number of epochs to run training for.
              early_stopping_steps: Stop training after this number of steps
                                    if no LER improvement has been made.
              max_valid_ler: Maximum LER for the validation set.
                             Training will continue until this is met or another
                             stopping condition occurs.
              max_train_ler: Maximum LER for the training set.
                             Training will continue until this is met or another
                             stopping condition occurs.
              restore_model_path: The path to restore a model from.
              epoch_callback: A callback that is called at the end of each training epoch.
                              The parameters passed to the callable will be the epoch number,
                              the current training LER and the current validation LER.
                              This can be useful for progress reporting.
          """
          logger.info("Training model")
          best_valid_ler = 2.0
          steps_since_last_record = 0
  
          #Get information about training for the names of output files.
          frame = inspect.currentframe()
          # pylint: disable=deprecated-method
          # It was a mistake to deprecate this in Python 3.5
          if frame:
              args, _, _, values = inspect.getargvalues(frame)
              with open(os.path.join(self.exp_dir, "train_description.txt"), 
                        "w", encoding=ENCODING) as desc_f:
                  for arg in args:
                      if type(values[arg]) in [str, int, float] or isinstance(
                              values[arg], type(None)):
                          print("%s=%s" % (arg, values[arg]), file=desc_f)
                      else:
                          print("%s=%s" % (arg, values[arg].__dict__), file=desc_f)
                  print("num_train=%s" % (self.corpus_reader.num_train), file=desc_f)
                  print("batch_size=%s" % (self.corpus_reader.batch_size), file=desc_f)
          else:
              logger.error("Couldn't find frame information, failed to write train_description.txt")
  
  
          # Load the validation set
          valid_x, valid_x_lens, valid_y = self.corpus_reader.valid_batch()
  
          saver = tf.train.Saver()
  
          with tf.Session(config=allow_growth_config) as sess:
  
              if restore_model_path:
                  logger.info("Restoring model from path %s", restore_model_path)
                  saver.restore(sess, restore_model_path)
              else:
                  sess.run(tf.global_variables_initializer())
  
              # Prepare directory to output hypotheses to
              hyps_dir = os.path.join(self.exp_dir, "decoded")
              if not os.path.isdir(hyps_dir):
                  os.mkdir(hyps_dir)
  
              best_epoch_str = None
  
              training_log_path = os.path.join(self.exp_dir, "train_log.txt")
              if os.path.exists(training_log_path):
                  logger.error("Error, overwriting existing log file at path {}".format(training_log_path))
              with open(training_log_path, "w",
                        encoding=ENCODING) as out_file:
                  for epoch in itertools.count(start=1):
                      print("\nexp_dir %s, epoch %d" % (self.exp_dir, epoch))
                      batch_gen = self.corpus_reader.train_batch_gen()
  
                      train_ler_total = 0
                      print("\tBatch...", end="")
                      for batch_i, batch in enumerate(batch_gen):
                          print("%d..." % batch_i, end="")
                          sys.stdout.flush()
                          batch_x, batch_x_lens, batch_y = batch
  
                          feed_dict = {self.batch_x: batch_x,
                                      self.batch_x_lens: batch_x_lens,
                                      self.batch_y: batch_y}
  
                          _, ler, = sess.run([self.optimizer, self.ler],
                                          feed_dict=feed_dict)
  
                          train_ler_total += ler
                      #else:
                      #    raise PersephoneException("No training data was provided."
                      #                              " Check your batch generation.")
  
                      feed_dict = {self.batch_x: valid_x,
                                  self.batch_x_lens: valid_x_lens,
                                  self.batch_y: valid_y}
  
                      try:
                          valid_ler, dense_decoded, dense_ref = sess.run(
                              [self.ler, self.dense_decoded, self.dense_ref],
                              feed_dict=feed_dict)
                      except tf.errors.ResourceExhaustedError:
                          import pprint
                          print("Ran out of memory allocating a batch:")
                          pprint.pprint(feed_dict)
                          logger.critical("Ran out of memory allocating a batch: %s", pprint.pformat(feed_dict))
                          raise
                      hyps, refs = self.corpus_reader.human_readable_hyp_ref(
                          dense_decoded, dense_ref)
                      # Log hypotheses
                      with open(os.path.join(hyps_dir, "epoch%d_hyps" % epoch),
                                "w", encoding=ENCODING) as hyps_f:
                          for hyp in hyps:
                              print(" ".join(hyp), file=hyps_f)
                      if epoch == 0:
                          with open(os.path.join(hyps_dir, "refs"), "w", 
                                    encoding=ENCODING) as refs_f:
                              for ref in refs:
                                  print(" ".join(ref), file=refs_f)
  
                      valid_per = utils.batch_per(hyps, refs)
  
                      epoch_str = "Epoch %d. Training LER: %f, validation LER: %f" % (
                          epoch, (train_ler_total / (batch_i + 1)), valid_ler)
                      print(epoch_str, flush=True, file=out_file)
                      if best_epoch_str is None:
                          best_epoch_str = epoch_str
  
                      # Call the callback here if it was defined
                      if epoch_callback:
                          epoch_callback({
                              "epoch": epoch,
                              "training_ler": (train_ler_total / (batch_i + 1)), # current training LER
                              "valid_ler": valid_ler, # Current validation LER
                          })
  
                      # Implement early stopping.
                      if valid_ler < best_valid_ler:
                          print("New best valid_ler", file=out_file)
                          best_valid_ler = valid_ler
                          best_epoch_str = epoch_str
                          steps_since_last_record = 0
  
                          # Save the model.
                          checkpoint_path = os.path.join(self.exp_dir, "model", "model_best.ckpt")
                          if not os.path.exists(os.path.dirname(checkpoint_path)):
                              os.mkdir(os.path.dirname(checkpoint_path))
                          saver.save(sess, checkpoint_path)
                          self.saved_model_path = checkpoint_path
  
                          # Output best hyps
                          with open(os.path.join(hyps_dir, "best_hyps"),
                                    "w", encoding=ENCODING) as hyps_f:
                              for hyp in hyps:
                                  print(" ".join(hyp), file=hyps_f)
  
                      else:
                          print("Steps since last best valid_ler: %d" % (steps_since_last_record), file=out_file)
                          steps_since_last_record += 1
                          if epoch >= max_epochs:
                              self.output_best_scores(best_epoch_str)
                              break
                          if steps_since_last_record >= early_stopping_steps:
                              if epoch >= min_epochs:
                                  # Then we've done the minimum number of epochs.
                                  if valid_ler <= max_valid_ler and ler <= max_train_ler:
                                      # Then training error has moved sufficiently
                                      # towards convergence.
                                      print("Stopping since best validation score hasn't been"
                                          " beaten in %d epochs and at least %d have been"
                                          " done. The valid ler (%d) is below %d and"
                                          " the train ler (%d) is below %d." %
                                          (early_stopping_steps, min_epochs, valid_ler,
                                          max_valid_ler, ler, max_train_ler),
                                          file=out_file, flush=True)
                                      self.output_best_scores(best_epoch_str)
                                      break
                                  else:
                                      # Keep training because we haven't achieved
                                      # convergence.
                                      continue
                              else:
                                  # Keep training because we haven't done the minimum
                                  # numper of epochs.
                                  continue
  
                  # Check we actually saved a checkpoint
                  if not self.saved_model_path:
                      raise PersephoneException(
                          "No checkpoint was saved so model evaluation cannot be performed. "
                          "This can happen if the validaion LER never converges.")
                  # Finally, run evaluation on the test set.
                  self.eval(restore_model_path=self.saved_model_path)