class_docstr: Class used to train a pipeline using various data splits and callbacks for evaluation purposes.
  It loops on splits that the splitter yields, and on epochs as well, to train and validate the pipeline
  with the given metrics and other callbacks.
  
  If the predicted expected output of a pipeline's prediction dact is not empty, then it will be used
  in the metrics instead of using the fed dacts' expected output. This is to allow for the use of
  autoregressive models, where the expected output is not known at the time of sending it to the model at train-time, but is known for the least at validation time as per the validation splitter.
docstr: Train a pipeline split. You probably want to use `self.train` instead, to use the validation splitter.
  If validation DACT is None, the evaluation metrics will not save validation results.
  
  It is to be noted that here, if the data container, after a prediction at train and validation time,
  has an empty expected output (of .expected_outputs of length 0 or that is None), then the
  trainer will pick the expected output of the pre-predicted data container that was fed as an input.
docstr_tok: ["Train","a","pipeline","split",".","You","probably","want","to","use","`","self.train","`","instead",",","to","use","the","validation","splitter",".","If","validation","DACT","is","None",",","the","evaluation","metrics","will","not","save","validation","results",".","It","is","to","be","noted","that","here",",","if","the","data","container",",","after","a","prediction","at","train","and","validation","time",",","has","an","empty","expected","output","(","of",".expected_outputs","of","length","0","or","that","is","None",")",",","then","the","trainer","will","pick","the","expected","output","of","the","pre-predicted","data","container","that","was","fed","as","an","input","."]
code: def train_split(
      self,
      pipeline: BaseStep,
      train_dact: DACT,
      val_dact: Optional[DACT],
      trial_split_scope: TrialSplit,
  ) -> BaseStep:
      """
      Train a pipeline split. You probably want to use `self.train` instead, to use the validation splitter.
      If validation DACT is None, the evaluation metrics will not save validation results.
  
      It is to be noted that here, if the data container, after a prediction at train and validation time,
      has an empty expected output (of .expected_outputs of length 0 or that is None), then the
      trainer will pick the expected output of the pre-predicted data container that was fed as an input.
      """
      trial_split_scope: TrialSplit = trial_split_scope.with_n_epochs(
          self.n_epochs
      )
      p: BaseStep = pipeline._copy(trial_split_scope.context, deep=True)
      p.set_hyperparams(trial_split_scope.get_hyperparams())
      context: AutoMLContext = trial_split_scope.context
  
      for _ in range(self.n_epochs):
          e = trial_split_scope.next_epoch()
  
          # Fit train
          p = p.set_train(True)
          context = context.train()
          p = p.handle_fit(train_dact.copy(), context)
  
          # Predict train & val
          p = p.set_train(False)
          context = context.validation()
          eval_dact_train = p.handle_predict(
              train_dact.without_eo(), context
          )
          eval_dact_train: DACT[
              IDT, ARG_Y_PREDICTD, ARG_Y_EXPECTED
          ] = eval_dact_train
          _has_empty_eo = eval_dact_train.expected_outputs is None or (
              hasattr(eval_dact_train.expected_outputs, "__len__")
              and len(eval_dact_train.expected_outputs) == 0
          )
          if (
              _has_empty_eo
              or self.validation_splitter.force_fixed_metric_expected_outputs
              is True
          ):
              eval_dact_train = eval_dact_train.with_eo(
                  train_dact.expected_outputs
              )
  
          if val_dact is not None:
              context = context.validation()
              eval_dact_valid = p.handle_predict(
                  val_dact.without_eo(), context
              )
              eval_dact_valid: DACT[
                  IDT, ARG_Y_PREDICTD, ARG_Y_EXPECTED
              ] = eval_dact_valid
              _has_empty_eo = (
                  eval_dact_valid.expected_outputs is None
                  or (
                      hasattr(
                          eval_dact_valid.expected_outputs, "__len__"
                      )
                      and len(eval_dact_valid.expected_outputs) == 0
                  )
              )
              if (
                  _has_empty_eo
                  or self.validation_splitter.force_fixed_metric_expected_outputs
                  is True
              ):
                  eval_dact_valid = eval_dact_valid.with_eo(
                      val_dact.expected_outputs
                  )
          else:
              eval_dact_valid = None
  
          # Log metrics / evaluate
          if self.callbacks.call(
              trial_split_scope,
              eval_dact_train,
              eval_dact_valid,
              e == self.n_epochs,
          ):
              break  # Saves stats using the '__exit__' method of managed scoped aggregates.
  
      return p
  
imports: import gc
  from copy import copy
  from typing import ContextManager, Iterator, List, Optional, Tuple
  from neuraxle.base import CX, BaseService, BaseStep, BaseStepT, ExecutionContext, ExecutionMode, ForceHandleMixin, TruncableService, _HasChildrenMixin
  from neuraxle.data_container import ARG_X_INPUTTED, IDT
  from neuraxle.data_container import DataContainer
  from neuraxle.data_container import TrainDACT
  from neuraxle.hyperparams.space import HyperparameterSpace
  from neuraxle.metaopt.callbacks import ARG_Y_EXPECTED, ARG_Y_PREDICTD, BaseCallback, CallbackList, MetricCallback, ScoringCallback
  from neuraxle.metaopt.context import AutoMLContext
  from neuraxle.metaopt.data.aggregates import Client, Project, Root, Round, Trial, TrialSplit
  from neuraxle.metaopt.data.reporting import RoundReport
  from neuraxle.metaopt.data.vanilla import DEFAULT_CLIENT, DEFAULT_PROJECT, RoundDataclass, ScopedLocation
  from neuraxle.metaopt.optimizer import BaseHyperparameterOptimizer, GridExplorationSampler, RandomSearchSampler
  from neuraxle.metaopt.repositories.repo import HyperparamsRepository
  from neuraxle.metaopt.validation import BaseValidationSplitter, ValidationSplitter
label_desc: Use image node_id 3 for calling the Trainer obj's underlying member method code with example usage: obj.train_split(pipeline, train_dact, val_dact, trial_split_scope) and returns: p