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
|
@@ -2,29 +2,29 @@ import logging
|
|
|
2
2
|
import math
|
|
3
3
|
import os
|
|
4
4
|
import time
|
|
5
|
-
from typing import Any, Callable,
|
|
5
|
+
from typing import Any, Callable, Literal, Optional, Type
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
|
+
import scipy.stats
|
|
9
10
|
from joblib import Parallel, cpu_count, delayed
|
|
10
11
|
|
|
12
|
+
from autogluon.common.loaders import load_pkl
|
|
13
|
+
from autogluon.common.savers import save_pkl
|
|
11
14
|
from autogluon.common.utils.pandas_utils import get_approximate_df_mem_usage
|
|
12
15
|
from autogluon.common.utils.resource_utils import ResourceManager
|
|
13
|
-
from autogluon.core.constants import QUANTILE
|
|
16
|
+
from autogluon.core.constants import QUANTILE, REGRESSION
|
|
14
17
|
from autogluon.tabular.models import AbstractModel as AbstractTabularModel
|
|
15
18
|
from autogluon.tabular.registry import ag_model_registry
|
|
16
19
|
from autogluon.timeseries import TimeSeriesDataFrame
|
|
17
|
-
from autogluon.timeseries.dataset.ts_dataframe import ITEMID, TIMESTAMP
|
|
18
20
|
from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
|
|
19
21
|
from autogluon.timeseries.utils.datetime import get_lags_for_frequency, get_time_features_for_frequency
|
|
20
|
-
from autogluon.timeseries.utils.warning_filters import set_loggers_level
|
|
22
|
+
from autogluon.timeseries.utils.warning_filters import set_loggers_level, warning_filter
|
|
21
23
|
|
|
22
24
|
from .utils import MLF_ITEMID, MLF_TARGET, MLF_TIMESTAMP
|
|
23
25
|
|
|
24
26
|
logger = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
|
-
DUMMY_FREQ = "D"
|
|
27
|
-
|
|
28
28
|
|
|
29
29
|
class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
30
30
|
"""Fit a separate tabular regression model for each time step in the forecast horizon.
|
|
@@ -36,43 +36,53 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
36
36
|
- known covariates (if available)
|
|
37
37
|
- static features of each item (if available)
|
|
38
38
|
|
|
39
|
-
This model is typically
|
|
39
|
+
This model is typically slower to fit compared to other tabular forecasting models.
|
|
40
|
+
|
|
41
|
+
If ``eval_metric.needs_quantile``, the tabular regression models will be trained with ``"quantile"`` problem type.
|
|
42
|
+
Otherwise, the models will be trained with ``"regression"`` problem type, and dummy quantiles will be
|
|
43
|
+
obtained by assuming that the residuals follow zero-mean normal distribution.
|
|
40
44
|
|
|
41
45
|
This model uses `mlforecast <https://github.com/Nixtla/mlforecast>`_ under the hood for efficient preprocessing,
|
|
42
|
-
but the implementation of the per-step forecasting strategy is different from the
|
|
46
|
+
but the implementation of the per-step forecasting strategy is different from the ``max_horizon`` in ``mlforecast``.
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
Other Parameters
|
|
46
50
|
----------------
|
|
47
|
-
trailing_lags :
|
|
51
|
+
trailing_lags : list[int], default = None
|
|
48
52
|
Trailing window lags of the target that will be used as features for predictions.
|
|
49
|
-
Trailing lags are shifted per forecast step: model for step
|
|
50
|
-
If None, defaults to [1, 2, ..., 12]
|
|
51
|
-
seasonal_lags:
|
|
53
|
+
Trailing lags are shifted per forecast step: model for step ``h`` uses ``[lag+h for lag in trailing_lags]``.
|
|
54
|
+
If None, defaults to ``[1, 2, ..., 12]``.
|
|
55
|
+
seasonal_lags : list[int], default = None
|
|
52
56
|
Seasonal lags of the target used as features. Unlike trailing lags, seasonal lags are not shifted
|
|
53
|
-
but filtered by availability: model for step
|
|
57
|
+
but filtered by availability: model for step ``h`` uses ``[lag for lag in seasonal_lags if lag > h]``.
|
|
54
58
|
If None, determined automatically based on data frequency.
|
|
55
|
-
date_features :
|
|
59
|
+
date_features : list[Union[str, Callable]], default = None
|
|
56
60
|
Features computed from the dates. Can be pandas date attributes or functions that will take the dates as input.
|
|
57
61
|
If None, will be determined automatically based on the frequency of the data.
|
|
58
62
|
target_scaler : {"standard", "mean_abs", "min_max", "robust", None}, default = "mean_abs"
|
|
59
63
|
Scaling applied to each time series.
|
|
60
64
|
model_name : str, default = "CAT"
|
|
61
|
-
Name of the tabular regression model. See
|
|
65
|
+
Name of the tabular regression model. See ``autogluon.tabular.registry.ag_model_registry`` or
|
|
62
66
|
`the documentation <https://auto.gluon.ai/stable/api/autogluon.tabular.models.html>`_ for the list of available
|
|
63
67
|
tabular models.
|
|
64
|
-
model_hyperparameters :
|
|
68
|
+
model_hyperparameters : dict[str, Any], optional
|
|
65
69
|
Hyperparameters passed to the tabular regression model.
|
|
70
|
+
validation_fraction : float or None, default = 0.1
|
|
71
|
+
Fraction of the training data to use for validation. If None or 0.0, no validation set is created.
|
|
72
|
+
Validation set contains the most recent observations (chronologically). Must be between 0.0 and 1.0.
|
|
66
73
|
max_num_items : int or None, default = 20_000
|
|
67
74
|
If not None, the model will randomly select this many time series for training and validation.
|
|
68
75
|
max_num_samples : int or None, default = 1_000_000
|
|
69
|
-
If not None, training dataset passed to
|
|
70
|
-
end of each time series).
|
|
76
|
+
If not None, training dataset passed to the tabular regression model will contain at most this many rows
|
|
77
|
+
(starting from the end of each time series).
|
|
71
78
|
n_jobs : int or None, default = None
|
|
72
79
|
Number of parallel jobs for fitting models across forecast horizons.
|
|
73
80
|
If None, automatically determined based on available memory to prevent OOM errors.
|
|
74
81
|
"""
|
|
75
82
|
|
|
83
|
+
ag_priority = 70
|
|
84
|
+
_dummy_freq = "D"
|
|
85
|
+
|
|
76
86
|
def __init__(self, *args, **kwargs):
|
|
77
87
|
super().__init__(*args, **kwargs)
|
|
78
88
|
# We save the relative paths to per-step models. Each worker process independently saves/loads the model.
|
|
@@ -83,11 +93,11 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
83
93
|
self._date_features: list[Callable]
|
|
84
94
|
self._model_cls: Type[AbstractTabularModel]
|
|
85
95
|
self._n_jobs: int
|
|
86
|
-
self._non_boolean_real_covariates:
|
|
96
|
+
self._non_boolean_real_covariates: list[str] = []
|
|
87
97
|
self._max_ts_length: Optional[int] = None
|
|
88
98
|
|
|
89
99
|
@property
|
|
90
|
-
def allowed_hyperparameters(self) ->
|
|
100
|
+
def allowed_hyperparameters(self) -> list[str]:
|
|
91
101
|
# TODO: Differencing is currently not supported because it greatly complicates the preprocessing logic
|
|
92
102
|
return super().allowed_hyperparameters + [
|
|
93
103
|
"trailing_lags",
|
|
@@ -104,7 +114,11 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
104
114
|
|
|
105
115
|
@property
|
|
106
116
|
def _ag_to_nixtla(self) -> dict:
|
|
107
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
self.target: MLF_TARGET,
|
|
119
|
+
TimeSeriesDataFrame.ITEMID: MLF_ITEMID,
|
|
120
|
+
TimeSeriesDataFrame.TIMESTAMP: MLF_TIMESTAMP,
|
|
121
|
+
}
|
|
108
122
|
|
|
109
123
|
def _get_default_hyperparameters(self):
|
|
110
124
|
return {
|
|
@@ -116,13 +130,16 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
116
130
|
"max_num_items": 20_000,
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
@
|
|
133
|
+
@classmethod
|
|
120
134
|
def _fit_single_model(
|
|
135
|
+
cls,
|
|
121
136
|
train_df: pd.DataFrame,
|
|
122
137
|
path_root: str,
|
|
123
138
|
step: int,
|
|
124
139
|
model_cls: Type[AbstractTabularModel],
|
|
125
140
|
model_hyperparameters: dict,
|
|
141
|
+
problem_type: Literal["quantile", "regression"],
|
|
142
|
+
eval_metric: str,
|
|
126
143
|
validation_fraction: Optional[float],
|
|
127
144
|
quantile_levels: list[float],
|
|
128
145
|
lags: list[int],
|
|
@@ -135,13 +152,15 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
135
152
|
|
|
136
153
|
start_time = time.monotonic()
|
|
137
154
|
|
|
138
|
-
mlf = MLForecast(models=[], freq=
|
|
155
|
+
mlf = MLForecast(models=[], freq=cls._dummy_freq, lags=lags, date_features=date_features)
|
|
139
156
|
|
|
140
|
-
|
|
157
|
+
with warning_filter():
|
|
158
|
+
features_df = mlf.preprocess(train_df, static_features=[], dropna=False)
|
|
141
159
|
del train_df
|
|
142
160
|
del mlf
|
|
143
161
|
# Sort chronologically for efficient train/test split
|
|
144
162
|
features_df = features_df.sort_values(by=MLF_TIMESTAMP)
|
|
163
|
+
item_ids = features_df[MLF_ITEMID]
|
|
145
164
|
X = features_df.drop(columns=[MLF_ITEMID, MLF_TIMESTAMP, MLF_TARGET])
|
|
146
165
|
y = features_df[MLF_TARGET]
|
|
147
166
|
del features_df
|
|
@@ -162,14 +181,16 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
162
181
|
|
|
163
182
|
elapsed = time.monotonic() - start_time
|
|
164
183
|
time_left = time_limit - elapsed if time_limit is not None else None
|
|
184
|
+
if problem_type == QUANTILE:
|
|
185
|
+
model_hyperparameters = model_hyperparameters | {"ag.quantile_levels": quantile_levels}
|
|
165
186
|
try:
|
|
166
187
|
with set_loggers_level(regex=r"^autogluon.tabular.*", level=logging.ERROR):
|
|
167
188
|
model = model_cls(
|
|
168
189
|
path=os.path.join(path_root, f"step_{step}"),
|
|
169
190
|
name=model_cls.__name__, # explicitly provide name to avoid warnings
|
|
170
|
-
problem_type=
|
|
171
|
-
eval_metric=
|
|
172
|
-
hyperparameters=
|
|
191
|
+
problem_type=problem_type,
|
|
192
|
+
eval_metric=eval_metric,
|
|
193
|
+
hyperparameters=model_hyperparameters,
|
|
173
194
|
)
|
|
174
195
|
model.fit(
|
|
175
196
|
X=X,
|
|
@@ -184,6 +205,9 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
184
205
|
except Exception as e:
|
|
185
206
|
raise RuntimeError(f"Failed when fitting model for {step=}") from e
|
|
186
207
|
model.save()
|
|
208
|
+
if problem_type == REGRESSION:
|
|
209
|
+
residuals_std = pd.Series((model.predict(X) - y) ** 2).groupby(item_ids).mean() ** 0.5
|
|
210
|
+
save_pkl.save(cls._get_residuals_std_path(model.path), residuals_std)
|
|
187
211
|
relative_path = os.path.relpath(path=model.path, start=path_root)
|
|
188
212
|
return relative_path
|
|
189
213
|
|
|
@@ -225,7 +249,7 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
225
249
|
self._non_boolean_real_covariates.append(col)
|
|
226
250
|
|
|
227
251
|
if len(self._non_boolean_real_covariates) > 0:
|
|
228
|
-
item_ids = data.index.get_level_values(level=ITEMID)
|
|
252
|
+
item_ids = data.index.get_level_values(level=TimeSeriesDataFrame.ITEMID)
|
|
229
253
|
scale_per_column: dict[str, pd.Series] = {}
|
|
230
254
|
columns_grouped = data[self._non_boolean_real_covariates].abs().groupby(item_ids)
|
|
231
255
|
for col in self._non_boolean_real_covariates:
|
|
@@ -256,7 +280,11 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
256
280
|
train_df = train_data.to_data_frame().reset_index()
|
|
257
281
|
if train_data.static_features is not None:
|
|
258
282
|
train_df = pd.merge(
|
|
259
|
-
left=train_df,
|
|
283
|
+
left=train_df,
|
|
284
|
+
right=train_data.static_features,
|
|
285
|
+
left_on=TimeSeriesDataFrame.ITEMID,
|
|
286
|
+
right_index=True,
|
|
287
|
+
how="left",
|
|
260
288
|
)
|
|
261
289
|
train_df = train_df.rename(columns=self._ag_to_nixtla)
|
|
262
290
|
train_df = train_df.assign(**{MLF_TARGET: train_df[MLF_TARGET].fillna(float("inf"))})
|
|
@@ -264,10 +292,10 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
264
292
|
|
|
265
293
|
@staticmethod
|
|
266
294
|
def _get_lags_for_step(
|
|
267
|
-
trailing_lags:
|
|
268
|
-
seasonal_lags:
|
|
295
|
+
trailing_lags: list[int],
|
|
296
|
+
seasonal_lags: list[int],
|
|
269
297
|
step: int,
|
|
270
|
-
) ->
|
|
298
|
+
) -> list[int]:
|
|
271
299
|
"""Get the list of lags that can be used by the model for the given step."""
|
|
272
300
|
shifted_trailing_lags = [lag + step for lag in trailing_lags]
|
|
273
301
|
# Only keep lags that are available for model predicting `step` values ahead at prediction time
|
|
@@ -313,13 +341,8 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
313
341
|
date_features = get_time_features_for_frequency(self.freq)
|
|
314
342
|
self._date_features = date_features
|
|
315
343
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if supported_problem_types is not None and QUANTILE not in supported_problem_types:
|
|
319
|
-
raise ValueError(
|
|
320
|
-
f"Chosen model_name='{model_params['model_name']}' cannot be used by {self.name} because it does not "
|
|
321
|
-
f"support problem_type='quantile' ({supported_problem_types=})"
|
|
322
|
-
)
|
|
344
|
+
model_name = model_params["model_name"]
|
|
345
|
+
self._model_cls = ag_model_registry.key_to_cls(model_name)
|
|
323
346
|
model_hyperparameters = model_params["model_hyperparameters"]
|
|
324
347
|
# User-provided n_jobs takes priority over the automatic estimate
|
|
325
348
|
if model_params.get("n_jobs") is not None:
|
|
@@ -339,18 +362,35 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
339
362
|
time_limit_per_model = time_limit / math.ceil(self.prediction_length / n_jobs)
|
|
340
363
|
else:
|
|
341
364
|
time_limit_per_model = None
|
|
365
|
+
|
|
366
|
+
if self.eval_metric.needs_quantile:
|
|
367
|
+
problem_type = QUANTILE
|
|
368
|
+
eval_metric = "pinball_loss"
|
|
369
|
+
else:
|
|
370
|
+
problem_type = REGRESSION
|
|
371
|
+
eval_metric = self.eval_metric.equivalent_tabular_regression_metric or "mean_absolute_error"
|
|
372
|
+
|
|
373
|
+
supported_problem_types = self._model_cls.supported_problem_types()
|
|
374
|
+
if supported_problem_types is not None and problem_type not in supported_problem_types:
|
|
375
|
+
raise ValueError(
|
|
376
|
+
f"Chosen model_name='{model_name}' cannot be used by {self.name} with eval_metric={self.eval_metric}"
|
|
377
|
+
f"because {model_name} does not support problem_type={problem_type} ({supported_problem_types=})"
|
|
378
|
+
)
|
|
342
379
|
model_fit_kwargs = dict(
|
|
343
380
|
train_df=train_df,
|
|
344
381
|
path_root=self.path,
|
|
345
382
|
model_cls=self._model_cls,
|
|
346
383
|
quantile_levels=self.quantile_levels,
|
|
347
384
|
validation_fraction=model_params["validation_fraction"],
|
|
385
|
+
problem_type=problem_type,
|
|
386
|
+
eval_metric=eval_metric,
|
|
348
387
|
date_features=self._date_features,
|
|
349
388
|
time_limit=time_limit_per_model,
|
|
350
389
|
num_cpus=num_cpus_per_model,
|
|
351
390
|
model_hyperparameters=model_hyperparameters.copy(),
|
|
352
391
|
verbosity=verbosity - 1,
|
|
353
392
|
)
|
|
393
|
+
|
|
354
394
|
logger.debug(f"Fitting models in parallel with {n_jobs=}, {num_cpus_per_model=}, {time_limit_per_model=}")
|
|
355
395
|
self._relative_paths_to_models = Parallel(n_jobs=n_jobs)( # type: ignore
|
|
356
396
|
delayed(self._fit_single_model)(
|
|
@@ -363,12 +403,19 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
363
403
|
for step in range(self.prediction_length)
|
|
364
404
|
)
|
|
365
405
|
|
|
366
|
-
@
|
|
406
|
+
@classmethod
|
|
407
|
+
def _get_residuals_std_path(cls, model_path: str) -> str:
|
|
408
|
+
"""Path to the pd.Series storing the standard deviation of residuals for each item_id."""
|
|
409
|
+
return os.path.join(model_path, "residuals_std.pkl")
|
|
410
|
+
|
|
411
|
+
@classmethod
|
|
367
412
|
def _predict_with_single_model(
|
|
413
|
+
cls,
|
|
368
414
|
full_df: pd.DataFrame,
|
|
369
415
|
path_to_model: str,
|
|
370
416
|
model_cls: Type[AbstractTabularModel],
|
|
371
417
|
step: int,
|
|
418
|
+
quantile_levels: list[float],
|
|
372
419
|
prediction_length: int,
|
|
373
420
|
lags: list[int],
|
|
374
421
|
date_features: list[Callable],
|
|
@@ -377,14 +424,15 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
377
424
|
|
|
378
425
|
Returns
|
|
379
426
|
-------
|
|
380
|
-
predictions
|
|
427
|
+
predictions
|
|
381
428
|
Predictions of the model for the given step. Shape: (num_items, len(quantile_levels)).
|
|
382
429
|
"""
|
|
383
430
|
from mlforecast import MLForecast
|
|
384
431
|
|
|
385
|
-
mlf = MLForecast(models=[], freq=
|
|
432
|
+
mlf = MLForecast(models=[], freq=cls._dummy_freq, lags=lags, date_features=date_features)
|
|
386
433
|
|
|
387
|
-
|
|
434
|
+
with warning_filter():
|
|
435
|
+
features_df = mlf.preprocess(full_df, static_features=[], dropna=False)
|
|
388
436
|
del mlf
|
|
389
437
|
|
|
390
438
|
end_idx_per_item = np.cumsum(features_df[MLF_ITEMID].value_counts(sort=False).to_numpy(dtype="int32"))
|
|
@@ -395,12 +443,19 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
395
443
|
logger.error(f"Could not load model for {step=} from {path_to_model}")
|
|
396
444
|
raise
|
|
397
445
|
predictions = model.predict(features_for_step)
|
|
446
|
+
if model.problem_type == REGRESSION:
|
|
447
|
+
predictions = np.tile(predictions[:, None], (1, len(quantile_levels)))
|
|
448
|
+
residuals_std: pd.Series = load_pkl.load(cls._get_residuals_std_path(model.path))
|
|
449
|
+
item_ids = features_for_step[MLF_ITEMID]
|
|
450
|
+
residuals_repeated = residuals_std.reindex(item_ids).fillna(residuals_std.mean()).to_numpy()
|
|
451
|
+
for i, q in enumerate(quantile_levels):
|
|
452
|
+
predictions[:, i] += scipy.stats.norm.ppf(q) * residuals_repeated
|
|
398
453
|
return predictions
|
|
399
454
|
|
|
400
455
|
def _predict(
|
|
401
456
|
self,
|
|
402
457
|
data: TimeSeriesDataFrame,
|
|
403
|
-
known_covariates: TimeSeriesDataFrame
|
|
458
|
+
known_covariates: Optional[TimeSeriesDataFrame] = None,
|
|
404
459
|
**kwargs,
|
|
405
460
|
) -> TimeSeriesDataFrame:
|
|
406
461
|
if known_covariates is not None:
|
|
@@ -414,7 +469,9 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
414
469
|
full_df = full_df.slice_by_timestep(-(self._max_ts_length + self.prediction_length), None)
|
|
415
470
|
full_df = full_df.to_data_frame().reset_index()
|
|
416
471
|
if data.static_features is not None:
|
|
417
|
-
full_df = pd.merge(
|
|
472
|
+
full_df = pd.merge(
|
|
473
|
+
full_df, data.static_features, left_on=TimeSeriesDataFrame.ITEMID, right_index=True, how="left"
|
|
474
|
+
)
|
|
418
475
|
|
|
419
476
|
full_df = (
|
|
420
477
|
full_df.rename(columns=self._ag_to_nixtla)
|
|
@@ -425,6 +482,7 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
425
482
|
|
|
426
483
|
model_predict_kwargs = dict(
|
|
427
484
|
full_df=full_df,
|
|
485
|
+
quantile_levels=self.quantile_levels,
|
|
428
486
|
prediction_length=self.prediction_length,
|
|
429
487
|
model_cls=self._model_cls,
|
|
430
488
|
date_features=self._date_features,
|
|
@@ -449,5 +507,5 @@ class PerStepTabularModel(AbstractTimeSeriesModel):
|
|
|
449
507
|
predictions["mean"] = predictions["0.5"]
|
|
450
508
|
return TimeSeriesDataFrame(predictions)
|
|
451
509
|
|
|
452
|
-
def _more_tags(self) ->
|
|
510
|
+
def _more_tags(self) -> dict[str, Any]:
|
|
453
511
|
return {"allow_nan": True, "can_refit_full": True}
|
|
@@ -8,11 +8,7 @@ from mlforecast.target_transforms import (
|
|
|
8
8
|
_BaseGroupedArrayTargetTransform,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
from autogluon.timeseries.dataset
|
|
12
|
-
ITEMID,
|
|
13
|
-
TIMESTAMP,
|
|
14
|
-
TimeSeriesDataFrame,
|
|
15
|
-
)
|
|
11
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
16
12
|
from autogluon.timeseries.transforms.target_scaler import TargetScaler, get_target_scaler
|
|
17
13
|
|
|
18
14
|
from .utils import MLF_ITEMID, MLF_TIMESTAMP
|
|
@@ -26,11 +22,17 @@ class MLForecastScaler(BaseTargetTransform):
|
|
|
26
22
|
|
|
27
23
|
def _df_to_tsdf(self, df: pd.DataFrame) -> TimeSeriesDataFrame:
|
|
28
24
|
return TimeSeriesDataFrame(
|
|
29
|
-
df.rename(
|
|
25
|
+
df.rename(
|
|
26
|
+
columns={self.id_col: TimeSeriesDataFrame.ITEMID, self.time_col: TimeSeriesDataFrame.TIMESTAMP}
|
|
27
|
+
).set_index([TimeSeriesDataFrame.ITEMID, TimeSeriesDataFrame.TIMESTAMP])
|
|
30
28
|
)
|
|
31
29
|
|
|
32
30
|
def _tsdf_to_df(self, ts_df: TimeSeriesDataFrame) -> pd.DataFrame:
|
|
33
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
pd.DataFrame(ts_df)
|
|
33
|
+
.reset_index()
|
|
34
|
+
.rename(columns={TimeSeriesDataFrame.ITEMID: self.id_col, TimeSeriesDataFrame.TIMESTAMP: self.time_col})
|
|
35
|
+
)
|
|
34
36
|
|
|
35
37
|
def fit_transform(self, df: pd.DataFrame) -> pd.DataFrame: # type: ignore
|
|
36
38
|
self.ag_scaler = get_target_scaler(name=self.scaler_type, target=self.target_col)
|