autogluon.timeseries 1.3.2b20250712__py3-none-any.whl → 1.4.1b20251116__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.
- autogluon/timeseries/configs/__init__.py +3 -2
- autogluon/timeseries/configs/hyperparameter_presets.py +62 -0
- autogluon/timeseries/configs/predictor_presets.py +84 -0
- autogluon/timeseries/dataset/ts_dataframe.py +98 -72
- autogluon/timeseries/learner.py +19 -18
- autogluon/timeseries/metrics/__init__.py +5 -5
- autogluon/timeseries/metrics/abstract.py +17 -17
- autogluon/timeseries/metrics/point.py +1 -1
- autogluon/timeseries/metrics/quantile.py +2 -2
- autogluon/timeseries/metrics/utils.py +4 -4
- autogluon/timeseries/models/__init__.py +4 -0
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +52 -75
- autogluon/timeseries/models/abstract/tunable.py +6 -6
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +72 -76
- autogluon/timeseries/models/autogluon_tabular/per_step.py +104 -46
- autogluon/timeseries/models/autogluon_tabular/transforms.py +9 -7
- autogluon/timeseries/models/chronos/model.py +115 -78
- autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +76 -44
- autogluon/timeseries/models/ensemble/__init__.py +29 -2
- autogluon/timeseries/models/ensemble/abstract.py +16 -52
- autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
- autogluon/timeseries/models/ensemble/array_based/abstract.py +247 -0
- autogluon/timeseries/models/ensemble/array_based/models.py +50 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +10 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +87 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +133 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +141 -0
- autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +41 -0
- autogluon/timeseries/models/ensemble/{basic.py → weighted/basic.py} +8 -18
- autogluon/timeseries/models/ensemble/{greedy.py → weighted/greedy.py} +13 -13
- autogluon/timeseries/models/gluonts/abstract.py +26 -26
- autogluon/timeseries/models/gluonts/dataset.py +4 -4
- autogluon/timeseries/models/gluonts/models.py +27 -12
- autogluon/timeseries/models/local/abstract_local_model.py +14 -14
- autogluon/timeseries/models/local/naive.py +4 -0
- autogluon/timeseries/models/local/npts.py +1 -0
- autogluon/timeseries/models/local/statsforecast.py +30 -14
- autogluon/timeseries/models/multi_window/multi_window_model.py +34 -23
- autogluon/timeseries/models/registry.py +65 -0
- autogluon/timeseries/models/toto/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/__init__.py +9 -0
- autogluon/timeseries/models/toto/_internal/backbone/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/backbone/attention.py +197 -0
- autogluon/timeseries/models/toto/_internal/backbone/backbone.py +262 -0
- autogluon/timeseries/models/toto/_internal/backbone/distribution.py +70 -0
- autogluon/timeseries/models/toto/_internal/backbone/kvcache.py +136 -0
- autogluon/timeseries/models/toto/_internal/backbone/rope.py +94 -0
- autogluon/timeseries/models/toto/_internal/backbone/scaler.py +306 -0
- autogluon/timeseries/models/toto/_internal/backbone/transformer.py +333 -0
- autogluon/timeseries/models/toto/_internal/dataset.py +165 -0
- autogluon/timeseries/models/toto/_internal/forecaster.py +423 -0
- autogluon/timeseries/models/toto/dataloader.py +108 -0
- autogluon/timeseries/models/toto/hf_pretrained_model.py +119 -0
- autogluon/timeseries/models/toto/model.py +236 -0
- autogluon/timeseries/predictor.py +94 -107
- autogluon/timeseries/regressor.py +31 -27
- autogluon/timeseries/splitter.py +7 -31
- autogluon/timeseries/trainer/__init__.py +3 -0
- autogluon/timeseries/trainer/ensemble_composer.py +250 -0
- autogluon/timeseries/trainer/model_set_builder.py +256 -0
- autogluon/timeseries/trainer/prediction_cache.py +149 -0
- autogluon/timeseries/{trainer.py → trainer/trainer.py} +182 -307
- autogluon/timeseries/trainer/utils.py +18 -0
- autogluon/timeseries/transforms/covariate_scaler.py +4 -4
- autogluon/timeseries/transforms/target_scaler.py +14 -14
- autogluon/timeseries/utils/datetime/lags.py +2 -2
- autogluon/timeseries/utils/datetime/time_features.py +2 -2
- autogluon/timeseries/utils/features.py +41 -37
- autogluon/timeseries/utils/forecast.py +5 -5
- autogluon/timeseries/utils/warning_filters.py +3 -1
- autogluon/timeseries/version.py +1 -1
- autogluon.timeseries-1.4.1b20251116-py3.9-nspkg.pth +1 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/METADATA +32 -17
- autogluon_timeseries-1.4.1b20251116.dist-info/RECORD +96 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/WHEEL +1 -1
- autogluon/timeseries/configs/presets_configs.py +0 -79
- autogluon/timeseries/evaluator.py +0 -6
- autogluon/timeseries/models/chronos/pipeline/__init__.py +0 -10
- autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
- autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -544
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -530
- autogluon/timeseries/models/presets.py +0 -358
- autogluon.timeseries-1.3.2b20250712-py3.9-nspkg.pth +0 -1
- autogluon.timeseries-1.3.2b20250712.dist-info/RECORD +0 -71
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info/licenses}/LICENSE +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info/licenses}/NOTICE +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/zip-safe +0 -0
|
@@ -5,14 +5,14 @@ import time
|
|
|
5
5
|
import traceback
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Literal, Optional, Union
|
|
9
9
|
|
|
10
10
|
import networkx as nx
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pandas as pd
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
|
|
15
|
-
from autogluon.common.utils.utils import
|
|
15
|
+
from autogluon.common.utils.utils import seed_everything
|
|
16
16
|
from autogluon.core.trainer.abstract_trainer import AbstractTrainer
|
|
17
17
|
from autogluon.core.utils.exceptions import TimeLimitExceeded
|
|
18
18
|
from autogluon.core.utils.loaders import load_pkl
|
|
@@ -20,23 +20,25 @@ 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
|
|
23
|
+
from autogluon.timeseries.models.ensemble import AbstractTimeSeriesEnsembleModel
|
|
24
24
|
from autogluon.timeseries.models.multi_window import MultiWindowBacktestingModel
|
|
25
|
-
from autogluon.timeseries.models.presets import contains_searchspace, get_preset_models
|
|
26
25
|
from autogluon.timeseries.splitter import AbstractWindowSplitter, ExpandingWindowSplitter
|
|
26
|
+
from autogluon.timeseries.trainer.ensemble_composer import EnsembleComposer, validate_ensemble_hyperparameters
|
|
27
27
|
from autogluon.timeseries.utils.features import (
|
|
28
28
|
ConstantReplacementFeatureImportanceTransform,
|
|
29
29
|
CovariateMetadata,
|
|
30
30
|
PermutationFeatureImportanceTransform,
|
|
31
31
|
)
|
|
32
|
-
from autogluon.timeseries.utils.warning_filters import disable_tqdm
|
|
32
|
+
from autogluon.timeseries.utils.warning_filters import disable_tqdm
|
|
33
|
+
|
|
34
|
+
from .model_set_builder import TrainableModelSetBuilder, contains_searchspace
|
|
35
|
+
from .prediction_cache import PredictionCache, get_prediction_cache
|
|
36
|
+
from .utils import log_scores_and_times
|
|
33
37
|
|
|
34
38
|
logger = logging.getLogger("autogluon.timeseries.trainer")
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
38
|
-
_cached_predictions_filename = "cached_predictions.pkl"
|
|
39
|
-
|
|
40
42
|
max_rel_importance_score: float = 1e5
|
|
41
43
|
eps_abs_importance_score: float = 1e-5
|
|
42
44
|
max_ensemble_time_limit: float = 600.0
|
|
@@ -50,11 +52,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
50
52
|
skip_model_selection: bool = False,
|
|
51
53
|
enable_ensemble: bool = True,
|
|
52
54
|
verbosity: int = 2,
|
|
53
|
-
|
|
55
|
+
num_val_windows: Optional[int] = None,
|
|
56
|
+
val_step_size: Optional[int] = None,
|
|
54
57
|
refit_every_n_windows: Optional[int] = 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,32 +73,27 @@ 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
|
|
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
|
|
|
84
|
-
#:
|
|
84
|
+
#: dict of normal model -> FULL model. FULL models are produced by
|
|
85
85
|
#: self.refit_single_full() and self.refit_full().
|
|
86
86
|
self.model_refit_map = {}
|
|
87
87
|
|
|
88
88
|
self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
self.val_splitter = val_splitter
|
|
89
|
+
|
|
90
|
+
self.num_val_windows = num_val_windows
|
|
91
|
+
self.val_step_size = val_step_size
|
|
93
92
|
self.refit_every_n_windows = refit_every_n_windows
|
|
94
|
-
self.cache_predictions = cache_predictions
|
|
95
93
|
self.hpo_results = {}
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
self._cached_predictions_path.unlink()
|
|
95
|
+
self.prediction_cache: PredictionCache = get_prediction_cache(cache_predictions, self.path)
|
|
96
|
+
self.prediction_cache.clear()
|
|
100
97
|
|
|
101
98
|
@property
|
|
102
99
|
def path_pkl(self) -> str:
|
|
@@ -121,7 +118,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
121
118
|
else:
|
|
122
119
|
return None
|
|
123
120
|
|
|
124
|
-
def load_data(self) ->
|
|
121
|
+
def load_data(self) -> tuple[TimeSeriesDataFrame, Optional[TimeSeriesDataFrame]]:
|
|
125
122
|
train_data = self.load_train_data()
|
|
126
123
|
val_data = self.load_val_data()
|
|
127
124
|
return train_data, val_data
|
|
@@ -136,7 +133,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
136
133
|
|
|
137
134
|
self.models = models
|
|
138
135
|
|
|
139
|
-
def _get_model_oof_predictions(self, model_name: str) ->
|
|
136
|
+
def _get_model_oof_predictions(self, model_name: str) -> list[TimeSeriesDataFrame]:
|
|
140
137
|
model_path = os.path.join(self.path, self.get_model_attribute(model=model_name, attribute="path"))
|
|
141
138
|
model_type = self.get_model_attribute(model=model_name, attribute="type")
|
|
142
139
|
return model_type.load_oof_predictions(path=model_path)
|
|
@@ -144,16 +141,16 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
144
141
|
def _add_model(
|
|
145
142
|
self,
|
|
146
143
|
model: TimeSeriesModelBase,
|
|
147
|
-
base_models: Optional[
|
|
144
|
+
base_models: Optional[list[str]] = None,
|
|
148
145
|
):
|
|
149
146
|
"""Add a model to the model graph of the trainer. If the model is an ensemble, also add
|
|
150
147
|
information about dependencies to the model graph (list of models specified via ``base_models``).
|
|
151
148
|
|
|
152
149
|
Parameters
|
|
153
150
|
----------
|
|
154
|
-
model
|
|
151
|
+
model
|
|
155
152
|
The model to be added to the model graph.
|
|
156
|
-
base_models
|
|
153
|
+
base_models
|
|
157
154
|
If the model is an ensemble, the list of base model names that are included in the ensemble.
|
|
158
155
|
Expected only when ``model`` is a ``AbstractTimeSeriesEnsembleModel``.
|
|
159
156
|
|
|
@@ -176,7 +173,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
176
173
|
for base_model in base_models:
|
|
177
174
|
self.model_graph.add_edge(base_model, model.name)
|
|
178
175
|
|
|
179
|
-
def _get_model_levels(self) ->
|
|
176
|
+
def _get_model_levels(self) -> dict[str, int]:
|
|
180
177
|
"""Get a dictionary mapping each model to their level in the model graph"""
|
|
181
178
|
|
|
182
179
|
# get nodes without a parent
|
|
@@ -197,7 +194,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
197
194
|
|
|
198
195
|
return levels
|
|
199
196
|
|
|
200
|
-
def get_models_attribute_dict(self, attribute: str, models: Optional[
|
|
197
|
+
def get_models_attribute_dict(self, attribute: str, models: Optional[list[str]] = None) -> dict[str, Any]:
|
|
201
198
|
"""Get an attribute from the `model_graph` for each of the model names
|
|
202
199
|
specified. If `models` is none, the attribute will be returned for all models"""
|
|
203
200
|
results = {}
|
|
@@ -230,13 +227,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
230
227
|
key=lambda mns: (mns[1], -mns[2]), # (score, -level)
|
|
231
228
|
)[0]
|
|
232
229
|
|
|
233
|
-
def get_model_names(self, level: Optional[int] = None) ->
|
|
230
|
+
def get_model_names(self, level: Optional[int] = None) -> list[str]:
|
|
234
231
|
"""Get model names that are registered in the model graph"""
|
|
235
232
|
if level is not None:
|
|
236
233
|
return list(node for node, l in self._get_model_levels().items() if l == level) # noqa: E741
|
|
237
234
|
return list(self.model_graph.nodes)
|
|
238
235
|
|
|
239
|
-
def get_info(self, include_model_info: bool = False) ->
|
|
236
|
+
def get_info(self, include_model_info: bool = False) -> dict[str, Any]:
|
|
240
237
|
num_models_trained = len(self.get_model_names())
|
|
241
238
|
if self.model_best is not None:
|
|
242
239
|
best_model = self.model_best
|
|
@@ -261,25 +258,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
261
258
|
|
|
262
259
|
return info
|
|
263
260
|
|
|
264
|
-
def _train_single(
|
|
265
|
-
self,
|
|
266
|
-
train_data: TimeSeriesDataFrame,
|
|
267
|
-
model: AbstractTimeSeriesModel,
|
|
268
|
-
val_data: Optional[TimeSeriesDataFrame] = None,
|
|
269
|
-
time_limit: Optional[float] = None,
|
|
270
|
-
) -> AbstractTimeSeriesModel:
|
|
271
|
-
"""Train the single model and return the model object that was fitted. This method
|
|
272
|
-
does not save the resulting model."""
|
|
273
|
-
model.fit(
|
|
274
|
-
train_data=train_data,
|
|
275
|
-
val_data=val_data,
|
|
276
|
-
time_limit=time_limit,
|
|
277
|
-
verbosity=self.verbosity,
|
|
278
|
-
val_splitter=self.val_splitter,
|
|
279
|
-
refit_every_n_windows=self.refit_every_n_windows,
|
|
280
|
-
)
|
|
281
|
-
return model
|
|
282
|
-
|
|
283
261
|
def tune_model_hyperparameters(
|
|
284
262
|
self,
|
|
285
263
|
model: AbstractTimeSeriesModel,
|
|
@@ -302,7 +280,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
302
280
|
hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
|
|
303
281
|
time_limit=time_limit,
|
|
304
282
|
default_num_trials=default_num_trials,
|
|
305
|
-
val_splitter=self.
|
|
283
|
+
val_splitter=self._get_val_splitter(),
|
|
306
284
|
refit_every_n_windows=self.refit_every_n_windows,
|
|
307
285
|
)
|
|
308
286
|
total_tuning_time = time.time() - tuning_start_time
|
|
@@ -339,12 +317,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
339
317
|
model: AbstractTimeSeriesModel,
|
|
340
318
|
val_data: Optional[TimeSeriesDataFrame] = None,
|
|
341
319
|
time_limit: Optional[float] = None,
|
|
342
|
-
) ->
|
|
320
|
+
) -> list[str]:
|
|
343
321
|
"""Fit and save the given model on given training and validation data and save the trained model.
|
|
344
322
|
|
|
345
323
|
Returns
|
|
346
324
|
-------
|
|
347
|
-
model_names_trained
|
|
325
|
+
model_names_trained
|
|
326
|
+
the list of model names that were successfully trained
|
|
348
327
|
"""
|
|
349
328
|
fit_start_time = time.time()
|
|
350
329
|
model_names_trained = []
|
|
@@ -354,7 +333,15 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
354
333
|
logger.info(f"\tSkipping {model.name} due to lack of time remaining.")
|
|
355
334
|
return model_names_trained
|
|
356
335
|
|
|
357
|
-
model
|
|
336
|
+
model.fit(
|
|
337
|
+
train_data=train_data,
|
|
338
|
+
val_data=val_data,
|
|
339
|
+
time_limit=time_limit,
|
|
340
|
+
verbosity=self.verbosity,
|
|
341
|
+
val_splitter=self._get_val_splitter(),
|
|
342
|
+
refit_every_n_windows=self.refit_every_n_windows,
|
|
343
|
+
)
|
|
344
|
+
|
|
358
345
|
fit_end_time = time.time()
|
|
359
346
|
model.fit_time = model.fit_time or (fit_end_time - fit_start_time)
|
|
360
347
|
|
|
@@ -365,7 +352,12 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
365
352
|
val_data, store_val_score=True, store_predict_time=True, time_limit=time_limit
|
|
366
353
|
)
|
|
367
354
|
|
|
368
|
-
|
|
355
|
+
log_scores_and_times(
|
|
356
|
+
val_score=model.val_score,
|
|
357
|
+
fit_time=model.fit_time,
|
|
358
|
+
predict_time=model.predict_time,
|
|
359
|
+
eval_metric_name=self.eval_metric.name_with_sign,
|
|
360
|
+
)
|
|
369
361
|
|
|
370
362
|
self.save_model(model=model)
|
|
371
363
|
except TimeLimitExceeded:
|
|
@@ -381,31 +373,51 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
381
373
|
|
|
382
374
|
return model_names_trained
|
|
383
375
|
|
|
384
|
-
def
|
|
385
|
-
self,
|
|
386
|
-
val_score: Optional[float] = None,
|
|
387
|
-
fit_time: Optional[float] = None,
|
|
388
|
-
predict_time: Optional[float] = None,
|
|
389
|
-
):
|
|
390
|
-
if val_score is not None:
|
|
391
|
-
logger.info(f"\t{val_score:<7.4f}".ljust(15) + f"= Validation score ({self.eval_metric.name_with_sign})")
|
|
392
|
-
if fit_time is not None:
|
|
393
|
-
logger.info(f"\t{fit_time:<7.2f} s".ljust(15) + "= Training runtime")
|
|
394
|
-
if predict_time is not None:
|
|
395
|
-
logger.info(f"\t{predict_time:<7.2f} s".ljust(15) + "= Validation (prediction) runtime")
|
|
396
|
-
|
|
397
|
-
def _train_multi(
|
|
376
|
+
def fit(
|
|
398
377
|
self,
|
|
399
378
|
train_data: TimeSeriesDataFrame,
|
|
400
|
-
hyperparameters: Union[str,
|
|
379
|
+
hyperparameters: Union[str, dict[Any, dict]],
|
|
401
380
|
val_data: Optional[TimeSeriesDataFrame] = None,
|
|
381
|
+
ensemble_hyperparameters: Optional[dict] = None,
|
|
402
382
|
hyperparameter_tune_kwargs: Optional[Union[str, dict]] = None,
|
|
403
|
-
excluded_model_types: Optional[
|
|
383
|
+
excluded_model_types: Optional[list[str]] = None,
|
|
404
384
|
time_limit: Optional[float] = None,
|
|
405
385
|
random_seed: Optional[int] = None,
|
|
406
|
-
)
|
|
386
|
+
):
|
|
387
|
+
"""Fit a set of timeseries models specified by the `hyperparameters`
|
|
388
|
+
dictionary that maps model names to their specified hyperparameters.
|
|
389
|
+
|
|
390
|
+
Parameters
|
|
391
|
+
----------
|
|
392
|
+
train_data
|
|
393
|
+
Training data for fitting time series timeseries models.
|
|
394
|
+
hyperparameters
|
|
395
|
+
A dictionary mapping selected model names, model classes or model factory to hyperparameter
|
|
396
|
+
settings. Model names should be present in `trainer.presets.DEFAULT_MODEL_NAMES`. Optionally,
|
|
397
|
+
the user may provide one of "default", "light" and "very_light" to specify presets.
|
|
398
|
+
val_data
|
|
399
|
+
Optional validation data set to report validation scores on.
|
|
400
|
+
ensemble_hyperparameters
|
|
401
|
+
A dictionary mapping ensemble names to their specified hyperparameters. Ensemble names
|
|
402
|
+
should be defined in the models.ensemble namespace. defaults to `{"GreedyEnsemble": {}}`
|
|
403
|
+
which only fits a greedy weighted ensemble with default hyperparameters. Providing an
|
|
404
|
+
empty dictionary disables ensemble training.
|
|
405
|
+
hyperparameter_tune_kwargs
|
|
406
|
+
Args for hyperparameter tuning
|
|
407
|
+
excluded_model_types
|
|
408
|
+
Names of models that should not be trained, even if listed in `hyperparameters`.
|
|
409
|
+
time_limit
|
|
410
|
+
Time limit for training
|
|
411
|
+
random_seed
|
|
412
|
+
Random seed that will be set to each model during training
|
|
413
|
+
"""
|
|
407
414
|
logger.info(f"\nStarting training. Start time is {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
408
415
|
|
|
416
|
+
# Handle ensemble hyperparameters
|
|
417
|
+
if ensemble_hyperparameters is None:
|
|
418
|
+
ensemble_hyperparameters = {"GreedyEnsemble": {}}
|
|
419
|
+
ensemble_hyperparameters = validate_ensemble_hyperparameters(ensemble_hyperparameters)
|
|
420
|
+
|
|
409
421
|
time_start = time.time()
|
|
410
422
|
hyperparameters = copy.deepcopy(hyperparameters)
|
|
411
423
|
|
|
@@ -415,11 +427,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
415
427
|
self.save_val_data(val_data)
|
|
416
428
|
self.is_data_saved = True
|
|
417
429
|
|
|
418
|
-
models = self.
|
|
430
|
+
models = self.get_trainable_base_models(
|
|
419
431
|
hyperparameters=hyperparameters,
|
|
420
432
|
hyperparameter_tune=hyperparameter_tune_kwargs is not None, # TODO: remove hyperparameter_tune
|
|
421
433
|
freq=train_data.freq,
|
|
422
|
-
multi_window=self.
|
|
434
|
+
multi_window=self._get_val_splitter().num_val_windows > 0,
|
|
423
435
|
excluded_model_types=excluded_model_types,
|
|
424
436
|
)
|
|
425
437
|
|
|
@@ -439,8 +451,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
439
451
|
num_base_models = len(models)
|
|
440
452
|
model_names_trained = []
|
|
441
453
|
for i, model in enumerate(models):
|
|
442
|
-
assert isinstance(model, AbstractTimeSeriesModel)
|
|
443
|
-
|
|
444
454
|
if time_limit is None:
|
|
445
455
|
time_left = None
|
|
446
456
|
time_left_for_model = None
|
|
@@ -490,42 +500,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
490
500
|
train_data, model=model, val_data=val_data, time_limit=time_left_for_model
|
|
491
501
|
)
|
|
492
502
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if time_left_for_ensemble is not None and time_left_for_ensemble <= 0:
|
|
501
|
-
logger.info(
|
|
502
|
-
"Not fitting ensemble due to lack of time remaining. "
|
|
503
|
-
f"Time left: {time_left_for_ensemble:.1f} seconds"
|
|
504
|
-
)
|
|
505
|
-
elif len(models_available_for_ensemble) <= 1:
|
|
506
|
-
logger.info(
|
|
507
|
-
"Not fitting ensemble as "
|
|
508
|
-
+ (
|
|
509
|
-
"no models were successfully trained."
|
|
510
|
-
if not models_available_for_ensemble
|
|
511
|
-
else "only 1 model was trained."
|
|
512
|
-
)
|
|
513
|
-
)
|
|
514
|
-
else:
|
|
515
|
-
try:
|
|
516
|
-
model_names_trained.append(
|
|
517
|
-
self.fit_ensemble(
|
|
518
|
-
data_per_window=self._get_ensemble_oof_data(train_data=train_data, val_data=val_data),
|
|
519
|
-
model_names=models_available_for_ensemble,
|
|
520
|
-
time_limit=time_left_for_ensemble,
|
|
521
|
-
)
|
|
522
|
-
)
|
|
523
|
-
except Exception as err: # noqa
|
|
524
|
-
logger.error(
|
|
525
|
-
"\tWarning: Exception caused ensemble to fail during training... Skipping this model."
|
|
526
|
-
)
|
|
527
|
-
logger.error(f"\t{err}")
|
|
528
|
-
logger.debug(traceback.format_exc())
|
|
503
|
+
ensemble_names = self._fit_ensembles(
|
|
504
|
+
train_data=train_data,
|
|
505
|
+
val_data=val_data,
|
|
506
|
+
time_limit=None if time_limit is None else time_limit - (time.time() - time_start),
|
|
507
|
+
ensemble_hyperparameters=ensemble_hyperparameters,
|
|
508
|
+
)
|
|
509
|
+
model_names_trained.extend(ensemble_names)
|
|
529
510
|
|
|
530
511
|
logger.info(f"Training complete. Models trained: {model_names_trained}")
|
|
531
512
|
logger.info(f"Total runtime: {time.time() - time_start:.2f} s")
|
|
@@ -539,82 +520,72 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
539
520
|
|
|
540
521
|
return model_names_trained
|
|
541
522
|
|
|
542
|
-
def
|
|
543
|
-
self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
|
|
544
|
-
) -> List[TimeSeriesDataFrame]:
|
|
545
|
-
if val_data is None:
|
|
546
|
-
return [val_fold for _, val_fold in self.val_splitter.split(train_data)]
|
|
547
|
-
else:
|
|
548
|
-
return [val_data]
|
|
549
|
-
|
|
550
|
-
def _get_ensemble_model_name(self) -> str:
|
|
551
|
-
"""Ensure we don't have name collisions in the ensemble model name"""
|
|
552
|
-
ensemble_name = "WeightedEnsemble"
|
|
553
|
-
increment = 1
|
|
554
|
-
while ensemble_name in self._get_banned_model_names():
|
|
555
|
-
increment += 1
|
|
556
|
-
ensemble_name = f"WeightedEnsemble_{increment}"
|
|
557
|
-
return ensemble_name
|
|
558
|
-
|
|
559
|
-
def fit_ensemble(
|
|
523
|
+
def _fit_ensembles(
|
|
560
524
|
self,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
525
|
+
*,
|
|
526
|
+
train_data: TimeSeriesDataFrame,
|
|
527
|
+
val_data: Optional[TimeSeriesDataFrame],
|
|
528
|
+
time_limit: Optional[float],
|
|
529
|
+
ensemble_hyperparameters: dict,
|
|
530
|
+
) -> list[str]:
|
|
531
|
+
if not self.enable_ensemble or not ensemble_hyperparameters:
|
|
532
|
+
logger.warning("Ensemble training is disabled. Skipping ensemble training.")
|
|
533
|
+
return []
|
|
534
|
+
|
|
535
|
+
ensemble_composer = self._get_ensemble_composer(ensemble_hyperparameters).fit(
|
|
536
|
+
train_data,
|
|
537
|
+
val_data,
|
|
538
|
+
time_limit,
|
|
539
|
+
)
|
|
566
540
|
|
|
567
|
-
|
|
568
|
-
|
|
541
|
+
ensembles_trained = []
|
|
542
|
+
for _, model, base_models in ensemble_composer.iter_ensembles():
|
|
543
|
+
self._add_model(model=model, base_models=base_models)
|
|
544
|
+
self.save_model(model=model)
|
|
545
|
+
ensembles_trained.append(model.name)
|
|
569
546
|
|
|
570
|
-
|
|
571
|
-
predictions_per_window[model_name] = self._get_model_oof_predictions(model_name=model_name)
|
|
547
|
+
return ensembles_trained if ensembles_trained else []
|
|
572
548
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
549
|
+
def _get_val_splitter(self) -> AbstractWindowSplitter:
|
|
550
|
+
if self.num_val_windows is None:
|
|
551
|
+
val_splitter = ExpandingWindowSplitter(prediction_length=self.prediction_length)
|
|
552
|
+
else:
|
|
553
|
+
val_splitter = ExpandingWindowSplitter(
|
|
554
|
+
prediction_length=self.prediction_length,
|
|
555
|
+
num_val_windows=self.num_val_windows,
|
|
556
|
+
val_step_size=self.val_step_size,
|
|
557
|
+
)
|
|
558
|
+
return val_splitter
|
|
559
|
+
|
|
560
|
+
def _get_ensemble_composer(self, ensemble_hyperparameters: dict) -> "EnsembleComposer":
|
|
561
|
+
"""Create an ensemble composer instance for delegation."""
|
|
562
|
+
return EnsembleComposer(
|
|
563
|
+
path=self.path,
|
|
564
|
+
prediction_length=self.prediction_length,
|
|
576
565
|
eval_metric=self.eval_metric,
|
|
577
566
|
target=self.target,
|
|
578
|
-
prediction_length=self.prediction_length,
|
|
579
|
-
path=self.path,
|
|
580
|
-
freq=data_per_window[0].freq,
|
|
581
567
|
quantile_levels=self.quantile_levels,
|
|
582
|
-
|
|
568
|
+
model_graph=self.model_graph,
|
|
569
|
+
ensemble_hyperparameters=ensemble_hyperparameters,
|
|
570
|
+
window_splitter=self._get_val_splitter(),
|
|
583
571
|
)
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
predict_time += self.get_model_attribute(model=m, attribute="predict_time")
|
|
596
|
-
ensemble.predict_time = predict_time
|
|
597
|
-
|
|
598
|
-
score_per_fold = []
|
|
599
|
-
for window_idx, data in enumerate(data_per_window):
|
|
600
|
-
predictions = ensemble.predict({n: predictions_per_window[n][window_idx] for n in ensemble.model_names})
|
|
601
|
-
score_per_fold.append(self._score_with_predictions(data, predictions))
|
|
602
|
-
ensemble.val_score = float(np.mean(score_per_fold, dtype=np.float64))
|
|
603
|
-
|
|
604
|
-
self._log_scores_and_times(
|
|
605
|
-
val_score=ensemble.val_score,
|
|
606
|
-
fit_time=ensemble.fit_time,
|
|
607
|
-
predict_time=ensemble.predict_time,
|
|
608
|
-
)
|
|
609
|
-
self._add_model(model=ensemble, base_models=ensemble.model_names)
|
|
610
|
-
self.save_model(model=ensemble)
|
|
611
|
-
return ensemble.name
|
|
572
|
+
|
|
573
|
+
def _get_validation_windows(
|
|
574
|
+
self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
|
|
575
|
+
) -> list[TimeSeriesDataFrame]:
|
|
576
|
+
"""If validation data is provided, return this as a single validation window. If not,
|
|
577
|
+
use the validation splitter to create a list of validation splits.
|
|
578
|
+
"""
|
|
579
|
+
if val_data is None:
|
|
580
|
+
return [val_fold for _, val_fold in self._get_val_splitter().split(train_data)]
|
|
581
|
+
else:
|
|
582
|
+
return [val_data]
|
|
612
583
|
|
|
613
584
|
def leaderboard(
|
|
614
585
|
self,
|
|
615
586
|
data: Optional[TimeSeriesDataFrame] = None,
|
|
616
587
|
extra_info: bool = False,
|
|
617
|
-
extra_metrics: Optional[
|
|
588
|
+
extra_metrics: Optional[list[Union[str, TimeSeriesScorer]]] = None,
|
|
618
589
|
use_cache: bool = True,
|
|
619
590
|
) -> pd.DataFrame:
|
|
620
591
|
logger.debug("Generating leaderboard for all models trained")
|
|
@@ -704,8 +675,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
704
675
|
return df[explicit_column_order]
|
|
705
676
|
|
|
706
677
|
def persist(
|
|
707
|
-
self, model_names: Union[Literal["all", "best"],
|
|
708
|
-
) ->
|
|
678
|
+
self, model_names: Union[Literal["all", "best"], list[str]] = "all", with_ancestors: bool = False
|
|
679
|
+
) -> list[str]:
|
|
709
680
|
if model_names == "all":
|
|
710
681
|
model_names = self.get_model_names()
|
|
711
682
|
elif model_names == "best":
|
|
@@ -729,7 +700,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
729
700
|
|
|
730
701
|
return model_names
|
|
731
702
|
|
|
732
|
-
def unpersist(self, model_names: Union[Literal["all"],
|
|
703
|
+
def unpersist(self, model_names: Union[Literal["all"], list[str]] = "all") -> list[str]:
|
|
733
704
|
if model_names == "all":
|
|
734
705
|
model_names = list(self.models.keys())
|
|
735
706
|
if not isinstance(model_names, list):
|
|
@@ -826,9 +797,9 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
826
797
|
self,
|
|
827
798
|
data: TimeSeriesDataFrame,
|
|
828
799
|
model: Optional[Union[str, TimeSeriesModelBase]] = None,
|
|
829
|
-
metrics: Optional[Union[str, TimeSeriesScorer,
|
|
800
|
+
metrics: Optional[Union[str, TimeSeriesScorer, list[Union[str, TimeSeriesScorer]]]] = None,
|
|
830
801
|
use_cache: bool = True,
|
|
831
|
-
) ->
|
|
802
|
+
) -> dict[str, float]:
|
|
832
803
|
past_data, known_covariates = data.get_model_inputs_for_scoring(
|
|
833
804
|
prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
|
|
834
805
|
)
|
|
@@ -846,7 +817,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
846
817
|
def get_feature_importance(
|
|
847
818
|
self,
|
|
848
819
|
data: TimeSeriesDataFrame,
|
|
849
|
-
features:
|
|
820
|
+
features: list[str],
|
|
850
821
|
model: Optional[Union[str, TimeSeriesModelBase]] = None,
|
|
851
822
|
metric: Optional[Union[str, TimeSeriesScorer]] = None,
|
|
852
823
|
time_limit: Optional[float] = None,
|
|
@@ -996,7 +967,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
996
967
|
self,
|
|
997
968
|
model: Union[str, TimeSeriesModelBase],
|
|
998
969
|
data: TimeSeriesDataFrame,
|
|
999
|
-
model_pred_dict:
|
|
970
|
+
model_pred_dict: dict[str, Optional[TimeSeriesDataFrame]],
|
|
1000
971
|
known_covariates: Optional[TimeSeriesDataFrame] = None,
|
|
1001
972
|
) -> TimeSeriesDataFrame:
|
|
1002
973
|
"""Generate predictions using the given model.
|
|
@@ -1012,8 +983,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1012
983
|
self,
|
|
1013
984
|
model: Union[str, TimeSeriesModelBase],
|
|
1014
985
|
data: TimeSeriesDataFrame,
|
|
1015
|
-
model_pred_dict:
|
|
1016
|
-
) -> Union[TimeSeriesDataFrame,
|
|
986
|
+
model_pred_dict: dict[str, Optional[TimeSeriesDataFrame]],
|
|
987
|
+
) -> Union[TimeSeriesDataFrame, dict[str, Optional[TimeSeriesDataFrame]]]:
|
|
1017
988
|
"""Get the first argument that should be passed to model.predict.
|
|
1018
989
|
|
|
1019
990
|
This method assumes that model_pred_dict contains the predictions of all base models, if model is an ensemble.
|
|
@@ -1029,13 +1000,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1029
1000
|
|
|
1030
1001
|
def get_model_pred_dict(
|
|
1031
1002
|
self,
|
|
1032
|
-
model_names:
|
|
1003
|
+
model_names: list[str],
|
|
1033
1004
|
data: TimeSeriesDataFrame,
|
|
1034
1005
|
known_covariates: Optional[TimeSeriesDataFrame] = None,
|
|
1035
1006
|
raise_exception_if_failed: bool = True,
|
|
1036
1007
|
use_cache: bool = True,
|
|
1037
1008
|
random_seed: Optional[int] = None,
|
|
1038
|
-
) ->
|
|
1009
|
+
) -> tuple[dict[str, Optional[TimeSeriesDataFrame]], dict[str, float]]:
|
|
1039
1010
|
"""Return a dictionary with predictions of all models for the given dataset.
|
|
1040
1011
|
|
|
1041
1012
|
Parameters
|
|
@@ -1055,12 +1026,13 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1055
1026
|
use_cache
|
|
1056
1027
|
If False, will ignore the cache even if it's available.
|
|
1057
1028
|
"""
|
|
1058
|
-
if
|
|
1059
|
-
|
|
1060
|
-
|
|
1029
|
+
if use_cache:
|
|
1030
|
+
model_pred_dict, pred_time_dict_marginal = self.prediction_cache.get(
|
|
1031
|
+
data=data, known_covariates=known_covariates
|
|
1032
|
+
)
|
|
1061
1033
|
else:
|
|
1062
1034
|
model_pred_dict = {}
|
|
1063
|
-
pred_time_dict_marginal:
|
|
1035
|
+
pred_time_dict_marginal: dict[str, Any] = {}
|
|
1064
1036
|
|
|
1065
1037
|
model_set = set()
|
|
1066
1038
|
for model_name in model_names:
|
|
@@ -1093,9 +1065,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1093
1065
|
|
|
1094
1066
|
if len(failed_models) > 0 and raise_exception_if_failed:
|
|
1095
1067
|
raise RuntimeError(f"Following models failed to predict: {failed_models}")
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1068
|
+
|
|
1069
|
+
if use_cache:
|
|
1070
|
+
self.prediction_cache.put(
|
|
1071
|
+
data=data,
|
|
1072
|
+
known_covariates=known_covariates,
|
|
1099
1073
|
model_pred_dict=model_pred_dict,
|
|
1100
1074
|
pred_time_dict=pred_time_dict_marginal,
|
|
1101
1075
|
)
|
|
@@ -1106,7 +1080,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1106
1080
|
|
|
1107
1081
|
return final_model_pred_dict, final_pred_time_dict_total
|
|
1108
1082
|
|
|
1109
|
-
def _get_total_pred_time_from_marginal(self, pred_time_dict_marginal:
|
|
1083
|
+
def _get_total_pred_time_from_marginal(self, pred_time_dict_marginal: dict[str, float]) -> dict[str, float]:
|
|
1110
1084
|
pred_time_dict_total = defaultdict(float)
|
|
1111
1085
|
for model_name in pred_time_dict_marginal.keys():
|
|
1112
1086
|
for base_model in self.get_minimum_model_set(model_name):
|
|
@@ -1114,62 +1088,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1114
1088
|
pred_time_dict_total[model_name] += pred_time_dict_marginal[base_model]
|
|
1115
1089
|
return dict(pred_time_dict_total)
|
|
1116
1090
|
|
|
1117
|
-
@property
|
|
1118
|
-
def _cached_predictions_path(self) -> Path:
|
|
1119
|
-
return Path(self.path) / self._cached_predictions_filename
|
|
1120
|
-
|
|
1121
|
-
@staticmethod
|
|
1122
|
-
def _compute_dataset_hash(
|
|
1123
|
-
data: TimeSeriesDataFrame, known_covariates: Optional[TimeSeriesDataFrame] = None
|
|
1124
|
-
) -> str:
|
|
1125
|
-
"""Compute a unique string that identifies the time series dataset."""
|
|
1126
|
-
combined_hash = hash_pandas_df(data) + hash_pandas_df(known_covariates) + hash_pandas_df(data.static_features)
|
|
1127
|
-
return combined_hash
|
|
1128
|
-
|
|
1129
|
-
def _load_cached_predictions(self) -> dict[str, dict[str, dict[str, Any]]]:
|
|
1130
|
-
"""Load cached predictions from disk. If loading fails, an empty dictionary is returned."""
|
|
1131
|
-
if self._cached_predictions_path.exists():
|
|
1132
|
-
try:
|
|
1133
|
-
cached_predictions = load_pkl.load(str(self._cached_predictions_path))
|
|
1134
|
-
except Exception:
|
|
1135
|
-
cached_predictions = {}
|
|
1136
|
-
else:
|
|
1137
|
-
cached_predictions = {}
|
|
1138
|
-
return cached_predictions
|
|
1139
|
-
|
|
1140
|
-
def _get_cached_pred_dicts(
|
|
1141
|
-
self, dataset_hash: str
|
|
1142
|
-
) -> Tuple[Dict[str, Optional[TimeSeriesDataFrame]], Dict[str, float]]:
|
|
1143
|
-
"""Load cached predictions for given dataset_hash from disk, if possible.
|
|
1144
|
-
|
|
1145
|
-
If loading fails for any reason, empty dicts are returned.
|
|
1146
|
-
"""
|
|
1147
|
-
cached_predictions = self._load_cached_predictions()
|
|
1148
|
-
if dataset_hash in cached_predictions:
|
|
1149
|
-
try:
|
|
1150
|
-
model_pred_dict = cached_predictions[dataset_hash]["model_pred_dict"]
|
|
1151
|
-
pred_time_dict = cached_predictions[dataset_hash]["pred_time_dict"]
|
|
1152
|
-
assert model_pred_dict.keys() == pred_time_dict.keys()
|
|
1153
|
-
return model_pred_dict, pred_time_dict
|
|
1154
|
-
except Exception:
|
|
1155
|
-
logger.warning("Cached predictions are corrupted. Predictions will be made from scratch.")
|
|
1156
|
-
return {}, {}
|
|
1157
|
-
|
|
1158
|
-
def _save_cached_pred_dicts(
|
|
1159
|
-
self,
|
|
1160
|
-
dataset_hash: str,
|
|
1161
|
-
model_pred_dict: Dict[str, Optional[TimeSeriesDataFrame]],
|
|
1162
|
-
pred_time_dict: Dict[str, float],
|
|
1163
|
-
) -> None:
|
|
1164
|
-
cached_predictions = self._load_cached_predictions()
|
|
1165
|
-
# Do not save results for models that failed
|
|
1166
|
-
cached_predictions[dataset_hash] = {
|
|
1167
|
-
"model_pred_dict": {k: v for k, v in model_pred_dict.items() if v is not None},
|
|
1168
|
-
"pred_time_dict": {k: v for k, v in pred_time_dict.items() if v is not None},
|
|
1169
|
-
}
|
|
1170
|
-
save_pkl.save(str(self._cached_predictions_path), object=cached_predictions)
|
|
1171
|
-
logger.debug(f"Cached predictions saved to {self._cached_predictions_path}")
|
|
1172
|
-
|
|
1173
1091
|
def _merge_refit_full_data(
|
|
1174
1092
|
self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
|
|
1175
1093
|
) -> TimeSeriesDataFrame:
|
|
@@ -1183,8 +1101,8 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1183
1101
|
self,
|
|
1184
1102
|
train_data: Optional[TimeSeriesDataFrame] = None,
|
|
1185
1103
|
val_data: Optional[TimeSeriesDataFrame] = None,
|
|
1186
|
-
models: Optional[
|
|
1187
|
-
) ->
|
|
1104
|
+
models: Optional[list[str]] = None,
|
|
1105
|
+
) -> list[str]:
|
|
1188
1106
|
train_data = train_data or self.load_train_data()
|
|
1189
1107
|
val_data = val_data or self.load_val_data()
|
|
1190
1108
|
refit_full_data = self._merge_refit_full_data(train_data, val_data)
|
|
@@ -1228,7 +1146,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1228
1146
|
self.save()
|
|
1229
1147
|
return models_trained_full
|
|
1230
1148
|
|
|
1231
|
-
def refit_full(self, model: str = "all") ->
|
|
1149
|
+
def refit_full(self, model: str = "all") -> dict[str, str]:
|
|
1232
1150
|
time_start = time.time()
|
|
1233
1151
|
existing_models = self.get_model_names()
|
|
1234
1152
|
if model == "all":
|
|
@@ -1260,70 +1178,27 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
|
|
|
1260
1178
|
logger.info(f"Total runtime: {time.time() - time_start:.2f} s")
|
|
1261
1179
|
return copy.deepcopy(self.model_refit_map)
|
|
1262
1180
|
|
|
1263
|
-
def
|
|
1181
|
+
def get_trainable_base_models(
|
|
1264
1182
|
self,
|
|
1265
|
-
hyperparameters: Union[str,
|
|
1183
|
+
hyperparameters: Union[str, dict[str, Any]],
|
|
1266
1184
|
*,
|
|
1267
1185
|
multi_window: bool = False,
|
|
1268
1186
|
freq: Optional[str] = None,
|
|
1269
|
-
excluded_model_types: Optional[
|
|
1187
|
+
excluded_model_types: Optional[list[str]] = None,
|
|
1270
1188
|
hyperparameter_tune: bool = False,
|
|
1271
|
-
) ->
|
|
1272
|
-
return
|
|
1189
|
+
) -> list[AbstractTimeSeriesModel]:
|
|
1190
|
+
return TrainableModelSetBuilder(
|
|
1191
|
+
freq=freq,
|
|
1192
|
+
prediction_length=self.prediction_length,
|
|
1273
1193
|
path=self.path,
|
|
1274
1194
|
eval_metric=self.eval_metric,
|
|
1275
|
-
prediction_length=self.prediction_length,
|
|
1276
|
-
freq=freq,
|
|
1277
|
-
hyperparameters=hyperparameters,
|
|
1278
|
-
hyperparameter_tune=hyperparameter_tune,
|
|
1279
1195
|
quantile_levels=self.quantile_levels,
|
|
1280
|
-
all_assigned_names=self._get_banned_model_names(),
|
|
1281
1196
|
target=self.target,
|
|
1282
1197
|
covariate_metadata=self.covariate_metadata,
|
|
1283
|
-
excluded_model_types=excluded_model_types,
|
|
1284
|
-
# if skip_model_selection = True, we skip backtesting
|
|
1285
1198
|
multi_window=multi_window and not self.skip_model_selection,
|
|
1286
|
-
)
|
|
1287
|
-
|
|
1288
|
-
def fit(
|
|
1289
|
-
self,
|
|
1290
|
-
train_data: TimeSeriesDataFrame,
|
|
1291
|
-
hyperparameters: Union[str, Dict[Any, Dict]],
|
|
1292
|
-
val_data: Optional[TimeSeriesDataFrame] = None,
|
|
1293
|
-
hyperparameter_tune_kwargs: Optional[Union[str, Dict]] = None,
|
|
1294
|
-
excluded_model_types: Optional[List[str]] = None,
|
|
1295
|
-
time_limit: Optional[float] = None,
|
|
1296
|
-
random_seed: Optional[int] = None,
|
|
1297
|
-
):
|
|
1298
|
-
"""
|
|
1299
|
-
Fit a set of timeseries models specified by the `hyperparameters`
|
|
1300
|
-
dictionary that maps model names to their specified hyperparameters.
|
|
1301
|
-
|
|
1302
|
-
Parameters
|
|
1303
|
-
----------
|
|
1304
|
-
train_data: TimeSeriesDataFrame
|
|
1305
|
-
Training data for fitting time series timeseries models.
|
|
1306
|
-
hyperparameters: str or Dict
|
|
1307
|
-
A dictionary mapping selected model names, model classes or model factory to hyperparameter
|
|
1308
|
-
settings. Model names should be present in `trainer.presets.DEFAULT_MODEL_NAMES`. Optionally,
|
|
1309
|
-
the user may provide one of "default", "light" and "very_light" to specify presets.
|
|
1310
|
-
val_data: TimeSeriesDataFrame
|
|
1311
|
-
Optional validation data set to report validation scores on.
|
|
1312
|
-
hyperparameter_tune_kwargs
|
|
1313
|
-
Args for hyperparameter tuning
|
|
1314
|
-
excluded_model_types
|
|
1315
|
-
Names of models that should not be trained, even if listed in `hyperparameters`.
|
|
1316
|
-
time_limit
|
|
1317
|
-
Time limit for training
|
|
1318
|
-
random_seed
|
|
1319
|
-
Random seed that will be set to each model during training
|
|
1320
|
-
"""
|
|
1321
|
-
self._train_multi(
|
|
1322
|
-
train_data,
|
|
1323
|
-
val_data=val_data,
|
|
1199
|
+
).get_model_set(
|
|
1324
1200
|
hyperparameters=hyperparameters,
|
|
1325
|
-
|
|
1201
|
+
hyperparameter_tune=hyperparameter_tune,
|
|
1326
1202
|
excluded_model_types=excluded_model_types,
|
|
1327
|
-
|
|
1328
|
-
random_seed=random_seed,
|
|
1203
|
+
banned_model_names=self._get_banned_model_names(),
|
|
1329
1204
|
)
|