autogluon.timeseries 1.4.1b20251115__py3-none-any.whl → 1.5.0b20251221__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/hyperparameter_presets.py +13 -28
- autogluon/timeseries/configs/predictor_presets.py +23 -39
- autogluon/timeseries/dataset/ts_dataframe.py +32 -34
- autogluon/timeseries/learner.py +67 -33
- autogluon/timeseries/metrics/__init__.py +4 -4
- autogluon/timeseries/metrics/abstract.py +8 -8
- autogluon/timeseries/metrics/point.py +9 -9
- autogluon/timeseries/metrics/quantile.py +4 -4
- autogluon/timeseries/models/__init__.py +2 -1
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +52 -50
- autogluon/timeseries/models/abstract/model_trial.py +2 -1
- autogluon/timeseries/models/abstract/tunable.py +8 -8
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +30 -26
- autogluon/timeseries/models/autogluon_tabular/per_step.py +13 -11
- autogluon/timeseries/models/autogluon_tabular/transforms.py +2 -2
- autogluon/timeseries/models/chronos/__init__.py +2 -1
- autogluon/timeseries/models/chronos/chronos2.py +395 -0
- autogluon/timeseries/models/chronos/model.py +30 -25
- autogluon/timeseries/models/chronos/utils.py +5 -5
- autogluon/timeseries/models/ensemble/__init__.py +17 -10
- autogluon/timeseries/models/ensemble/abstract.py +13 -9
- autogluon/timeseries/models/ensemble/array_based/__init__.py +2 -2
- autogluon/timeseries/models/ensemble/array_based/abstract.py +24 -31
- autogluon/timeseries/models/ensemble/array_based/models.py +146 -11
- autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +2 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +6 -5
- autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +44 -83
- autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +21 -55
- autogluon/timeseries/models/ensemble/ensemble_selection.py +167 -0
- autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +7 -3
- autogluon/timeseries/models/ensemble/weighted/basic.py +26 -13
- autogluon/timeseries/models/ensemble/weighted/greedy.py +21 -144
- autogluon/timeseries/models/gluonts/abstract.py +30 -29
- autogluon/timeseries/models/gluonts/dataset.py +9 -9
- autogluon/timeseries/models/gluonts/models.py +0 -7
- autogluon/timeseries/models/local/__init__.py +0 -7
- autogluon/timeseries/models/local/abstract_local_model.py +13 -16
- autogluon/timeseries/models/local/naive.py +2 -2
- autogluon/timeseries/models/local/npts.py +7 -1
- autogluon/timeseries/models/local/statsforecast.py +13 -13
- autogluon/timeseries/models/multi_window/multi_window_model.py +38 -23
- autogluon/timeseries/models/registry.py +3 -4
- autogluon/timeseries/models/toto/_internal/backbone/attention.py +3 -4
- autogluon/timeseries/models/toto/_internal/backbone/backbone.py +6 -6
- autogluon/timeseries/models/toto/_internal/backbone/rope.py +4 -9
- autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
- autogluon/timeseries/models/toto/_internal/backbone/scaler.py +2 -3
- autogluon/timeseries/models/toto/_internal/backbone/transformer.py +10 -10
- autogluon/timeseries/models/toto/_internal/dataset.py +2 -2
- autogluon/timeseries/models/toto/_internal/forecaster.py +8 -8
- autogluon/timeseries/models/toto/dataloader.py +4 -4
- autogluon/timeseries/models/toto/hf_pretrained_model.py +97 -16
- autogluon/timeseries/models/toto/model.py +30 -17
- autogluon/timeseries/predictor.py +531 -136
- autogluon/timeseries/regressor.py +18 -23
- autogluon/timeseries/splitter.py +2 -2
- autogluon/timeseries/trainer/ensemble_composer.py +323 -129
- autogluon/timeseries/trainer/model_set_builder.py +9 -9
- autogluon/timeseries/trainer/prediction_cache.py +16 -16
- autogluon/timeseries/trainer/trainer.py +235 -145
- autogluon/timeseries/trainer/utils.py +3 -4
- autogluon/timeseries/transforms/covariate_scaler.py +7 -7
- autogluon/timeseries/transforms/target_scaler.py +8 -8
- autogluon/timeseries/utils/constants.py +10 -0
- autogluon/timeseries/utils/datetime/lags.py +1 -3
- autogluon/timeseries/utils/datetime/seasonality.py +1 -3
- autogluon/timeseries/utils/features.py +22 -9
- autogluon/timeseries/utils/forecast.py +1 -2
- autogluon/timeseries/utils/timer.py +173 -0
- autogluon/timeseries/version.py +1 -1
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/METADATA +23 -21
- autogluon_timeseries-1.5.0b20251221.dist-info/RECORD +103 -0
- autogluon_timeseries-1.4.1b20251115.dist-info/RECORD +0 -96
- /autogluon.timeseries-1.4.1b20251115-py3.9-nspkg.pth → /autogluon.timeseries-1.5.0b20251221-py3.11-nspkg.pth +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/WHEEL +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/licenses/LICENSE +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/licenses/NOTICE +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/namespace_packages.txt +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/top_level.txt +0 -0
- {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/zip-safe +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
@@ -8,14 +8,20 @@ from .abstract import AbstractWeightedTimeSeriesEnsembleModel
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class SimpleAverageEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
11
|
-
"""
|
|
11
|
+
"""Simple ensemble that assigns equal weights to all base models for uniform averaging.
|
|
12
|
+
|
|
13
|
+
This ensemble computes predictions as the arithmetic mean of all base model forecasts,
|
|
14
|
+
giving each model equal influence. Simple averaging is robust and often performs well when base
|
|
15
|
+
models have similar accuracy levels or when validation data is insufficient to reliably
|
|
16
|
+
estimate performance differences.
|
|
17
|
+
"""
|
|
12
18
|
|
|
13
19
|
def _fit(
|
|
14
20
|
self,
|
|
15
21
|
predictions_per_window: dict[str, list[TimeSeriesDataFrame]],
|
|
16
22
|
data_per_window: list[TimeSeriesDataFrame],
|
|
17
|
-
model_scores:
|
|
18
|
-
time_limit:
|
|
23
|
+
model_scores: dict[str, float] | None = None,
|
|
24
|
+
time_limit: float | None = None,
|
|
19
25
|
):
|
|
20
26
|
self.model_to_weight = {}
|
|
21
27
|
num_models = len(predictions_per_window)
|
|
@@ -24,16 +30,23 @@ class SimpleAverageEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
class PerformanceWeightedEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
27
|
-
"""
|
|
28
|
-
|
|
33
|
+
"""Performance-based weighted ensemble that assigns weights proportional to validation scores.
|
|
34
|
+
|
|
35
|
+
This ensemble computes model weights based on their validation performance, giving higher
|
|
36
|
+
weights to better-performing models. The weighting scheme transforms validation scores
|
|
37
|
+
(higher is better) into ensemble weights using configurable transformation functions.
|
|
38
|
+
|
|
39
|
+
.. warning::
|
|
40
|
+
This ensemble method is deprecated and may be removed in a future version.
|
|
29
41
|
|
|
30
42
|
Other Parameters
|
|
31
43
|
----------------
|
|
32
|
-
weight_scheme: Literal["sq", "inv", "
|
|
44
|
+
weight_scheme : Literal["sq", "inv", "sqrt"], default = "sqrt"
|
|
33
45
|
Method used to compute the weights as a function of the validation scores.
|
|
34
|
-
|
|
35
|
-
- "
|
|
36
|
-
- "
|
|
46
|
+
|
|
47
|
+
- "sqrt" computes weights in proportion to ``sqrt(1 / S)``. This is the default.
|
|
48
|
+
- "inv" computes weights in proportion to ``(1 / S)``.
|
|
49
|
+
- "sq" computes the weights in proportion to ``(1 / S)^2`` as outlined in [PC2020]_.
|
|
37
50
|
|
|
38
51
|
References
|
|
39
52
|
----------
|
|
@@ -49,12 +62,12 @@ class PerformanceWeightedEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
|
49
62
|
self,
|
|
50
63
|
predictions_per_window: dict[str, list[TimeSeriesDataFrame]],
|
|
51
64
|
data_per_window: list[TimeSeriesDataFrame],
|
|
52
|
-
model_scores:
|
|
53
|
-
time_limit:
|
|
65
|
+
model_scores: dict[str, float] | None = None,
|
|
66
|
+
time_limit: float | None = None,
|
|
54
67
|
):
|
|
55
68
|
assert model_scores is not None
|
|
56
69
|
|
|
57
|
-
weight_scheme = self.
|
|
70
|
+
weight_scheme = self.get_hyperparameter("weight_scheme")
|
|
58
71
|
|
|
59
72
|
# drop NaNs
|
|
60
73
|
model_scores = {k: v for k, v in model_scores.items() if np.isfinite(v)}
|
|
@@ -1,156 +1,38 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import logging
|
|
3
2
|
import pprint
|
|
4
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
5
4
|
|
|
6
|
-
import numpy as np
|
|
7
|
-
|
|
8
|
-
import autogluon.core as ag
|
|
9
|
-
from autogluon.core.models.greedy_ensemble.ensemble_selection import EnsembleSelection
|
|
10
5
|
from autogluon.timeseries import TimeSeriesDataFrame
|
|
11
|
-
from autogluon.timeseries.metrics import TimeSeriesScorer
|
|
12
|
-
from autogluon.timeseries.utils.datetime import get_seasonality
|
|
13
6
|
|
|
7
|
+
from ..ensemble_selection import fit_time_series_ensemble_selection
|
|
14
8
|
from .abstract import AbstractWeightedTimeSeriesEnsembleModel
|
|
15
9
|
|
|
16
10
|
logger = logging.getLogger(__name__)
|
|
17
11
|
|
|
18
12
|
|
|
19
|
-
class
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ensemble_size: int,
|
|
23
|
-
metric: TimeSeriesScorer,
|
|
24
|
-
problem_type: str = ag.constants.QUANTILE,
|
|
25
|
-
sorted_initialization: bool = False,
|
|
26
|
-
bagging: bool = False,
|
|
27
|
-
tie_breaker: str = "random",
|
|
28
|
-
random_state: Optional[np.random.RandomState] = None,
|
|
29
|
-
prediction_length: int = 1,
|
|
30
|
-
target: str = "target",
|
|
31
|
-
**kwargs,
|
|
32
|
-
):
|
|
33
|
-
super().__init__(
|
|
34
|
-
ensemble_size=ensemble_size,
|
|
35
|
-
metric=metric, # type: ignore
|
|
36
|
-
problem_type=problem_type,
|
|
37
|
-
sorted_initialization=sorted_initialization,
|
|
38
|
-
bagging=bagging,
|
|
39
|
-
tie_breaker=tie_breaker,
|
|
40
|
-
random_state=random_state,
|
|
41
|
-
**kwargs,
|
|
42
|
-
)
|
|
43
|
-
self.prediction_length = prediction_length
|
|
44
|
-
self.target = target
|
|
45
|
-
self.metric: TimeSeriesScorer
|
|
46
|
-
|
|
47
|
-
self.dummy_pred_per_window = []
|
|
48
|
-
self.scorer_per_window = []
|
|
49
|
-
|
|
50
|
-
self.dummy_pred_per_window: Optional[list[TimeSeriesDataFrame]]
|
|
51
|
-
self.scorer_per_window: Optional[list[TimeSeriesScorer]]
|
|
52
|
-
self.data_future_per_window: Optional[list[TimeSeriesDataFrame]]
|
|
53
|
-
|
|
54
|
-
def fit( # type: ignore
|
|
55
|
-
self,
|
|
56
|
-
predictions: list[list[TimeSeriesDataFrame]],
|
|
57
|
-
labels: list[TimeSeriesDataFrame],
|
|
58
|
-
time_limit: Optional[float] = None,
|
|
59
|
-
):
|
|
60
|
-
return super().fit(
|
|
61
|
-
predictions=predictions, # type: ignore
|
|
62
|
-
labels=labels, # type: ignore
|
|
63
|
-
time_limit=time_limit,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
def _fit( # type: ignore
|
|
67
|
-
self,
|
|
68
|
-
predictions: list[list[TimeSeriesDataFrame]],
|
|
69
|
-
labels: list[TimeSeriesDataFrame],
|
|
70
|
-
time_limit: Optional[float] = None,
|
|
71
|
-
sample_weight: Optional[list[float]] = None,
|
|
72
|
-
):
|
|
73
|
-
# Stack predictions for each model into a 3d tensor of shape [num_val_windows, num_rows, num_cols]
|
|
74
|
-
stacked_predictions = [np.stack(preds) for preds in predictions]
|
|
75
|
-
|
|
76
|
-
self.dummy_pred_per_window = []
|
|
77
|
-
self.scorer_per_window = []
|
|
78
|
-
self.data_future_per_window = []
|
|
79
|
-
|
|
80
|
-
seasonal_period = self.metric.seasonal_period
|
|
81
|
-
if seasonal_period is None:
|
|
82
|
-
seasonal_period = get_seasonality(labels[0].freq)
|
|
83
|
-
|
|
84
|
-
for window_idx, data in enumerate(labels):
|
|
85
|
-
dummy_pred = copy.deepcopy(predictions[0][window_idx])
|
|
86
|
-
# This should never happen; sanity check to make sure that all predictions have the same index
|
|
87
|
-
assert all(dummy_pred.index.equals(pred[window_idx].index) for pred in predictions)
|
|
88
|
-
assert all(dummy_pred.columns.equals(pred[window_idx].columns) for pred in predictions)
|
|
89
|
-
|
|
90
|
-
self.dummy_pred_per_window.append(dummy_pred)
|
|
91
|
-
|
|
92
|
-
scorer = copy.deepcopy(self.metric)
|
|
93
|
-
# Split the observed time series once to avoid repeated computations inside the evaluator
|
|
94
|
-
data_past = data.slice_by_timestep(None, -self.prediction_length)
|
|
95
|
-
data_future = data.slice_by_timestep(-self.prediction_length, None)
|
|
96
|
-
scorer.save_past_metrics(data_past, target=self.target, seasonal_period=seasonal_period)
|
|
97
|
-
self.scorer_per_window.append(scorer)
|
|
98
|
-
self.data_future_per_window.append(data_future)
|
|
99
|
-
|
|
100
|
-
super()._fit(
|
|
101
|
-
predictions=stacked_predictions,
|
|
102
|
-
labels=data_future, # type: ignore
|
|
103
|
-
time_limit=time_limit,
|
|
104
|
-
)
|
|
105
|
-
self.dummy_pred_per_window = None
|
|
106
|
-
self.evaluator_per_window = None
|
|
107
|
-
self.data_future_per_window = None
|
|
108
|
-
|
|
109
|
-
def _calculate_regret( # type: ignore
|
|
110
|
-
self,
|
|
111
|
-
y_true,
|
|
112
|
-
y_pred_proba,
|
|
113
|
-
metric: TimeSeriesScorer,
|
|
114
|
-
sample_weight=None,
|
|
115
|
-
):
|
|
116
|
-
# Compute average score across all validation windows
|
|
117
|
-
total_score = 0.0
|
|
118
|
-
|
|
119
|
-
assert self.data_future_per_window is not None
|
|
120
|
-
assert self.dummy_pred_per_window is not None
|
|
121
|
-
assert self.scorer_per_window is not None
|
|
122
|
-
|
|
123
|
-
for window_idx, data_future in enumerate(self.data_future_per_window):
|
|
124
|
-
dummy_pred = self.dummy_pred_per_window[window_idx]
|
|
125
|
-
dummy_pred[list(dummy_pred.columns)] = y_pred_proba[window_idx]
|
|
126
|
-
# We use scorer.compute_metric instead of scorer.score to avoid repeated calls to scorer.save_past_metrics
|
|
127
|
-
metric_value = self.scorer_per_window[window_idx].compute_metric(
|
|
128
|
-
data_future,
|
|
129
|
-
dummy_pred,
|
|
130
|
-
target=self.target,
|
|
131
|
-
)
|
|
132
|
-
total_score += metric.sign * metric_value
|
|
133
|
-
avg_score = total_score / len(self.data_future_per_window)
|
|
134
|
-
# score: higher is better, regret: lower is better, so we flip the sign
|
|
135
|
-
return -avg_score
|
|
13
|
+
class GreedyEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
14
|
+
"""Greedy ensemble selection algorithm that iteratively builds an ensemble by selecting models with
|
|
15
|
+
replacement.
|
|
136
16
|
|
|
17
|
+
Also known as ``WeightedEnsemble`` for backward compatibility.
|
|
137
18
|
|
|
138
|
-
class
|
|
139
|
-
|
|
140
|
-
|
|
19
|
+
This class implements the Ensemble Selection algorithm by Caruana et al. [Car2004]_, which starts
|
|
20
|
+
with an empty ensemble and repeatedly adds the model that most improves the ensemble's validation
|
|
21
|
+
performance. Models can be selected multiple times, allowing the algorithm to assign higher effective
|
|
22
|
+
weights to better-performing models.
|
|
141
23
|
|
|
142
24
|
Other Parameters
|
|
143
25
|
----------------
|
|
144
|
-
ensemble_size: int, default = 100
|
|
26
|
+
ensemble_size : int, default = 100
|
|
145
27
|
Number of models (with replacement) to include in the ensemble.
|
|
146
28
|
|
|
147
29
|
References
|
|
148
30
|
----------
|
|
149
|
-
.. [
|
|
31
|
+
.. [Car2004] Caruana, Rich, et al. "Ensemble selection from libraries of models."
|
|
150
32
|
Proceedings of the twenty-first international conference on Machine learning. 2004.
|
|
151
33
|
"""
|
|
152
34
|
|
|
153
|
-
def __init__(self, name:
|
|
35
|
+
def __init__(self, name: str | None = None, **kwargs):
|
|
154
36
|
if name is None:
|
|
155
37
|
# FIXME: the name here is kept for backward compatibility. it will be called
|
|
156
38
|
# GreedyEnsemble in v1.4 once ensemble choices are exposed
|
|
@@ -164,24 +46,19 @@ class GreedyEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
|
|
|
164
46
|
self,
|
|
165
47
|
predictions_per_window: dict[str, list[TimeSeriesDataFrame]],
|
|
166
48
|
data_per_window: list[TimeSeriesDataFrame],
|
|
167
|
-
model_scores:
|
|
168
|
-
time_limit:
|
|
49
|
+
model_scores: dict[str, float] | None = None,
|
|
50
|
+
time_limit: float | None = None,
|
|
169
51
|
):
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
52
|
+
model_to_weight = fit_time_series_ensemble_selection(
|
|
53
|
+
data_per_window=data_per_window,
|
|
54
|
+
predictions_per_window=predictions_per_window,
|
|
55
|
+
ensemble_size=self.get_hyperparameter("ensemble_size"),
|
|
56
|
+
eval_metric=self.eval_metric,
|
|
173
57
|
prediction_length=self.prediction_length,
|
|
174
58
|
target=self.target,
|
|
175
|
-
)
|
|
176
|
-
ensemble_selection.fit(
|
|
177
|
-
predictions=list(predictions_per_window.values()),
|
|
178
|
-
labels=data_per_window,
|
|
179
59
|
time_limit=time_limit,
|
|
180
60
|
)
|
|
181
|
-
self.model_to_weight = {}
|
|
182
|
-
for model_name, weight in zip(predictions_per_window.keys(), ensemble_selection.weights_):
|
|
183
|
-
if weight != 0:
|
|
184
|
-
self.model_to_weight[model_name] = weight
|
|
61
|
+
self.model_to_weight = {model: weight for model, weight in model_to_weight.items() if weight > 0}
|
|
185
62
|
|
|
186
63
|
weights_for_printing = {model: round(float(weight), 2) for model, weight in self.model_to_weight.items()}
|
|
187
64
|
logger.info(f"\tEnsemble weights: {pprint.pformat(weights_for_printing, width=200)}")
|
|
@@ -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, Type, cast, overload
|
|
7
7
|
|
|
8
8
|
import gluonts
|
|
9
9
|
import gluonts.core.settings
|
|
@@ -72,12 +72,12 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
72
72
|
|
|
73
73
|
def __init__(
|
|
74
74
|
self,
|
|
75
|
-
freq:
|
|
75
|
+
freq: str | None = None,
|
|
76
76
|
prediction_length: int = 1,
|
|
77
|
-
path:
|
|
78
|
-
name:
|
|
79
|
-
eval_metric:
|
|
80
|
-
hyperparameters:
|
|
77
|
+
path: str | None = None,
|
|
78
|
+
name: str | None = None,
|
|
79
|
+
eval_metric: str | None = None,
|
|
80
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
81
81
|
**kwargs, # noqa
|
|
82
82
|
):
|
|
83
83
|
super().__init__(
|
|
@@ -89,9 +89,9 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
89
89
|
hyperparameters=hyperparameters,
|
|
90
90
|
**kwargs,
|
|
91
91
|
)
|
|
92
|
-
self.gts_predictor:
|
|
93
|
-
self._ohe_generator_known:
|
|
94
|
-
self._ohe_generator_past:
|
|
92
|
+
self.gts_predictor: GluonTSPredictor | None = None
|
|
93
|
+
self._ohe_generator_known: OneHotEncoder | None = None
|
|
94
|
+
self._ohe_generator_past: OneHotEncoder | None = None
|
|
95
95
|
self.callbacks = []
|
|
96
96
|
# Following attributes may be overridden during fit() based on train_data & model parameters
|
|
97
97
|
self.num_feat_static_cat = 0
|
|
@@ -105,7 +105,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
105
105
|
self.past_feat_dynamic_cat_cardinality: list[int] = []
|
|
106
106
|
self.negative_data = True
|
|
107
107
|
|
|
108
|
-
def save(self, path:
|
|
108
|
+
def save(self, path: str | None = None, verbose: bool = True) -> str:
|
|
109
109
|
# we flush callbacks instance variable if it has been set. it can keep weak references which breaks training
|
|
110
110
|
self.callbacks = []
|
|
111
111
|
# The GluonTS predictor is serialized using custom logic
|
|
@@ -153,18 +153,17 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
153
153
|
assert dataset.static_features is not None, (
|
|
154
154
|
"Static features must be provided if num_feat_static_cat > 0"
|
|
155
155
|
)
|
|
156
|
-
|
|
157
|
-
self.feat_static_cat_cardinality = feat_static_cat.nunique().tolist()
|
|
156
|
+
self.feat_static_cat_cardinality = list(self.covariate_metadata.static_cat_cardinality.values())
|
|
158
157
|
|
|
159
158
|
disable_known_covariates = model_params.get("disable_known_covariates", False)
|
|
160
159
|
if not disable_known_covariates and self.supports_known_covariates:
|
|
161
160
|
self.num_feat_dynamic_cat = len(self.covariate_metadata.known_covariates_cat)
|
|
162
161
|
self.num_feat_dynamic_real = len(self.covariate_metadata.known_covariates_real)
|
|
163
162
|
if self.num_feat_dynamic_cat > 0:
|
|
164
|
-
feat_dynamic_cat = dataset[self.covariate_metadata.known_covariates_cat]
|
|
165
163
|
if self.supports_cat_covariates:
|
|
166
|
-
self.feat_dynamic_cat_cardinality =
|
|
164
|
+
self.feat_dynamic_cat_cardinality = list(self.covariate_metadata.known_cat_cardinality.values())
|
|
167
165
|
else:
|
|
166
|
+
feat_dynamic_cat = dataset[self.covariate_metadata.known_covariates_cat]
|
|
168
167
|
# If model doesn't support categorical covariates, convert them to real via one hot encoding
|
|
169
168
|
self._ohe_generator_known = OneHotEncoder(
|
|
170
169
|
max_levels=model_params.get("max_cat_cardinality", 100),
|
|
@@ -180,10 +179,12 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
180
179
|
self.num_past_feat_dynamic_cat = len(self.covariate_metadata.past_covariates_cat)
|
|
181
180
|
self.num_past_feat_dynamic_real = len(self.covariate_metadata.past_covariates_real)
|
|
182
181
|
if self.num_past_feat_dynamic_cat > 0:
|
|
183
|
-
past_feat_dynamic_cat = dataset[self.covariate_metadata.past_covariates_cat]
|
|
184
182
|
if self.supports_cat_covariates:
|
|
185
|
-
self.past_feat_dynamic_cat_cardinality =
|
|
183
|
+
self.past_feat_dynamic_cat_cardinality = list(
|
|
184
|
+
self.covariate_metadata.past_cat_cardinality.values()
|
|
185
|
+
)
|
|
186
186
|
else:
|
|
187
|
+
past_feat_dynamic_cat = dataset[self.covariate_metadata.past_covariates_cat]
|
|
187
188
|
# If model doesn't support categorical covariates, convert them to real via one hot encoding
|
|
188
189
|
self._ohe_generator_past = OneHotEncoder(
|
|
189
190
|
max_levels=model_params.get("max_cat_cardinality", 100),
|
|
@@ -277,8 +278,8 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
277
278
|
|
|
278
279
|
return torch.cuda.is_available()
|
|
279
280
|
|
|
280
|
-
def get_minimum_resources(self, is_gpu_available: bool = False) -> dict[str,
|
|
281
|
-
minimum_resources: dict[str,
|
|
281
|
+
def get_minimum_resources(self, is_gpu_available: bool = False) -> dict[str, int | float]:
|
|
282
|
+
minimum_resources: dict[str, int | float] = {"num_cpus": 1}
|
|
282
283
|
# if GPU is available, we train with 1 GPU per trial
|
|
283
284
|
if is_gpu_available:
|
|
284
285
|
minimum_resources["num_gpus"] = 1
|
|
@@ -289,8 +290,8 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
289
290
|
@overload
|
|
290
291
|
def _to_gluonts_dataset(self, time_series_df: TimeSeriesDataFrame, known_covariates=None) -> GluonTSDataset: ...
|
|
291
292
|
def _to_gluonts_dataset(
|
|
292
|
-
self, time_series_df:
|
|
293
|
-
) ->
|
|
293
|
+
self, time_series_df: TimeSeriesDataFrame | None, known_covariates: TimeSeriesDataFrame | None = None
|
|
294
|
+
) -> GluonTSDataset | None:
|
|
294
295
|
if time_series_df is not None:
|
|
295
296
|
# TODO: Preprocess real-valued features with StdScaler?
|
|
296
297
|
if self.num_feat_static_cat > 0:
|
|
@@ -388,10 +389,10 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
388
389
|
def _fit(
|
|
389
390
|
self,
|
|
390
391
|
train_data: TimeSeriesDataFrame,
|
|
391
|
-
val_data:
|
|
392
|
-
time_limit:
|
|
393
|
-
num_cpus:
|
|
394
|
-
num_gpus:
|
|
392
|
+
val_data: TimeSeriesDataFrame | None = None,
|
|
393
|
+
time_limit: float | None = None,
|
|
394
|
+
num_cpus: int | None = None,
|
|
395
|
+
num_gpus: int | None = None,
|
|
395
396
|
verbosity: int = 2,
|
|
396
397
|
**kwargs,
|
|
397
398
|
) -> None:
|
|
@@ -438,8 +439,8 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
438
439
|
|
|
439
440
|
def _get_callbacks(
|
|
440
441
|
self,
|
|
441
|
-
time_limit:
|
|
442
|
-
early_stopping_patience:
|
|
442
|
+
time_limit: float | None,
|
|
443
|
+
early_stopping_patience: int | None = None,
|
|
443
444
|
) -> list[Callable]:
|
|
444
445
|
"""Retrieve a list of callback objects for the GluonTS trainer"""
|
|
445
446
|
from lightning.pytorch.callbacks import EarlyStopping, Timer
|
|
@@ -454,7 +455,7 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
454
455
|
def _predict(
|
|
455
456
|
self,
|
|
456
457
|
data: TimeSeriesDataFrame,
|
|
457
|
-
known_covariates:
|
|
458
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
458
459
|
**kwargs,
|
|
459
460
|
) -> TimeSeriesDataFrame:
|
|
460
461
|
if self.gts_predictor is None:
|
|
@@ -471,8 +472,8 @@ class AbstractGluonTSModel(AbstractTimeSeriesModel):
|
|
|
471
472
|
def _predict_gluonts_forecasts(
|
|
472
473
|
self,
|
|
473
474
|
data: TimeSeriesDataFrame,
|
|
474
|
-
known_covariates:
|
|
475
|
-
num_samples:
|
|
475
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
476
|
+
num_samples: int | None = None,
|
|
476
477
|
) -> list[Forecast]:
|
|
477
478
|
assert self.gts_predictor is not None, "GluonTS models must be fit before predicting."
|
|
478
479
|
gts_data = self._to_gluonts_dataset(data, known_covariates=known_covariates)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Iterator,
|
|
1
|
+
from typing import Any, Iterator, Type
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
@@ -17,14 +17,14 @@ class SimpleGluonTSDataset(GluonTSDataset):
|
|
|
17
17
|
target_df: TimeSeriesDataFrame,
|
|
18
18
|
freq: str,
|
|
19
19
|
target_column: str = "target",
|
|
20
|
-
feat_static_cat:
|
|
21
|
-
feat_static_real:
|
|
22
|
-
feat_dynamic_cat:
|
|
23
|
-
feat_dynamic_real:
|
|
24
|
-
past_feat_dynamic_cat:
|
|
25
|
-
past_feat_dynamic_real:
|
|
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
26
|
includes_future: bool = False,
|
|
27
|
-
prediction_length:
|
|
27
|
+
prediction_length: int | None = None,
|
|
28
28
|
):
|
|
29
29
|
assert target_df is not None
|
|
30
30
|
# Convert TimeSeriesDataFrame to pd.Series for faster processing
|
|
@@ -48,7 +48,7 @@ class SimpleGluonTSDataset(GluonTSDataset):
|
|
|
48
48
|
assert len(self.item_ids) == len(self.start_timestamps)
|
|
49
49
|
|
|
50
50
|
@staticmethod
|
|
51
|
-
def _astype(array:
|
|
51
|
+
def _astype(array: np.ndarray | None, dtype: Type[np.generic]) -> np.ndarray | None:
|
|
52
52
|
if array is None:
|
|
53
53
|
return None
|
|
54
54
|
else:
|
|
@@ -41,10 +41,8 @@ class DeepARModel(AbstractGluonTSModel):
|
|
|
41
41
|
Number of steps to unroll the RNN for before computing predictions
|
|
42
42
|
disable_static_features : bool, default = False
|
|
43
43
|
If True, static features won't be used by the model even if they are present in the dataset.
|
|
44
|
-
If False, static features will be used by the model if they are present in the dataset.
|
|
45
44
|
disable_known_covariates : bool, default = False
|
|
46
45
|
If True, known covariates won't be used by the model even if they are present in the dataset.
|
|
47
|
-
If False, known covariates will be used by the model if they are present in the dataset.
|
|
48
46
|
num_layers : int, default = 2
|
|
49
47
|
Number of RNN layers
|
|
50
48
|
hidden_size : int, default = 40
|
|
@@ -170,13 +168,10 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
|
|
|
170
168
|
Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
|
|
171
169
|
disable_static_features : bool, default = False
|
|
172
170
|
If True, static features won't be used by the model even if they are present in the dataset.
|
|
173
|
-
If False, static features will be used by the model if they are present in the dataset.
|
|
174
171
|
disable_known_covariates : bool, default = False
|
|
175
172
|
If True, known covariates won't be used by the model even if they are present in the dataset.
|
|
176
|
-
If False, known covariates will be used by the model if they are present in the dataset.
|
|
177
173
|
disable_past_covariates : bool, default = False
|
|
178
174
|
If True, past covariates won't be used by the model even if they are present in the dataset.
|
|
179
|
-
If False, past covariates will be used by the model if they are present in the dataset.
|
|
180
175
|
hidden_dim : int, default = 32
|
|
181
176
|
Size of the LSTM & transformer hidden states.
|
|
182
177
|
variable_dim : int, default = 32
|
|
@@ -470,10 +465,8 @@ class TiDEModel(AbstractGluonTSModel):
|
|
|
470
465
|
Number of past values used for prediction.
|
|
471
466
|
disable_static_features : bool, default = False
|
|
472
467
|
If True, static features won't be used by the model even if they are present in the dataset.
|
|
473
|
-
If False, static features will be used by the model if they are present in the dataset.
|
|
474
468
|
disable_known_covariates : bool, default = False
|
|
475
469
|
If True, known covariates won't be used by the model even if they are present in the dataset.
|
|
476
|
-
If False, known covariates will be used by the model if they are present in the dataset.
|
|
477
470
|
feat_proj_hidden_dim : int, default = 4
|
|
478
471
|
Size of the feature projection layer.
|
|
479
472
|
encoder_hidden_dim : int, default = 64
|
|
@@ -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,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
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
@@ -12,16 +12,13 @@ from autogluon.core.utils.exceptions import TimeLimitExceeded
|
|
|
12
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
|
+
from autogluon.timeseries.utils.constants import AG_DEFAULT_N_JOBS
|
|
15
16
|
from autogluon.timeseries.utils.datetime import get_seasonality
|
|
16
17
|
from autogluon.timeseries.utils.warning_filters import warning_filter
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
# We use the same default n_jobs across AG-TS to ensure that Joblib reuses the process pool
|
|
22
|
-
AG_DEFAULT_N_JOBS = max(cpu_count(only_physical_cores=True), 1)
|
|
23
|
-
|
|
24
|
-
|
|
25
22
|
class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
26
23
|
"""Abstract class for local forecasting models that are trained separately for each time series.
|
|
27
24
|
|
|
@@ -40,18 +37,18 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
40
37
|
"""
|
|
41
38
|
|
|
42
39
|
allowed_local_model_args: list[str] = []
|
|
43
|
-
default_max_ts_length:
|
|
40
|
+
default_max_ts_length: int | None = 2500
|
|
44
41
|
default_max_time_limit_ratio = 1.0
|
|
45
42
|
init_time_in_seconds: int = 0
|
|
46
43
|
|
|
47
44
|
def __init__(
|
|
48
45
|
self,
|
|
49
|
-
freq:
|
|
46
|
+
freq: str | None = None,
|
|
50
47
|
prediction_length: int = 1,
|
|
51
|
-
path:
|
|
52
|
-
name:
|
|
53
|
-
eval_metric:
|
|
54
|
-
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,
|
|
55
52
|
**kwargs, # noqa
|
|
56
53
|
):
|
|
57
54
|
super().__init__(
|
|
@@ -79,10 +76,10 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
79
76
|
def preprocess(
|
|
80
77
|
self,
|
|
81
78
|
data: TimeSeriesDataFrame,
|
|
82
|
-
known_covariates:
|
|
79
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
83
80
|
is_train: bool = False,
|
|
84
81
|
**kwargs,
|
|
85
|
-
) -> tuple[TimeSeriesDataFrame,
|
|
82
|
+
) -> tuple[TimeSeriesDataFrame, TimeSeriesDataFrame | None]:
|
|
86
83
|
if not self._get_tags()["allow_nan"]:
|
|
87
84
|
data = data.fill_missing_values()
|
|
88
85
|
return data, known_covariates
|
|
@@ -95,7 +92,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
@staticmethod
|
|
98
|
-
def _compute_n_jobs(n_jobs:
|
|
95
|
+
def _compute_n_jobs(n_jobs: int | float) -> int:
|
|
99
96
|
if isinstance(n_jobs, float) and 0 < n_jobs <= 1:
|
|
100
97
|
return max(int(cpu_count() * n_jobs), 1)
|
|
101
98
|
elif isinstance(n_jobs, int):
|
|
@@ -103,7 +100,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
103
100
|
else:
|
|
104
101
|
raise ValueError(f"n_jobs must be a float between 0 and 1 or an integer (received n_jobs = {n_jobs})")
|
|
105
102
|
|
|
106
|
-
def _fit(self, train_data: TimeSeriesDataFrame, time_limit:
|
|
103
|
+
def _fit(self, train_data: TimeSeriesDataFrame, time_limit: int | None = None, **kwargs):
|
|
107
104
|
self._check_fit_params()
|
|
108
105
|
|
|
109
106
|
if time_limit is not None and time_limit < self.init_time_in_seconds:
|
|
@@ -184,7 +181,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
|
|
|
184
181
|
self,
|
|
185
182
|
time_series: pd.Series,
|
|
186
183
|
use_fallback_model: bool,
|
|
187
|
-
end_time:
|
|
184
|
+
end_time: float | None = None,
|
|
188
185
|
) -> tuple[pd.DataFrame, bool]:
|
|
189
186
|
if end_time is not None and time.time() >= end_time:
|
|
190
187
|
raise TimeLimitExceeded
|
|
@@ -96,7 +96,7 @@ class AverageModel(AbstractLocalModel):
|
|
|
96
96
|
When set to a float between 0.0 and 1.0, that fraction of available CPU cores is used.
|
|
97
97
|
When set to a positive integer, that many cores are used.
|
|
98
98
|
When set to -1, all CPU cores are used.
|
|
99
|
-
max_ts_length :
|
|
99
|
+
max_ts_length : int | None, default = None
|
|
100
100
|
If not None, only the last ``max_ts_length`` time steps of each time series will be used to train the model.
|
|
101
101
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
102
102
|
"""
|
|
@@ -136,7 +136,7 @@ class SeasonalAverageModel(AbstractLocalModel):
|
|
|
136
136
|
When set to a float between 0.0 and 1.0, that fraction of available CPU cores is used.
|
|
137
137
|
When set to a positive integer, that many cores are used.
|
|
138
138
|
When set to -1, all CPU cores are used.
|
|
139
|
-
max_ts_length :
|
|
139
|
+
max_ts_length : int | None, default = None
|
|
140
140
|
If not None, only the last ``max_ts_length`` time steps of each time series will be used to train the model.
|
|
141
141
|
This significantly speeds up fitting and usually leads to no change in accuracy.
|
|
142
142
|
"""
|
|
@@ -31,7 +31,7 @@ class NPTSModel(AbstractLocalModel):
|
|
|
31
31
|
When set to a float between 0.0 and 1.0, that fraction of available CPU cores is used.
|
|
32
32
|
When set to a positive integer, that many cores are used.
|
|
33
33
|
When set to -1, all CPU cores are used.
|
|
34
|
-
max_ts_length :
|
|
34
|
+
max_ts_length : int | None, default = 2500
|
|
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
37
|
"""
|
|
@@ -59,6 +59,11 @@ class NPTSModel(AbstractLocalModel):
|
|
|
59
59
|
) -> pd.DataFrame:
|
|
60
60
|
from gluonts.model.npts import NPTSPredictor
|
|
61
61
|
|
|
62
|
+
# NPTS model is non-deterministic due to sampling. Set seed for reproducibility in parallel processes
|
|
63
|
+
# and restore original state to avoid side effects when running with n_jobs=1
|
|
64
|
+
original_random_state = np.random.get_state()
|
|
65
|
+
np.random.seed(123)
|
|
66
|
+
|
|
62
67
|
local_model_args.pop("seasonal_period")
|
|
63
68
|
num_samples = local_model_args.pop("num_samples")
|
|
64
69
|
num_default_time_features = local_model_args.pop("num_default_time_features")
|
|
@@ -88,6 +93,7 @@ class NPTSModel(AbstractLocalModel):
|
|
|
88
93
|
forecast_dict = {"mean": forecast.mean}
|
|
89
94
|
for q in self.quantile_levels:
|
|
90
95
|
forecast_dict[str(q)] = forecast.quantile(q)
|
|
96
|
+
np.random.set_state(original_random_state)
|
|
91
97
|
return pd.DataFrame(forecast_dict)
|
|
92
98
|
|
|
93
99
|
def _more_tags(self) -> dict:
|