class_docstr: Class representing an ensemble of multiple forecasting models.
class_docstr_tok: ["Class","representing","an","ensemble","of","multiple","forecasting","models","."]
code: def _train_with_exog(
      self,
      train_data: TimeSeries,
      train_config: EnsembleTrainConfig = None,
      exog_data: TimeSeries = None,
  ) -> Tuple[Optional[TimeSeries], None]:
      train, valid = self.train_valid_split(train_data, train_config)
  
      per_model_train_configs = train_config.per_model_train_configs
      if per_model_train_configs is None:
          per_model_train_configs = [None] * len(self.models)
      assert len(per_model_train_configs) == len(self.models), (
          f"You must provide the same number of per-model train configs "
          f"as models, but received received {len(per_model_train_configs)} "
          f"train configs for an ensemble with {len(self.models)} models"
      )
  
      # Train individual models on the training data
      preds, errs = [], []
      eval_cfg = ForecastEvaluatorConfig(
          retrain_freq=None, horizon=self.get_max_common_horizon(train)
      )
      # TODO: parallelize me
      for i, (model, cfg) in enumerate(
          zip(self.models, per_model_train_configs)
      ):
          logger.info(
              f"Training & evaluating model {i+1}/{len(self.models)} ({type(model).__name__})..."
          )
          try:
              train_kwargs = dict(train_config=cfg)
              (train_pred, train_err), pred = ForecastEvaluator(
                  model=model, config=eval_cfg
              ).get_predict(
                  train_vals=train,
                  test_vals=valid,
                  exog_data=exog_data,
                  train_kwargs=train_kwargs,
              )
              preds.append(train_pred if valid is None else pred)
              errs.append(train_err if valid is None else None)
          except Exception:
              logger.warning(
                  f"Caught an exception while training model {i+1}/{len(self.models)} ({type(model).__name__}). "
                  f"Model will not be used. {traceback.format_exc()}"
              )
              self.combiner.set_model_used(i, False)
              preds.append(None)
              errs.append(None)
  
      # Train the combiner on the train data if we didn't use validation data.
      if valid is None:
          pred = self.train_combiner(preds, train_data)
          err = (
              None
              if any(e is None for e in errs)
              else self.combiner(errs, train_data)
          )
          return pred, err
  
      # Otherwise, train the combiner on the validation data, and re-train the models on the full data
      self.train_combiner(preds, valid)
      full_preds, full_errs = [], []
      # TODO: parallelize me
      for i, (model, used, cfg) in enumerate(
          zip(self.models, self.models_used, per_model_train_configs)
      ):
          model.reset()
          if used:
              logger.info(
                  f"Re-training model {i+1}/{len(self.models)} ({type(model).__name__}) on full data..."
              )
              pred, err = model.train(
                  train_data, train_config=cfg, exog_data=exog_data
              )
          else:
              pred, err = None, None
          full_preds.append(pred)
          full_errs.append(err)
      if any(
          used and e is None
          for used, e in zip(self.models_used, full_errs)
      ):
          err = None
      else:
          err = self.combiner(full_errs, train_data)
      return self.combiner(full_preds, train_data), err
  
imports: import logging
  import traceback
  from typing import List, Optional, Tuple, Union
  import pandas
  from merlion.evaluate.forecast import ForecastEvaluator, ForecastEvaluatorConfig
  from merlion.models.ensemble.base import EnsembleConfig, EnsembleTrainConfig, EnsembleBase
  from merlion.models.ensemble.combine import Mean
  from merlion.models.forecast.base import ForecasterBase, ForecasterExogConfig, ForecasterExogBase
  from merlion.utils.time_series import TimeSeries
label_desc: Use image node_id 7 for calling the ForecasterEnsemble obj's underlying member method code with example usage: obj._train_with_exog(train_data, train_config, exog_data) and returns: self,  err, pred,  err