docs: def train_schedule(
      estimator, train_eval_iterations, single_iteration_train_steps=None,
      single_iteration_train_epochs=None, bleu_source=None, bleu_ref=None,
      bleu_threshold=None):
    """Train and evaluate model, and optionally compute model's BLEU score.
  
    **Step vs. Epoch vs. Iteration**
  
    Steps and epochs are canonical terms used in TensorFlow and general machine
    learning. They are used to describe running a single process (train/eval):
      - Step refers to running the process through a single or batch of examples.
      - Epoch refers to running the process through an entire dataset.
  
    E.g. training a dataset with 100 examples. The dataset is
    divided into 20 batches with 5 examples per batch. A single training step
    trains the model on one batch. After 20 training steps, the model will have
    trained on every batch in the dataset, or, in other words, one epoch.
  
    Meanwhile, iteration is used in this implementation to describe running
    multiple processes (training and eval).
      - A single iteration:
        1. trains the model for a specific number of steps or epochs.
        2. evaluates the model.
        3. (if source and ref files are provided) compute BLEU score.
  
    This function runs through multiple train+eval+bleu iterations.
  
    Args:
      estimator: tf.Estimator containing model to train.
      train_eval_iterations: Number of times to repeat the train+eval iteration.
      single_iteration_train_steps: Number of steps to train in one iteration.
      single_iteration_train_epochs: Number of epochs to train in one iteration.
      bleu_source: File containing text to be translated for BLEU calculation.
      bleu_ref: File containing reference translations for BLEU calculation.
      bleu_threshold: minimum BLEU score before training is stopped.
  
    Raises:
      ValueError: if both or none of single_iteration_train_steps and
        single_iteration_train_epochs were defined.
    """
    # Ensure that exactly one of single_iteration_train_steps and
    # single_iteration_train_epochs is defined.
    if single_iteration_train_steps is None:
      if single_iteration_train_epochs is None:
        raise ValueError(
            "Exactly one of single_iteration_train_steps or "
            "single_iteration_train_epochs must be defined. Both were none.")
    else:
      if single_iteration_train_epochs is not None:
        raise ValueError(
            "Exactly one of single_iteration_train_steps or "
            "single_iteration_train_epochs must be defined. Both were defined.")
  
    evaluate_bleu = bleu_source is not None and bleu_ref is not None
  
    # Print out training schedule
    print("Training schedule:")
    if single_iteration_train_epochs is not None:
      print("\t1. Train for %d epochs." % single_iteration_train_epochs)
    else:
      print("\t1. Train for %d steps." % single_iteration_train_steps)
    print("\t2. Evaluate model.")
    if evaluate_bleu:
      print("\t3. Compute BLEU score.")
      if bleu_threshold is not None:
        print("Repeat above steps until the BLEU score reaches", bleu_threshold)
    if not evaluate_bleu or bleu_threshold is None:
      print("Repeat above steps %d times." % train_eval_iterations)
  
    if evaluate_bleu:
      # Set summary writer to log bleu score.
      bleu_writer = tf.summary.FileWriter(
          os.path.join(estimator.model_dir, BLEU_DIR))
      if bleu_threshold is not None:
        # Change loop stopping condition if bleu_threshold is defined.
        train_eval_iterations = INF
  
    # Loop training/evaluation/bleu cycles
    mlperf_log.transformer_print(key=mlperf_log.TRAIN_LOOP)
    for i in xrange(train_eval_iterations):
      print("Starting iteration", i + 1)
  
      mlperf_log.transformer_print(key=mlperf_log.TRAIN_EPOCH,
                                   value=i * single_iteration_train_epochs + 1)
  
      # Train the model for single_iteration_train_steps or until the input fn
      # runs out of examples (if single_iteration_train_steps is None).
      estimator.train(dataset.train_input_fn, steps=single_iteration_train_steps)
  
      mlperf_log.transformer_print(key=mlperf_log.EVAL_START)
      eval_results = estimator.evaluate(dataset.eval_input_fn)
      print("Evaluation results (iter %d/%d):" % (i + 1, train_eval_iterations),
            eval_results)
  
      if evaluate_bleu:
        uncased_score, _ = evaluate_and_log_bleu(
            estimator, bleu_writer, bleu_source, bleu_ref)
        if bleu_threshold is not None and uncased_score > bleu_threshold:
          bleu_writer.close()
          break
        mlperf_log.transformer_print(key=mlperf_log.EVAL_TARGET, value=bleu_threshold)
        mlperf_log.transformer_print(key=mlperf_log.EVAL_ACCURACY, value=uncased_score)
      mlperf_log.transformer_print(key=mlperf_log.EVAL_STOP)