autogluon.timeseries 1.2.1b20250224__py3-none-any.whl → 1.4.1b20251215__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of autogluon.timeseries might be problematic. Click here for more details.
- autogluon/timeseries/configs/__init__.py +3 -2
- autogluon/timeseries/configs/hyperparameter_presets.py +62 -0
- autogluon/timeseries/configs/predictor_presets.py +106 -0
- autogluon/timeseries/dataset/ts_dataframe.py +256 -141
- autogluon/timeseries/learner.py +86 -52
- autogluon/timeseries/metrics/__init__.py +42 -8
- autogluon/timeseries/metrics/abstract.py +89 -19
- autogluon/timeseries/metrics/point.py +142 -53
- autogluon/timeseries/metrics/quantile.py +46 -21
- autogluon/timeseries/metrics/utils.py +4 -4
- autogluon/timeseries/models/__init__.py +8 -2
- autogluon/timeseries/models/abstract/__init__.py +2 -2
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +361 -592
- autogluon/timeseries/models/abstract/model_trial.py +2 -1
- autogluon/timeseries/models/abstract/tunable.py +189 -0
- autogluon/timeseries/models/autogluon_tabular/__init__.py +2 -0
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +282 -194
- autogluon/timeseries/models/autogluon_tabular/per_step.py +513 -0
- autogluon/timeseries/models/autogluon_tabular/transforms.py +25 -18
- autogluon/timeseries/models/chronos/__init__.py +2 -1
- autogluon/timeseries/models/chronos/chronos2.py +361 -0
- autogluon/timeseries/models/chronos/model.py +219 -138
- autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +81 -50
- autogluon/timeseries/models/ensemble/__init__.py +37 -2
- autogluon/timeseries/models/ensemble/abstract.py +107 -0
- autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
- autogluon/timeseries/models/ensemble/array_based/abstract.py +240 -0
- autogluon/timeseries/models/ensemble/array_based/models.py +185 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +12 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +88 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +94 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +107 -0
- autogluon/timeseries/models/ensemble/ensemble_selection.py +167 -0
- autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
- autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +45 -0
- autogluon/timeseries/models/ensemble/weighted/basic.py +91 -0
- autogluon/timeseries/models/ensemble/weighted/greedy.py +62 -0
- autogluon/timeseries/models/gluonts/__init__.py +1 -1
- autogluon/timeseries/models/gluonts/{abstract_gluonts.py → abstract.py} +148 -208
- autogluon/timeseries/models/gluonts/dataset.py +109 -0
- autogluon/timeseries/models/gluonts/{torch/models.py → models.py} +38 -22
- autogluon/timeseries/models/local/__init__.py +0 -7
- autogluon/timeseries/models/local/abstract_local_model.py +71 -74
- autogluon/timeseries/models/local/naive.py +13 -9
- autogluon/timeseries/models/local/npts.py +9 -2
- autogluon/timeseries/models/local/statsforecast.py +52 -36
- autogluon/timeseries/models/multi_window/multi_window_model.py +65 -45
- autogluon/timeseries/models/registry.py +64 -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 +196 -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 +89 -0
- autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
- autogluon/timeseries/models/toto/_internal/backbone/scaler.py +305 -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 +200 -0
- autogluon/timeseries/models/toto/model.py +249 -0
- autogluon/timeseries/predictor.py +685 -297
- autogluon/timeseries/regressor.py +94 -44
- autogluon/timeseries/splitter.py +8 -32
- autogluon/timeseries/trainer/__init__.py +3 -0
- autogluon/timeseries/trainer/ensemble_composer.py +444 -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} +387 -390
- autogluon/timeseries/trainer/utils.py +17 -0
- autogluon/timeseries/transforms/__init__.py +2 -13
- autogluon/timeseries/transforms/covariate_scaler.py +34 -40
- autogluon/timeseries/transforms/target_scaler.py +37 -20
- autogluon/timeseries/utils/constants.py +10 -0
- autogluon/timeseries/utils/datetime/lags.py +3 -5
- autogluon/timeseries/utils/datetime/seasonality.py +1 -3
- autogluon/timeseries/utils/datetime/time_features.py +2 -2
- autogluon/timeseries/utils/features.py +70 -47
- autogluon/timeseries/utils/forecast.py +19 -14
- autogluon/timeseries/utils/timer.py +173 -0
- autogluon/timeseries/utils/warning_filters.py +4 -2
- autogluon/timeseries/version.py +1 -1
- autogluon.timeseries-1.4.1b20251215-py3.11-nspkg.pth +1 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/METADATA +49 -36
- autogluon_timeseries-1.4.1b20251215.dist-info/RECORD +103 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.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 -11
- autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
- autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -585
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -518
- autogluon/timeseries/models/ensemble/abstract_timeseries_ensemble.py +0 -78
- autogluon/timeseries/models/ensemble/greedy_ensemble.py +0 -170
- autogluon/timeseries/models/gluonts/torch/__init__.py +0 -0
- autogluon/timeseries/models/presets.py +0 -360
- autogluon.timeseries-1.2.1b20250224-py3.9-nspkg.pth +0 -1
- autogluon.timeseries-1.2.1b20250224.dist-info/RECORD +0 -68
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/LICENSE +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/NOTICE +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import Any, Iterator, Type
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from gluonts.dataset.common import Dataset as GluonTSDataset
|
|
6
|
+
from gluonts.dataset.field_names import FieldName
|
|
7
|
+
|
|
8
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
9
|
+
from autogluon.timeseries.utils.datetime import norm_freq_str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimpleGluonTSDataset(GluonTSDataset):
|
|
13
|
+
"""Wrapper for TimeSeriesDataFrame that is compatible with the GluonTS Dataset API."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
target_df: TimeSeriesDataFrame,
|
|
18
|
+
freq: str,
|
|
19
|
+
target_column: str = "target",
|
|
20
|
+
feat_static_cat: np.ndarray | None = None,
|
|
21
|
+
feat_static_real: np.ndarray | None = None,
|
|
22
|
+
feat_dynamic_cat: np.ndarray | None = None,
|
|
23
|
+
feat_dynamic_real: np.ndarray | None = None,
|
|
24
|
+
past_feat_dynamic_cat: np.ndarray | None = None,
|
|
25
|
+
past_feat_dynamic_real: np.ndarray | None = None,
|
|
26
|
+
includes_future: bool = False,
|
|
27
|
+
prediction_length: int | None = None,
|
|
28
|
+
):
|
|
29
|
+
assert target_df is not None
|
|
30
|
+
# Convert TimeSeriesDataFrame to pd.Series for faster processing
|
|
31
|
+
self.target_array = target_df[target_column].to_numpy(np.float32)
|
|
32
|
+
self.feat_static_cat = self._astype(feat_static_cat, dtype=np.int64)
|
|
33
|
+
self.feat_static_real = self._astype(feat_static_real, dtype=np.float32)
|
|
34
|
+
self.feat_dynamic_cat = self._astype(feat_dynamic_cat, dtype=np.int64)
|
|
35
|
+
self.feat_dynamic_real = self._astype(feat_dynamic_real, dtype=np.float32)
|
|
36
|
+
self.past_feat_dynamic_cat = self._astype(past_feat_dynamic_cat, dtype=np.int64)
|
|
37
|
+
self.past_feat_dynamic_real = self._astype(past_feat_dynamic_real, dtype=np.float32)
|
|
38
|
+
self.freq = self._get_freq_for_period(freq)
|
|
39
|
+
|
|
40
|
+
# Necessary to compute indptr for known_covariates at prediction time
|
|
41
|
+
self.includes_future = includes_future
|
|
42
|
+
self.prediction_length = prediction_length
|
|
43
|
+
|
|
44
|
+
# Replace inefficient groupby ITEMID with indptr that stores start:end of each time series
|
|
45
|
+
self.item_ids = target_df.item_ids
|
|
46
|
+
self.indptr = target_df.get_indptr()
|
|
47
|
+
self.start_timestamps = target_df.index[self.indptr[:-1]].to_frame(index=False)[TimeSeriesDataFrame.TIMESTAMP]
|
|
48
|
+
assert len(self.item_ids) == len(self.start_timestamps)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _astype(array: np.ndarray | None, dtype: Type[np.generic]) -> np.ndarray | None:
|
|
52
|
+
if array is None:
|
|
53
|
+
return None
|
|
54
|
+
else:
|
|
55
|
+
return array.astype(dtype)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _get_freq_for_period(freq: str) -> str:
|
|
59
|
+
"""Convert freq to format compatible with pd.Period.
|
|
60
|
+
|
|
61
|
+
For example, ME freq must be converted to M when creating a pd.Period.
|
|
62
|
+
"""
|
|
63
|
+
offset = pd.tseries.frequencies.to_offset(freq)
|
|
64
|
+
assert offset is not None
|
|
65
|
+
freq_name = norm_freq_str(offset)
|
|
66
|
+
if freq_name == "SME":
|
|
67
|
+
# Replace unsupported frequency "SME" with "2W"
|
|
68
|
+
return "2W"
|
|
69
|
+
elif freq_name == "bh":
|
|
70
|
+
# Replace unsupported frequency "bh" with dummy value "Y"
|
|
71
|
+
return "Y"
|
|
72
|
+
else:
|
|
73
|
+
freq_name_for_period = {"YE": "Y", "QE": "Q", "ME": "M"}.get(freq_name, freq_name)
|
|
74
|
+
return f"{offset.n}{freq_name_for_period}"
|
|
75
|
+
|
|
76
|
+
def __len__(self):
|
|
77
|
+
return len(self.indptr) - 1 # noqa
|
|
78
|
+
|
|
79
|
+
def __iter__(self) -> Iterator[dict[str, Any]]:
|
|
80
|
+
for j in range(len(self.indptr) - 1):
|
|
81
|
+
start_idx = self.indptr[j]
|
|
82
|
+
end_idx = self.indptr[j + 1]
|
|
83
|
+
# GluonTS expects item_id to be a string
|
|
84
|
+
ts = {
|
|
85
|
+
FieldName.ITEM_ID: str(self.item_ids[j]),
|
|
86
|
+
FieldName.START: pd.Period(self.start_timestamps.iloc[j], freq=self.freq),
|
|
87
|
+
FieldName.TARGET: self.target_array[start_idx:end_idx],
|
|
88
|
+
}
|
|
89
|
+
if self.feat_static_cat is not None:
|
|
90
|
+
ts[FieldName.FEAT_STATIC_CAT] = self.feat_static_cat[j]
|
|
91
|
+
if self.feat_static_real is not None:
|
|
92
|
+
ts[FieldName.FEAT_STATIC_REAL] = self.feat_static_real[j]
|
|
93
|
+
if self.past_feat_dynamic_cat is not None:
|
|
94
|
+
ts[FieldName.PAST_FEAT_DYNAMIC_CAT] = self.past_feat_dynamic_cat[start_idx:end_idx].T
|
|
95
|
+
if self.past_feat_dynamic_real is not None:
|
|
96
|
+
ts[FieldName.PAST_FEAT_DYNAMIC_REAL] = self.past_feat_dynamic_real[start_idx:end_idx].T
|
|
97
|
+
|
|
98
|
+
# Dynamic features that may extend into the future
|
|
99
|
+
if self.includes_future:
|
|
100
|
+
assert self.prediction_length is not None, (
|
|
101
|
+
"Prediction length must be provided if includes_future is True"
|
|
102
|
+
)
|
|
103
|
+
start_idx = start_idx + j * self.prediction_length
|
|
104
|
+
end_idx = end_idx + (j + 1) * self.prediction_length
|
|
105
|
+
if self.feat_dynamic_cat is not None:
|
|
106
|
+
ts[FieldName.FEAT_DYNAMIC_CAT] = self.feat_dynamic_cat[start_idx:end_idx].T
|
|
107
|
+
if self.feat_dynamic_real is not None:
|
|
108
|
+
ts[FieldName.FEAT_DYNAMIC_REAL] = self.feat_dynamic_real[start_idx:end_idx].T
|
|
109
|
+
yield ts
|
|
@@ -3,17 +3,18 @@ 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
|
|
|
10
|
-
from autogluon.timeseries.models.gluonts.abstract_gluonts import AbstractGluonTSModel
|
|
11
10
|
from autogluon.timeseries.utils.datetime import (
|
|
12
11
|
get_lags_for_frequency,
|
|
13
12
|
get_seasonality,
|
|
14
13
|
get_time_features_for_frequency,
|
|
15
14
|
)
|
|
16
15
|
|
|
16
|
+
from .abstract import AbstractGluonTSModel
|
|
17
|
+
|
|
17
18
|
# NOTE: We avoid imports for torch and lightning.pytorch at the top level and hide them inside class methods.
|
|
18
19
|
# This is done to skip these imports during multiprocessing (which may cause bugs)
|
|
19
20
|
|
|
@@ -59,7 +60,7 @@ class DeepARModel(AbstractGluonTSModel):
|
|
|
59
60
|
Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
|
|
60
61
|
scaling: bool, default = True
|
|
61
62
|
If True, mean absolute scaling will be applied to each *context window* during training & prediction.
|
|
62
|
-
Note that this is different from the
|
|
63
|
+
Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
|
|
63
64
|
max_epochs : int, default = 100
|
|
64
65
|
Number of epochs the model will be trained for
|
|
65
66
|
batch_size : int, default = 64
|
|
@@ -80,6 +81,8 @@ class DeepARModel(AbstractGluonTSModel):
|
|
|
80
81
|
|
|
81
82
|
# TODO: Replace "scaling: bool" with "window_scaler": {"mean_abs", None} for consistency?
|
|
82
83
|
|
|
84
|
+
ag_priority = 40
|
|
85
|
+
|
|
83
86
|
_supports_known_covariates = True
|
|
84
87
|
_supports_static_features = True
|
|
85
88
|
|
|
@@ -88,13 +91,13 @@ class DeepARModel(AbstractGluonTSModel):
|
|
|
88
91
|
|
|
89
92
|
return DeepAREstimator
|
|
90
93
|
|
|
91
|
-
def _get_estimator_init_args(self) ->
|
|
94
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
|
92
95
|
init_kwargs = super()._get_estimator_init_args()
|
|
93
96
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
|
94
97
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
|
95
98
|
init_kwargs["cardinality"] = self.feat_static_cat_cardinality
|
|
96
99
|
init_kwargs["num_feat_dynamic_real"] = self.num_feat_dynamic_real
|
|
97
|
-
init_kwargs.setdefault("lags_seq", get_lags_for_frequency(self.freq))
|
|
100
|
+
init_kwargs.setdefault("lags_seq", get_lags_for_frequency(self.freq)) # type: ignore
|
|
98
101
|
init_kwargs.setdefault("time_features", get_time_features_for_frequency(self.freq))
|
|
99
102
|
return init_kwargs
|
|
100
103
|
|
|
@@ -110,7 +113,7 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
|
|
|
110
113
|
----------------
|
|
111
114
|
context_length : int, default = max(10, 2 * prediction_length)
|
|
112
115
|
Number of time units that condition the predictions
|
|
113
|
-
hidden_dimensions:
|
|
116
|
+
hidden_dimensions: list[int], default = [20, 20]
|
|
114
117
|
Size of hidden layers in the feedforward network
|
|
115
118
|
distr_output : gluonts.torch.distributions.Output, default = StudentTOutput()
|
|
116
119
|
Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
|
|
@@ -118,7 +121,7 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
|
|
|
118
121
|
Whether to use batch normalization
|
|
119
122
|
mean_scaling : bool, default = True
|
|
120
123
|
If True, mean absolute scaling will be applied to each *context window* during training & prediction.
|
|
121
|
-
Note that this is different from the
|
|
124
|
+
Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
|
|
122
125
|
max_epochs : int, default = 100
|
|
123
126
|
Number of epochs the model will be trained for
|
|
124
127
|
batch_size : int, default = 64
|
|
@@ -137,6 +140,8 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
|
|
|
137
140
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
138
141
|
"""
|
|
139
142
|
|
|
143
|
+
ag_priority = 10
|
|
144
|
+
|
|
140
145
|
def _get_estimator_class(self) -> Type[GluonTSEstimator]:
|
|
141
146
|
from gluonts.torch.model.simple_feedforward import SimpleFeedForwardEstimator
|
|
142
147
|
|
|
@@ -198,6 +203,9 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
|
|
|
198
203
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
199
204
|
"""
|
|
200
205
|
|
|
206
|
+
ag_priority = 45
|
|
207
|
+
ag_model_aliases = ["TFT"]
|
|
208
|
+
|
|
201
209
|
_supports_known_covariates = True
|
|
202
210
|
_supports_past_covariates = True
|
|
203
211
|
_supports_cat_covariates = True
|
|
@@ -208,12 +216,12 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
|
|
|
208
216
|
|
|
209
217
|
return TemporalFusionTransformerEstimator
|
|
210
218
|
|
|
211
|
-
def
|
|
212
|
-
return super().
|
|
219
|
+
def _get_default_hyperparameters(self):
|
|
220
|
+
return super()._get_default_hyperparameters() | {
|
|
213
221
|
"context_length": min(512, max(64, 2 * self.prediction_length)),
|
|
214
222
|
}
|
|
215
223
|
|
|
216
|
-
def _get_estimator_init_args(self) ->
|
|
224
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
|
217
225
|
init_kwargs = super()._get_estimator_init_args()
|
|
218
226
|
if self.num_feat_dynamic_real > 0:
|
|
219
227
|
init_kwargs["dynamic_dims"] = [self.num_feat_dynamic_real]
|
|
@@ -260,7 +268,7 @@ class DLinearModel(AbstractGluonTSModel):
|
|
|
260
268
|
Scaling applied to each *context window* during training & prediction.
|
|
261
269
|
One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
|
|
262
270
|
|
|
263
|
-
Note that this is different from the
|
|
271
|
+
Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
|
|
264
272
|
max_epochs : int, default = 100
|
|
265
273
|
Number of epochs the model will be trained for
|
|
266
274
|
batch_size : int, default = 64
|
|
@@ -281,8 +289,10 @@ class DLinearModel(AbstractGluonTSModel):
|
|
|
281
289
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
282
290
|
"""
|
|
283
291
|
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
ag_priority = 10
|
|
293
|
+
|
|
294
|
+
def _get_default_hyperparameters(self):
|
|
295
|
+
return super()._get_default_hyperparameters() | {
|
|
286
296
|
"context_length": 96,
|
|
287
297
|
}
|
|
288
298
|
|
|
@@ -324,7 +334,7 @@ class PatchTSTModel(AbstractGluonTSModel):
|
|
|
324
334
|
Scaling applied to each *context window* during training & prediction.
|
|
325
335
|
One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
|
|
326
336
|
|
|
327
|
-
Note that this is different from the
|
|
337
|
+
Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
|
|
328
338
|
max_epochs : int, default = 100
|
|
329
339
|
Number of epochs the model will be trained for
|
|
330
340
|
batch_size : int, default = 64
|
|
@@ -339,6 +349,8 @@ class PatchTSTModel(AbstractGluonTSModel):
|
|
|
339
349
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
340
350
|
"""
|
|
341
351
|
|
|
352
|
+
ag_priority = 30
|
|
353
|
+
|
|
342
354
|
_supports_known_covariates = True
|
|
343
355
|
|
|
344
356
|
def _get_estimator_class(self) -> Type[GluonTSEstimator]:
|
|
@@ -346,10 +358,10 @@ class PatchTSTModel(AbstractGluonTSModel):
|
|
|
346
358
|
|
|
347
359
|
return PatchTSTEstimator
|
|
348
360
|
|
|
349
|
-
def
|
|
350
|
-
return super().
|
|
361
|
+
def _get_default_hyperparameters(self):
|
|
362
|
+
return super()._get_default_hyperparameters() | {"context_length": 96, "patch_len": 16}
|
|
351
363
|
|
|
352
|
-
def _get_estimator_init_args(self) ->
|
|
364
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
|
353
365
|
init_kwargs = super()._get_estimator_init_args()
|
|
354
366
|
init_kwargs["num_feat_dynamic_real"] = self.num_feat_dynamic_real
|
|
355
367
|
return init_kwargs
|
|
@@ -415,6 +427,8 @@ class WaveNetModel(AbstractGluonTSModel):
|
|
|
415
427
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
416
428
|
"""
|
|
417
429
|
|
|
430
|
+
ag_priority = 25
|
|
431
|
+
|
|
418
432
|
_supports_known_covariates = True
|
|
419
433
|
_supports_static_features = True
|
|
420
434
|
default_num_samples: int = 100
|
|
@@ -424,7 +438,7 @@ class WaveNetModel(AbstractGluonTSModel):
|
|
|
424
438
|
|
|
425
439
|
return WaveNetEstimator
|
|
426
440
|
|
|
427
|
-
def _get_estimator_init_args(self) ->
|
|
441
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
|
428
442
|
init_kwargs = super()._get_estimator_init_args()
|
|
429
443
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
|
430
444
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
|
@@ -488,7 +502,7 @@ class TiDEModel(AbstractGluonTSModel):
|
|
|
488
502
|
Scaling applied to each *context window* during training & prediction.
|
|
489
503
|
One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
|
|
490
504
|
|
|
491
|
-
Note that this is different from the
|
|
505
|
+
Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
|
|
492
506
|
max_epochs : int, default = 100
|
|
493
507
|
Number of epochs the model will be trained for
|
|
494
508
|
batch_size : int, default = 256
|
|
@@ -507,6 +521,8 @@ class TiDEModel(AbstractGluonTSModel):
|
|
|
507
521
|
If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
|
|
508
522
|
"""
|
|
509
523
|
|
|
524
|
+
ag_priority = 30
|
|
525
|
+
|
|
510
526
|
_supports_known_covariates = True
|
|
511
527
|
_supports_static_features = True
|
|
512
528
|
|
|
@@ -515,8 +531,8 @@ class TiDEModel(AbstractGluonTSModel):
|
|
|
515
531
|
|
|
516
532
|
return TiDEEstimator
|
|
517
533
|
|
|
518
|
-
def
|
|
519
|
-
return super().
|
|
534
|
+
def _get_default_hyperparameters(self):
|
|
535
|
+
return super()._get_default_hyperparameters() | {
|
|
520
536
|
"context_length": min(512, max(64, 2 * self.prediction_length)),
|
|
521
537
|
"encoder_hidden_dim": 64,
|
|
522
538
|
"decoder_hidden_dim": 64,
|
|
@@ -531,7 +547,7 @@ class TiDEModel(AbstractGluonTSModel):
|
|
|
531
547
|
"batch_size": 256,
|
|
532
548
|
}
|
|
533
549
|
|
|
534
|
-
def _get_estimator_init_args(self) ->
|
|
550
|
+
def _get_estimator_init_args(self) -> dict[str, Any]:
|
|
535
551
|
init_kwargs = super()._get_estimator_init_args()
|
|
536
552
|
init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
|
|
537
553
|
init_kwargs["num_feat_static_real"] = self.num_feat_static_real
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import joblib.externals.loky
|
|
2
|
-
|
|
3
1
|
from .naive import AverageModel, NaiveModel, SeasonalAverageModel, SeasonalNaiveModel
|
|
4
2
|
from .npts import NPTSModel
|
|
5
3
|
from .statsforecast import (
|
|
@@ -15,8 +13,3 @@ from .statsforecast import (
|
|
|
15
13
|
ThetaModel,
|
|
16
14
|
ZeroModel,
|
|
17
15
|
)
|
|
18
|
-
|
|
19
|
-
# By default, joblib w/ loky backend kills processes that take >300MB of RAM assuming that this is caused by a memory
|
|
20
|
-
# leak. This leads to problems for some memory-hungry models like AutoARIMA/Theta.
|
|
21
|
-
# This monkey patch removes this undesired behavior
|
|
22
|
-
joblib.externals.loky.process_executor._MAX_MEMORY_LEAK_SIZE = int(3e10)
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from multiprocessing import TimeoutError
|
|
4
|
-
from typing import Any, Callable
|
|
3
|
+
from multiprocessing import TimeoutError
|
|
4
|
+
from typing import Any, Callable
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
|
-
from joblib import Parallel, delayed
|
|
8
|
+
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
|
+
from autogluon.timeseries.metrics import TimeSeriesScorer
|
|
13
14
|
from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
|
|
15
|
+
from autogluon.timeseries.utils.constants import AG_DEFAULT_N_JOBS
|
|
14
16
|
from autogluon.timeseries.utils.datetime import get_seasonality
|
|
15
17
|
from autogluon.timeseries.utils.warning_filters import warning_filter
|
|
16
18
|
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
# We use the same default n_jobs across AG-TS to ensure that Joblib reuses the process pool
|
|
21
|
-
AG_DEFAULT_N_JOBS = max(int(cpu_count() * 0.5), 1)
|
|
22
|
-
|
|
23
|
-
|
|
24
22
|
class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
25
23
|
"""Abstract class for local forecasting models that are trained separately for each time series.
|
|
26
24
|
|
|
@@ -28,50 +26,31 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
28
26
|
|
|
29
27
|
Attributes
|
|
30
28
|
----------
|
|
31
|
-
allowed_local_model_args
|
|
29
|
+
allowed_local_model_args
|
|
32
30
|
Argument that can be passed to the underlying local model.
|
|
33
|
-
|
|
34
|
-
Default number of CPU cores used to train models. If float, this fraction of CPU cores will be used.
|
|
35
|
-
default_max_ts_length : Optional[int]
|
|
31
|
+
default_max_ts_length
|
|
36
32
|
If not None, only the last ``max_ts_length`` time steps of each time series will be used to train the model.
|
|
37
33
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
38
|
-
init_time_in_seconds
|
|
34
|
+
init_time_in_seconds
|
|
39
35
|
Time that it takes to initialize the model in seconds (e.g., because of JIT compilation by Numba).
|
|
40
36
|
If time_limit is below this number, model won't be trained.
|
|
41
37
|
"""
|
|
42
38
|
|
|
43
|
-
allowed_local_model_args:
|
|
44
|
-
|
|
45
|
-
default_max_ts_length: Optional[int] = 2500
|
|
39
|
+
allowed_local_model_args: list[str] = []
|
|
40
|
+
default_max_ts_length: int | None = 2500
|
|
46
41
|
default_max_time_limit_ratio = 1.0
|
|
47
42
|
init_time_in_seconds: int = 0
|
|
48
43
|
|
|
49
44
|
def __init__(
|
|
50
45
|
self,
|
|
51
|
-
freq:
|
|
46
|
+
freq: str | None = None,
|
|
52
47
|
prediction_length: int = 1,
|
|
53
|
-
path:
|
|
54
|
-
name:
|
|
55
|
-
eval_metric: str = None,
|
|
56
|
-
hyperparameters:
|
|
48
|
+
path: str | None = None,
|
|
49
|
+
name: str | None = None,
|
|
50
|
+
eval_metric: str | TimeSeriesScorer | None = None,
|
|
51
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
57
52
|
**kwargs, # noqa
|
|
58
53
|
):
|
|
59
|
-
if hyperparameters is None:
|
|
60
|
-
hyperparameters = {}
|
|
61
|
-
else:
|
|
62
|
-
hyperparameters = hyperparameters.copy()
|
|
63
|
-
# TODO: Replace with 'num_cpus' argument passed to fit (after predictor API is changed)
|
|
64
|
-
n_jobs = hyperparameters.pop("n_jobs", self.default_n_jobs)
|
|
65
|
-
if isinstance(n_jobs, float) and 0 < n_jobs <= 1:
|
|
66
|
-
self.n_jobs = max(int(cpu_count() * n_jobs), 1)
|
|
67
|
-
elif isinstance(n_jobs, int):
|
|
68
|
-
self.n_jobs = n_jobs
|
|
69
|
-
else:
|
|
70
|
-
raise ValueError(f"n_jobs must be a float between 0 and 1 or an integer (received n_jobs = {n_jobs})")
|
|
71
|
-
# Default values, potentially overridden inside _fit()
|
|
72
|
-
self.use_fallback_model = hyperparameters.pop("use_fallback_model", True)
|
|
73
|
-
self.max_ts_length = hyperparameters.pop("max_ts_length", self.default_max_ts_length)
|
|
74
|
-
|
|
75
54
|
super().__init__(
|
|
76
55
|
path=path,
|
|
77
56
|
freq=freq,
|
|
@@ -82,12 +61,12 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
82
61
|
**kwargs,
|
|
83
62
|
)
|
|
84
63
|
|
|
85
|
-
self._local_model_args:
|
|
86
|
-
self._seasonal_period:
|
|
87
|
-
self._dummy_forecast:
|
|
64
|
+
self._local_model_args: dict[str, Any]
|
|
65
|
+
self._seasonal_period: int
|
|
66
|
+
self._dummy_forecast: pd.DataFrame
|
|
88
67
|
|
|
89
68
|
@property
|
|
90
|
-
def allowed_hyperparameters(self) ->
|
|
69
|
+
def allowed_hyperparameters(self) -> list[str]:
|
|
91
70
|
return (
|
|
92
71
|
super().allowed_hyperparameters
|
|
93
72
|
+ ["use_fallback_model", "max_ts_length", "n_jobs"]
|
|
@@ -97,40 +76,42 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
97
76
|
def preprocess(
|
|
98
77
|
self,
|
|
99
78
|
data: TimeSeriesDataFrame,
|
|
100
|
-
known_covariates:
|
|
79
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
101
80
|
is_train: bool = False,
|
|
102
81
|
**kwargs,
|
|
103
|
-
) ->
|
|
82
|
+
) -> tuple[TimeSeriesDataFrame, TimeSeriesDataFrame | None]:
|
|
104
83
|
if not self._get_tags()["allow_nan"]:
|
|
105
84
|
data = data.fill_missing_values()
|
|
106
85
|
return data, known_covariates
|
|
107
86
|
|
|
108
|
-
def
|
|
87
|
+
def _get_default_hyperparameters(self) -> dict:
|
|
88
|
+
return {
|
|
89
|
+
"n_jobs": AG_DEFAULT_N_JOBS,
|
|
90
|
+
"use_fallback_model": True,
|
|
91
|
+
"max_ts_length": self.default_max_ts_length,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _compute_n_jobs(n_jobs: int | float) -> int:
|
|
96
|
+
if isinstance(n_jobs, float) and 0 < n_jobs <= 1:
|
|
97
|
+
return max(int(cpu_count() * n_jobs), 1)
|
|
98
|
+
elif isinstance(n_jobs, int):
|
|
99
|
+
return n_jobs
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"n_jobs must be a float between 0 and 1 or an integer (received n_jobs = {n_jobs})")
|
|
102
|
+
|
|
103
|
+
def _fit(self, train_data: TimeSeriesDataFrame, time_limit: int | None = None, **kwargs):
|
|
109
104
|
self._check_fit_params()
|
|
110
105
|
|
|
111
106
|
if time_limit is not None and time_limit < self.init_time_in_seconds:
|
|
112
107
|
raise TimeLimitExceeded
|
|
113
108
|
|
|
114
|
-
# Initialize parameters passed to each local model
|
|
115
|
-
raw_local_model_args = self._get_model_params().copy()
|
|
116
|
-
|
|
117
|
-
unused_local_model_args = []
|
|
118
109
|
local_model_args = {}
|
|
119
|
-
|
|
120
|
-
for key, value in raw_local_model_args.items():
|
|
110
|
+
for key, value in self.get_hyperparameters().items():
|
|
121
111
|
if key in self.allowed_local_model_args:
|
|
122
112
|
local_model_args[key] = value
|
|
123
|
-
elif key in self.allowed_hyperparameters:
|
|
124
|
-
# Quietly ignore params in self.allowed_hyperparameters - they are used by AbstractTimeSeriesModel
|
|
125
|
-
pass
|
|
126
|
-
else:
|
|
127
|
-
unused_local_model_args.append(key)
|
|
128
113
|
|
|
129
|
-
|
|
130
|
-
logger.warning(
|
|
131
|
-
f"{self.name} ignores following hyperparameters: {unused_local_model_args}. "
|
|
132
|
-
f"See the docstring of {self.name} for the list of supported hyperparameters."
|
|
133
|
-
)
|
|
114
|
+
self._log_unused_hyperparameters(extra_allowed_hyperparameters=self.allowed_local_model_args)
|
|
134
115
|
|
|
135
116
|
if "seasonal_period" not in local_model_args or local_model_args["seasonal_period"] is None:
|
|
136
117
|
local_model_args["seasonal_period"] = get_seasonality(self.freq)
|
|
@@ -141,35 +122,46 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
141
122
|
self._dummy_forecast = self._get_dummy_forecast(train_data)
|
|
142
123
|
return self
|
|
143
124
|
|
|
144
|
-
def _get_dummy_forecast(self, train_data: TimeSeriesDataFrame) -> pd.DataFrame:
|
|
125
|
+
def _get_dummy_forecast(self, train_data: TimeSeriesDataFrame, max_num_rows: int = 20_000) -> pd.DataFrame:
|
|
145
126
|
agg_functions = ["mean"] + [get_quantile_function(q) for q in self.quantile_levels]
|
|
146
|
-
|
|
127
|
+
target_series = train_data[self.target]
|
|
128
|
+
if len(target_series) > max_num_rows:
|
|
129
|
+
target_series = target_series.sample(max_num_rows, replace=True)
|
|
130
|
+
stats_marginal = target_series.agg(agg_functions)
|
|
147
131
|
stats_repeated = np.tile(stats_marginal.values, [self.prediction_length, 1])
|
|
148
132
|
return pd.DataFrame(stats_repeated, columns=stats_marginal.index)
|
|
149
133
|
|
|
150
|
-
def _update_local_model_args(self, local_model_args:
|
|
134
|
+
def _update_local_model_args(self, local_model_args: dict[str, Any]) -> dict[str, Any]:
|
|
151
135
|
return local_model_args
|
|
152
136
|
|
|
153
137
|
def _predict(self, data: TimeSeriesDataFrame, **kwargs) -> TimeSeriesDataFrame:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
138
|
+
model_params = self.get_hyperparameters()
|
|
139
|
+
max_ts_length = model_params["max_ts_length"]
|
|
140
|
+
if max_ts_length is not None:
|
|
141
|
+
logger.debug(f"Shortening all time series to at most {max_ts_length}")
|
|
142
|
+
data = data.slice_by_timestep(-max_ts_length, None)
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
|
|
144
|
+
indptr = data.get_indptr()
|
|
145
|
+
target_series = data[self.target].droplevel(level=TimeSeriesDataFrame.ITEMID)
|
|
146
|
+
all_series = (target_series[indptr[i] : indptr[i + 1]] for i in range(len(indptr) - 1))
|
|
160
147
|
|
|
161
148
|
# timeout ensures that no individual job takes longer than time_limit
|
|
162
149
|
# TODO: a job started late may still exceed time_limit - how to prevent that?
|
|
163
150
|
time_limit = kwargs.get("time_limit")
|
|
164
|
-
|
|
151
|
+
# TODO: Take into account num_cpus once the TimeSeriesPredictor API is updated
|
|
152
|
+
n_jobs = self._compute_n_jobs(model_params["n_jobs"])
|
|
153
|
+
timeout = None if n_jobs == 1 else time_limit
|
|
165
154
|
# end_time ensures that no new jobs are started after time_limit is exceeded
|
|
166
155
|
end_time = None if time_limit is None else time.time() + time_limit
|
|
167
|
-
executor = Parallel(
|
|
156
|
+
executor = Parallel(n_jobs=n_jobs, timeout=timeout)
|
|
168
157
|
|
|
169
158
|
try:
|
|
170
159
|
with warning_filter():
|
|
171
160
|
predictions_with_flags = executor(
|
|
172
|
-
delayed(self._predict_wrapper)(
|
|
161
|
+
delayed(self._predict_wrapper)(
|
|
162
|
+
ts, use_fallback_model=model_params["use_fallback_model"], end_time=end_time
|
|
163
|
+
)
|
|
164
|
+
for ts in all_series
|
|
173
165
|
)
|
|
174
166
|
except TimeoutError:
|
|
175
167
|
raise TimeLimitExceeded
|
|
@@ -185,7 +177,12 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
185
177
|
predictions_df.index = self.get_forecast_horizon_index(data)
|
|
186
178
|
return TimeSeriesDataFrame(predictions_df)
|
|
187
179
|
|
|
188
|
-
def _predict_wrapper(
|
|
180
|
+
def _predict_wrapper(
|
|
181
|
+
self,
|
|
182
|
+
time_series: pd.Series,
|
|
183
|
+
use_fallback_model: bool,
|
|
184
|
+
end_time: float | None = None,
|
|
185
|
+
) -> tuple[pd.DataFrame, bool]:
|
|
189
186
|
if end_time is not None and time.time() >= end_time:
|
|
190
187
|
raise TimeLimitExceeded
|
|
191
188
|
|
|
@@ -201,7 +198,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
201
198
|
if not np.isfinite(result.values).all():
|
|
202
199
|
raise RuntimeError("Forecast contains NaN or Inf values.")
|
|
203
200
|
except Exception:
|
|
204
|
-
if
|
|
201
|
+
if use_fallback_model:
|
|
205
202
|
result = seasonal_naive_forecast(
|
|
206
203
|
target=time_series.values.ravel(),
|
|
207
204
|
prediction_length=self.prediction_length,
|
|
@@ -222,7 +219,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
222
219
|
|
|
223
220
|
|
|
224
221
|
def seasonal_naive_forecast(
|
|
225
|
-
target: np.ndarray, prediction_length: int, quantile_levels:
|
|
222
|
+
target: np.ndarray, prediction_length: int, quantile_levels: list[float], seasonal_period: int
|
|
226
223
|
) -> pd.DataFrame:
|
|
227
224
|
"""Generate seasonal naive forecast, predicting the last observed value from the same period."""
|
|
228
225
|
|