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
|
@@ -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
|
|
@@ -9,7 +9,7 @@ from joblib import Parallel, cpu_count, delayed
|
|
|
9
9
|
from scipy.stats import norm
|
|
10
10
|
|
|
11
11
|
from autogluon.core.utils.exceptions import TimeLimitExceeded
|
|
12
|
-
from autogluon.timeseries.dataset
|
|
12
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
13
13
|
from autogluon.timeseries.metrics import TimeSeriesScorer
|
|
14
14
|
from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
|
|
15
15
|
from autogluon.timeseries.utils.datetime import get_seasonality
|
|
@@ -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:
|
|
@@ -145,7 +145,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
145
145
|
data = data.slice_by_timestep(-max_ts_length, None)
|
|
146
146
|
|
|
147
147
|
indptr = data.get_indptr()
|
|
148
|
-
target_series = data[self.target].droplevel(level=ITEMID)
|
|
148
|
+
target_series = data[self.target].droplevel(level=TimeSeriesDataFrame.ITEMID)
|
|
149
149
|
all_series = (target_series[indptr[i] : indptr[i + 1]] for i in range(len(indptr) - 1))
|
|
150
150
|
|
|
151
151
|
# timeout ensures that no individual job takes longer than time_limit
|
|
@@ -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
|
|
|
@@ -24,6 +24,7 @@ class NaiveModel(AbstractLocalModel):
|
|
|
24
24
|
When set to -1, all CPU cores are used.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
ag_priority = 100
|
|
27
28
|
allowed_local_model_args = ["seasonal_period"]
|
|
28
29
|
|
|
29
30
|
def _predict_with_local_model(
|
|
@@ -66,6 +67,7 @@ class SeasonalNaiveModel(AbstractLocalModel):
|
|
|
66
67
|
When set to -1, all CPU cores are used.
|
|
67
68
|
"""
|
|
68
69
|
|
|
70
|
+
ag_priority = 100
|
|
69
71
|
allowed_local_model_args = ["seasonal_period"]
|
|
70
72
|
|
|
71
73
|
def _predict_with_local_model(
|
|
@@ -99,6 +101,7 @@ class AverageModel(AbstractLocalModel):
|
|
|
99
101
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
100
102
|
"""
|
|
101
103
|
|
|
104
|
+
ag_priority = 100
|
|
102
105
|
allowed_local_model_args = ["seasonal_period"]
|
|
103
106
|
default_max_ts_length = None
|
|
104
107
|
|
|
@@ -138,6 +141,7 @@ class SeasonalAverageModel(AbstractLocalModel):
|
|
|
138
141
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
139
142
|
"""
|
|
140
143
|
|
|
144
|
+
ag_priority = 100
|
|
141
145
|
allowed_local_model_args = ["seasonal_period"]
|
|
142
146
|
default_max_ts_length = None
|
|
143
147
|
|
|
@@ -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()
|
|
@@ -133,6 +133,7 @@ class AutoARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
|
133
133
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
134
134
|
"""
|
|
135
135
|
|
|
136
|
+
ag_priority = 60
|
|
136
137
|
init_time_in_seconds = 0 # C++ models require no compilation
|
|
137
138
|
allowed_local_model_args = [
|
|
138
139
|
"d",
|
|
@@ -175,9 +176,9 @@ class ARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
|
175
176
|
|
|
176
177
|
Other Parameters
|
|
177
178
|
----------------
|
|
178
|
-
order:
|
|
179
|
+
order: tuple[int, int, int], default = (1, 1, 1)
|
|
179
180
|
The (p, d, q) order of the model for the number of AR parameters, differences, and MA parameters to use.
|
|
180
|
-
seasonal_order:
|
|
181
|
+
seasonal_order: tuple[int, int, int], default = (0, 0, 0)
|
|
181
182
|
The (P, D, Q) parameters of the seasonal ARIMA model. Setting to (0, 0, 0) disables seasonality.
|
|
182
183
|
include_mean : bool, default = True
|
|
183
184
|
Should the ARIMA model include a mean term?
|
|
@@ -193,7 +194,7 @@ class ARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
|
193
194
|
method : {"CSS-ML", "CSS", "ML"}, default = "CSS-ML"
|
|
194
195
|
Fitting method: CSS (conditional sum of squares), ML (maximum likelihood), CSS-ML (initialize with CSS, then
|
|
195
196
|
optimize with ML).
|
|
196
|
-
fixed :
|
|
197
|
+
fixed : dict[str, float], optional
|
|
197
198
|
Dictionary containing fixed coefficients for the ARIMA model.
|
|
198
199
|
seasonal_period : int or None, default = None
|
|
199
200
|
Number of time steps in a complete seasonal cycle for seasonal models. For example, 7 for daily data with a
|
|
@@ -211,6 +212,7 @@ class ARIMAModel(AbstractProbabilisticStatsForecastModel):
|
|
|
211
212
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
212
213
|
"""
|
|
213
214
|
|
|
215
|
+
ag_priority = 10
|
|
214
216
|
init_time_in_seconds = 0 # C++ models require no compilation
|
|
215
217
|
allowed_local_model_args = [
|
|
216
218
|
"order",
|
|
@@ -267,6 +269,7 @@ class AutoETSModel(AbstractProbabilisticStatsForecastModel):
|
|
|
267
269
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
268
270
|
"""
|
|
269
271
|
|
|
272
|
+
ag_priority = 70
|
|
270
273
|
init_time_in_seconds = 0 # C++ models require no compilation
|
|
271
274
|
allowed_local_model_args = [
|
|
272
275
|
"damped",
|
|
@@ -330,6 +333,8 @@ class ETSModel(AutoETSModel):
|
|
|
330
333
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
331
334
|
"""
|
|
332
335
|
|
|
336
|
+
ag_priority = 80
|
|
337
|
+
|
|
333
338
|
def _update_local_model_args(self, local_model_args: dict) -> dict:
|
|
334
339
|
local_model_args = super()._update_local_model_args(local_model_args)
|
|
335
340
|
local_model_args.setdefault("model", "AAA")
|
|
@@ -369,6 +374,7 @@ class DynamicOptimizedThetaModel(AbstractProbabilisticStatsForecastModel):
|
|
|
369
374
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
370
375
|
"""
|
|
371
376
|
|
|
377
|
+
ag_priority = 75
|
|
372
378
|
allowed_local_model_args = [
|
|
373
379
|
"decomposition_type",
|
|
374
380
|
"seasonal_period",
|
|
@@ -413,6 +419,7 @@ class ThetaModel(AbstractProbabilisticStatsForecastModel):
|
|
|
413
419
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
414
420
|
"""
|
|
415
421
|
|
|
422
|
+
ag_priority = 75
|
|
416
423
|
allowed_local_model_args = [
|
|
417
424
|
"decomposition_type",
|
|
418
425
|
"seasonal_period",
|
|
@@ -442,7 +449,7 @@ class AbstractConformalizedStatsForecastModel(AbstractStatsForecastModel):
|
|
|
442
449
|
def _get_nonconformity_scores(
|
|
443
450
|
self,
|
|
444
451
|
time_series: pd.Series,
|
|
445
|
-
local_model_args:
|
|
452
|
+
local_model_args: dict,
|
|
446
453
|
) -> np.ndarray:
|
|
447
454
|
h = self.prediction_length
|
|
448
455
|
y = time_series.values.ravel()
|
|
@@ -533,6 +540,7 @@ class AutoCESModel(AbstractProbabilisticStatsForecastModel):
|
|
|
533
540
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
534
541
|
"""
|
|
535
542
|
|
|
543
|
+
ag_priority = 10
|
|
536
544
|
allowed_local_model_args = [
|
|
537
545
|
"model",
|
|
538
546
|
"seasonal_period",
|
|
@@ -548,7 +556,7 @@ class AutoCESModel(AbstractProbabilisticStatsForecastModel):
|
|
|
548
556
|
local_model_args.setdefault("model", "Z")
|
|
549
557
|
return local_model_args
|
|
550
558
|
|
|
551
|
-
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):
|
|
552
560
|
# Disable seasonality if time series too short for chosen season_length or season_length == 1,
|
|
553
561
|
# otherwise model will crash
|
|
554
562
|
if len(time_series) < 5:
|
|
@@ -560,7 +568,7 @@ class AutoCESModel(AbstractProbabilisticStatsForecastModel):
|
|
|
560
568
|
|
|
561
569
|
|
|
562
570
|
class AbstractStatsForecastIntermittentDemandModel(AbstractConformalizedStatsForecastModel):
|
|
563
|
-
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]:
|
|
564
572
|
_ = local_model_args.pop("seasonal_period")
|
|
565
573
|
return local_model_args
|
|
566
574
|
|
|
@@ -600,6 +608,8 @@ class ADIDAModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
600
608
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
601
609
|
"""
|
|
602
610
|
|
|
611
|
+
ag_priority = 10
|
|
612
|
+
|
|
603
613
|
def _get_model_type(self, variant: Optional[str] = None):
|
|
604
614
|
from statsforecast.models import ADIDA
|
|
605
615
|
|
|
@@ -622,9 +632,9 @@ class CrostonModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
622
632
|
variant : {"SBA", "classic", "optimized"}, default = "SBA"
|
|
623
633
|
Variant of the Croston model that is used. Available options:
|
|
624
634
|
|
|
625
|
-
-
|
|
626
|
-
-
|
|
627
|
-
-
|
|
635
|
+
- ``"classic"`` - variant of the Croston method where the smoothing parameter is fixed to 0.1 (based on `statsforecast.models.CrostonClassic <https://nixtla.mintlify.app/statsforecast/docs/models/crostonclassic.html>`_)
|
|
636
|
+
- ``"SBA"`` - variant of the Croston method based on Syntetos-Boylan Approximation (based on `statsforecast.models.CrostonSBA <https://nixtla.mintlify.app/statsforecast/docs/models/crostonsba.html>`_)
|
|
637
|
+
- ``"optimized"`` - variant of the Croston method where the smoothing parameter is optimized (based on `statsforecast.models.CrostonOptimized <https://nixtla.mintlify.app/statsforecast/docs/models/crostonoptimized.html>`_)
|
|
628
638
|
|
|
629
639
|
n_jobs : int or float, default = joblib.cpu_count(only_physical_cores=True)
|
|
630
640
|
Number of CPU cores used to fit the models in parallel.
|
|
@@ -636,6 +646,8 @@ class CrostonModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
636
646
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
637
647
|
"""
|
|
638
648
|
|
|
649
|
+
ag_model_aliases = ["CrostonSBA"]
|
|
650
|
+
ag_priority = 80
|
|
639
651
|
allowed_local_model_args = [
|
|
640
652
|
"variant",
|
|
641
653
|
]
|
|
@@ -688,6 +700,8 @@ class IMAPAModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
688
700
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
689
701
|
"""
|
|
690
702
|
|
|
703
|
+
ag_priority = 10
|
|
704
|
+
|
|
691
705
|
def _get_model_type(self, variant: Optional[str] = None):
|
|
692
706
|
from statsforecast.models import IMAPA
|
|
693
707
|
|
|
@@ -710,6 +724,8 @@ class ZeroModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
710
724
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
711
725
|
"""
|
|
712
726
|
|
|
727
|
+
ag_priority = 100
|
|
728
|
+
|
|
713
729
|
def _get_model_type(self, variant: Optional[str] = None):
|
|
714
730
|
# ZeroModel does not depend on a StatsForecast implementation
|
|
715
731
|
raise NotImplementedError
|
|
@@ -717,6 +733,6 @@ class ZeroModel(AbstractStatsForecastIntermittentDemandModel):
|
|
|
717
733
|
def _get_point_forecast(
|
|
718
734
|
self,
|
|
719
735
|
time_series: pd.Series,
|
|
720
|
-
local_model_args:
|
|
736
|
+
local_model_args: dict,
|
|
721
737
|
):
|
|
722
738
|
return np.zeros(self.prediction_length)
|
|
@@ -4,12 +4,13 @@ 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
|
-
from autogluon.timeseries.dataset
|
|
13
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
13
14
|
from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
|
|
14
15
|
from autogluon.timeseries.models.local.abstract_local_model import AbstractLocalModel
|
|
15
16
|
from autogluon.timeseries.splitter import AbstractWindowSplitter, ExpandingWindowSplitter
|
|
@@ -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
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from inspect import isabstract
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ModelRecord:
|
|
9
|
+
model_class: type
|
|
10
|
+
ag_priority: int
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ModelRegistry(ABCMeta):
|
|
14
|
+
"""Registry metaclass for time series models. Ensures that TimeSeriesModel classes
|
|
15
|
+
which implement this metaclass are automatically registered, in order to centralize
|
|
16
|
+
access to model types.
|
|
17
|
+
|
|
18
|
+
See, https://github.com/faif/python-patterns.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
REGISTRY: dict[str, ModelRecord] = {}
|
|
22
|
+
|
|
23
|
+
def __new__(cls, name, bases, attrs):
|
|
24
|
+
new_cls = super().__new__(cls, name, bases, attrs)
|
|
25
|
+
|
|
26
|
+
if name is not None and not isabstract(new_cls):
|
|
27
|
+
record = ModelRecord(
|
|
28
|
+
model_class=new_cls,
|
|
29
|
+
ag_priority=getattr(new_cls, "ag_priority", 0),
|
|
30
|
+
)
|
|
31
|
+
cls._add(name.removesuffix("Model"), record)
|
|
32
|
+
|
|
33
|
+
# if the class provides additional aliases, register them too
|
|
34
|
+
if aliases := attrs.get("ag_model_aliases"):
|
|
35
|
+
for alias in aliases:
|
|
36
|
+
cls._add(alias, record)
|
|
37
|
+
|
|
38
|
+
return new_cls
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _add(cls, alias: str, record: ModelRecord) -> None:
|
|
42
|
+
if alias in cls.REGISTRY:
|
|
43
|
+
raise ValueError(f"You are trying to define a new model with {alias}, but this model already exists.")
|
|
44
|
+
cls.REGISTRY[alias] = record
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def _get_model_record(cls, alias: Union[str, type]) -> ModelRecord:
|
|
48
|
+
if isinstance(alias, type):
|
|
49
|
+
alias = alias.__name__
|
|
50
|
+
alias = alias.removesuffix("Model")
|
|
51
|
+
if alias not in cls.REGISTRY:
|
|
52
|
+
raise ValueError(f"Unknown model: {alias}, available models are: {cls.available_aliases()}")
|
|
53
|
+
return cls.REGISTRY[alias]
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def get_model_class(cls, alias: Union[str, type]) -> type:
|
|
57
|
+
return cls._get_model_record(alias).model_class
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_model_priority(cls, alias: Union[str, type]) -> int:
|
|
61
|
+
return cls._get_model_record(alias).ag_priority
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def available_aliases(cls) -> list[str]:
|
|
65
|
+
return sorted(cls.REGISTRY.keys())
|