autogluon.timeseries 1.4.1b20250907__py3-none-any.whl → 1.5.1b20260122__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of autogluon.timeseries might be problematic. Click here for more details.

Files changed (95) hide show
  1. autogluon/timeseries/configs/hyperparameter_presets.py +13 -28
  2. autogluon/timeseries/configs/predictor_presets.py +23 -39
  3. autogluon/timeseries/dataset/ts_dataframe.py +97 -86
  4. autogluon/timeseries/learner.py +70 -35
  5. autogluon/timeseries/metrics/__init__.py +4 -4
  6. autogluon/timeseries/metrics/abstract.py +8 -8
  7. autogluon/timeseries/metrics/point.py +9 -9
  8. autogluon/timeseries/metrics/quantile.py +5 -5
  9. autogluon/timeseries/metrics/utils.py +4 -4
  10. autogluon/timeseries/models/__init__.py +4 -1
  11. autogluon/timeseries/models/abstract/abstract_timeseries_model.py +52 -50
  12. autogluon/timeseries/models/abstract/model_trial.py +2 -1
  13. autogluon/timeseries/models/abstract/tunable.py +8 -8
  14. autogluon/timeseries/models/autogluon_tabular/mlforecast.py +58 -62
  15. autogluon/timeseries/models/autogluon_tabular/per_step.py +27 -16
  16. autogluon/timeseries/models/autogluon_tabular/transforms.py +11 -9
  17. autogluon/timeseries/models/chronos/__init__.py +2 -1
  18. autogluon/timeseries/models/chronos/chronos2.py +395 -0
  19. autogluon/timeseries/models/chronos/model.py +127 -89
  20. autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +69 -37
  21. autogluon/timeseries/models/ensemble/__init__.py +36 -2
  22. autogluon/timeseries/models/ensemble/abstract.py +14 -46
  23. autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
  24. autogluon/timeseries/models/ensemble/array_based/abstract.py +240 -0
  25. autogluon/timeseries/models/ensemble/array_based/models.py +185 -0
  26. autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +12 -0
  27. autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +88 -0
  28. autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
  29. autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +94 -0
  30. autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +107 -0
  31. autogluon/timeseries/models/ensemble/{greedy.py → ensemble_selection.py} +41 -61
  32. autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
  33. autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
  34. autogluon/timeseries/models/ensemble/weighted/abstract.py +45 -0
  35. autogluon/timeseries/models/ensemble/{basic.py → weighted/basic.py} +25 -22
  36. autogluon/timeseries/models/ensemble/weighted/greedy.py +64 -0
  37. autogluon/timeseries/models/gluonts/abstract.py +32 -31
  38. autogluon/timeseries/models/gluonts/dataset.py +11 -11
  39. autogluon/timeseries/models/gluonts/models.py +0 -7
  40. autogluon/timeseries/models/local/__init__.py +0 -7
  41. autogluon/timeseries/models/local/abstract_local_model.py +15 -18
  42. autogluon/timeseries/models/local/naive.py +2 -2
  43. autogluon/timeseries/models/local/npts.py +7 -1
  44. autogluon/timeseries/models/local/statsforecast.py +13 -13
  45. autogluon/timeseries/models/multi_window/multi_window_model.py +39 -24
  46. autogluon/timeseries/models/registry.py +3 -4
  47. autogluon/timeseries/models/toto/__init__.py +3 -0
  48. autogluon/timeseries/models/toto/_internal/__init__.py +9 -0
  49. autogluon/timeseries/models/toto/_internal/backbone/__init__.py +3 -0
  50. autogluon/timeseries/models/toto/_internal/backbone/attention.py +196 -0
  51. autogluon/timeseries/models/toto/_internal/backbone/backbone.py +262 -0
  52. autogluon/timeseries/models/toto/_internal/backbone/distribution.py +70 -0
  53. autogluon/timeseries/models/toto/_internal/backbone/kvcache.py +136 -0
  54. autogluon/timeseries/models/toto/_internal/backbone/rope.py +89 -0
  55. autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
  56. autogluon/timeseries/models/toto/_internal/backbone/scaler.py +305 -0
  57. autogluon/timeseries/models/toto/_internal/backbone/transformer.py +333 -0
  58. autogluon/timeseries/models/toto/_internal/dataset.py +165 -0
  59. autogluon/timeseries/models/toto/_internal/forecaster.py +423 -0
  60. autogluon/timeseries/models/toto/dataloader.py +108 -0
  61. autogluon/timeseries/models/toto/hf_pretrained_model.py +200 -0
  62. autogluon/timeseries/models/toto/model.py +249 -0
  63. autogluon/timeseries/predictor.py +541 -162
  64. autogluon/timeseries/regressor.py +27 -30
  65. autogluon/timeseries/splitter.py +3 -27
  66. autogluon/timeseries/trainer/ensemble_composer.py +444 -0
  67. autogluon/timeseries/trainer/model_set_builder.py +9 -9
  68. autogluon/timeseries/trainer/prediction_cache.py +16 -16
  69. autogluon/timeseries/trainer/trainer.py +300 -279
  70. autogluon/timeseries/trainer/utils.py +17 -0
  71. autogluon/timeseries/transforms/covariate_scaler.py +8 -8
  72. autogluon/timeseries/transforms/target_scaler.py +15 -15
  73. autogluon/timeseries/utils/constants.py +10 -0
  74. autogluon/timeseries/utils/datetime/lags.py +1 -3
  75. autogluon/timeseries/utils/datetime/seasonality.py +1 -3
  76. autogluon/timeseries/utils/features.py +31 -14
  77. autogluon/timeseries/utils/forecast.py +6 -7
  78. autogluon/timeseries/utils/timer.py +173 -0
  79. autogluon/timeseries/version.py +1 -1
  80. autogluon.timeseries-1.5.1b20260122-py3.11-nspkg.pth +1 -0
  81. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info}/METADATA +39 -22
  82. autogluon_timeseries-1.5.1b20260122.dist-info/RECORD +103 -0
  83. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info}/WHEEL +1 -1
  84. autogluon/timeseries/evaluator.py +0 -6
  85. autogluon/timeseries/models/chronos/pipeline/__init__.py +0 -10
  86. autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
  87. autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -544
  88. autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -580
  89. autogluon.timeseries-1.4.1b20250907-py3.9-nspkg.pth +0 -1
  90. autogluon.timeseries-1.4.1b20250907.dist-info/RECORD +0 -75
  91. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info/licenses}/LICENSE +0 -0
  92. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info/licenses}/NOTICE +0 -0
  93. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info}/namespace_packages.txt +0 -0
  94. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info}/top_level.txt +0 -0
  95. {autogluon.timeseries-1.4.1b20250907.dist-info → autogluon_timeseries-1.5.1b20260122.dist-info}/zip-safe +0 -0
@@ -5,7 +5,7 @@ import time
5
5
  import traceback
6
6
  from collections import defaultdict
7
7
  from pathlib import Path
8
- from typing import Any, Literal, Optional, Type, Union
8
+ from typing import Any, Literal
9
9
 
10
10
  import networkx as nx
11
11
  import numpy as np
@@ -20,18 +20,20 @@ from autogluon.core.utils.savers import save_pkl
20
20
  from autogluon.timeseries import TimeSeriesDataFrame
21
21
  from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_metric
22
22
  from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel, TimeSeriesModelBase
23
- from autogluon.timeseries.models.ensemble import AbstractTimeSeriesEnsembleModel, GreedyEnsemble
23
+ from autogluon.timeseries.models.ensemble import AbstractTimeSeriesEnsembleModel
24
24
  from autogluon.timeseries.models.multi_window import MultiWindowBacktestingModel
25
25
  from autogluon.timeseries.splitter import AbstractWindowSplitter, ExpandingWindowSplitter
26
+ from autogluon.timeseries.trainer.ensemble_composer import EnsembleComposer, validate_ensemble_hyperparameters
26
27
  from autogluon.timeseries.utils.features import (
27
28
  ConstantReplacementFeatureImportanceTransform,
28
29
  CovariateMetadata,
29
30
  PermutationFeatureImportanceTransform,
30
31
  )
31
- from autogluon.timeseries.utils.warning_filters import disable_tqdm, warning_filter
32
+ from autogluon.timeseries.utils.warning_filters import disable_tqdm
32
33
 
33
34
  from .model_set_builder import TrainableModelSetBuilder, contains_searchspace
34
35
  from .prediction_cache import PredictionCache, get_prediction_cache
36
+ from .utils import log_scores_and_times
35
37
 
36
38
  logger = logging.getLogger("autogluon.timeseries.trainer")
37
39
 
@@ -45,16 +47,16 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
45
47
  self,
46
48
  path: str,
47
49
  prediction_length: int = 1,
48
- eval_metric: Union[str, TimeSeriesScorer, None] = None,
50
+ eval_metric: str | TimeSeriesScorer | None = None,
49
51
  save_data: bool = True,
50
52
  skip_model_selection: bool = False,
51
53
  enable_ensemble: bool = True,
52
54
  verbosity: int = 2,
53
- val_splitter: Optional[AbstractWindowSplitter] = None,
54
- refit_every_n_windows: Optional[int] = 1,
55
+ num_val_windows: tuple[int, ...] = (1,),
56
+ val_step_size: int | None = None,
57
+ refit_every_n_windows: int | None = 1,
55
58
  # TODO: Set cache_predictions=False by default once all models in default presets have a reasonable inference speed
56
59
  cache_predictions: bool = True,
57
- ensemble_model_type: Optional[Type] = None,
58
60
  **kwargs,
59
61
  ):
60
62
  super().__init__(
@@ -71,13 +73,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
71
73
  self.skip_model_selection = skip_model_selection
72
74
  # Ensemble cannot be fit if val_scores are not computed
73
75
  self.enable_ensemble = enable_ensemble and not skip_model_selection
74
- if ensemble_model_type is None:
75
- ensemble_model_type = GreedyEnsemble
76
- else:
76
+ if kwargs.get("ensemble_model_type") is not None:
77
77
  logger.warning(
78
- "Using a custom `ensemble_model_type` is experimental functionality that may break in future versions."
78
+ "Using a custom `ensemble_model_type` is no longer supported. Use the `ensemble_hyperparameters` "
79
+ "argument to `fit` instead."
79
80
  )
80
- self.ensemble_model_type: Type[AbstractTimeSeriesEnsembleModel] = ensemble_model_type
81
81
 
82
82
  self.verbosity = verbosity
83
83
 
@@ -86,10 +86,16 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
86
86
  self.model_refit_map = {}
87
87
 
88
88
  self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
89
- if val_splitter is None:
90
- val_splitter = ExpandingWindowSplitter(prediction_length=self.prediction_length)
91
- assert isinstance(val_splitter, AbstractWindowSplitter), "val_splitter must be of type AbstractWindowSplitter"
92
- self.val_splitter = val_splitter
89
+
90
+ self.num_val_windows = num_val_windows
91
+
92
+ # Validate num_val_windows
93
+ if len(self.num_val_windows) == 0:
94
+ raise ValueError("num_val_windows cannot be empty")
95
+ if not all(isinstance(w, int) and w > 0 for w in self.num_val_windows):
96
+ raise ValueError(f"num_val_windows must contain only positive integers, got {self.num_val_windows}")
97
+
98
+ self.val_step_size = val_step_size
93
99
  self.refit_every_n_windows = refit_every_n_windows
94
100
  self.hpo_results = {}
95
101
 
@@ -112,14 +118,14 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
112
118
  path = os.path.join(self.path_data, "train.pkl")
113
119
  return load_pkl.load(path=path)
114
120
 
115
- def load_val_data(self) -> Optional[TimeSeriesDataFrame]:
121
+ def load_val_data(self) -> TimeSeriesDataFrame | None:
116
122
  path = os.path.join(self.path_data, "val.pkl")
117
123
  if os.path.exists(path):
118
124
  return load_pkl.load(path=path)
119
125
  else:
120
126
  return None
121
127
 
122
- def load_data(self) -> tuple[TimeSeriesDataFrame, Optional[TimeSeriesDataFrame]]:
128
+ def load_data(self) -> tuple[TimeSeriesDataFrame, TimeSeriesDataFrame | None]:
123
129
  train_data = self.load_train_data()
124
130
  val_data = self.load_val_data()
125
131
  return train_data, val_data
@@ -142,7 +148,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
142
148
  def _add_model(
143
149
  self,
144
150
  model: TimeSeriesModelBase,
145
- base_models: Optional[list[str]] = None,
151
+ base_models: list[str] | None = None,
146
152
  ):
147
153
  """Add a model to the model graph of the trainer. If the model is an ensemble, also add
148
154
  information about dependencies to the model graph (list of models specified via ``base_models``).
@@ -174,8 +180,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
174
180
  for base_model in base_models:
175
181
  self.model_graph.add_edge(base_model, model.name)
176
182
 
177
- def _get_model_levels(self) -> dict[str, int]:
178
- """Get a dictionary mapping each model to their level in the model graph"""
183
+ def _get_model_layers(self) -> dict[str, int]:
184
+ """Get a dictionary mapping each model to their layer in the model graph"""
179
185
 
180
186
  # get nodes without a parent
181
187
  rootset = set(self.model_graph.nodes)
@@ -188,14 +194,14 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
188
194
  for dest_node in paths_to:
189
195
  paths_from[dest_node][source_node] = paths_to[dest_node]
190
196
 
191
- # determine levels
192
- levels = {}
197
+ # determine layers
198
+ layers = {}
193
199
  for n in paths_from:
194
- levels[n] = max(paths_from[n].get(src, 0) for src in rootset)
200
+ layers[n] = max(paths_from[n].get(src, 0) for src in rootset)
195
201
 
196
- return levels
202
+ return layers
197
203
 
198
- def get_models_attribute_dict(self, attribute: str, models: Optional[list[str]] = None) -> dict[str, Any]:
204
+ def get_models_attribute_dict(self, attribute: str, models: list[str] | None = None) -> dict[str, Any]:
199
205
  """Get an attribute from the `model_graph` for each of the model names
200
206
  specified. If `models` is none, the attribute will be returned for all models"""
201
207
  results = {}
@@ -213,25 +219,25 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
213
219
  if len(models) == 1:
214
220
  return models[0]
215
221
  model_performances = self.get_models_attribute_dict(attribute="val_score")
216
- model_levels = self._get_model_levels()
217
- model_name_score_level_list = [
218
- (m, model_performances[m], model_levels.get(m, 0)) for m in models if model_performances[m] is not None
222
+ model_layers = self._get_model_layers()
223
+ model_name_score_layer_list = [
224
+ (m, model_performances[m], model_layers.get(m, 0)) for m in models if model_performances[m] is not None
219
225
  ]
220
226
 
221
- if not model_name_score_level_list:
227
+ if not model_name_score_layer_list:
222
228
  raise ValueError("No fitted models have validation scores computed.")
223
229
 
224
230
  # rank models in terms of validation score. if two models have the same validation score,
225
- # rank them by their level in the model graph (lower level models are preferred).
231
+ # rank them by their layer in the model graph (lower layer models are preferred).
226
232
  return max(
227
- model_name_score_level_list,
228
- key=lambda mns: (mns[1], -mns[2]), # (score, -level)
233
+ model_name_score_layer_list,
234
+ key=lambda mns: (mns[1], -mns[2]), # (score, -layer)
229
235
  )[0]
230
236
 
231
- def get_model_names(self, level: Optional[int] = None) -> list[str]:
237
+ def get_model_names(self, layer: int | None = None) -> list[str]:
232
238
  """Get model names that are registered in the model graph"""
233
- if level is not None:
234
- return list(node for node, l in self._get_model_levels().items() if l == level) # noqa: E741
239
+ if layer is not None:
240
+ return list(node for node, l in self._get_model_layers().items() if l == layer) # noqa: E741
235
241
  return list(self.model_graph.nodes)
236
242
 
237
243
  def get_info(self, include_model_info: bool = False) -> dict[str, Any]:
@@ -259,32 +265,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
259
265
 
260
266
  return info
261
267
 
262
- def _train_single(
263
- self,
264
- train_data: TimeSeriesDataFrame,
265
- model: AbstractTimeSeriesModel,
266
- val_data: Optional[TimeSeriesDataFrame] = None,
267
- time_limit: Optional[float] = None,
268
- ) -> AbstractTimeSeriesModel:
269
- """Train the single model and return the model object that was fitted. This method
270
- does not save the resulting model."""
271
- model.fit(
272
- train_data=train_data,
273
- val_data=val_data,
274
- time_limit=time_limit,
275
- verbosity=self.verbosity,
276
- val_splitter=self.val_splitter,
277
- refit_every_n_windows=self.refit_every_n_windows,
278
- )
279
- return model
280
-
281
268
  def tune_model_hyperparameters(
282
269
  self,
283
270
  model: AbstractTimeSeriesModel,
284
271
  train_data: TimeSeriesDataFrame,
285
- time_limit: Optional[float] = None,
286
- val_data: Optional[TimeSeriesDataFrame] = None,
287
- hyperparameter_tune_kwargs: Union[str, dict] = "auto",
272
+ time_limit: float | None = None,
273
+ val_data: TimeSeriesDataFrame | None = None,
274
+ hyperparameter_tune_kwargs: str | dict = "auto",
288
275
  ):
289
276
  default_num_trials = None
290
277
  if time_limit is None and (
@@ -300,7 +287,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
300
287
  hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
301
288
  time_limit=time_limit,
302
289
  default_num_trials=default_num_trials,
303
- val_splitter=self.val_splitter,
290
+ val_splitter=self._get_val_splitter(use_val_data=val_data is not None),
304
291
  refit_every_n_windows=self.refit_every_n_windows,
305
292
  )
306
293
  total_tuning_time = time.time() - tuning_start_time
@@ -310,11 +297,21 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
310
297
  # add each of the trained HPO configurations to the trained models
311
298
  for model_hpo_name, model_info in hpo_models.items():
312
299
  model_path = os.path.join(self.path, model_info["path"])
300
+
313
301
  # Only load model configurations that didn't fail
314
- if Path(model_path).exists():
315
- model_hpo = self.load_model(model_hpo_name, path=model_path, model_type=type(model))
316
- self._add_model(model_hpo)
317
- model_names_trained.append(model_hpo.name)
302
+ if not Path(model_path).exists():
303
+ continue
304
+
305
+ model_hpo = self.load_model(model_hpo_name, path=model_path, model_type=type(model))
306
+
307
+ # override validation score to align evaluations on the final ensemble layer's window
308
+ if isinstance(model_hpo, MultiWindowBacktestingModel):
309
+ model_hpo.val_score = float(
310
+ np.mean([info["val_score"] for info in model_hpo.info_per_val_window[-self.num_val_windows[-1] :]])
311
+ )
312
+
313
+ self._add_model(model_hpo)
314
+ model_names_trained.append(model_hpo.name)
318
315
 
319
316
  logger.info(f"\tTrained {len(model_names_trained)} models while tuning {model.name}.")
320
317
 
@@ -335,8 +332,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
335
332
  self,
336
333
  train_data: TimeSeriesDataFrame,
337
334
  model: AbstractTimeSeriesModel,
338
- val_data: Optional[TimeSeriesDataFrame] = None,
339
- time_limit: Optional[float] = None,
335
+ val_data: TimeSeriesDataFrame | None = None,
336
+ time_limit: float | None = None,
340
337
  ) -> list[str]:
341
338
  """Fit and save the given model on given training and validation data and save the trained model.
342
339
 
@@ -353,18 +350,39 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
353
350
  logger.info(f"\tSkipping {model.name} due to lack of time remaining.")
354
351
  return model_names_trained
355
352
 
356
- model = self._train_single(train_data, model, val_data=val_data, time_limit=time_limit)
353
+ model.fit(
354
+ train_data=train_data,
355
+ val_data=None if isinstance(model, MultiWindowBacktestingModel) else val_data,
356
+ time_limit=time_limit,
357
+ verbosity=self.verbosity,
358
+ val_splitter=self._get_val_splitter(use_val_data=val_data is not None),
359
+ refit_every_n_windows=self.refit_every_n_windows,
360
+ )
361
+
357
362
  fit_end_time = time.time()
358
363
  model.fit_time = model.fit_time or (fit_end_time - fit_start_time)
359
364
 
360
365
  if time_limit is not None:
361
366
  time_limit = time_limit - (fit_end_time - fit_start_time)
362
- if val_data is not None and not self.skip_model_selection:
367
+ if val_data is not None:
363
368
  model.score_and_cache_oof(
364
369
  val_data, store_val_score=True, store_predict_time=True, time_limit=time_limit
365
370
  )
366
371
 
367
- self._log_scores_and_times(model.val_score, model.fit_time, model.predict_time)
372
+ # by default, MultiWindowBacktestingModel computes validation score on all windows. However,
373
+ # when doing multi-layer stacking, the trainer only scores on the windows of the last layer.
374
+ # we override the val_score to align scores.
375
+ if isinstance(model, MultiWindowBacktestingModel):
376
+ model.val_score = float(
377
+ np.mean([info["val_score"] for info in model.info_per_val_window[-self.num_val_windows[-1] :]])
378
+ )
379
+
380
+ log_scores_and_times(
381
+ val_score=model.val_score,
382
+ fit_time=model.fit_time,
383
+ predict_time=model.predict_time,
384
+ eval_metric_name=self.eval_metric.name_with_sign,
385
+ )
368
386
 
369
387
  self.save_model(model=model)
370
388
  except TimeLimitExceeded:
@@ -380,34 +398,64 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
380
398
 
381
399
  return model_names_trained
382
400
 
383
- def _log_scores_and_times(
384
- self,
385
- val_score: Optional[float] = None,
386
- fit_time: Optional[float] = None,
387
- predict_time: Optional[float] = None,
388
- ):
389
- if val_score is not None:
390
- logger.info(f"\t{val_score:<7.4f}".ljust(15) + f"= Validation score ({self.eval_metric.name_with_sign})")
391
- if fit_time is not None:
392
- logger.info(f"\t{fit_time:<7.2f} s".ljust(15) + "= Training runtime")
393
- if predict_time is not None:
394
- logger.info(f"\t{predict_time:<7.2f} s".ljust(15) + "= Validation (prediction) runtime")
395
-
396
- def _train_multi(
401
+ def fit(
397
402
  self,
398
403
  train_data: TimeSeriesDataFrame,
399
- hyperparameters: Union[str, dict],
400
- val_data: Optional[TimeSeriesDataFrame] = None,
401
- hyperparameter_tune_kwargs: Optional[Union[str, dict]] = None,
402
- excluded_model_types: Optional[list[str]] = None,
403
- time_limit: Optional[float] = None,
404
- random_seed: Optional[int] = None,
405
- ) -> list[str]:
404
+ hyperparameters: str | dict[Any, dict],
405
+ val_data: TimeSeriesDataFrame | None = None,
406
+ ensemble_hyperparameters: dict | list[dict] | None = None,
407
+ hyperparameter_tune_kwargs: str | dict | None = None,
408
+ excluded_model_types: list[str] | None = None,
409
+ time_limit: float | None = None,
410
+ random_seed: int | None = None,
411
+ ):
412
+ """Fit a set of timeseries models specified by the `hyperparameters`
413
+ dictionary that maps model names to their specified hyperparameters.
414
+
415
+ Parameters
416
+ ----------
417
+ train_data
418
+ Training data for fitting time series timeseries models.
419
+ hyperparameters
420
+ A dictionary mapping selected model names, model classes or model factory to hyperparameter
421
+ settings. Model names should be present in `trainer.presets.DEFAULT_MODEL_NAMES`. Optionally,
422
+ the user may provide one of "default", "light" and "very_light" to specify presets.
423
+ val_data
424
+ Optional validation data set to report validation scores on.
425
+ ensemble_hyperparameters
426
+ A dictionary mapping ensemble names to their specified hyperparameters. Ensemble names
427
+ should be defined in the models.ensemble namespace. defaults to `{"GreedyEnsemble": {}}`
428
+ which only fits a greedy weighted ensemble with default hyperparameters. Providing an
429
+ empty dictionary disables ensemble training.
430
+ hyperparameter_tune_kwargs
431
+ Args for hyperparameter tuning
432
+ excluded_model_types
433
+ Names of models that should not be trained, even if listed in `hyperparameters`.
434
+ time_limit
435
+ Time limit for training
436
+ random_seed
437
+ Random seed that will be set to each model during training
438
+ """
406
439
  logger.info(f"\nStarting training. Start time is {time.strftime('%Y-%m-%d %H:%M:%S')}")
407
440
 
441
+ # Handle ensemble hyperparameters
442
+ if ensemble_hyperparameters is None:
443
+ ensemble_hyperparameters = [{"GreedyEnsemble": {}}]
444
+ if isinstance(ensemble_hyperparameters, dict):
445
+ ensemble_hyperparameters = [ensemble_hyperparameters]
446
+ validate_ensemble_hyperparameters(ensemble_hyperparameters)
447
+
408
448
  time_start = time.time()
409
449
  hyperparameters = copy.deepcopy(hyperparameters)
410
450
 
451
+ if val_data is not None:
452
+ if self.num_val_windows[-1] != 1:
453
+ raise ValueError(
454
+ f"When val_data is provided, the last element of num_val_windows must be 1, "
455
+ f"got {self.num_val_windows[-1]}"
456
+ )
457
+ multi_window = self._get_val_splitter(use_val_data=val_data is not None).num_val_windows > 0
458
+
411
459
  if self.save_data and not self.is_data_saved:
412
460
  self.save_train_data(train_data)
413
461
  if val_data is not None:
@@ -418,7 +466,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
418
466
  hyperparameters=hyperparameters,
419
467
  hyperparameter_tune=hyperparameter_tune_kwargs is not None, # TODO: remove hyperparameter_tune
420
468
  freq=train_data.freq,
421
- multi_window=self.val_splitter.num_val_windows > 0,
469
+ multi_window=multi_window,
422
470
  excluded_model_types=excluded_model_types,
423
471
  )
424
472
 
@@ -447,7 +495,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
447
495
  time_reserved_for_ensemble = min(
448
496
  self.max_ensemble_time_limit, time_left / (num_base_models - i + 1)
449
497
  )
450
- logger.debug(f"Reserving {time_reserved_for_ensemble:.1f}s for ensemble")
451
498
  else:
452
499
  time_reserved_for_ensemble = 0.0
453
500
  time_left_for_model = (time_left - time_reserved_for_ensemble) / (num_base_models - i)
@@ -487,42 +534,16 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
487
534
  train_data, model=model, val_data=val_data, time_limit=time_left_for_model
488
535
  )
489
536
 
490
- if self.enable_ensemble:
491
- models_available_for_ensemble = self.get_model_names(level=0)
492
-
493
- time_left_for_ensemble = None
494
- if time_limit is not None:
495
- time_left_for_ensemble = time_limit - (time.time() - time_start)
496
-
497
- if time_left_for_ensemble is not None and time_left_for_ensemble <= 0:
498
- logger.info(
499
- "Not fitting ensemble due to lack of time remaining. "
500
- f"Time left: {time_left_for_ensemble:.1f} seconds"
501
- )
502
- elif len(models_available_for_ensemble) <= 1:
503
- logger.info(
504
- "Not fitting ensemble as "
505
- + (
506
- "no models were successfully trained."
507
- if not models_available_for_ensemble
508
- else "only 1 model was trained."
509
- )
510
- )
511
- else:
512
- try:
513
- model_names_trained.append(
514
- self.fit_ensemble(
515
- data_per_window=self._get_ensemble_oof_data(train_data=train_data, val_data=val_data),
516
- model_names=models_available_for_ensemble,
517
- time_limit=time_left_for_ensemble,
518
- )
519
- )
520
- except Exception as err: # noqa
521
- logger.error(
522
- "\tWarning: Exception caused ensemble to fail during training... Skipping this model."
523
- )
524
- logger.error(f"\t{err}")
525
- logger.debug(traceback.format_exc())
537
+ if self.enable_ensemble and ensemble_hyperparameters:
538
+ model_names = self.get_model_names(layer=0)
539
+ ensemble_names = self._fit_ensembles(
540
+ data_per_window=self._get_validation_windows(train_data, val_data),
541
+ predictions_per_window=self._get_base_model_predictions(model_names),
542
+ time_limit=None if time_limit is None else time_limit - (time.time() - time_start),
543
+ ensemble_hyperparameters=ensemble_hyperparameters,
544
+ num_windows_per_layer=self.num_val_windows,
545
+ )
546
+ model_names_trained.extend(ensemble_names)
526
547
 
527
548
  logger.info(f"Training complete. Models trained: {model_names_trained}")
528
549
  logger.info(f"Total runtime: {time.time() - time_start:.2f} s")
@@ -536,82 +557,64 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
536
557
 
537
558
  return model_names_trained
538
559
 
539
- def _get_ensemble_oof_data(
540
- self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
541
- ) -> list[TimeSeriesDataFrame]:
542
- if val_data is None:
543
- return [val_fold for _, val_fold in self.val_splitter.split(train_data)]
544
- else:
545
- return [val_data]
546
-
547
- def _get_ensemble_model_name(self) -> str:
548
- """Ensure we don't have name collisions in the ensemble model name"""
549
- ensemble_name = "WeightedEnsemble"
550
- increment = 1
551
- while ensemble_name in self._get_banned_model_names():
552
- increment += 1
553
- ensemble_name = f"WeightedEnsemble_{increment}"
554
- return ensemble_name
555
-
556
- def fit_ensemble(
560
+ def _fit_ensembles(
557
561
  self,
562
+ *,
558
563
  data_per_window: list[TimeSeriesDataFrame],
559
- model_names: list[str],
560
- time_limit: Optional[float] = None,
561
- ) -> str:
562
- logger.info("Fitting simple weighted ensemble.")
563
-
564
- predictions_per_window: dict[str, list[TimeSeriesDataFrame]] = {}
565
- base_model_scores = self.get_models_attribute_dict(attribute="val_score", models=self.get_model_names(0))
566
-
567
- for model_name in model_names:
568
- predictions_per_window[model_name] = self._get_model_oof_predictions(model_name=model_name)
569
-
570
- time_start = time.time()
571
- ensemble = self.ensemble_model_type(
572
- name=self._get_ensemble_model_name(),
564
+ predictions_per_window: dict[str, list[TimeSeriesDataFrame]],
565
+ time_limit: float | None,
566
+ ensemble_hyperparameters: list[dict],
567
+ num_windows_per_layer: tuple[int, ...],
568
+ ) -> list[str]:
569
+ ensemble_composer = EnsembleComposer(
570
+ path=self.path,
571
+ prediction_length=self.prediction_length,
573
572
  eval_metric=self.eval_metric,
574
573
  target=self.target,
575
- prediction_length=self.prediction_length,
576
- path=self.path,
577
- freq=data_per_window[0].freq,
574
+ ensemble_hyperparameters=ensemble_hyperparameters,
575
+ num_windows_per_layer=num_windows_per_layer,
578
576
  quantile_levels=self.quantile_levels,
579
- covariate_metadata=self.covariate_metadata,
577
+ model_graph=self.model_graph,
578
+ ).fit(
579
+ data_per_window=data_per_window,
580
+ predictions_per_window=predictions_per_window,
581
+ time_limit=time_limit,
580
582
  )
581
- with warning_filter():
582
- ensemble.fit(
583
- predictions_per_window=predictions_per_window,
584
- data_per_window=data_per_window,
585
- model_scores=base_model_scores,
586
- time_limit=time_limit,
587
- )
588
- ensemble.fit_time = time.time() - time_start
589
-
590
- predict_time = 0
591
- for m in ensemble.model_names:
592
- predict_time += self.get_model_attribute(model=m, attribute="predict_time")
593
- ensemble.predict_time = predict_time
594
-
595
- score_per_fold = []
596
- for window_idx, data in enumerate(data_per_window):
597
- predictions = ensemble.predict({n: predictions_per_window[n][window_idx] for n in ensemble.model_names})
598
- score_per_fold.append(self._score_with_predictions(data, predictions))
599
- ensemble.val_score = float(np.mean(score_per_fold, dtype=np.float64))
600
-
601
- self._log_scores_and_times(
602
- val_score=ensemble.val_score,
603
- fit_time=ensemble.fit_time,
604
- predict_time=ensemble.predict_time,
583
+
584
+ ensembles_trained = []
585
+ for _, model, base_models in ensemble_composer.iter_ensembles():
586
+ self._add_model(model=model, base_models=base_models)
587
+ self.save_model(model=model)
588
+ ensembles_trained.append(model.name)
589
+
590
+ return ensembles_trained
591
+
592
+ def _get_validation_windows(self, train_data: TimeSeriesDataFrame, val_data: TimeSeriesDataFrame | None):
593
+ train_splitter = self._get_val_splitter(use_val_data=val_data is not None)
594
+ return [val_fold for _, val_fold in train_splitter.split(train_data)] + (
595
+ [] if val_data is None else [val_data]
596
+ )
597
+
598
+ def _get_val_splitter(self, use_val_data: bool = False) -> AbstractWindowSplitter:
599
+ num_windows_from_train = sum(self.num_val_windows[:-1]) if use_val_data else sum(self.num_val_windows)
600
+ return ExpandingWindowSplitter(
601
+ prediction_length=self.prediction_length,
602
+ num_val_windows=num_windows_from_train,
603
+ val_step_size=self.val_step_size,
605
604
  )
606
- self._add_model(model=ensemble, base_models=ensemble.model_names)
607
- self.save_model(model=ensemble)
608
- return ensemble.name
605
+
606
+ def _get_base_model_predictions(self, model_names: list[str]) -> dict[str, list[TimeSeriesDataFrame]]:
607
+ """Get base model predictions for ensemble training / inference."""
608
+ predictions_per_window = {}
609
+ for model_name in model_names:
610
+ predictions_per_window[model_name] = self._get_model_oof_predictions(model_name)
611
+ return predictions_per_window
609
612
 
610
613
  def leaderboard(
611
614
  self,
612
- data: Optional[TimeSeriesDataFrame] = None,
615
+ data: TimeSeriesDataFrame | None = None,
613
616
  extra_info: bool = False,
614
- extra_metrics: Optional[list[Union[str, TimeSeriesScorer]]] = None,
617
+ extra_metrics: list[str | TimeSeriesScorer] | None = None,
615
618
  use_cache: bool = True,
616
619
  ) -> pd.DataFrame:
617
620
  logger.debug("Generating leaderboard for all models trained")
@@ -701,7 +704,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
701
704
  return df[explicit_column_order]
702
705
 
703
706
  def persist(
704
- self, model_names: Union[Literal["all", "best"], list[str]] = "all", with_ancestors: bool = False
707
+ self, model_names: Literal["all", "best"] | list[str] = "all", with_ancestors: bool = False
705
708
  ) -> list[str]:
706
709
  if model_names == "all":
707
710
  model_names = self.get_model_names()
@@ -726,7 +729,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
726
729
 
727
730
  return model_names
728
731
 
729
- def unpersist(self, model_names: Union[Literal["all"], list[str]] = "all") -> list[str]:
732
+ def unpersist(self, model_names: Literal["all"] | list[str] = "all") -> list[str]:
730
733
  if model_names == "all":
731
734
  model_names = list(self.models.keys())
732
735
  if not isinstance(model_names, list):
@@ -738,9 +741,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
738
741
  unpersisted_models.append(model)
739
742
  return unpersisted_models
740
743
 
741
- def _get_model_for_prediction(
742
- self, model: Optional[Union[str, TimeSeriesModelBase]] = None, verbose: bool = True
743
- ) -> str:
744
+ def _get_model_for_prediction(self, model: str | TimeSeriesModelBase | None = None, verbose: bool = True) -> str:
744
745
  """Given an optional identifier or model object, return the name of the model with which to predict.
745
746
 
746
747
  If the model is not provided, this method will default to the best model according to the validation score.
@@ -766,10 +767,10 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
766
767
  def predict(
767
768
  self,
768
769
  data: TimeSeriesDataFrame,
769
- known_covariates: Optional[TimeSeriesDataFrame] = None,
770
- model: Optional[Union[str, TimeSeriesModelBase]] = None,
770
+ known_covariates: TimeSeriesDataFrame | None = None,
771
+ model: str | TimeSeriesModelBase | None = None,
771
772
  use_cache: bool = True,
772
- random_seed: Optional[int] = None,
773
+ random_seed: int | None = None,
773
774
  ) -> TimeSeriesDataFrame:
774
775
  model_name = self._get_model_for_prediction(model)
775
776
  model_pred_dict, _ = self.get_model_pred_dict(
@@ -784,7 +785,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
784
785
  raise ValueError(f"Model {model_name} failed to predict. Please check the model's logs.")
785
786
  return predictions
786
787
 
787
- def _get_eval_metric(self, metric: Union[str, TimeSeriesScorer, None]) -> TimeSeriesScorer:
788
+ def _get_eval_metric(self, metric: str | TimeSeriesScorer | None) -> TimeSeriesScorer:
788
789
  if metric is None:
789
790
  return self.eval_metric
790
791
  else:
@@ -799,7 +800,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
799
800
  self,
800
801
  data: TimeSeriesDataFrame,
801
802
  predictions: TimeSeriesDataFrame,
802
- metric: Union[str, TimeSeriesScorer, None] = None,
803
+ metric: str | TimeSeriesScorer | None = None,
803
804
  ) -> float:
804
805
  """Compute the score measuring how well the predictions align with the data."""
805
806
  return self._get_eval_metric(metric).score(
@@ -811,8 +812,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
811
812
  def score(
812
813
  self,
813
814
  data: TimeSeriesDataFrame,
814
- model: Optional[Union[str, TimeSeriesModelBase]] = None,
815
- metric: Union[str, TimeSeriesScorer, None] = None,
815
+ model: str | TimeSeriesModelBase | None = None,
816
+ metric: str | TimeSeriesScorer | None = None,
816
817
  use_cache: bool = True,
817
818
  ) -> float:
818
819
  eval_metric = self._get_eval_metric(metric)
@@ -822,8 +823,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
822
823
  def evaluate(
823
824
  self,
824
825
  data: TimeSeriesDataFrame,
825
- model: Optional[Union[str, TimeSeriesModelBase]] = None,
826
- metrics: Optional[Union[str, TimeSeriesScorer, list[Union[str, TimeSeriesScorer]]]] = None,
826
+ model: str | TimeSeriesModelBase | None = None,
827
+ metrics: str | TimeSeriesScorer | list[str | TimeSeriesScorer] | None = None,
827
828
  use_cache: bool = True,
828
829
  ) -> dict[str, float]:
829
830
  past_data, known_covariates = data.get_model_inputs_for_scoring(
@@ -844,13 +845,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
844
845
  self,
845
846
  data: TimeSeriesDataFrame,
846
847
  features: list[str],
847
- model: Optional[Union[str, TimeSeriesModelBase]] = None,
848
- metric: Optional[Union[str, TimeSeriesScorer]] = None,
849
- time_limit: Optional[float] = None,
848
+ model: str | TimeSeriesModelBase | None = None,
849
+ metric: str | TimeSeriesScorer | None = None,
850
+ time_limit: float | None = None,
850
851
  method: Literal["naive", "permutation"] = "permutation",
851
852
  subsample_size: int = 50,
852
- num_iterations: Optional[int] = None,
853
- random_seed: Optional[int] = None,
853
+ num_iterations: int | None = None,
854
+ random_seed: int | None = None,
854
855
  relative_scores: bool = False,
855
856
  include_confidence_band: bool = True,
856
857
  confidence_level: float = 0.99,
@@ -867,9 +868,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
867
868
  # start timer and cap subsample size if it's greater than the number of items in the provided data set
868
869
  time_start = time.time()
869
870
  if subsample_size > data.num_items:
870
- logger.info(
871
- f"Subsample_size {subsample_size} is larger than the number of items in the data and will be ignored"
872
- )
873
871
  subsample_size = data.num_items
874
872
 
875
873
  # set default number of iterations and cap iterations if the number of items in the data is smaller
@@ -949,7 +947,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
949
947
 
950
948
  return importance_df
951
949
 
952
- def _model_uses_feature(self, model: Union[str, TimeSeriesModelBase], feature: str) -> bool:
950
+ def _model_uses_feature(self, model: str | TimeSeriesModelBase, feature: str) -> bool:
953
951
  """Check if the given model uses the given feature."""
954
952
  models_with_ancestors = set(self.get_minimum_model_set(model))
955
953
 
@@ -962,6 +960,72 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
962
960
 
963
961
  return False
964
962
 
963
+ def backtest_predictions(
964
+ self,
965
+ data: TimeSeriesDataFrame | None,
966
+ model_names: list[str],
967
+ num_val_windows: int | None = None,
968
+ val_step_size: int | None = None,
969
+ use_cache: bool = True,
970
+ ) -> dict[str, list[TimeSeriesDataFrame]]:
971
+ if data is None:
972
+ assert num_val_windows is None, "num_val_windows must be None when data is None"
973
+ assert val_step_size is None, "val_step_size must be None when data is None"
974
+ return {model_name: self._get_model_oof_predictions(model_name) for model_name in model_names}
975
+
976
+ if val_step_size is None:
977
+ val_step_size = self.prediction_length
978
+ if num_val_windows is None:
979
+ num_val_windows = 1
980
+
981
+ splitter = ExpandingWindowSplitter(
982
+ prediction_length=self.prediction_length,
983
+ num_val_windows=num_val_windows,
984
+ val_step_size=val_step_size,
985
+ )
986
+
987
+ result: dict[str, list[TimeSeriesDataFrame]] = {model_name: [] for model_name in model_names}
988
+ for past_data, full_data in splitter.split(data):
989
+ known_covariates = full_data.slice_by_timestep(-self.prediction_length, None)[
990
+ self.covariate_metadata.known_covariates
991
+ ]
992
+ pred_dict, _ = self.get_model_pred_dict(
993
+ model_names=model_names,
994
+ data=past_data,
995
+ known_covariates=known_covariates,
996
+ use_cache=use_cache,
997
+ )
998
+ for model_name in model_names:
999
+ result[model_name].append(pred_dict[model_name]) # type: ignore
1000
+
1001
+ return result
1002
+
1003
+ def backtest_targets(
1004
+ self,
1005
+ data: TimeSeriesDataFrame | None,
1006
+ num_val_windows: int | None = None,
1007
+ val_step_size: int | None = None,
1008
+ ) -> list[TimeSeriesDataFrame]:
1009
+ if data is None:
1010
+ assert num_val_windows is None, "num_val_windows must be None when data is None"
1011
+ assert val_step_size is None, "val_step_size must be None when data is None"
1012
+ train_data = self.load_train_data()
1013
+ val_data = self.load_val_data()
1014
+ return self._get_validation_windows(train_data=train_data, val_data=val_data)
1015
+
1016
+ if val_step_size is None:
1017
+ val_step_size = self.prediction_length
1018
+ if num_val_windows is None:
1019
+ num_val_windows = 1
1020
+
1021
+ splitter = ExpandingWindowSplitter(
1022
+ prediction_length=self.prediction_length,
1023
+ num_val_windows=num_val_windows,
1024
+ val_step_size=val_step_size,
1025
+ )
1026
+
1027
+ return [val_fold for _, val_fold in splitter.split(data)]
1028
+
965
1029
  def _add_ci_to_feature_importance(
966
1030
  self, importance_df: pd.DataFrame, confidence_level: float = 0.99
967
1031
  ) -> pd.DataFrame:
@@ -991,10 +1055,10 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
991
1055
 
992
1056
  def _predict_model(
993
1057
  self,
994
- model: Union[str, TimeSeriesModelBase],
1058
+ model: str | TimeSeriesModelBase,
995
1059
  data: TimeSeriesDataFrame,
996
- model_pred_dict: dict[str, Optional[TimeSeriesDataFrame]],
997
- known_covariates: Optional[TimeSeriesDataFrame] = None,
1060
+ model_pred_dict: dict[str, TimeSeriesDataFrame | None],
1061
+ known_covariates: TimeSeriesDataFrame | None = None,
998
1062
  ) -> TimeSeriesDataFrame:
999
1063
  """Generate predictions using the given model.
1000
1064
 
@@ -1007,10 +1071,10 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1007
1071
 
1008
1072
  def _get_inputs_to_model(
1009
1073
  self,
1010
- model: Union[str, TimeSeriesModelBase],
1074
+ model: str | TimeSeriesModelBase,
1011
1075
  data: TimeSeriesDataFrame,
1012
- model_pred_dict: dict[str, Optional[TimeSeriesDataFrame]],
1013
- ) -> Union[TimeSeriesDataFrame, dict[str, Optional[TimeSeriesDataFrame]]]:
1076
+ model_pred_dict: dict[str, TimeSeriesDataFrame | None],
1077
+ ) -> TimeSeriesDataFrame | dict[str, TimeSeriesDataFrame | None]:
1014
1078
  """Get the first argument that should be passed to model.predict.
1015
1079
 
1016
1080
  This method assumes that model_pred_dict contains the predictions of all base models, if model is an ensemble.
@@ -1028,11 +1092,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1028
1092
  self,
1029
1093
  model_names: list[str],
1030
1094
  data: TimeSeriesDataFrame,
1031
- known_covariates: Optional[TimeSeriesDataFrame] = None,
1095
+ known_covariates: TimeSeriesDataFrame | None = None,
1032
1096
  raise_exception_if_failed: bool = True,
1033
1097
  use_cache: bool = True,
1034
- random_seed: Optional[int] = None,
1035
- ) -> tuple[dict[str, Optional[TimeSeriesDataFrame]], dict[str, float]]:
1098
+ random_seed: int | None = None,
1099
+ ) -> tuple[dict[str, TimeSeriesDataFrame | None], dict[str, float]]:
1036
1100
  """Return a dictionary with predictions of all models for the given dataset.
1037
1101
 
1038
1102
  Parameters
@@ -1064,8 +1128,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1064
1128
  for model_name in model_names:
1065
1129
  model_set.update(self.get_minimum_model_set(model_name))
1066
1130
  if len(model_set) > 1:
1067
- model_to_level = self._get_model_levels()
1068
- model_set = sorted(model_set, key=model_to_level.get) # type: ignore
1131
+ model_to_layer = self._get_model_layers()
1132
+ model_set = sorted(model_set, key=model_to_layer.get) # type: ignore
1069
1133
  logger.debug(f"Prediction order: {model_set}")
1070
1134
 
1071
1135
  failed_models = []
@@ -1115,7 +1179,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1115
1179
  return dict(pred_time_dict_total)
1116
1180
 
1117
1181
  def _merge_refit_full_data(
1118
- self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
1182
+ self, train_data: TimeSeriesDataFrame, val_data: TimeSeriesDataFrame | None
1119
1183
  ) -> TimeSeriesDataFrame:
1120
1184
  if val_data is None:
1121
1185
  return train_data
@@ -1125,9 +1189,9 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1125
1189
 
1126
1190
  def refit_single_full(
1127
1191
  self,
1128
- train_data: Optional[TimeSeriesDataFrame] = None,
1129
- val_data: Optional[TimeSeriesDataFrame] = None,
1130
- models: Optional[list[str]] = None,
1192
+ train_data: TimeSeriesDataFrame | None = None,
1193
+ val_data: TimeSeriesDataFrame | None = None,
1194
+ models: list[str] | None = None,
1131
1195
  ) -> list[str]:
1132
1196
  train_data = train_data or self.load_train_data()
1133
1197
  val_data = val_data or self.load_val_data()
@@ -1136,12 +1200,12 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1136
1200
  if models is None:
1137
1201
  models = self.get_model_names()
1138
1202
 
1139
- model_to_level = self._get_model_levels()
1140
- models_sorted_by_level = sorted(models, key=model_to_level.get) # type: ignore
1203
+ model_to_layer = self._get_model_layers()
1204
+ models_sorted_by_layer = sorted(models, key=model_to_layer.get) # type: ignore
1141
1205
 
1142
1206
  model_refit_map = {}
1143
1207
  models_trained_full = []
1144
- for model in models_sorted_by_level:
1208
+ for model in models_sorted_by_layer:
1145
1209
  model = self.load_model(model)
1146
1210
  model_name = model.name
1147
1211
  if model._get_tags()["can_refit_full"]:
@@ -1206,11 +1270,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1206
1270
 
1207
1271
  def get_trainable_base_models(
1208
1272
  self,
1209
- hyperparameters: Union[str, dict[str, Any]],
1273
+ hyperparameters: str | dict[str, Any],
1210
1274
  *,
1211
1275
  multi_window: bool = False,
1212
- freq: Optional[str] = None,
1213
- excluded_model_types: Optional[list[str]] = None,
1276
+ freq: str | None = None,
1277
+ excluded_model_types: list[str] | None = None,
1214
1278
  hyperparameter_tune: bool = False,
1215
1279
  ) -> list[AbstractTimeSeriesModel]:
1216
1280
  return TrainableModelSetBuilder(
@@ -1228,46 +1292,3 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1228
1292
  excluded_model_types=excluded_model_types,
1229
1293
  banned_model_names=self._get_banned_model_names(),
1230
1294
  )
1231
-
1232
- def fit(
1233
- self,
1234
- train_data: TimeSeriesDataFrame,
1235
- hyperparameters: Union[str, dict[Any, dict]],
1236
- val_data: Optional[TimeSeriesDataFrame] = None,
1237
- hyperparameter_tune_kwargs: Optional[Union[str, dict]] = None,
1238
- excluded_model_types: Optional[list[str]] = None,
1239
- time_limit: Optional[float] = None,
1240
- random_seed: Optional[int] = None,
1241
- ):
1242
- """
1243
- Fit a set of timeseries models specified by the `hyperparameters`
1244
- dictionary that maps model names to their specified hyperparameters.
1245
-
1246
- Parameters
1247
- ----------
1248
- train_data
1249
- Training data for fitting time series timeseries models.
1250
- hyperparameters
1251
- A dictionary mapping selected model names, model classes or model factory to hyperparameter
1252
- settings. Model names should be present in `trainer.presets.DEFAULT_MODEL_NAMES`. Optionally,
1253
- the user may provide one of "default", "light" and "very_light" to specify presets.
1254
- val_data
1255
- Optional validation data set to report validation scores on.
1256
- hyperparameter_tune_kwargs
1257
- Args for hyperparameter tuning
1258
- excluded_model_types
1259
- Names of models that should not be trained, even if listed in `hyperparameters`.
1260
- time_limit
1261
- Time limit for training
1262
- random_seed
1263
- Random seed that will be set to each model during training
1264
- """
1265
- self._train_multi(
1266
- train_data,
1267
- val_data=val_data,
1268
- hyperparameters=hyperparameters,
1269
- hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
1270
- excluded_model_types=excluded_model_types,
1271
- time_limit=time_limit,
1272
- random_seed=random_seed,
1273
- )