autogluon.timeseries 1.4.1b20250820__py3-none-any.whl → 1.4.1b20250901__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 +9 -9
- autogluon/timeseries/learner.py +14 -14
- autogluon/timeseries/metrics/__init__.py +5 -5
- autogluon/timeseries/metrics/abstract.py +11 -12
- autogluon/timeseries/models/__init__.py +2 -0
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +39 -41
- autogluon/timeseries/models/abstract/tunable.py +6 -6
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +30 -30
- autogluon/timeseries/models/autogluon_tabular/per_step.py +12 -12
- autogluon/timeseries/models/chronos/model.py +10 -10
- autogluon/timeseries/models/chronos/pipeline/base.py +8 -8
- autogluon/timeseries/models/chronos/pipeline/chronos.py +12 -12
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +12 -12
- autogluon/timeseries/models/chronos/pipeline/utils.py +12 -12
- autogluon/timeseries/models/ensemble/abstract.py +19 -19
- autogluon/timeseries/models/ensemble/basic.py +8 -8
- autogluon/timeseries/models/ensemble/greedy.py +13 -13
- autogluon/timeseries/models/gluonts/abstract.py +24 -24
- autogluon/timeseries/models/gluonts/dataset.py +2 -2
- autogluon/timeseries/models/gluonts/models.py +7 -7
- autogluon/timeseries/models/local/abstract_local_model.py +12 -12
- autogluon/timeseries/models/local/statsforecast.py +11 -11
- autogluon/timeseries/models/multi_window/multi_window_model.py +33 -22
- autogluon/timeseries/models/registry.py +3 -3
- autogluon/timeseries/predictor.py +37 -37
- autogluon/timeseries/regressor.py +13 -13
- autogluon/timeseries/splitter.py +6 -6
- autogluon/timeseries/trainer/__init__.py +3 -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} +72 -128
- autogluon/timeseries/transforms/covariate_scaler.py +3 -3
- autogluon/timeseries/transforms/target_scaler.py +7 -7
- autogluon/timeseries/utils/datetime/lags.py +2 -2
- autogluon/timeseries/utils/datetime/time_features.py +2 -2
- autogluon/timeseries/utils/features.py +32 -32
- autogluon/timeseries/version.py +1 -1
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/METADATA +5 -5
- autogluon.timeseries-1.4.1b20250901.dist-info/RECORD +75 -0
- autogluon/timeseries/configs/presets_configs.py +0 -79
- autogluon/timeseries/models/presets.py +0 -280
- autogluon.timeseries-1.4.1b20250820.dist-info/RECORD +0 -72
- /autogluon.timeseries-1.4.1b20250820-py3.9-nspkg.pth → /autogluon.timeseries-1.4.1b20250901-py3.9-nspkg.pth +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/LICENSE +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/NOTICE +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/WHEEL +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.4.1b20250820.dist-info → autogluon.timeseries-1.4.1b20250901.dist-info}/zip-safe +0 -0
@@ -3,7 +3,7 @@ import os
|
|
3
3
|
import shutil
|
4
4
|
from datetime import timedelta
|
5
5
|
from pathlib import Path
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable,
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Type, Union, cast, overload
|
7
7
|
|
8
8
|
import gluonts
|
9
9
|
import gluonts.core.settings
|
@@ -42,20 +42,20 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
42
42
|
|
43
43
|
Parameters
|
44
44
|
----------
|
45
|
-
path
|
45
|
+
path
|
46
46
|
directory to store model artifacts.
|
47
|
-
freq
|
47
|
+
freq
|
48
48
|
string representation (compatible with GluonTS frequency strings) for the data provided.
|
49
49
|
For example, "1D" for daily data, "1H" for hourly data, etc.
|
50
|
-
prediction_length
|
50
|
+
prediction_length
|
51
51
|
Number of time steps ahead (length of the forecast horizon) the model will be optimized
|
52
52
|
to predict. At inference time, this will be the number of time steps the model will
|
53
53
|
predict.
|
54
|
-
name
|
54
|
+
name
|
55
55
|
Name of the model. Also, name of subdirectory inside path where model will be saved.
|
56
|
-
eval_metric
|
56
|
+
eval_metric
|
57
57
|
objective function the model intends to optimize, will use WQL by default.
|
58
|
-
hyperparameters
|
58
|
+
hyperparameters
|
59
59
|
various hyperparameters that will be used by model (can be search spaces instead of
|
60
60
|
fixed values). See *Other Parameters* in each inheriting model's documentation for
|
61
61
|
possible values.
|
@@ -77,7 +77,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
77
77
|
path: Optional[str] = None,
|
78
78
|
name: Optional[str] = None,
|
79
79
|
eval_metric: Optional[str] = None,
|
80
|
-
hyperparameters: Optional[
|
80
|
+
hyperparameters: Optional[dict[str, Any]] = None,
|
81
81
|
**kwargs, # noqa
|
82
82
|
):
|
83
83
|
super().__init__(
|
@@ -100,9 +100,9 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
100
100
|
self.num_feat_dynamic_real = 0
|
101
101
|
self.num_past_feat_dynamic_cat = 0
|
102
102
|
self.num_past_feat_dynamic_real = 0
|
103
|
-
self.feat_static_cat_cardinality:
|
104
|
-
self.feat_dynamic_cat_cardinality:
|
105
|
-
self.past_feat_dynamic_cat_cardinality:
|
103
|
+
self.feat_static_cat_cardinality: list[int] = []
|
104
|
+
self.feat_dynamic_cat_cardinality: list[int] = []
|
105
|
+
self.past_feat_dynamic_cat_cardinality: list[int] = []
|
106
106
|
self.negative_data = True
|
107
107
|
|
108
108
|
def save(self, path: Optional[str] = None, verbose: bool = True) -> str:
|
@@ -234,7 +234,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
234
234
|
|
235
235
|
return self._get_default_hyperparameters() | init_args
|
236
236
|
|
237
|
-
def _get_estimator_init_args(self) ->
|
237
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
238
238
|
"""Get GluonTS specific constructor arguments for estimator objects, an alias to `self.get_hyperparameters`
|
239
239
|
for better readability."""
|
240
240
|
return self.get_hyperparameters()
|
@@ -277,8 +277,8 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
277
277
|
|
278
278
|
return torch.cuda.is_available()
|
279
279
|
|
280
|
-
def get_minimum_resources(self, is_gpu_available: bool = False) ->
|
281
|
-
minimum_resources:
|
280
|
+
def get_minimum_resources(self, is_gpu_available: bool = False) -> dict[str, Union[int, float]]:
|
281
|
+
minimum_resources: dict[str, Union[int, float]] = {"num_cpus": 1}
|
282
282
|
# if GPU is available, we train with 1 GPU per trial
|
283
283
|
if is_gpu_available:
|
284
284
|
minimum_resources["num_gpus"] = 1
|
@@ -440,7 +440,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
440
440
|
self,
|
441
441
|
time_limit: Optional[float],
|
442
442
|
early_stopping_patience: Optional[int] = None,
|
443
|
-
) ->
|
443
|
+
) -> list[Callable]:
|
444
444
|
"""Retrieve a list of callback objects for the GluonTS trainer"""
|
445
445
|
from lightning.pytorch.callbacks import EarlyStopping, Timer
|
446
446
|
|
@@ -473,7 +473,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
473
473
|
data: TimeSeriesDataFrame,
|
474
474
|
known_covariates: Optional[TimeSeriesDataFrame] = None,
|
475
475
|
num_samples: Optional[int] = None,
|
476
|
-
) ->
|
476
|
+
) -> list[Forecast]:
|
477
477
|
assert self.gts_predictor is not None, "GluonTS models must be fit before predicting."
|
478
478
|
gts_data = self._to_gluonts_dataset(data, known_covariates=known_covariates)
|
479
479
|
return list(
|
@@ -483,7 +483,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
483
483
|
)
|
484
484
|
)
|
485
485
|
|
486
|
-
def _stack_quantile_forecasts(self, forecasts:
|
486
|
+
def _stack_quantile_forecasts(self, forecasts: list[QuantileForecast], item_ids: pd.Index) -> pd.DataFrame:
|
487
487
|
# GluonTS always saves item_id as a string
|
488
488
|
item_id_to_forecast = {str(f.item_id): f for f in forecasts}
|
489
489
|
result_dfs = []
|
@@ -496,7 +496,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
496
496
|
columns_order = ["mean"] + [str(q) for q in self.quantile_levels]
|
497
497
|
return forecast_df[columns_order]
|
498
498
|
|
499
|
-
def _stack_sample_forecasts(self, forecasts:
|
499
|
+
def _stack_sample_forecasts(self, forecasts: list[SampleForecast], item_ids: pd.Index) -> pd.DataFrame:
|
500
500
|
item_id_to_forecast = {str(f.item_id): f for f in forecasts}
|
501
501
|
samples_per_item = []
|
502
502
|
for item_id in item_ids:
|
@@ -509,7 +509,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
509
509
|
return pd.DataFrame(forecast_array, columns=["mean"] + [str(q) for q in self.quantile_levels])
|
510
510
|
|
511
511
|
def _stack_distribution_forecasts(
|
512
|
-
self, forecasts:
|
512
|
+
self, forecasts: list["DistributionForecast"], item_ids: pd.Index
|
513
513
|
) -> pd.DataFrame:
|
514
514
|
import torch
|
515
515
|
from gluonts.torch.distributions import AffineTransformed
|
@@ -523,7 +523,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
523
523
|
"Expected forecast.distribution to be an instance of AffineTransformed"
|
524
524
|
)
|
525
525
|
|
526
|
-
def stack_distributions(distributions:
|
526
|
+
def stack_distributions(distributions: list[Distribution]) -> Distribution:
|
527
527
|
"""Stack multiple torch.Distribution objects into a single distribution"""
|
528
528
|
last_dist: Distribution = distributions[-1]
|
529
529
|
|
@@ -561,18 +561,18 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
561
561
|
|
562
562
|
def _gluonts_forecasts_to_data_frame(
|
563
563
|
self,
|
564
|
-
forecasts:
|
564
|
+
forecasts: list[Forecast],
|
565
565
|
forecast_index: pd.MultiIndex,
|
566
566
|
) -> TimeSeriesDataFrame:
|
567
567
|
from gluonts.torch.model.forecast import DistributionForecast
|
568
568
|
|
569
569
|
item_ids = forecast_index.unique(level=ITEMID)
|
570
570
|
if isinstance(forecasts[0], SampleForecast):
|
571
|
-
forecast_df = self._stack_sample_forecasts(cast(
|
571
|
+
forecast_df = self._stack_sample_forecasts(cast(list[SampleForecast], forecasts), item_ids)
|
572
572
|
elif isinstance(forecasts[0], QuantileForecast):
|
573
|
-
forecast_df = self._stack_quantile_forecasts(cast(
|
573
|
+
forecast_df = self._stack_quantile_forecasts(cast(list[QuantileForecast], forecasts), item_ids)
|
574
574
|
elif isinstance(forecasts[0], DistributionForecast):
|
575
|
-
forecast_df = self._stack_distribution_forecasts(cast(
|
575
|
+
forecast_df = self._stack_distribution_forecasts(cast(list[DistributionForecast], forecasts), item_ids)
|
576
576
|
else:
|
577
577
|
raise ValueError(f"Unrecognized forecast type {type(forecasts[0])}")
|
578
578
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any,
|
1
|
+
from typing import Any, Iterator, Optional, Type
|
2
2
|
|
3
3
|
import numpy as np
|
4
4
|
import pandas as pd
|
@@ -76,7 +76,7 @@ class SimpleGluonTSDataset(GluonTSDataset):
|
|
76
76
|
def __len__(self):
|
77
77
|
return len(self.indptr) - 1 # noqa
|
78
78
|
|
79
|
-
def __iter__(self) -> Iterator[
|
79
|
+
def __iter__(self) -> Iterator[dict[str, Any]]:
|
80
80
|
for j in range(len(self.indptr) - 1):
|
81
81
|
start_idx = self.indptr[j]
|
82
82
|
end_idx = self.indptr[j + 1]
|
@@ -3,7 +3,7 @@ Module including wrappers for PyTorch implementations of models in GluonTS
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
from typing import Any,
|
6
|
+
from typing import Any, Type
|
7
7
|
|
8
8
|
from gluonts.model.estimator import Estimator as GluonTSEstimator
|
9
9
|
|
@@ -91,7 +91,7 @@ class DeepARModel(AbstractGluonTSModel):
|
|
91
91
|
|
92
92
|
return DeepAREstimator
|
93
93
|
|
94
|
-
def _get_estimator_init_args(self) ->
|
94
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
95
95
|
init_kwargs = super()._get_estimator_init_args()
|
96
96
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
97
97
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
@@ -113,7 +113,7 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
|
|
113
113
|
----------------
|
114
114
|
context_length : int, default = max(10, 2 * prediction_length)
|
115
115
|
Number of time units that condition the predictions
|
116
|
-
hidden_dimensions:
|
116
|
+
hidden_dimensions: list[int], default = [20, 20]
|
117
117
|
Size of hidden layers in the feedforward network
|
118
118
|
distr_output : gluonts.torch.distributions.Output, default = StudentTOutput()
|
119
119
|
Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
|
@@ -221,7 +221,7 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
|
|
221
221
|
"context_length": min(512, max(64, 2 * self.prediction_length)),
|
222
222
|
}
|
223
223
|
|
224
|
-
def _get_estimator_init_args(self) ->
|
224
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
225
225
|
init_kwargs = super()._get_estimator_init_args()
|
226
226
|
if self.num_feat_dynamic_real > 0:
|
227
227
|
init_kwargs["dynamic_dims"] = [self.num_feat_dynamic_real]
|
@@ -361,7 +361,7 @@ class PatchTSTModel(AbstractGluonTSModel):
|
|
361
361
|
def _get_default_hyperparameters(self):
|
362
362
|
return super()._get_default_hyperparameters() | {"context_length": 96, "patch_len": 16}
|
363
363
|
|
364
|
-
def _get_estimator_init_args(self) ->
|
364
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
365
365
|
init_kwargs = super()._get_estimator_init_args()
|
366
366
|
init_kwargs["num_feat_dynamic_real"] = self.num_feat_dynamic_real
|
367
367
|
return init_kwargs
|
@@ -438,7 +438,7 @@ class WaveNetModel(AbstractGluonTSModel):
|
|
438
438
|
|
439
439
|
return WaveNetEstimator
|
440
440
|
|
441
|
-
def _get_estimator_init_args(self) ->
|
441
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
442
442
|
init_kwargs = super()._get_estimator_init_args()
|
443
443
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
444
444
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
@@ -547,7 +547,7 @@ class TiDEModel(AbstractGluonTSModel):
|
|
547
547
|
"batch_size": 256,
|
548
548
|
}
|
549
549
|
|
550
|
-
def _get_estimator_init_args(self) ->
|
550
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
551
551
|
init_kwargs = super()._get_estimator_init_args()
|
552
552
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
553
553
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import logging
|
2
2
|
import time
|
3
3
|
from multiprocessing import TimeoutError
|
4
|
-
from typing import Any, Callable,
|
4
|
+
from typing import Any, Callable, Optional, Union
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
import pandas as pd
|
@@ -29,17 +29,17 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
29
29
|
|
30
30
|
Attributes
|
31
31
|
----------
|
32
|
-
allowed_local_model_args
|
32
|
+
allowed_local_model_args
|
33
33
|
Argument that can be passed to the underlying local model.
|
34
|
-
default_max_ts_length
|
34
|
+
default_max_ts_length
|
35
35
|
If not None, only the last ``max_ts_length`` time steps of each time series will be used to train the model.
|
36
36
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
37
|
-
init_time_in_seconds
|
37
|
+
init_time_in_seconds
|
38
38
|
Time that it takes to initialize the model in seconds (e.g., because of JIT compilation by Numba).
|
39
39
|
If time_limit is below this number, model won't be trained.
|
40
40
|
"""
|
41
41
|
|
42
|
-
allowed_local_model_args:
|
42
|
+
allowed_local_model_args: list[str] = []
|
43
43
|
default_max_ts_length: Optional[int] = 2500
|
44
44
|
default_max_time_limit_ratio = 1.0
|
45
45
|
init_time_in_seconds: int = 0
|
@@ -51,7 +51,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
51
51
|
path: Optional[str] = None,
|
52
52
|
name: Optional[str] = None,
|
53
53
|
eval_metric: Union[str, TimeSeriesScorer, None] = None,
|
54
|
-
hyperparameters: Optional[
|
54
|
+
hyperparameters: Optional[dict[str, Any]] = None,
|
55
55
|
**kwargs, # noqa
|
56
56
|
):
|
57
57
|
super().__init__(
|
@@ -64,12 +64,12 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
64
64
|
**kwargs,
|
65
65
|
)
|
66
66
|
|
67
|
-
self._local_model_args:
|
67
|
+
self._local_model_args: dict[str, Any]
|
68
68
|
self._seasonal_period: int
|
69
69
|
self._dummy_forecast: pd.DataFrame
|
70
70
|
|
71
71
|
@property
|
72
|
-
def allowed_hyperparameters(self) ->
|
72
|
+
def allowed_hyperparameters(self) -> list[str]:
|
73
73
|
return (
|
74
74
|
super().allowed_hyperparameters
|
75
75
|
+ ["use_fallback_model", "max_ts_length", "n_jobs"]
|
@@ -82,7 +82,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
82
82
|
known_covariates: Optional[TimeSeriesDataFrame] = None,
|
83
83
|
is_train: bool = False,
|
84
84
|
**kwargs,
|
85
|
-
) ->
|
85
|
+
) -> tuple[TimeSeriesDataFrame, Optional[TimeSeriesDataFrame]]:
|
86
86
|
if not self._get_tags()["allow_nan"]:
|
87
87
|
data = data.fill_missing_values()
|
88
88
|
return data, known_covariates
|
@@ -134,7 +134,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
134
134
|
stats_repeated = np.tile(stats_marginal.values, [self.prediction_length, 1])
|
135
135
|
return pd.DataFrame(stats_repeated, columns=stats_marginal.index)
|
136
136
|
|
137
|
-
def _update_local_model_args(self, local_model_args:
|
137
|
+
def _update_local_model_args(self, local_model_args: dict[str, Any]) -> dict[str, Any]:
|
138
138
|
return local_model_args
|
139
139
|
|
140
140
|
def _predict(self, data: TimeSeriesDataFrame, **kwargs) -> TimeSeriesDataFrame:
|
@@ -185,7 +185,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
185
185
|
time_series: pd.Series,
|
186
186
|
use_fallback_model: bool,
|
187
187
|
end_time: Optional[float] = None,
|
188
|
-
) ->
|
188
|
+
) -> tuple[pd.DataFrame, bool]:
|
189
189
|
if end_time is not None and time.time() >= end_time:
|
190
190
|
raise TimeLimitExceeded
|
191
191
|
|
@@ -222,7 +222,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
222
222
|
|
223
223
|
|
224
224
|
def seasonal_naive_forecast(
|
225
|
-
target: np.ndarray, prediction_length: int, quantile_levels:
|
225
|
+
target: np.ndarray, prediction_length: int, quantile_levels: list[float], seasonal_period: int
|
226
226
|
) -> pd.DataFrame:
|
227
227
|
"""Generate seasonal naive forecast, predicting the last observed value from the same period."""
|
228
228
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import Any,
|
2
|
+
from typing import Any, Optional, Type
|
3
3
|
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
@@ -14,7 +14,7 @@ class AbstractStatsForecastModel(AbstractLocalModel):
|
|
14
14
|
|
15
15
|
init_time_in_seconds = 15 # numba compilation for the first run
|
16
16
|
|
17
|
-
def _update_local_model_args(self, local_model_args:
|
17
|
+
def _update_local_model_args(self, local_model_args: dict[str, Any]) -> dict[str, Any]:
|
18
18
|
seasonal_period = local_model_args.pop("seasonal_period")
|
19
19
|
local_model_args["season_length"] = seasonal_period
|
20
20
|
return local_model_args
|
@@ -22,7 +22,7 @@ class AbstractStatsForecastModel(AbstractLocalModel):
|
|
22
22
|
def _get_model_type(self, variant: Optional[str] = None) -> Type:
|
23
23
|
raise NotImplementedError
|
24
24
|
|
25
|
-
def _get_local_model(self, local_model_args:
|
25
|
+
def _get_local_model(self, local_model_args: dict):
|
26
26
|
local_model_args = local_model_args.copy()
|
27
27
|
variant = local_model_args.pop("variant", None)
|
28
28
|
model_type = self._get_model_type(variant)
|
@@ -31,7 +31,7 @@ class AbstractStatsForecastModel(AbstractLocalModel):
|
|
31
31
|
def _get_point_forecast(
|
32
32
|
self,
|
33
33
|
time_series: pd.Series,
|
34
|
-
local_model_args:
|
34
|
+
local_model_args: dict,
|
35
35
|
) -> np.ndarray:
|
36
36
|
return self._get_local_model(local_model_args).forecast(
|
37
37
|
h=self.prediction_length, y=time_series.values.ravel()
|
@@ -176,9 +176,9 @@ class ARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
176
176
|
|
177
177
|
Other Parameters
|
178
178
|
----------------
|
179
|
-
order:
|
179
|
+
order: tuple[int, int, int], default = (1, 1, 1)
|
180
180
|
The (p, d, q) order of the model for the number of AR parameters, differences, and MA parameters to use.
|
181
|
-
seasonal_order:
|
181
|
+
seasonal_order: tuple[int, int, int], default = (0, 0, 0)
|
182
182
|
The (P, D, Q) parameters of the seasonal ARIMA model. Setting to (0, 0, 0) disables seasonality.
|
183
183
|
include_mean : bool, default = True
|
184
184
|
Should the ARIMA model include a mean term?
|
@@ -194,7 +194,7 @@ class ARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
194
194
|
method : {"CSS-ML", "CSS", "ML"}, default = "CSS-ML"
|
195
195
|
Fitting method: CSS (conditional sum of squares), ML (maximum likelihood), CSS-ML (initialize with CSS, then
|
196
196
|
optimize with ML).
|
197
|
-
fixed :
|
197
|
+
fixed : dict[str, float], optional
|
198
198
|
Dictionary containing fixed coefficients for the ARIMA model.
|
199
199
|
seasonal_period : int or None, default = None
|
200
200
|
Number of time steps in a complete seasonal cycle for seasonal models. For example, 7 for daily data with a
|
@@ -449,7 +449,7 @@ class AbstractConformalizedStatsForecastModel(AbstractStatsForecastModel):
|
|
449
449
|
def _get_nonconformity_scores(
|
450
450
|
self,
|
451
451
|
time_series: pd.Series,
|
452
|
-
local_model_args:
|
452
|
+
local_model_args: dict,
|
453
453
|
) -> np.ndarray:
|
454
454
|
h = self.prediction_length
|
455
455
|
y = time_series.values.ravel()
|
@@ -556,7 +556,7 @@ class AutoCESModel(AbstractProbabilisticStatsForecastModel):
|
|
556
556
|
local_model_args.setdefault("model", "Z")
|
557
557
|
return local_model_args
|
558
558
|
|
559
|
-
def _get_point_forecast(self, time_series: pd.Series, local_model_args:
|
559
|
+
def _get_point_forecast(self, time_series: pd.Series, local_model_args: dict):
|
560
560
|
# Disable seasonality if time series too short for chosen season_length or season_length == 1,
|
561
561
|
# otherwise model will crash
|
562
562
|
if len(time_series) < 5:
|
@@ -568,7 +568,7 @@ class AutoCESModel(AbstractProbabilisticStatsForecastModel):
|
|
568
568
|
|
569
569
|
|
570
570
|
class AbstractStatsForecastIntermittentDemandModel(AbstractConformalizedStatsForecastModel):
|
571
|
-
def _update_local_model_args(self, local_model_args:
|
571
|
+
def _update_local_model_args(self, local_model_args: dict[str, Any]) -> dict[str, Any]:
|
572
572
|
_ = local_model_args.pop("seasonal_period")
|
573
573
|
return local_model_args
|
574
574
|
|
@@ -733,6 +733,6 @@ class ZeroModel(AbstractStatsForecastIntermittentDemandModel):
|
|
733
733
|
def _get_point_forecast(
|
734
734
|
self,
|
735
735
|
time_series: pd.Series,
|
736
|
-
local_model_args:
|
736
|
+
local_model_args: dict,
|
737
737
|
):
|
738
738
|
return np.zeros(self.prediction_length)
|
@@ -4,9 +4,10 @@ import logging
|
|
4
4
|
import math
|
5
5
|
import os
|
6
6
|
import time
|
7
|
-
from typing import Any,
|
7
|
+
from typing import Any, Optional, Type, Union
|
8
8
|
|
9
9
|
import numpy as np
|
10
|
+
from typing_extensions import Self
|
10
11
|
|
11
12
|
import autogluon.core as ag
|
12
13
|
from autogluon.timeseries.dataset.ts_dataframe import TimeSeriesDataFrame
|
@@ -25,10 +26,10 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
25
26
|
|
26
27
|
Parameters
|
27
28
|
----------
|
28
|
-
model_base
|
29
|
+
model_base
|
29
30
|
The base model to repeatedly train. If a AbstractTimeSeriesModel class, then also provide model_base_kwargs
|
30
31
|
which will be used to initialize the model via model_base(**model_base_kwargs).
|
31
|
-
model_base_kwargs
|
32
|
+
model_base_kwargs
|
32
33
|
kwargs used to initialize model_base if model_base is a class.
|
33
34
|
"""
|
34
35
|
|
@@ -38,7 +39,7 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
38
39
|
def __init__(
|
39
40
|
self,
|
40
41
|
model_base: Union[AbstractTimeSeriesModel, Type[AbstractTimeSeriesModel]],
|
41
|
-
model_base_kwargs: Optional[
|
42
|
+
model_base_kwargs: Optional[dict[str, Any]] = None,
|
42
43
|
**kwargs,
|
43
44
|
):
|
44
45
|
if inspect.isclass(model_base) and issubclass(model_base, AbstractTimeSeriesModel):
|
@@ -73,10 +74,6 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
73
74
|
def supports_past_covariates(self) -> bool:
|
74
75
|
return self.model_base.supports_past_covariates
|
75
76
|
|
76
|
-
@property
|
77
|
-
def supports_cat_covariates(self) -> bool:
|
78
|
-
return self.model_base.supports_cat_covariates
|
79
|
-
|
80
77
|
def _get_model_base(self):
|
81
78
|
return self.model_base
|
82
79
|
|
@@ -86,15 +83,18 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
86
83
|
def _is_gpu_available(self) -> bool:
|
87
84
|
return self._get_model_base()._is_gpu_available()
|
88
85
|
|
89
|
-
def get_minimum_resources(self, is_gpu_available: bool = False) ->
|
86
|
+
def get_minimum_resources(self, is_gpu_available: bool = False) -> dict[str, Union[int, float]]:
|
90
87
|
return self._get_model_base().get_minimum_resources(is_gpu_available)
|
91
88
|
|
92
89
|
def _fit(
|
93
90
|
self,
|
94
91
|
train_data: TimeSeriesDataFrame,
|
95
92
|
val_data: Optional[TimeSeriesDataFrame] = None,
|
96
|
-
time_limit: Optional[
|
97
|
-
|
93
|
+
time_limit: Optional[float] = None,
|
94
|
+
num_cpus: Optional[int] = None,
|
95
|
+
num_gpus: Optional[int] = None,
|
96
|
+
verbosity: int = 2,
|
97
|
+
val_splitter: Optional[AbstractWindowSplitter] = None,
|
98
98
|
refit_every_n_windows: Optional[int] = 1,
|
99
99
|
**kwargs,
|
100
100
|
):
|
@@ -111,11 +111,15 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
111
111
|
|
112
112
|
oof_predictions_per_window = []
|
113
113
|
global_fit_start_time = time.time()
|
114
|
+
model: Optional[AbstractTimeSeriesModel] = None
|
114
115
|
|
115
116
|
for window_index, (train_fold, val_fold) in enumerate(val_splitter.split(train_data)):
|
116
117
|
logger.debug(f"\tWindow {window_index}")
|
118
|
+
|
117
119
|
# refit_this_window is always True for the 0th window
|
118
120
|
refit_this_window = window_index % refit_every_n_windows == 0
|
121
|
+
assert window_index != 0 or refit_this_window
|
122
|
+
|
119
123
|
if time_limit is None:
|
120
124
|
time_left_for_window = None
|
121
125
|
else:
|
@@ -148,6 +152,7 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
148
152
|
else:
|
149
153
|
time_left_for_prediction = time_limit - (time.time() - global_fit_start_time)
|
150
154
|
|
155
|
+
assert model is not None
|
151
156
|
model.score_and_cache_oof(
|
152
157
|
val_fold, store_val_score=True, store_predict_time=True, time_limit=time_left_for_prediction
|
153
158
|
)
|
@@ -172,11 +177,13 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
172
177
|
|
173
178
|
# Only the model trained on most recent data is saved & used for prediction
|
174
179
|
self.most_recent_model = model
|
175
|
-
self.
|
180
|
+
assert self.most_recent_model is not None
|
181
|
+
|
182
|
+
self.most_recent_model_folder = most_recent_refit_window # type: ignore
|
176
183
|
self.predict_time = self.most_recent_model.predict_time
|
177
|
-
self.fit_time = time.time() - global_fit_start_time - self.predict_time
|
184
|
+
self.fit_time = time.time() - global_fit_start_time - self.predict_time # type: ignore
|
178
185
|
self._oof_predictions = oof_predictions_per_window
|
179
|
-
self.val_score = np.mean([info["val_score"] for info in self.info_per_val_window])
|
186
|
+
self.val_score = np.mean([info["val_score"] for info in self.info_per_val_window]) # type: ignore
|
180
187
|
|
181
188
|
def get_info(self) -> dict:
|
182
189
|
info = super().get_info()
|
@@ -227,7 +234,7 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
227
234
|
train_fn_kwargs["init_params"]["model_base_kwargs"] = self.get_params()
|
228
235
|
return train_fn_kwargs
|
229
236
|
|
230
|
-
def save(self, path: str = None, verbose=True) -> str:
|
237
|
+
def save(self, path: Optional[str] = None, verbose: bool = True) -> str:
|
231
238
|
most_recent_model = self.most_recent_model
|
232
239
|
self.most_recent_model = None
|
233
240
|
save_path = super().save(path, verbose)
|
@@ -238,32 +245,36 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
238
245
|
most_recent_model.save()
|
239
246
|
return save_path
|
240
247
|
|
241
|
-
def persist(self):
|
248
|
+
def persist(self) -> Self:
|
242
249
|
if self.most_recent_model is None:
|
243
250
|
raise ValueError(f"{self.name} must be fit before persisting")
|
244
251
|
self.most_recent_model.persist()
|
252
|
+
return self
|
245
253
|
|
246
254
|
@classmethod
|
247
255
|
def load(
|
248
256
|
cls, path: str, reset_paths: bool = True, load_oof: bool = False, verbose: bool = True
|
249
257
|
) -> AbstractTimeSeriesModel:
|
250
258
|
model = super().load(path=path, reset_paths=reset_paths, load_oof=load_oof, verbose=verbose)
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
259
|
+
if model.most_recent_model_folder is not None:
|
260
|
+
most_recent_model_path = os.path.join(model.path, model.most_recent_model_folder)
|
261
|
+
model.most_recent_model = model.model_base_type.load(
|
262
|
+
most_recent_model_path,
|
263
|
+
reset_paths=reset_paths,
|
264
|
+
verbose=verbose,
|
265
|
+
)
|
257
266
|
return model
|
258
267
|
|
259
268
|
def convert_to_refit_full_template(self) -> AbstractTimeSeriesModel:
|
260
269
|
# refit_model is an instance of base model type, not MultiWindowBacktestingModel
|
270
|
+
assert self.most_recent_model is not None, "Most recent model is None. Model must be fit first."
|
261
271
|
refit_model = self.most_recent_model.convert_to_refit_full_template()
|
262
272
|
refit_model.rename(self.name + ag.constants.REFIT_FULL_SUFFIX)
|
263
273
|
return refit_model
|
264
274
|
|
265
275
|
def convert_to_refit_full_via_copy(self) -> AbstractTimeSeriesModel:
|
266
276
|
# refit_model is an instance of base model type, not MultiWindowBacktestingModel
|
277
|
+
assert self.most_recent_model is not None, "Most recent model is None. Model must be fit first."
|
267
278
|
refit_model = self.most_recent_model.convert_to_refit_full_via_copy()
|
268
279
|
refit_model.rename(self.name + ag.constants.REFIT_FULL_SUFFIX)
|
269
280
|
return refit_model
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from abc import ABCMeta
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from inspect import isabstract
|
4
|
-
from typing import
|
4
|
+
from typing import Union
|
5
5
|
|
6
6
|
|
7
7
|
@dataclass
|
@@ -18,7 +18,7 @@ class ModelRegistry(ABCMeta):
|
|
18
18
|
See, https://github.com/faif/python-patterns.
|
19
19
|
"""
|
20
20
|
|
21
|
-
REGISTRY:
|
21
|
+
REGISTRY: dict[str, ModelRecord] = {}
|
22
22
|
|
23
23
|
def __new__(cls, name, bases, attrs):
|
24
24
|
new_cls = super().__new__(cls, name, bases, attrs)
|
@@ -61,5 +61,5 @@ class ModelRegistry(ABCMeta):
|
|
61
61
|
return cls._get_model_record(alias).ag_priority
|
62
62
|
|
63
63
|
@classmethod
|
64
|
-
def available_aliases(cls) ->
|
64
|
+
def available_aliases(cls) -> list[str]:
|
65
65
|
return sorted(cls.REGISTRY.keys())
|