autogluon.timeseries 1.2.1b20250304__py3-none-any.whl → 1.2.1b20250306__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/models/abstract/abstract_timeseries_model.py +246 -446
- autogluon/timeseries/models/abstract/tunable.py +189 -0
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +3 -4
- autogluon/timeseries/models/autogluon_tabular/transforms.py +2 -2
- autogluon/timeseries/models/ensemble/abstract_timeseries_ensemble.py +8 -0
- autogluon/timeseries/models/ensemble/greedy_ensemble.py +4 -2
- autogluon/timeseries/models/multi_window/multi_window_model.py +0 -5
- autogluon/timeseries/models/presets.py +0 -3
- autogluon/timeseries/regressor.py +54 -6
- autogluon/timeseries/transforms/__init__.py +2 -13
- autogluon/timeseries/transforms/covariate_scaler.py +28 -34
- autogluon/timeseries/transforms/target_scaler.py +22 -5
- autogluon/timeseries/version.py +1 -1
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/METADATA +4 -4
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/RECORD +22 -21
- /autogluon.timeseries-1.2.1b20250304-py3.9-nspkg.pth → /autogluon.timeseries-1.2.1b20250306-py3.9-nspkg.pth +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/LICENSE +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/NOTICE +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/WHEEL +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.2.1b20250304.dist-info → autogluon.timeseries-1.2.1b20250306.dist-info}/zip-safe +0 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import time
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
from contextlib import nullcontext
|
8
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
9
|
+
|
10
|
+
from typing_extensions import Self
|
11
|
+
|
12
|
+
from autogluon.common.savers import save_pkl
|
13
|
+
from autogluon.common.utils.distribute_utils import DistributedContext
|
14
|
+
from autogluon.common.utils.log_utils import DuplicateFilter
|
15
|
+
from autogluon.common.utils.try_import import try_import_ray
|
16
|
+
from autogluon.core.hpo.constants import CUSTOM_BACKEND, RAY_BACKEND
|
17
|
+
from autogluon.core.hpo.exceptions import EmptySearchSpace
|
18
|
+
from autogluon.core.hpo.executors import HpoExecutor, HpoExecutorFactory, RayHpoExecutor
|
19
|
+
from autogluon.core.models import Tunable
|
20
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
21
|
+
from autogluon.timeseries.utils.warning_filters import disable_stdout, warning_filter
|
22
|
+
|
23
|
+
from .model_trial import model_trial, skip_hpo
|
24
|
+
|
25
|
+
logger = logging.getLogger(__name__)
|
26
|
+
dup_filter = DuplicateFilter()
|
27
|
+
logger.addFilter(dup_filter)
|
28
|
+
|
29
|
+
|
30
|
+
class TimeSeriesTunable(Tunable, ABC):
|
31
|
+
@abstractmethod
|
32
|
+
def __init__(self) -> None:
|
33
|
+
self.name: str
|
34
|
+
self.path: str
|
35
|
+
self.path_root: str
|
36
|
+
|
37
|
+
def hyperparameter_tune(
|
38
|
+
self,
|
39
|
+
train_data: TimeSeriesDataFrame,
|
40
|
+
val_data: Optional[TimeSeriesDataFrame],
|
41
|
+
val_splitter: Any = None,
|
42
|
+
default_num_trials: Optional[int] = 1,
|
43
|
+
refit_every_n_windows: Optional[int] = 1,
|
44
|
+
hyperparameter_tune_kwargs: Union[str, dict] = "auto",
|
45
|
+
time_limit: Optional[float] = None,
|
46
|
+
) -> Tuple[Dict[str, Any], Any]:
|
47
|
+
hpo_executor = self._get_default_hpo_executor()
|
48
|
+
hpo_executor.initialize(
|
49
|
+
hyperparameter_tune_kwargs, default_num_trials=default_num_trials, time_limit=time_limit
|
50
|
+
)
|
51
|
+
|
52
|
+
# we use k_fold=1 to circumvent autogluon.core logic to manage resources during parallelization
|
53
|
+
# of different folds
|
54
|
+
# FIXME: we pass in self which currently does not inherit from AbstractModel
|
55
|
+
hpo_executor.register_resources(self, k_fold=1, **self._get_system_resources()) # type: ignore
|
56
|
+
|
57
|
+
time_start = time.time()
|
58
|
+
logger.debug(f"\tStarting hyperparameter tuning for {self.name}")
|
59
|
+
search_space = self._get_search_space()
|
60
|
+
|
61
|
+
try:
|
62
|
+
hpo_executor.validate_search_space(search_space, self.name)
|
63
|
+
except EmptySearchSpace:
|
64
|
+
return skip_hpo(self, train_data, val_data, time_limit=hpo_executor.time_limit)
|
65
|
+
|
66
|
+
train_path, val_path = self._save_with_data(train_data, val_data)
|
67
|
+
|
68
|
+
train_fn_kwargs = self._get_hpo_train_fn_kwargs(
|
69
|
+
model_cls=self.__class__,
|
70
|
+
init_params=self.get_params(),
|
71
|
+
time_start=time_start,
|
72
|
+
time_limit=hpo_executor.time_limit,
|
73
|
+
fit_kwargs=dict(
|
74
|
+
val_splitter=val_splitter,
|
75
|
+
refit_every_n_windows=refit_every_n_windows,
|
76
|
+
),
|
77
|
+
train_path=train_path,
|
78
|
+
val_path=val_path,
|
79
|
+
hpo_executor=hpo_executor,
|
80
|
+
)
|
81
|
+
|
82
|
+
minimum_resources = self.get_minimum_resources(is_gpu_available=self._is_gpu_available())
|
83
|
+
hpo_context = disable_stdout if isinstance(hpo_executor, RayHpoExecutor) else nullcontext
|
84
|
+
|
85
|
+
minimum_cpu_per_trial = minimum_resources.get("num_cpus", 1)
|
86
|
+
if not isinstance(minimum_cpu_per_trial, int):
|
87
|
+
logger.warning(
|
88
|
+
f"Minimum number of CPUs per trial for {self.name} is not an integer. "
|
89
|
+
f"Setting to 1. Minimum number of CPUs per trial: {minimum_cpu_per_trial}"
|
90
|
+
)
|
91
|
+
minimum_cpu_per_trial = 1
|
92
|
+
|
93
|
+
with hpo_context(), warning_filter(): # prevent Ray from outputting its results to stdout with print
|
94
|
+
hpo_executor.execute(
|
95
|
+
model_trial=model_trial,
|
96
|
+
train_fn_kwargs=train_fn_kwargs,
|
97
|
+
directory=self.path,
|
98
|
+
minimum_cpu_per_trial=minimum_cpu_per_trial,
|
99
|
+
minimum_gpu_per_trial=minimum_resources.get("num_gpus", 0),
|
100
|
+
model_estimate_memory_usage=None, # type: ignore
|
101
|
+
adapter_type="timeseries",
|
102
|
+
)
|
103
|
+
|
104
|
+
assert self.path_root is not None
|
105
|
+
hpo_models, analysis = hpo_executor.get_hpo_results(
|
106
|
+
model_name=self.name,
|
107
|
+
model_path_root=self.path_root,
|
108
|
+
time_start=time_start,
|
109
|
+
)
|
110
|
+
|
111
|
+
return hpo_models, analysis
|
112
|
+
|
113
|
+
def _get_default_hpo_executor(self) -> HpoExecutor:
|
114
|
+
backend = (
|
115
|
+
self._get_model_base()._get_hpo_backend()
|
116
|
+
) # If ensemble, will use the base model to determine backend
|
117
|
+
if backend == RAY_BACKEND:
|
118
|
+
try:
|
119
|
+
try_import_ray()
|
120
|
+
except Exception as e:
|
121
|
+
warning_msg = f"Will use custom hpo logic because ray import failed. Reason: {str(e)}"
|
122
|
+
dup_filter.attach_filter_targets(warning_msg)
|
123
|
+
logger.warning(warning_msg)
|
124
|
+
backend = CUSTOM_BACKEND
|
125
|
+
hpo_executor = HpoExecutorFactory.get_hpo_executor(backend)() # type: ignore
|
126
|
+
return hpo_executor
|
127
|
+
|
128
|
+
def _get_hpo_backend(self) -> str:
|
129
|
+
"""Choose which backend("ray" or "custom") to use for hpo"""
|
130
|
+
if DistributedContext.is_distributed_mode():
|
131
|
+
return RAY_BACKEND
|
132
|
+
return CUSTOM_BACKEND
|
133
|
+
|
134
|
+
def _get_hpo_train_fn_kwargs(self, **train_fn_kwargs) -> dict:
|
135
|
+
"""Update kwargs passed to model_trial depending on the model configuration.
|
136
|
+
|
137
|
+
These kwargs need to be updated, for example, by MultiWindowBacktestingModel.
|
138
|
+
"""
|
139
|
+
return train_fn_kwargs
|
140
|
+
|
141
|
+
def estimate_memory_usage(self, *args, **kwargs) -> float | None:
|
142
|
+
"""Return the estimated memory usage of the model. None if memory usage cannot be
|
143
|
+
estimated.
|
144
|
+
"""
|
145
|
+
return None
|
146
|
+
|
147
|
+
def get_minimum_resources(self, is_gpu_available: bool = False) -> Dict[str, Union[int, float]]:
|
148
|
+
return {
|
149
|
+
"num_cpus": 1,
|
150
|
+
}
|
151
|
+
|
152
|
+
def _save_with_data(
|
153
|
+
self, train_data: TimeSeriesDataFrame, val_data: Optional[TimeSeriesDataFrame]
|
154
|
+
) -> Tuple[str, str]:
|
155
|
+
self.path = os.path.abspath(self.path)
|
156
|
+
self.path_root = self.path.rsplit(self.name, 1)[0]
|
157
|
+
|
158
|
+
dataset_train_filename = "dataset_train.pkl"
|
159
|
+
train_path = os.path.join(self.path, dataset_train_filename)
|
160
|
+
save_pkl.save(path=train_path, object=train_data)
|
161
|
+
|
162
|
+
dataset_val_filename = "dataset_val.pkl"
|
163
|
+
val_path = os.path.join(self.path, dataset_val_filename)
|
164
|
+
save_pkl.save(path=val_path, object=val_data)
|
165
|
+
return train_path, val_path
|
166
|
+
|
167
|
+
@abstractmethod
|
168
|
+
def _get_model_base(self) -> Self:
|
169
|
+
pass
|
170
|
+
|
171
|
+
@abstractmethod
|
172
|
+
def _is_gpu_available(self) -> bool:
|
173
|
+
pass
|
174
|
+
|
175
|
+
@abstractmethod
|
176
|
+
def _get_search_space(self) -> Dict[str, Any]:
|
177
|
+
pass
|
178
|
+
|
179
|
+
@abstractmethod
|
180
|
+
def get_params(self) -> dict:
|
181
|
+
"""Return a clean copy of constructor parameters that can be used to
|
182
|
+
clone the current model.
|
183
|
+
"""
|
184
|
+
pass
|
185
|
+
|
186
|
+
@staticmethod
|
187
|
+
@abstractmethod
|
188
|
+
def _get_system_resources() -> Dict[str, Any]:
|
189
|
+
pass
|
@@ -88,6 +88,9 @@ class AbstractMLForecastModel(AbstractTimeSeriesModel):
|
|
88
88
|
self._train_target_median: Optional[float] = None
|
89
89
|
self._non_boolean_real_covariates: List[str] = []
|
90
90
|
|
91
|
+
# Do not create a scaler in the model, scaler will be passed to MLForecast
|
92
|
+
self.target_scaler = None
|
93
|
+
|
91
94
|
@property
|
92
95
|
def tabular_predictor_path(self) -> str:
|
93
96
|
return os.path.join(self.path, "tabular_predictor")
|
@@ -420,10 +423,6 @@ class AbstractMLForecastModel(AbstractTimeSeriesModel):
|
|
420
423
|
def _more_tags(self) -> dict:
|
421
424
|
return {"allow_nan": True, "can_refit_full": True}
|
422
425
|
|
423
|
-
def _create_target_scaler(self):
|
424
|
-
# Do not create a scaler in the model, scaler will be passed to MLForecast
|
425
|
-
return None
|
426
|
-
|
427
426
|
|
428
427
|
class DirectTabularModel(AbstractMLForecastModel):
|
429
428
|
"""Predict all future time series values simultaneously using TabularPredictor from AutoGluon-Tabular.
|
@@ -13,7 +13,7 @@ from autogluon.timeseries.dataset.ts_dataframe import (
|
|
13
13
|
TIMESTAMP,
|
14
14
|
TimeSeriesDataFrame,
|
15
15
|
)
|
16
|
-
from autogluon.timeseries.transforms.target_scaler import LocalTargetScaler,
|
16
|
+
from autogluon.timeseries.transforms.target_scaler import LocalTargetScaler, get_target_scaler
|
17
17
|
|
18
18
|
from .utils import MLF_ITEMID, MLF_TIMESTAMP
|
19
19
|
|
@@ -31,7 +31,7 @@ class MLForecastScaler(BaseTargetTransform):
|
|
31
31
|
return pd.DataFrame(ts_df).reset_index().rename(columns={ITEMID: self.id_col, TIMESTAMP: self.time_col})
|
32
32
|
|
33
33
|
def fit_transform(self, df: pd.DataFrame) -> pd.DataFrame:
|
34
|
-
self.ag_scaler =
|
34
|
+
self.ag_scaler = get_target_scaler(name=self.scaler_type, target=self.target_col)
|
35
35
|
transformed = self.ag_scaler.fit_transform(self._df_to_tsdf(df)).reset_index()
|
36
36
|
return self._tsdf_to_df(transformed)
|
37
37
|
|
@@ -76,3 +76,11 @@ class AbstractTimeSeriesEnsembleModel(AbstractTimeSeriesModel):
|
|
76
76
|
This method should be called after performing refit_full to point to the refitted base models, if necessary.
|
77
77
|
"""
|
78
78
|
raise NotImplementedError
|
79
|
+
|
80
|
+
# TODO: remove
|
81
|
+
def _fit(*args, **kwargs):
|
82
|
+
pass
|
83
|
+
|
84
|
+
# TODO: remove
|
85
|
+
def _predict(*args, **kwargs):
|
86
|
+
pass
|
@@ -101,7 +101,9 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
|
|
101
101
|
class TimeSeriesGreedyEnsemble(AbstractTimeSeriesEnsembleModel):
|
102
102
|
"""Constructs a weighted ensemble using the greedy Ensemble Selection algorithm."""
|
103
103
|
|
104
|
-
def __init__(self, name: str, ensemble_size: int = 100, **kwargs):
|
104
|
+
def __init__(self, name: Optional[str] = None, ensemble_size: int = 100, **kwargs):
|
105
|
+
if name is None:
|
106
|
+
name = "WeightedEnsemble"
|
105
107
|
super().__init__(name=name, **kwargs)
|
106
108
|
self.ensemble_size = ensemble_size
|
107
109
|
self.model_to_weight: Dict[str, float] = {}
|
@@ -144,7 +146,7 @@ class TimeSeriesGreedyEnsemble(AbstractTimeSeriesEnsembleModel):
|
|
144
146
|
return np.array(list(self.model_to_weight.values()), dtype=np.float64)
|
145
147
|
|
146
148
|
def predict(self, data: Dict[str, Optional[TimeSeriesDataFrame]], **kwargs) -> TimeSeriesDataFrame:
|
147
|
-
if set(data.keys())
|
149
|
+
if not set(self.model_names).issubset(set(data.keys())):
|
148
150
|
raise ValueError(
|
149
151
|
f"Set of models given for prediction in {self.name} differ from those provided during initialization."
|
150
152
|
)
|
@@ -222,11 +222,6 @@ class MultiWindowBacktestingModel(AbstractTimeSeriesModel):
|
|
222
222
|
# Do not initialize the target_scaler and covariate_regressor in the multi window model!
|
223
223
|
pass
|
224
224
|
|
225
|
-
def initialize(self, **kwargs) -> dict:
|
226
|
-
super().initialize(**kwargs)
|
227
|
-
self.model_base.initialize(**kwargs)
|
228
|
-
return kwargs
|
229
|
-
|
230
225
|
def _get_hpo_train_fn_kwargs(self, **train_fn_kwargs) -> dict:
|
231
226
|
train_fn_kwargs["is_bagged_model"] = True
|
232
227
|
train_fn_kwargs["init_params"]["model_base"] = self.model_base.__class__
|
@@ -235,9 +235,6 @@ def get_preset_models(
|
|
235
235
|
"is present in `excluded_model_types` and will be removed."
|
236
236
|
)
|
237
237
|
continue
|
238
|
-
if "mxnet" in model.lower():
|
239
|
-
logger.info(f"\tMXNet model '{model}' given in `hyperparameters` is deprecated and won't be trained. ")
|
240
|
-
continue
|
241
238
|
model_type = MODEL_TYPES[model]
|
242
239
|
elif isinstance(model, type):
|
243
240
|
if not issubclass(model, AbstractTimeSeriesModel):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import time
|
3
|
-
from typing import Any, Dict, Optional
|
3
|
+
from typing import Any, Dict, Optional, Protocol, Union, overload, runtime_checkable
|
4
4
|
|
5
5
|
import numpy as np
|
6
6
|
import pandas as pd
|
@@ -13,7 +13,27 @@ from autogluon.timeseries.utils.features import CovariateMetadata
|
|
13
13
|
logger = logging.getLogger(__name__)
|
14
14
|
|
15
15
|
|
16
|
-
|
16
|
+
@runtime_checkable
|
17
|
+
class CovariateRegressor(Protocol):
|
18
|
+
def is_fit(self) -> bool: ...
|
19
|
+
|
20
|
+
def fit(self, data: TimeSeriesDataFrame, time_limit: Optional[float] = None, **kwargs) -> "CovariateRegressor": ...
|
21
|
+
|
22
|
+
def transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
23
|
+
|
24
|
+
def fit_transform(
|
25
|
+
self, data: TimeSeriesDataFrame, time_limit: Optional[float] = None, **kwargs
|
26
|
+
) -> TimeSeriesDataFrame: ...
|
27
|
+
|
28
|
+
def inverse_transform(
|
29
|
+
self,
|
30
|
+
predictions: TimeSeriesDataFrame,
|
31
|
+
known_covariates: TimeSeriesDataFrame,
|
32
|
+
static_features: Optional[pd.DataFrame],
|
33
|
+
) -> TimeSeriesDataFrame: ...
|
34
|
+
|
35
|
+
|
36
|
+
class GlobalCovariateRegressor(CovariateRegressor):
|
17
37
|
"""Predicts target values from the covariates for the same observation.
|
18
38
|
|
19
39
|
The model construct the feature matrix using known_covariates and static_features.
|
@@ -33,7 +53,7 @@ class CovariateRegressor:
|
|
33
53
|
`transform`.
|
34
54
|
max_num_samples : int or None
|
35
55
|
If not None, training dataset passed to regression model will contain at most this many rows.
|
36
|
-
|
56
|
+
covariate_metadata : CovariateMetadata
|
37
57
|
Metadata object describing the covariates available in the dataset.
|
38
58
|
target : str
|
39
59
|
Name of the target column.
|
@@ -58,7 +78,7 @@ class CovariateRegressor:
|
|
58
78
|
eval_metric: str = "mean_absolute_error",
|
59
79
|
refit_during_predict: bool = False,
|
60
80
|
max_num_samples: Optional[int] = 500_000,
|
61
|
-
|
81
|
+
covariate_metadata: Optional[CovariateMetadata] = None,
|
62
82
|
target: str = "target",
|
63
83
|
validation_fraction: Optional[float] = 0.1,
|
64
84
|
fit_time_fraction: float = 0.5,
|
@@ -83,7 +103,7 @@ class CovariateRegressor:
|
|
83
103
|
|
84
104
|
self.model: Optional[AbstractModel] = None
|
85
105
|
self.disabled = False
|
86
|
-
self.
|
106
|
+
self.covariate_metadata = covariate_metadata or CovariateMetadata()
|
87
107
|
|
88
108
|
def is_fit(self) -> bool:
|
89
109
|
return self.model is not None
|
@@ -188,7 +208,7 @@ class CovariateRegressor:
|
|
188
208
|
include_target: bool = False,
|
189
209
|
) -> pd.DataFrame:
|
190
210
|
"""Construct a tabular dataframe from known covariates and static features."""
|
191
|
-
available_columns = [ITEMID] + self.
|
211
|
+
available_columns = [ITEMID] + self.covariate_metadata.known_covariates
|
192
212
|
if include_target:
|
193
213
|
available_columns += [self.target]
|
194
214
|
tabular_df = pd.DataFrame(data).reset_index()[available_columns].astype({ITEMID: "category"})
|
@@ -201,3 +221,31 @@ class CovariateRegressor:
|
|
201
221
|
if self.max_num_samples is not None and len(df) > self.max_num_samples:
|
202
222
|
df = df.sample(n=self.max_num_samples)
|
203
223
|
return df
|
224
|
+
|
225
|
+
|
226
|
+
@overload
|
227
|
+
def get_covariate_regressor(covariate_regressor: None, target: str, covariate_metadata: CovariateMetadata) -> None: ...
|
228
|
+
@overload
|
229
|
+
def get_covariate_regressor(
|
230
|
+
covariate_regressor: Union[str, dict], target: str, covariate_metadata: CovariateMetadata
|
231
|
+
) -> CovariateRegressor: ...
|
232
|
+
def get_covariate_regressor(
|
233
|
+
covariate_regressor: Optional[Union[str, dict]], target: str, covariate_metadata: CovariateMetadata
|
234
|
+
) -> Optional[CovariateRegressor]:
|
235
|
+
"""Create a CovariateRegressor object based on the value of the `covariate_regressor` hyperparameter."""
|
236
|
+
if covariate_regressor is None:
|
237
|
+
return None
|
238
|
+
elif len(covariate_metadata.known_covariates + covariate_metadata.static_features) == 0:
|
239
|
+
logger.info("\tSkipping covariate_regressor since the dataset contains no covariates or static features.")
|
240
|
+
return None
|
241
|
+
else:
|
242
|
+
if isinstance(covariate_regressor, str):
|
243
|
+
return GlobalCovariateRegressor(covariate_regressor, target=target, covariate_metadata=covariate_metadata)
|
244
|
+
elif isinstance(covariate_regressor, dict):
|
245
|
+
return GlobalCovariateRegressor(
|
246
|
+
**covariate_regressor, target=target, covariate_metadata=covariate_metadata
|
247
|
+
)
|
248
|
+
else:
|
249
|
+
raise ValueError(
|
250
|
+
f"Invalid value for covariate_regressor {covariate_regressor} of type {type(covariate_regressor)}"
|
251
|
+
)
|
@@ -1,13 +1,2 @@
|
|
1
|
-
from .covariate_scaler import
|
2
|
-
|
3
|
-
GlobalCovariateScaler,
|
4
|
-
get_covariate_scaler_from_name,
|
5
|
-
)
|
6
|
-
from .target_scaler import (
|
7
|
-
LocalStandardScaler,
|
8
|
-
LocalMinMaxScaler,
|
9
|
-
LocalMeanAbsScaler,
|
10
|
-
LocalRobustScaler,
|
11
|
-
LocalTargetScaler,
|
12
|
-
get_target_scaler_from_name
|
13
|
-
)
|
1
|
+
from .covariate_scaler import CovariateScaler, get_covariate_scaler
|
2
|
+
from .target_scaler import TargetScaler, get_target_scaler
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import Dict, List, Literal, Optional
|
2
|
+
from typing import Dict, List, Literal, Optional, Protocol, overload, runtime_checkable
|
3
3
|
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
@@ -13,35 +13,20 @@ from autogluon.timeseries.utils.warning_filters import warning_filter
|
|
13
13
|
logger = logging.getLogger(__name__)
|
14
14
|
|
15
15
|
|
16
|
-
|
16
|
+
@runtime_checkable
|
17
|
+
class CovariateScaler(Protocol):
|
17
18
|
"""Apply scaling to covariates and static features.
|
18
19
|
|
19
20
|
This can be helpful for deep learning models that assume that the inputs are normalized.
|
20
21
|
"""
|
21
22
|
|
22
|
-
def
|
23
|
-
self,
|
24
|
-
metadata: CovariateMetadata,
|
25
|
-
use_known_covariates: bool = True,
|
26
|
-
use_past_covariates: bool = True,
|
27
|
-
use_static_features: bool = True,
|
28
|
-
**kwargs,
|
29
|
-
):
|
30
|
-
self.metadata = metadata
|
31
|
-
self.use_known_covariates = use_known_covariates
|
32
|
-
self.use_past_covariates = use_past_covariates
|
33
|
-
self.use_static_features = use_static_features
|
34
|
-
|
35
|
-
def fit_transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame:
|
36
|
-
raise NotImplementedError
|
23
|
+
def fit_transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
37
24
|
|
38
|
-
def transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame:
|
39
|
-
raise NotImplementedError
|
25
|
+
def transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
40
26
|
|
41
27
|
def transform_known_covariates(
|
42
28
|
self, known_covariates: Optional[TimeSeriesDataFrame] = None
|
43
|
-
) -> Optional[TimeSeriesDataFrame]:
|
44
|
-
raise NotImplementedError
|
29
|
+
) -> Optional[TimeSeriesDataFrame]: ...
|
45
30
|
|
46
31
|
|
47
32
|
class GlobalCovariateScaler(CovariateScaler):
|
@@ -57,13 +42,16 @@ class GlobalCovariateScaler(CovariateScaler):
|
|
57
42
|
|
58
43
|
def __init__(
|
59
44
|
self,
|
60
|
-
|
45
|
+
covariate_metadata: CovariateMetadata,
|
61
46
|
use_known_covariates: bool = True,
|
62
47
|
use_past_covariates: bool = True,
|
63
48
|
use_static_features: bool = True,
|
64
49
|
skew_threshold: float = 0.99,
|
65
50
|
):
|
66
|
-
|
51
|
+
self.covariate_metadata = covariate_metadata
|
52
|
+
self.use_known_covariates = use_known_covariates
|
53
|
+
self.use_past_covariates = use_past_covariates
|
54
|
+
self.use_static_features = use_static_features
|
67
55
|
self.skew_threshold = skew_threshold
|
68
56
|
self._column_transformers: Optional[Dict[Literal["known", "past", "static"], ColumnTransformer]] = None
|
69
57
|
|
@@ -73,18 +61,18 @@ class GlobalCovariateScaler(CovariateScaler):
|
|
73
61
|
def fit(self, data: TimeSeriesDataFrame) -> "GlobalCovariateScaler":
|
74
62
|
self._column_transformers = {}
|
75
63
|
|
76
|
-
if self.use_known_covariates and len(self.
|
64
|
+
if self.use_known_covariates and len(self.covariate_metadata.known_covariates_real) > 0:
|
77
65
|
self._column_transformers["known"] = self._get_transformer_for_columns(
|
78
|
-
data, columns=self.
|
66
|
+
data, columns=self.covariate_metadata.known_covariates_real
|
79
67
|
)
|
80
|
-
if self.use_past_covariates and len(self.
|
68
|
+
if self.use_past_covariates and len(self.covariate_metadata.past_covariates_real) > 0:
|
81
69
|
self._column_transformers["past"] = self._get_transformer_for_columns(
|
82
|
-
data, columns=self.
|
70
|
+
data, columns=self.covariate_metadata.past_covariates_real
|
83
71
|
)
|
84
|
-
if self.use_static_features and len(self.
|
72
|
+
if self.use_static_features and len(self.covariate_metadata.static_features_real) > 0:
|
85
73
|
assert data.static_features is not None
|
86
74
|
self._column_transformers["static"] = self._get_transformer_for_columns(
|
87
|
-
data.static_features, columns=self.
|
75
|
+
data.static_features, columns=self.covariate_metadata.static_features_real
|
88
76
|
)
|
89
77
|
|
90
78
|
return self
|
@@ -100,15 +88,15 @@ class GlobalCovariateScaler(CovariateScaler):
|
|
100
88
|
assert self._column_transformers is not None, "CovariateScaler must be fit before transform can be called"
|
101
89
|
|
102
90
|
if "known" in self._column_transformers:
|
103
|
-
columns = self.
|
91
|
+
columns = self.covariate_metadata.known_covariates_real
|
104
92
|
data[columns] = self._column_transformers["known"].transform(data[columns])
|
105
93
|
|
106
94
|
if "past" in self._column_transformers:
|
107
|
-
columns = self.
|
95
|
+
columns = self.covariate_metadata.past_covariates_real
|
108
96
|
data[columns] = self._column_transformers["past"].transform(data[columns])
|
109
97
|
|
110
98
|
if "static" in self._column_transformers:
|
111
|
-
columns = self.
|
99
|
+
columns = self.covariate_metadata.static_features_real
|
112
100
|
assert data.static_features is not None
|
113
101
|
|
114
102
|
data.static_features[columns] = self._column_transformers["static"].transform(
|
@@ -122,7 +110,7 @@ class GlobalCovariateScaler(CovariateScaler):
|
|
122
110
|
assert self._column_transformers is not None, "CovariateScaler must be fit before transform can be called"
|
123
111
|
|
124
112
|
if "known" in self._column_transformers:
|
125
|
-
columns = self.
|
113
|
+
columns = self.covariate_metadata.known_covariates_real
|
126
114
|
assert known_covariates is not None
|
127
115
|
|
128
116
|
known_covariates = known_covariates.copy()
|
@@ -162,7 +150,13 @@ AVAILABLE_COVARIATE_SCALERS = {
|
|
162
150
|
}
|
163
151
|
|
164
152
|
|
165
|
-
|
153
|
+
@overload
|
154
|
+
def get_covariate_scaler(name: None, **scaler_kwargs) -> None: ...
|
155
|
+
@overload
|
156
|
+
def get_covariate_scaler(name: Literal["global"], **scaler_kwargs) -> GlobalCovariateScaler: ...
|
157
|
+
def get_covariate_scaler(name: Optional[Literal["global"]] = None, **scaler_kwargs) -> Optional[CovariateScaler]:
|
158
|
+
if name is None:
|
159
|
+
return None
|
166
160
|
if name not in AVAILABLE_COVARIATE_SCALERS:
|
167
161
|
raise KeyError(
|
168
162
|
f"Covariate scaler type {name} not supported. Available scalers: {list(AVAILABLE_COVARIATE_SCALERS)}"
|
@@ -1,12 +1,23 @@
|
|
1
|
-
from typing import Literal, Optional, Tuple, Union
|
1
|
+
from typing import Literal, Optional, Protocol, Tuple, Union, overload
|
2
2
|
|
3
3
|
import numpy as np
|
4
4
|
import pandas as pd
|
5
|
+
from typing_extensions import Self
|
5
6
|
|
6
7
|
from autogluon.timeseries.dataset.ts_dataframe import ITEMID, TimeSeriesDataFrame
|
7
8
|
|
8
9
|
|
9
|
-
class
|
10
|
+
class TargetScaler(Protocol):
|
11
|
+
def fit_transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
12
|
+
|
13
|
+
def fit(self, data: TimeSeriesDataFrame) -> Self: ...
|
14
|
+
|
15
|
+
def transform(self, data: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
16
|
+
|
17
|
+
def inverse_transform(self, predictions: TimeSeriesDataFrame) -> TimeSeriesDataFrame: ...
|
18
|
+
|
19
|
+
|
20
|
+
class LocalTargetScaler(TargetScaler):
|
10
21
|
"""Applies an affine transformation (x - loc) / scale independently to each time series in the dataset."""
|
11
22
|
|
12
23
|
def __init__(
|
@@ -123,10 +134,16 @@ AVAILABLE_TARGET_SCALERS = {
|
|
123
134
|
}
|
124
135
|
|
125
136
|
|
126
|
-
|
127
|
-
|
128
|
-
|
137
|
+
@overload
|
138
|
+
def get_target_scaler(name: None, **scaler_kwargs) -> None: ...
|
139
|
+
@overload
|
140
|
+
def get_target_scaler(name: Literal["standard", "mean_abs", "min_max", "robust"], **scaler_kwargs) -> TargetScaler: ...
|
141
|
+
def get_target_scaler(
|
142
|
+
name: Optional[Literal["standard", "mean_abs", "min_max", "robust"]], **scaler_kwargs
|
143
|
+
) -> Optional[TargetScaler]:
|
129
144
|
"""Get LocalTargetScaler object from a string."""
|
145
|
+
if name is None:
|
146
|
+
return None
|
130
147
|
if name not in AVAILABLE_TARGET_SCALERS:
|
131
148
|
raise KeyError(f"Scaler type {name} not supported. Available scalers: {list(AVAILABLE_TARGET_SCALERS)}")
|
132
149
|
return AVAILABLE_TARGET_SCALERS[name](**scaler_kwargs)
|
autogluon/timeseries/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: autogluon.timeseries
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.1b20250306
|
4
4
|
Summary: Fast and Accurate ML in 3 Lines of Code
|
5
5
|
Home-page: https://github.com/autogluon/autogluon
|
6
6
|
Author: AutoGluon Community
|
@@ -55,9 +55,9 @@ Requires-Dist: fugue>=0.9.0
|
|
55
55
|
Requires-Dist: tqdm<5,>=4.38
|
56
56
|
Requires-Dist: orjson~=3.9
|
57
57
|
Requires-Dist: tensorboard<3,>=2.9
|
58
|
-
Requires-Dist: autogluon.core[raytune]==1.2.
|
59
|
-
Requires-Dist: autogluon.common==1.2.
|
60
|
-
Requires-Dist: autogluon.tabular[catboost,lightgbm,xgboost]==1.2.
|
58
|
+
Requires-Dist: autogluon.core[raytune]==1.2.1b20250306
|
59
|
+
Requires-Dist: autogluon.common==1.2.1b20250306
|
60
|
+
Requires-Dist: autogluon.tabular[catboost,lightgbm,xgboost]==1.2.1b20250306
|
61
61
|
Provides-Extra: all
|
62
62
|
Provides-Extra: chronos-onnx
|
63
63
|
Requires-Dist: optimum[onnxruntime]<1.20,>=1.17; extra == "chronos-onnx"
|