autogluon.timeseries 1.2.1b20250224__py3-none-any.whl → 1.4.1b20251215__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of autogluon.timeseries might be problematic. Click here for more details.
- autogluon/timeseries/configs/__init__.py +3 -2
- autogluon/timeseries/configs/hyperparameter_presets.py +62 -0
- autogluon/timeseries/configs/predictor_presets.py +106 -0
- autogluon/timeseries/dataset/ts_dataframe.py +256 -141
- autogluon/timeseries/learner.py +86 -52
- autogluon/timeseries/metrics/__init__.py +42 -8
- autogluon/timeseries/metrics/abstract.py +89 -19
- autogluon/timeseries/metrics/point.py +142 -53
- autogluon/timeseries/metrics/quantile.py +46 -21
- autogluon/timeseries/metrics/utils.py +4 -4
- autogluon/timeseries/models/__init__.py +8 -2
- autogluon/timeseries/models/abstract/__init__.py +2 -2
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +361 -592
- autogluon/timeseries/models/abstract/model_trial.py +2 -1
- autogluon/timeseries/models/abstract/tunable.py +189 -0
- autogluon/timeseries/models/autogluon_tabular/__init__.py +2 -0
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +282 -194
- autogluon/timeseries/models/autogluon_tabular/per_step.py +513 -0
- autogluon/timeseries/models/autogluon_tabular/transforms.py +25 -18
- autogluon/timeseries/models/chronos/__init__.py +2 -1
- autogluon/timeseries/models/chronos/chronos2.py +361 -0
- autogluon/timeseries/models/chronos/model.py +219 -138
- autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +81 -50
- autogluon/timeseries/models/ensemble/__init__.py +37 -2
- autogluon/timeseries/models/ensemble/abstract.py +107 -0
- autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
- autogluon/timeseries/models/ensemble/array_based/abstract.py +240 -0
- autogluon/timeseries/models/ensemble/array_based/models.py +185 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +12 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +88 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +94 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +107 -0
- autogluon/timeseries/models/ensemble/ensemble_selection.py +167 -0
- autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
- autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +45 -0
- autogluon/timeseries/models/ensemble/weighted/basic.py +91 -0
- autogluon/timeseries/models/ensemble/weighted/greedy.py +62 -0
- autogluon/timeseries/models/gluonts/__init__.py +1 -1
- autogluon/timeseries/models/gluonts/{abstract_gluonts.py → abstract.py} +148 -208
- autogluon/timeseries/models/gluonts/dataset.py +109 -0
- autogluon/timeseries/models/gluonts/{torch/models.py → models.py} +38 -22
- autogluon/timeseries/models/local/__init__.py +0 -7
- autogluon/timeseries/models/local/abstract_local_model.py +71 -74
- autogluon/timeseries/models/local/naive.py +13 -9
- autogluon/timeseries/models/local/npts.py +9 -2
- autogluon/timeseries/models/local/statsforecast.py +52 -36
- autogluon/timeseries/models/multi_window/multi_window_model.py +65 -45
- autogluon/timeseries/models/registry.py +64 -0
- autogluon/timeseries/models/toto/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/__init__.py +9 -0
- autogluon/timeseries/models/toto/_internal/backbone/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/backbone/attention.py +196 -0
- autogluon/timeseries/models/toto/_internal/backbone/backbone.py +262 -0
- autogluon/timeseries/models/toto/_internal/backbone/distribution.py +70 -0
- autogluon/timeseries/models/toto/_internal/backbone/kvcache.py +136 -0
- autogluon/timeseries/models/toto/_internal/backbone/rope.py +89 -0
- autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
- autogluon/timeseries/models/toto/_internal/backbone/scaler.py +305 -0
- autogluon/timeseries/models/toto/_internal/backbone/transformer.py +333 -0
- autogluon/timeseries/models/toto/_internal/dataset.py +165 -0
- autogluon/timeseries/models/toto/_internal/forecaster.py +423 -0
- autogluon/timeseries/models/toto/dataloader.py +108 -0
- autogluon/timeseries/models/toto/hf_pretrained_model.py +200 -0
- autogluon/timeseries/models/toto/model.py +249 -0
- autogluon/timeseries/predictor.py +685 -297
- autogluon/timeseries/regressor.py +94 -44
- autogluon/timeseries/splitter.py +8 -32
- autogluon/timeseries/trainer/__init__.py +3 -0
- autogluon/timeseries/trainer/ensemble_composer.py +444 -0
- autogluon/timeseries/trainer/model_set_builder.py +256 -0
- autogluon/timeseries/trainer/prediction_cache.py +149 -0
- autogluon/timeseries/{trainer.py → trainer/trainer.py} +387 -390
- autogluon/timeseries/trainer/utils.py +17 -0
- autogluon/timeseries/transforms/__init__.py +2 -13
- autogluon/timeseries/transforms/covariate_scaler.py +34 -40
- autogluon/timeseries/transforms/target_scaler.py +37 -20
- autogluon/timeseries/utils/constants.py +10 -0
- autogluon/timeseries/utils/datetime/lags.py +3 -5
- autogluon/timeseries/utils/datetime/seasonality.py +1 -3
- autogluon/timeseries/utils/datetime/time_features.py +2 -2
- autogluon/timeseries/utils/features.py +70 -47
- autogluon/timeseries/utils/forecast.py +19 -14
- autogluon/timeseries/utils/timer.py +173 -0
- autogluon/timeseries/utils/warning_filters.py +4 -2
- autogluon/timeseries/version.py +1 -1
- autogluon.timeseries-1.4.1b20251215-py3.11-nspkg.pth +1 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/METADATA +49 -36
- autogluon_timeseries-1.4.1b20251215.dist-info/RECORD +103 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/WHEEL +1 -1
- autogluon/timeseries/configs/presets_configs.py +0 -79
- autogluon/timeseries/evaluator.py +0 -6
- autogluon/timeseries/models/chronos/pipeline/__init__.py +0 -11
- autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
- autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -585
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -518
- autogluon/timeseries/models/ensemble/abstract_timeseries_ensemble.py +0 -78
- autogluon/timeseries/models/ensemble/greedy_ensemble.py +0 -170
- autogluon/timeseries/models/gluonts/torch/__init__.py +0 -0
- autogluon/timeseries/models/presets.py +0 -360
- autogluon.timeseries-1.2.1b20250224-py3.9-nspkg.pth +0 -1
- autogluon.timeseries-1.2.1b20250224.dist-info/RECORD +0 -68
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/LICENSE +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/NOTICE +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/zip-safe +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import copy
|
|
4
2
|
import logging
|
|
5
3
|
import os
|
|
6
4
|
import re
|
|
7
5
|
import time
|
|
8
|
-
from
|
|
9
|
-
from typing import Any,
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, Sequence
|
|
10
8
|
|
|
11
9
|
import pandas as pd
|
|
12
10
|
from typing_extensions import Self
|
|
@@ -14,143 +12,51 @@ from typing_extensions import Self
|
|
|
14
12
|
from autogluon.common import space
|
|
15
13
|
from autogluon.common.loaders import load_pkl
|
|
16
14
|
from autogluon.common.savers import save_pkl
|
|
17
|
-
from autogluon.common.utils.distribute_utils import DistributedContext
|
|
18
|
-
from autogluon.common.utils.log_utils import DuplicateFilter
|
|
19
15
|
from autogluon.common.utils.resource_utils import get_resource_manager
|
|
20
|
-
from autogluon.common.utils.try_import import try_import_ray
|
|
21
16
|
from autogluon.common.utils.utils import setup_outputdir
|
|
22
|
-
from autogluon.core.constants import
|
|
23
|
-
from autogluon.core.hpo.constants import CUSTOM_BACKEND, RAY_BACKEND
|
|
24
|
-
from autogluon.core.hpo.exceptions import EmptySearchSpace
|
|
25
|
-
from autogluon.core.hpo.executors import HpoExecutor, HpoExecutorFactory, RayHpoExecutor
|
|
17
|
+
from autogluon.core.constants import AG_ARGS_FIT, REFIT_FULL_SUFFIX
|
|
26
18
|
from autogluon.core.models import ModelBase
|
|
27
19
|
from autogluon.core.utils.exceptions import TimeLimitExceeded
|
|
28
20
|
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
29
21
|
from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_metric
|
|
30
|
-
from autogluon.timeseries.
|
|
31
|
-
from autogluon.timeseries.
|
|
32
|
-
|
|
33
|
-
LocalTargetScaler,
|
|
34
|
-
get_covariate_scaler_from_name,
|
|
35
|
-
get_target_scaler_from_name,
|
|
36
|
-
)
|
|
22
|
+
from autogluon.timeseries.models.registry import ModelRegistry
|
|
23
|
+
from autogluon.timeseries.regressor import CovariateRegressor, get_covariate_regressor
|
|
24
|
+
from autogluon.timeseries.transforms import CovariateScaler, TargetScaler, get_covariate_scaler, get_target_scaler
|
|
37
25
|
from autogluon.timeseries.utils.features import CovariateMetadata
|
|
38
|
-
from autogluon.timeseries.utils.forecast import
|
|
39
|
-
from autogluon.timeseries.utils.warning_filters import disable_stdout, warning_filter
|
|
26
|
+
from autogluon.timeseries.utils.forecast import make_future_data_frame
|
|
40
27
|
|
|
41
|
-
from .
|
|
28
|
+
from .tunable import TimeSeriesTunable
|
|
42
29
|
|
|
43
30
|
logger = logging.getLogger(__name__)
|
|
44
|
-
dup_filter = DuplicateFilter()
|
|
45
|
-
logger.addFilter(dup_filter)
|
|
46
31
|
|
|
47
32
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
ag_args_fit: str = AG_ARGS_FIT,
|
|
52
|
-
ag_arg_prefix: str = AG_ARG_PREFIX,
|
|
53
|
-
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
54
|
-
"""
|
|
55
|
-
Given the user-specified hyperparameters, split into `params` and `params_aux`.
|
|
33
|
+
class TimeSeriesModelBase(ModelBase, ABC):
|
|
34
|
+
"""Abstract base class for all `Model` objects in autogluon.timeseries, including both
|
|
35
|
+
forecasting models and forecast combination/ensemble models.
|
|
56
36
|
|
|
57
37
|
Parameters
|
|
58
38
|
----------
|
|
59
|
-
|
|
60
|
-
The model hyperparameters dictionary
|
|
61
|
-
ag_args_fit : str, default = "ag_args_fit"
|
|
62
|
-
The params key to look for that contains params_aux.
|
|
63
|
-
If the key is present, the value is used for params_aux and popped from params.
|
|
64
|
-
If no such key is found, then initialize params_aux as an empty dictionary.
|
|
65
|
-
ag_arg_prefix : str, default = "ag."
|
|
66
|
-
The key prefix to look for that indicates a parameter is intended for params_aux.
|
|
67
|
-
If None, this logic is skipped.
|
|
68
|
-
If a key starts with this prefix, it is popped from params and added to params_aux with the prefix removed.
|
|
69
|
-
For example:
|
|
70
|
-
input: params={'ag.foo': 2, 'abc': 7}, params_aux={'bar': 3}, and ag_arg_prefix='.ag',
|
|
71
|
-
output: params={'abc': 7}, params_aux={'bar': 3, 'foo': 2}
|
|
72
|
-
In cases where the key is specified multiple times, the value of the key with the prefix will always take priority.
|
|
73
|
-
A warning will be logged if a key is present multiple times.
|
|
74
|
-
For example, given the most complex scenario:
|
|
75
|
-
input: params={'ag.foo': 1, 'foo': 2, 'ag_args_fit': {'ag.foo': 3, 'foo': 4}}
|
|
76
|
-
output: params={'foo': 2}, params_aux={'foo': 1}
|
|
77
|
-
|
|
78
|
-
Returns
|
|
79
|
-
-------
|
|
80
|
-
params, params_aux : (Dict[str, Any], Dict[str, Any])
|
|
81
|
-
params will contain the native model hyperparameters
|
|
82
|
-
params_aux will contain special auxiliary hyperparameters
|
|
83
|
-
"""
|
|
84
|
-
params = copy.deepcopy(params) if params is not None else dict()
|
|
85
|
-
assert isinstance(params, dict), f"Invalid dtype of params! Expected dict, but got {type(params)}"
|
|
86
|
-
for k in params.keys():
|
|
87
|
-
if not isinstance(k, str):
|
|
88
|
-
logger.warning(
|
|
89
|
-
f"Warning: Specified hyperparameter key is not of type str: {k} (type={type(k)}). "
|
|
90
|
-
f"There might be a bug in your configuration."
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
params_aux = params.pop(ag_args_fit, dict())
|
|
94
|
-
if params_aux is None:
|
|
95
|
-
params_aux = dict()
|
|
96
|
-
assert isinstance(params_aux, dict), f"Invalid dtype of params_aux! Expected dict, but got {type(params_aux)}"
|
|
97
|
-
if ag_arg_prefix is not None:
|
|
98
|
-
param_aux_keys = list(params_aux.keys())
|
|
99
|
-
for k in param_aux_keys:
|
|
100
|
-
if isinstance(k, str) and k.startswith(ag_arg_prefix):
|
|
101
|
-
k_no_prefix = k[len(ag_arg_prefix) :]
|
|
102
|
-
if k_no_prefix in params_aux:
|
|
103
|
-
logger.warning(
|
|
104
|
-
f'Warning: hyperparameter "{k}" is present '
|
|
105
|
-
f'in `ag_args_fit` as both "{k}" and "{k_no_prefix}". '
|
|
106
|
-
f'Will use "{k}" and ignore "{k_no_prefix}".'
|
|
107
|
-
)
|
|
108
|
-
params_aux[k_no_prefix] = params_aux.pop(k)
|
|
109
|
-
param_keys = list(params.keys())
|
|
110
|
-
for k in param_keys:
|
|
111
|
-
if isinstance(k, str) and k.startswith(ag_arg_prefix):
|
|
112
|
-
k_no_prefix = k[len(ag_arg_prefix) :]
|
|
113
|
-
if k_no_prefix in params_aux:
|
|
114
|
-
logger.warning(
|
|
115
|
-
f'Warning: hyperparameter "{k}" is present '
|
|
116
|
-
f"in both `ag_args_fit` and `hyperparameters`. "
|
|
117
|
-
f"Will use `hyperparameters` value."
|
|
118
|
-
)
|
|
119
|
-
params_aux[k_no_prefix] = params.pop(k)
|
|
120
|
-
return params, params_aux
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# TODO: refactor. remove params_aux, etc. make class inherit from ABC, make overrides and abstract
|
|
124
|
-
# methods clear, change name to TimeSeriesModel, et al.
|
|
125
|
-
class AbstractTimeSeriesModel(ModelBase):
|
|
126
|
-
"""Abstract class for all `Model` objects in autogluon.timeseries.
|
|
127
|
-
|
|
128
|
-
Parameters
|
|
129
|
-
----------
|
|
130
|
-
path : str, default = None
|
|
39
|
+
path
|
|
131
40
|
Directory location to store all outputs.
|
|
132
41
|
If None, a new unique time-stamped directory is chosen.
|
|
133
|
-
freq
|
|
42
|
+
freq
|
|
134
43
|
Frequency string (cf. gluonts frequency strings) describing the frequency
|
|
135
44
|
of the time series data. For example, "h" for hourly or "D" for daily data.
|
|
136
|
-
prediction_length
|
|
45
|
+
prediction_length
|
|
137
46
|
Length of the prediction horizon, i.e., the number of time steps the model
|
|
138
47
|
is fit to forecast.
|
|
139
|
-
name
|
|
48
|
+
name
|
|
140
49
|
Name of the subdirectory inside path where model will be saved.
|
|
141
50
|
The final model directory will be os.path.join(path, name)
|
|
142
51
|
If None, defaults to the model's class name: self.__class__.__name__
|
|
143
|
-
|
|
52
|
+
covariate_metadata
|
|
144
53
|
A mapping of different covariate types known to autogluon.timeseries to column names
|
|
145
54
|
in the data set.
|
|
146
|
-
eval_metric
|
|
55
|
+
eval_metric
|
|
147
56
|
Metric by which predictions will be ultimately evaluated on future test data. This only impacts
|
|
148
57
|
``model.score()``, as eval_metric is not used during training. Available metrics can be found in
|
|
149
58
|
``autogluon.timeseries.metrics``.
|
|
150
|
-
|
|
151
|
-
Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
|
|
152
|
-
``None``, in which case the seasonal period is computed based on the data frequency.
|
|
153
|
-
hyperparameters : dict, default = None
|
|
59
|
+
hyperparameters
|
|
154
60
|
Hyperparameters that will be used by the model (can be search spaces instead of fixed values).
|
|
155
61
|
If None, model defaults are used. This is identical to passing an empty dictionary.
|
|
156
62
|
"""
|
|
@@ -169,35 +75,34 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
169
75
|
|
|
170
76
|
def __init__(
|
|
171
77
|
self,
|
|
172
|
-
path:
|
|
173
|
-
name:
|
|
174
|
-
hyperparameters:
|
|
175
|
-
freq:
|
|
78
|
+
path: str | None = None,
|
|
79
|
+
name: str | None = None,
|
|
80
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
81
|
+
freq: str | None = None,
|
|
176
82
|
prediction_length: int = 1,
|
|
177
|
-
|
|
83
|
+
covariate_metadata: CovariateMetadata | None = None,
|
|
178
84
|
target: str = "target",
|
|
179
85
|
quantile_levels: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
|
|
180
|
-
eval_metric:
|
|
181
|
-
eval_metric_seasonal_period: Optional[int] = None,
|
|
86
|
+
eval_metric: str | TimeSeriesScorer | None = None,
|
|
182
87
|
):
|
|
183
88
|
self.name = name or re.sub(r"Model$", "", self.__class__.__name__)
|
|
184
89
|
|
|
185
90
|
self.path_root = path
|
|
186
91
|
if self.path_root is None:
|
|
187
92
|
path_suffix = self.name
|
|
188
|
-
# TODO: Would be ideal to not create dir, but still track that it is unique. However, this isn't possible
|
|
93
|
+
# TODO: Would be ideal to not create dir, but still track that it is unique. However, this isn't possible
|
|
94
|
+
# to do without a global list of used dirs or using UUID.
|
|
189
95
|
path_cur = setup_outputdir(path=None, create_dir=True, path_suffix=path_suffix)
|
|
190
96
|
self.path_root = path_cur.rsplit(self.name, 1)[0]
|
|
191
97
|
logger.log(20, f"Warning: No path was specified for model, defaulting to: {self.path_root}")
|
|
192
98
|
|
|
193
99
|
self.path = os.path.join(self.path_root, self.name)
|
|
194
100
|
|
|
195
|
-
self.eval_metric
|
|
196
|
-
self.eval_metric_seasonal_period = eval_metric_seasonal_period
|
|
101
|
+
self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
|
|
197
102
|
self.target: str = target
|
|
198
|
-
self.
|
|
103
|
+
self.covariate_metadata = covariate_metadata or CovariateMetadata()
|
|
199
104
|
|
|
200
|
-
self.freq:
|
|
105
|
+
self.freq: str | None = freq
|
|
201
106
|
self.prediction_length: int = prediction_length
|
|
202
107
|
self.quantile_levels: list[float] = list(quantile_levels)
|
|
203
108
|
|
|
@@ -212,27 +117,21 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
212
117
|
else:
|
|
213
118
|
self.must_drop_median = False
|
|
214
119
|
|
|
215
|
-
self._oof_predictions:
|
|
216
|
-
self.target_scaler: Optional[LocalTargetScaler] = None
|
|
217
|
-
self.covariate_scaler: Optional[CovariateScaler] = None
|
|
218
|
-
self.covariate_regressor: Optional[CovariateRegressor] = None
|
|
219
|
-
|
|
220
|
-
# TODO: remove the variables below
|
|
221
|
-
self.model = None
|
|
222
|
-
|
|
223
|
-
self._is_initialized = False
|
|
224
|
-
self._user_params, self._user_params_aux = check_and_split_hyperparameters(hyperparameters)
|
|
120
|
+
self._oof_predictions: list[TimeSeriesDataFrame] | None = None
|
|
225
121
|
|
|
226
|
-
|
|
227
|
-
self.
|
|
228
|
-
self.nondefault_params: List[str] = []
|
|
122
|
+
# user provided hyperparameters and extra arguments that are used during model training
|
|
123
|
+
self._hyperparameters, self._extra_ag_args = self._check_and_split_hyperparameters(hyperparameters)
|
|
229
124
|
|
|
230
|
-
|
|
231
|
-
self.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
self.
|
|
125
|
+
# Time taken to fit in seconds (Training data)
|
|
126
|
+
self.fit_time: float | None = None
|
|
127
|
+
# Time taken to predict in seconds, for a single prediction horizon on validation data
|
|
128
|
+
self.predict_time: float | None = None
|
|
129
|
+
# Time taken to predict 1 row of data in seconds (with batch size `predict_1_batch_size`)
|
|
130
|
+
self.predict_1_time: float | None = None
|
|
131
|
+
# Useful for ensembles, additional prediction time excluding base models. None for base models.
|
|
132
|
+
self.predict_time_marginal: float | None = None
|
|
133
|
+
# Score with eval_metric on validation data
|
|
134
|
+
self.val_score: float | None = None
|
|
236
135
|
|
|
237
136
|
def __repr__(self) -> str:
|
|
238
137
|
return self.name
|
|
@@ -248,7 +147,49 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
248
147
|
self.path = path_context
|
|
249
148
|
self.path_root = self.path.rsplit(self.name, 1)[0]
|
|
250
149
|
|
|
251
|
-
def
|
|
150
|
+
def cache_oof_predictions(self, predictions: TimeSeriesDataFrame | list[TimeSeriesDataFrame]) -> None:
|
|
151
|
+
if isinstance(predictions, TimeSeriesDataFrame):
|
|
152
|
+
predictions = [predictions]
|
|
153
|
+
self._oof_predictions = predictions
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def _check_and_split_hyperparameters(
|
|
157
|
+
cls, hyperparameters: dict[str, Any] | None = None
|
|
158
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
159
|
+
"""Given the user-specified hyperparameters, split into `hyperparameters` and `extra_ag_args`, intended
|
|
160
|
+
to be used during model initialization.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
hyperparameters
|
|
165
|
+
The model hyperparameters dictionary provided to the model constructor.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
hyperparameters
|
|
170
|
+
Native model hyperparameters that are passed into the "inner model" AutoGluon wraps
|
|
171
|
+
extra_ag_args
|
|
172
|
+
Special auxiliary parameters that modify the model training process used by AutoGluon
|
|
173
|
+
"""
|
|
174
|
+
hyperparameters = copy.deepcopy(hyperparameters) if hyperparameters is not None else dict()
|
|
175
|
+
assert isinstance(hyperparameters, dict), (
|
|
176
|
+
f"Invalid dtype for hyperparameters. Expected dict, but got {type(hyperparameters)}"
|
|
177
|
+
)
|
|
178
|
+
for k in hyperparameters.keys():
|
|
179
|
+
if not isinstance(k, str):
|
|
180
|
+
logger.warning(
|
|
181
|
+
f"Warning: Specified hyperparameter key is not of type str: {k} (type={type(k)}). "
|
|
182
|
+
f"There might be a bug in your configuration."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
extra_ag_args = hyperparameters.pop(AG_ARGS_FIT, {})
|
|
186
|
+
if not isinstance(extra_ag_args, dict):
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"Invalid hyperparameter type for `{AG_ARGS_FIT}`. Expected dict, but got {type(extra_ag_args)}"
|
|
189
|
+
)
|
|
190
|
+
return hyperparameters, extra_ag_args
|
|
191
|
+
|
|
192
|
+
def save(self, path: str | None = None, verbose: bool = True) -> str:
|
|
252
193
|
if path is None:
|
|
253
194
|
path = self.path
|
|
254
195
|
|
|
@@ -263,35 +204,30 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
263
204
|
self._oof_predictions = None
|
|
264
205
|
|
|
265
206
|
file_path = os.path.join(path, self.model_file_name)
|
|
266
|
-
_model = self.model
|
|
267
207
|
save_pkl.save(path=file_path, object=self, verbose=verbose)
|
|
268
|
-
self.model = _model
|
|
269
208
|
|
|
270
209
|
self._oof_predictions = oof_predictions
|
|
271
210
|
return path
|
|
272
211
|
|
|
273
212
|
@classmethod
|
|
274
|
-
def load(cls, path: str, reset_paths: bool = True, load_oof: bool = False, verbose: bool = True) -> Self:
|
|
213
|
+
def load(cls, path: str, reset_paths: bool = True, load_oof: bool = False, verbose: bool = True) -> Self:
|
|
275
214
|
file_path = os.path.join(path, cls.model_file_name)
|
|
276
215
|
model = load_pkl.load(path=file_path, verbose=verbose)
|
|
277
216
|
if reset_paths:
|
|
278
217
|
model.set_contexts(path)
|
|
279
|
-
if hasattr(model, "_compiler"):
|
|
280
|
-
if model._compiler is not None and not model._compiler.save_in_pkl:
|
|
281
|
-
model.model = model._compiler.load(path=path)
|
|
282
218
|
if load_oof and model._oof_predictions is None:
|
|
283
219
|
model._oof_predictions = cls.load_oof_predictions(path=path, verbose=verbose)
|
|
284
220
|
return model
|
|
285
221
|
|
|
286
222
|
@classmethod
|
|
287
|
-
def load_oof_predictions(cls, path: str, verbose: bool = True) ->
|
|
223
|
+
def load_oof_predictions(cls, path: str, verbose: bool = True) -> list[TimeSeriesDataFrame]:
|
|
288
224
|
"""Load the cached OOF predictions from disk."""
|
|
289
225
|
return load_pkl.load(path=os.path.join(path, "utils", cls._oof_filename), verbose=verbose)
|
|
290
226
|
|
|
291
227
|
@property
|
|
292
228
|
def supports_known_covariates(self) -> bool:
|
|
293
229
|
return (
|
|
294
|
-
self.
|
|
230
|
+
self.get_hyperparameters().get("covariate_regressor") is not None
|
|
295
231
|
or self.__class__._supports_known_covariates
|
|
296
232
|
)
|
|
297
233
|
|
|
@@ -302,7 +238,8 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
302
238
|
@property
|
|
303
239
|
def supports_static_features(self) -> bool:
|
|
304
240
|
return (
|
|
305
|
-
self.
|
|
241
|
+
self.get_hyperparameters().get("covariate_regressor") is not None
|
|
242
|
+
or self.__class__._supports_static_features
|
|
306
243
|
)
|
|
307
244
|
|
|
308
245
|
def get_oof_predictions(self):
|
|
@@ -310,61 +247,99 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
310
247
|
self._oof_predictions = self.load_oof_predictions(self.path)
|
|
311
248
|
return self._oof_predictions
|
|
312
249
|
|
|
313
|
-
def
|
|
314
|
-
return dict(
|
|
315
|
-
# ratio of given time_limit to use during fit(). If time_limit == 10 and max_time_limit_ratio=0.3,
|
|
316
|
-
# time_limit would be changed to 3.
|
|
317
|
-
max_time_limit_ratio=self.default_max_time_limit_ratio,
|
|
318
|
-
# max time_limit value during fit(). If the provided time_limit is greater than this value, it will be
|
|
319
|
-
# replaced by max_time_limit. Occurs after max_time_limit_ratio is applied.
|
|
320
|
-
max_time_limit=None,
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
# TODO: remove
|
|
324
|
-
@classmethod
|
|
325
|
-
def _get_default_ag_args(cls) -> dict:
|
|
250
|
+
def _get_default_hyperparameters(self) -> dict:
|
|
326
251
|
return {}
|
|
327
252
|
|
|
328
|
-
def
|
|
329
|
-
"""
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
def _init_params_aux(self):
|
|
253
|
+
def get_hyperparameters(self) -> dict:
|
|
254
|
+
"""Get dictionary of hyperparameters that will be passed to the "inner model" that AutoGluon wraps."""
|
|
255
|
+
return {**self._get_default_hyperparameters(), **self._hyperparameters}
|
|
256
|
+
|
|
257
|
+
def get_hyperparameter(self, key: str) -> Any:
|
|
258
|
+
"""Get a single hyperparameter value for the "inner model"."""
|
|
259
|
+
return self.get_hyperparameters()[key]
|
|
260
|
+
|
|
261
|
+
def get_info(self) -> dict:
|
|
339
262
|
"""
|
|
340
|
-
|
|
341
|
-
These parameters are generally not model specific and can have a wide variety of effects.
|
|
342
|
-
For documentation on some of the available options and their defaults, refer to `self._get_default_auxiliary_params`.
|
|
263
|
+
Returns a dictionary of numerous fields describing the model.
|
|
343
264
|
"""
|
|
344
|
-
|
|
345
|
-
|
|
265
|
+
info = {
|
|
266
|
+
"name": self.name,
|
|
267
|
+
"model_type": type(self).__name__,
|
|
268
|
+
"eval_metric": self.eval_metric,
|
|
269
|
+
"fit_time": self.fit_time,
|
|
270
|
+
"predict_time": self.predict_time,
|
|
271
|
+
"freq": self.freq,
|
|
272
|
+
"prediction_length": self.prediction_length,
|
|
273
|
+
"quantile_levels": self.quantile_levels,
|
|
274
|
+
"val_score": self.val_score,
|
|
275
|
+
"hyperparameters": self.get_hyperparameters(),
|
|
276
|
+
"covariate_metadata": self.covariate_metadata.to_dict(),
|
|
277
|
+
}
|
|
278
|
+
return info
|
|
346
279
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
280
|
+
@classmethod
|
|
281
|
+
def load_info(cls, path: str, load_model_if_required: bool = True) -> dict:
|
|
282
|
+
# TODO: remove?
|
|
283
|
+
load_path = os.path.join(path, cls.model_info_name)
|
|
284
|
+
try:
|
|
285
|
+
return load_pkl.load(path=load_path)
|
|
286
|
+
except:
|
|
287
|
+
if load_model_if_required:
|
|
288
|
+
model = cls.load(path=path, reset_paths=True)
|
|
289
|
+
return model.get_info()
|
|
290
|
+
else:
|
|
291
|
+
raise
|
|
353
292
|
|
|
354
|
-
def
|
|
355
|
-
|
|
356
|
-
self.covariate_scaler = self._create_covariate_scaler()
|
|
357
|
-
self.covariate_regressor = self._create_covariate_regressor()
|
|
293
|
+
def _is_gpu_available(self) -> bool:
|
|
294
|
+
return False
|
|
358
295
|
|
|
359
|
-
|
|
360
|
-
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _get_system_resources() -> dict[str, Any]:
|
|
298
|
+
resource_manager = get_resource_manager()
|
|
299
|
+
system_num_cpus = resource_manager.get_cpu_count()
|
|
300
|
+
system_num_gpus = resource_manager.get_gpu_count()
|
|
301
|
+
return {
|
|
302
|
+
"num_cpus": system_num_cpus,
|
|
303
|
+
"num_gpus": system_num_gpus,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
def _get_model_base(self) -> Self:
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
def persist(self) -> Self:
|
|
310
|
+
"""Ask the model to persist its assets in memory, i.e., to predict with low latency. In practice
|
|
311
|
+
this is used for pretrained models that have to lazy-load model parameters to device memory at
|
|
312
|
+
prediction time.
|
|
313
|
+
"""
|
|
314
|
+
return self
|
|
315
|
+
|
|
316
|
+
def _more_tags(self) -> dict:
|
|
317
|
+
"""Encode model properties using tags, similar to sklearn & autogluon.tabular.
|
|
318
|
+
|
|
319
|
+
For more details, see `autogluon.core.models.abstract.AbstractModel._get_tags()` and
|
|
320
|
+
https://scikit-learn.org/stable/_sources/developers/develop.rst.txt.
|
|
321
|
+
|
|
322
|
+
List of currently supported tags:
|
|
323
|
+
- allow_nan: Can the model handle data with missing values represented by np.nan?
|
|
324
|
+
- can_refit_full: Does it make sense to retrain the model without validation data?
|
|
325
|
+
See `autogluon.core.models.abstract._tags._DEFAULT_TAGS` for more details.
|
|
326
|
+
- can_use_train_data: Can the model use train_data if it's provided to model.fit()?
|
|
327
|
+
- can_use_val_data: Can the model use val_data if it's provided to model.fit()?
|
|
328
|
+
"""
|
|
329
|
+
return {
|
|
330
|
+
"allow_nan": False,
|
|
331
|
+
"can_refit_full": False,
|
|
332
|
+
"can_use_train_data": True,
|
|
333
|
+
"can_use_val_data": False,
|
|
334
|
+
}
|
|
361
335
|
|
|
362
336
|
def get_params(self) -> dict:
|
|
363
|
-
|
|
364
|
-
#
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
337
|
+
"""Get the constructor parameters required for cloning this model object"""
|
|
338
|
+
# We only use the user-provided hyperparameters for cloning. We cannot use the output of get_hyperparameters()
|
|
339
|
+
# since it may contain search spaces that won't be converted to concrete values during HPO
|
|
340
|
+
hyperparameters = self._hyperparameters.copy()
|
|
341
|
+
if self._extra_ag_args:
|
|
342
|
+
hyperparameters[AG_ARGS_FIT] = self._extra_ag_args.copy()
|
|
368
343
|
|
|
369
344
|
return dict(
|
|
370
345
|
path=self.path_root,
|
|
@@ -374,47 +349,105 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
374
349
|
freq=self.freq,
|
|
375
350
|
prediction_length=self.prediction_length,
|
|
376
351
|
quantile_levels=self.quantile_levels,
|
|
377
|
-
|
|
352
|
+
covariate_metadata=self.covariate_metadata,
|
|
378
353
|
target=self.target,
|
|
379
354
|
)
|
|
380
355
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
except:
|
|
388
|
-
if load_model_if_required:
|
|
389
|
-
model = cls.load(path=path, reset_paths=True)
|
|
390
|
-
return model.get_info()
|
|
391
|
-
else:
|
|
392
|
-
raise
|
|
356
|
+
def convert_to_refit_full_via_copy(self) -> Self:
|
|
357
|
+
# save the model as a new model on disk
|
|
358
|
+
previous_name = self.name
|
|
359
|
+
self.rename(self.name + REFIT_FULL_SUFFIX)
|
|
360
|
+
refit_model_path = self.path
|
|
361
|
+
self.save(path=self.path, verbose=False)
|
|
393
362
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
"
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
363
|
+
self.rename(previous_name)
|
|
364
|
+
|
|
365
|
+
refit_model = self.load(path=refit_model_path, verbose=False)
|
|
366
|
+
refit_model.val_score = None
|
|
367
|
+
refit_model.predict_time = None
|
|
368
|
+
|
|
369
|
+
return refit_model
|
|
370
|
+
|
|
371
|
+
def convert_to_refit_full_template(self) -> Self:
|
|
372
|
+
"""After calling this function, returned model should be able to be fit without `val_data`."""
|
|
373
|
+
params = copy.deepcopy(self.get_params())
|
|
374
|
+
|
|
375
|
+
# Remove 0.5 from quantile_levels so that the cloned model sets its must_drop_median correctly
|
|
376
|
+
if self.must_drop_median:
|
|
377
|
+
params["quantile_levels"].remove(0.5)
|
|
378
|
+
|
|
379
|
+
if "hyperparameters" not in params:
|
|
380
|
+
params["hyperparameters"] = dict()
|
|
381
|
+
|
|
382
|
+
if AG_ARGS_FIT not in params["hyperparameters"]:
|
|
383
|
+
params["hyperparameters"][AG_ARGS_FIT] = dict()
|
|
384
|
+
|
|
385
|
+
params["name"] = params["name"] + REFIT_FULL_SUFFIX
|
|
386
|
+
template = self.__class__(**params)
|
|
387
|
+
|
|
388
|
+
return template
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, metaclass=ModelRegistry):
|
|
392
|
+
"""Abstract base class for all time series models that take historical data as input and
|
|
393
|
+
make predictions for the forecast horizon.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
ag_priority: int = 0
|
|
397
|
+
|
|
398
|
+
def __init__(
|
|
399
|
+
self,
|
|
400
|
+
path: str | None = None,
|
|
401
|
+
name: str | None = None,
|
|
402
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
403
|
+
freq: str | None = None,
|
|
404
|
+
prediction_length: int = 1,
|
|
405
|
+
covariate_metadata: CovariateMetadata | None = None,
|
|
406
|
+
target: str = "target",
|
|
407
|
+
quantile_levels: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
|
|
408
|
+
eval_metric: str | TimeSeriesScorer | None = None,
|
|
409
|
+
):
|
|
410
|
+
# TODO: make freq a required argument in AbstractTimeSeriesModel
|
|
411
|
+
super().__init__(
|
|
412
|
+
path=path,
|
|
413
|
+
name=name,
|
|
414
|
+
hyperparameters=hyperparameters,
|
|
415
|
+
freq=freq,
|
|
416
|
+
prediction_length=prediction_length,
|
|
417
|
+
covariate_metadata=covariate_metadata,
|
|
418
|
+
target=target,
|
|
419
|
+
quantile_levels=quantile_levels,
|
|
420
|
+
eval_metric=eval_metric,
|
|
421
|
+
)
|
|
422
|
+
self.target_scaler: TargetScaler | None
|
|
423
|
+
self.covariate_scaler: CovariateScaler | None
|
|
424
|
+
self.covariate_regressor: CovariateRegressor | None
|
|
425
|
+
|
|
426
|
+
def _initialize_transforms_and_regressor(self) -> None:
|
|
427
|
+
self.target_scaler = get_target_scaler(self.get_hyperparameters().get("target_scaler"), target=self.target)
|
|
428
|
+
self.covariate_scaler = get_covariate_scaler(
|
|
429
|
+
self.get_hyperparameters().get("covariate_scaler"),
|
|
430
|
+
covariate_metadata=self.covariate_metadata,
|
|
431
|
+
use_static_features=self.supports_static_features,
|
|
432
|
+
use_known_covariates=self.supports_known_covariates,
|
|
433
|
+
use_past_covariates=self.supports_past_covariates,
|
|
434
|
+
)
|
|
435
|
+
self.covariate_regressor = get_covariate_regressor(
|
|
436
|
+
self.get_hyperparameters().get("covariate_regressor"),
|
|
437
|
+
target=self.target,
|
|
438
|
+
covariate_metadata=self.covariate_metadata,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def allowed_hyperparameters(self) -> list[str]:
|
|
443
|
+
"""List of hyperparameters allowed by the model."""
|
|
444
|
+
return ["target_scaler", "covariate_regressor", "covariate_scaler"]
|
|
412
445
|
|
|
413
|
-
def fit(
|
|
446
|
+
def fit(
|
|
414
447
|
self,
|
|
415
448
|
train_data: TimeSeriesDataFrame,
|
|
416
|
-
val_data:
|
|
417
|
-
time_limit:
|
|
449
|
+
val_data: TimeSeriesDataFrame | None = None,
|
|
450
|
+
time_limit: float | None = None,
|
|
418
451
|
verbosity: int = 2,
|
|
419
452
|
**kwargs,
|
|
420
453
|
) -> Self:
|
|
@@ -423,36 +456,36 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
423
456
|
Models should not override the `fit` method, but instead override the `_fit` method which
|
|
424
457
|
has the same arguments.
|
|
425
458
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
train_data
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
train_data
|
|
429
462
|
The training data provided in the library's `autogluon.timeseries.dataset.TimeSeriesDataFrame`
|
|
430
463
|
format.
|
|
431
|
-
val_data
|
|
464
|
+
val_data
|
|
432
465
|
The validation data set in the same format as training data.
|
|
433
|
-
time_limit
|
|
466
|
+
time_limit
|
|
434
467
|
Time limit in seconds to adhere to when fitting model.
|
|
435
468
|
Ideally, model should early stop during fit to avoid going over the time limit if specified.
|
|
436
|
-
num_cpus
|
|
469
|
+
num_cpus
|
|
437
470
|
How many CPUs to use during fit.
|
|
438
471
|
This is counted in virtual cores, not in physical cores.
|
|
439
472
|
If 'auto', model decides.
|
|
440
|
-
num_gpus
|
|
473
|
+
num_gpus
|
|
441
474
|
How many GPUs to use during fit.
|
|
442
475
|
If 'auto', model decides.
|
|
443
|
-
verbosity
|
|
476
|
+
verbosity
|
|
444
477
|
Verbosity levels range from 0 to 4 and control how much information is printed.
|
|
445
478
|
Higher levels correspond to more detailed print statements (you can set verbosity = 0 to suppress warnings).
|
|
446
|
-
**kwargs
|
|
479
|
+
**kwargs
|
|
447
480
|
Any additional fit arguments a model supports.
|
|
448
481
|
|
|
449
482
|
Returns
|
|
450
483
|
-------
|
|
451
|
-
model
|
|
484
|
+
model
|
|
452
485
|
The fitted model object
|
|
453
486
|
"""
|
|
454
487
|
start_time = time.monotonic()
|
|
455
|
-
self.
|
|
488
|
+
self._initialize_transforms_and_regressor()
|
|
456
489
|
|
|
457
490
|
if self.target_scaler is not None:
|
|
458
491
|
train_data = self.target_scaler.fit_transform(train_data)
|
|
@@ -467,7 +500,7 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
467
500
|
self.covariate_regressor.fit(
|
|
468
501
|
train_data,
|
|
469
502
|
time_limit=covariate_regressor_time_limit,
|
|
470
|
-
verbosity=verbosity,
|
|
503
|
+
verbosity=verbosity - 1,
|
|
471
504
|
)
|
|
472
505
|
|
|
473
506
|
if self._get_tags()["can_use_train_data"]:
|
|
@@ -503,35 +536,14 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
503
536
|
|
|
504
537
|
return self
|
|
505
538
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
max_time_limit_ratio = self.params_aux["max_time_limit_ratio"]
|
|
509
|
-
max_time_limit = self.params_aux["max_time_limit"]
|
|
510
|
-
|
|
511
|
-
time_limit *= max_time_limit_ratio
|
|
512
|
-
|
|
513
|
-
if max_time_limit is not None:
|
|
514
|
-
time_limit = min(time_limit, max_time_limit)
|
|
515
|
-
|
|
516
|
-
if original_time_limit != time_limit:
|
|
517
|
-
time_limit_og_str = f"{original_time_limit:.2f}s" if original_time_limit is not None else "None"
|
|
518
|
-
time_limit_str = f"{time_limit:.2f}s" if time_limit is not None else "None"
|
|
519
|
-
logger.debug(
|
|
520
|
-
f"\tTime limit adjusted due to model hyperparameters: "
|
|
521
|
-
f"{time_limit_og_str} -> {time_limit_str} "
|
|
522
|
-
f"(ag.max_time_limit={max_time_limit}, "
|
|
523
|
-
f"ag.max_time_limit_ratio={max_time_limit_ratio}"
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
return time_limit
|
|
527
|
-
|
|
528
|
-
def _fit( # type: ignore
|
|
539
|
+
@abstractmethod
|
|
540
|
+
def _fit(
|
|
529
541
|
self,
|
|
530
542
|
train_data: TimeSeriesDataFrame,
|
|
531
|
-
val_data:
|
|
532
|
-
time_limit:
|
|
533
|
-
num_cpus:
|
|
534
|
-
num_gpus:
|
|
543
|
+
val_data: TimeSeriesDataFrame | None = None,
|
|
544
|
+
time_limit: float | None = None,
|
|
545
|
+
num_cpus: int | None = None,
|
|
546
|
+
num_gpus: int | None = None,
|
|
535
547
|
verbosity: int = 2,
|
|
536
548
|
**kwargs,
|
|
537
549
|
) -> None:
|
|
@@ -539,78 +551,36 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
539
551
|
the model training logic, `fit` additionally implements other logic such as keeping
|
|
540
552
|
track of the time limit, etc.
|
|
541
553
|
"""
|
|
542
|
-
|
|
543
|
-
raise NotImplementedError
|
|
554
|
+
pass
|
|
544
555
|
|
|
545
|
-
# TODO:
|
|
556
|
+
# TODO: this check cannot be moved inside fit because of the complex way in which
|
|
557
|
+
# MultiWindowBacktestingModel handles hyperparameter spaces during initialization.
|
|
558
|
+
# Move inside fit() after refactoring MultiWindowBacktestingModel
|
|
546
559
|
def _check_fit_params(self):
|
|
547
560
|
# gracefully handle hyperparameter specifications if they are provided to fit instead
|
|
548
|
-
if any(isinstance(v, space.Space) for v in self.
|
|
561
|
+
if any(isinstance(v, space.Space) for v in self.get_hyperparameters().values()):
|
|
549
562
|
raise ValueError(
|
|
550
563
|
"Hyperparameter spaces provided to `fit`. Please provide concrete values "
|
|
551
564
|
"as hyperparameters when initializing or use `hyperparameter_tune` instead."
|
|
552
565
|
)
|
|
553
566
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
else:
|
|
566
|
-
return None
|
|
567
|
-
|
|
568
|
-
def _create_covariate_scaler(self) -> Optional[CovariateScaler]:
|
|
569
|
-
"""Create a CovariateScaler object based on the value of the `covariate_scaler` hyperparameter."""
|
|
570
|
-
covariate_scaler_type = self._get_model_params().get("covariate_scaler")
|
|
571
|
-
if covariate_scaler_type is not None:
|
|
572
|
-
return get_covariate_scaler_from_name(
|
|
573
|
-
covariate_scaler_type,
|
|
574
|
-
metadata=self.metadata,
|
|
575
|
-
use_static_features=self.supports_static_features,
|
|
576
|
-
use_known_covariates=self.supports_known_covariates,
|
|
577
|
-
use_past_covariates=self.supports_past_covariates,
|
|
567
|
+
def _log_unused_hyperparameters(self, extra_allowed_hyperparameters: list[str] | None = None) -> None:
|
|
568
|
+
"""Log a warning if unused hyperparameters were provided to the model."""
|
|
569
|
+
allowed_hyperparameters = self.allowed_hyperparameters
|
|
570
|
+
if extra_allowed_hyperparameters is not None:
|
|
571
|
+
allowed_hyperparameters = allowed_hyperparameters + extra_allowed_hyperparameters
|
|
572
|
+
|
|
573
|
+
unused_hyperparameters = [key for key in self.get_hyperparameters() if key not in allowed_hyperparameters]
|
|
574
|
+
if len(unused_hyperparameters) > 0:
|
|
575
|
+
logger.warning(
|
|
576
|
+
f"{self.name} ignores following hyperparameters: {unused_hyperparameters}. "
|
|
577
|
+
f"See the documentation for {self.name} for the list of supported hyperparameters."
|
|
578
578
|
)
|
|
579
|
-
else:
|
|
580
|
-
return None
|
|
581
|
-
|
|
582
|
-
def _create_covariate_regressor(self) -> Optional[CovariateRegressor]:
|
|
583
|
-
"""Create a CovariateRegressor object based on the value of the `covariate_regressor` hyperparameter."""
|
|
584
|
-
covariate_regressor = self._get_model_params().get("covariate_regressor")
|
|
585
|
-
if covariate_regressor is not None:
|
|
586
|
-
if len(self.metadata.known_covariates + self.metadata.static_features) == 0:
|
|
587
|
-
logger.info(
|
|
588
|
-
"\tSkipping covariate_regressor since the dataset contains no covariates or static features."
|
|
589
|
-
)
|
|
590
|
-
return None
|
|
591
|
-
else:
|
|
592
|
-
if isinstance(covariate_regressor, str):
|
|
593
|
-
return CovariateRegressor(covariate_regressor, target=self.target, metadata=self.metadata)
|
|
594
|
-
elif isinstance(covariate_regressor, dict):
|
|
595
|
-
return CovariateRegressor(**covariate_regressor, target=self.target, metadata=self.metadata)
|
|
596
|
-
elif isinstance(covariate_regressor, CovariateRegressor):
|
|
597
|
-
logger.warning(
|
|
598
|
-
"\tUsing a custom covariate_regressor is experimental functionality that may break in the future!"
|
|
599
|
-
)
|
|
600
|
-
covariate_regressor.target = self.target
|
|
601
|
-
covariate_regressor.metadata = self.metadata
|
|
602
|
-
return covariate_regressor
|
|
603
|
-
else:
|
|
604
|
-
raise ValueError(
|
|
605
|
-
f"Invalid value for covariate_regressor {covariate_regressor} of type {type(covariate_regressor)}"
|
|
606
|
-
)
|
|
607
|
-
else:
|
|
608
|
-
return None
|
|
609
579
|
|
|
610
|
-
def predict(
|
|
580
|
+
def predict(
|
|
611
581
|
self,
|
|
612
|
-
data:
|
|
613
|
-
known_covariates:
|
|
582
|
+
data: TimeSeriesDataFrame,
|
|
583
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
614
584
|
**kwargs,
|
|
615
585
|
) -> TimeSeriesDataFrame:
|
|
616
586
|
"""Given a dataset, predict the next `self.prediction_length` time steps.
|
|
@@ -622,22 +592,19 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
622
592
|
|
|
623
593
|
Parameters
|
|
624
594
|
----------
|
|
625
|
-
data
|
|
595
|
+
data
|
|
626
596
|
The dataset where each time series is the "context" for predictions. For ensemble models that depend on
|
|
627
597
|
the predictions of other models, this method may accept a dictionary of previous models' predictions.
|
|
628
|
-
known_covariates
|
|
598
|
+
known_covariates
|
|
629
599
|
A TimeSeriesDataFrame containing the values of the known covariates during the forecast horizon.
|
|
630
600
|
|
|
631
601
|
Returns
|
|
632
602
|
-------
|
|
633
|
-
predictions
|
|
634
|
-
pandas
|
|
603
|
+
predictions
|
|
604
|
+
pandas dataframes with a timestamp index, where each input item from the input
|
|
635
605
|
data is given as a separate forecast item in the dictionary, keyed by the `item_id`s
|
|
636
606
|
of input items.
|
|
637
607
|
"""
|
|
638
|
-
# TODO: the method signature is not aligned with the model interface in general as it allows dict
|
|
639
|
-
assert isinstance(data, TimeSeriesDataFrame)
|
|
640
|
-
|
|
641
608
|
if self.target_scaler is not None:
|
|
642
609
|
data = self.target_scaler.fit_transform(data)
|
|
643
610
|
if self.covariate_scaler is not None:
|
|
@@ -655,7 +622,13 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
655
622
|
predictions = self._predict(data=data, known_covariates=known_covariates, **kwargs)
|
|
656
623
|
self.covariate_regressor = covariate_regressor
|
|
657
624
|
|
|
658
|
-
#
|
|
625
|
+
# Ensure that 'mean' is the leading column. Trailing columns might not match quantile_levels if self is
|
|
626
|
+
# a MultiWindowBacktestingModel and base_model.must_drop_median=True
|
|
627
|
+
column_order = pd.Index(["mean"] + [col for col in predictions.columns if col != "mean"])
|
|
628
|
+
if not predictions.columns.equals(column_order):
|
|
629
|
+
predictions = predictions.reindex(columns=column_order)
|
|
630
|
+
|
|
631
|
+
# "0.5" might be missing from the quantiles if self is a MultiWindowBacktestingModel
|
|
659
632
|
if "0.5" in predictions.columns:
|
|
660
633
|
if self.eval_metric.optimized_by_median:
|
|
661
634
|
predictions["mean"] = predictions["0.5"]
|
|
@@ -680,34 +653,62 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
680
653
|
|
|
681
654
|
def get_forecast_horizon_index(self, data: TimeSeriesDataFrame) -> pd.MultiIndex:
|
|
682
655
|
"""For each item in the dataframe, get timestamps for the next `prediction_length` time steps into the future."""
|
|
683
|
-
return
|
|
656
|
+
return pd.MultiIndex.from_frame(
|
|
657
|
+
make_future_data_frame(data, prediction_length=self.prediction_length, freq=self.freq)
|
|
658
|
+
)
|
|
684
659
|
|
|
660
|
+
@abstractmethod
|
|
685
661
|
def _predict(
|
|
686
662
|
self,
|
|
687
|
-
data:
|
|
688
|
-
known_covariates:
|
|
663
|
+
data: TimeSeriesDataFrame,
|
|
664
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
689
665
|
**kwargs,
|
|
690
666
|
) -> TimeSeriesDataFrame:
|
|
691
667
|
"""Private method for `predict`. See `predict` for documentation of arguments."""
|
|
692
|
-
|
|
668
|
+
pass
|
|
669
|
+
|
|
670
|
+
def _preprocess_time_limit(self, time_limit: float) -> float:
|
|
671
|
+
original_time_limit = time_limit
|
|
672
|
+
max_time_limit_ratio = self._extra_ag_args.get("max_time_limit_ratio", self.default_max_time_limit_ratio)
|
|
673
|
+
max_time_limit = self._extra_ag_args.get("max_time_limit")
|
|
674
|
+
|
|
675
|
+
time_limit *= max_time_limit_ratio
|
|
676
|
+
|
|
677
|
+
if max_time_limit is not None:
|
|
678
|
+
time_limit = min(time_limit, max_time_limit)
|
|
679
|
+
|
|
680
|
+
if original_time_limit != time_limit:
|
|
681
|
+
time_limit_og_str = f"{original_time_limit:.2f}s" if original_time_limit is not None else "None"
|
|
682
|
+
time_limit_str = f"{time_limit:.2f}s" if time_limit is not None else "None"
|
|
683
|
+
logger.debug(
|
|
684
|
+
f"\tTime limit adjusted due to model hyperparameters: "
|
|
685
|
+
f"{time_limit_og_str} -> {time_limit_str} "
|
|
686
|
+
f"(ag.max_time_limit={max_time_limit}, "
|
|
687
|
+
f"ag.max_time_limit_ratio={max_time_limit_ratio}"
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
return time_limit
|
|
691
|
+
|
|
692
|
+
def _get_search_space(self):
|
|
693
|
+
"""Sets up default search space for HPO. Each hyperparameter which user did not specify is converted from
|
|
694
|
+
default fixed value to default search space.
|
|
695
|
+
"""
|
|
696
|
+
params = self._hyperparameters.copy()
|
|
697
|
+
return params
|
|
693
698
|
|
|
694
699
|
def _score_with_predictions(
|
|
695
700
|
self,
|
|
696
701
|
data: TimeSeriesDataFrame,
|
|
697
702
|
predictions: TimeSeriesDataFrame,
|
|
698
|
-
metric: Optional[str] = None,
|
|
699
703
|
) -> float:
|
|
700
704
|
"""Compute the score measuring how well the predictions align with the data."""
|
|
701
|
-
|
|
702
|
-
return eval_metric.score(
|
|
705
|
+
return self.eval_metric.score(
|
|
703
706
|
data=data,
|
|
704
707
|
predictions=predictions,
|
|
705
|
-
prediction_length=self.prediction_length,
|
|
706
708
|
target=self.target,
|
|
707
|
-
seasonal_period=self.eval_metric_seasonal_period,
|
|
708
709
|
)
|
|
709
710
|
|
|
710
|
-
def score(self, data: TimeSeriesDataFrame
|
|
711
|
+
def score(self, data: TimeSeriesDataFrame) -> float:
|
|
711
712
|
"""Return the evaluation scores for given metric and dataset. The last
|
|
712
713
|
`self.prediction_length` time steps of each time series in the input data set
|
|
713
714
|
will be held out and used for computing the evaluation score. Time series
|
|
@@ -715,31 +716,20 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
715
716
|
|
|
716
717
|
Parameters
|
|
717
718
|
----------
|
|
718
|
-
data
|
|
719
|
+
data
|
|
719
720
|
Dataset used for scoring.
|
|
720
|
-
metric: str
|
|
721
|
-
String identifier of evaluation metric to use, from one of
|
|
722
|
-
`autogluon.timeseries.utils.metric_utils.AVAILABLE_METRICS`.
|
|
723
|
-
|
|
724
|
-
Other Parameters
|
|
725
|
-
----------------
|
|
726
|
-
num_samples: int
|
|
727
|
-
Number of samples to use for making evaluation predictions if the probabilistic
|
|
728
|
-
forecasts are generated by forward sampling from the fitted model.
|
|
729
721
|
|
|
730
722
|
Returns
|
|
731
723
|
-------
|
|
732
|
-
score
|
|
724
|
+
score
|
|
733
725
|
The computed forecast evaluation score on the last `self.prediction_length`
|
|
734
726
|
time steps of each time series.
|
|
735
727
|
"""
|
|
736
|
-
# TODO: align method signature in the new AbstractModel
|
|
737
|
-
|
|
738
728
|
past_data, known_covariates = data.get_model_inputs_for_scoring(
|
|
739
|
-
prediction_length=self.prediction_length, known_covariates_names=self.
|
|
729
|
+
prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
|
|
740
730
|
)
|
|
741
731
|
predictions = self.predict(past_data, known_covariates=known_covariates)
|
|
742
|
-
return self._score_with_predictions(data=data, predictions=predictions
|
|
732
|
+
return self._score_with_predictions(data=data, predictions=predictions)
|
|
743
733
|
|
|
744
734
|
def score_and_cache_oof(
|
|
745
735
|
self,
|
|
@@ -750,243 +740,22 @@ class AbstractTimeSeriesModel(ModelBase):
|
|
|
750
740
|
) -> None:
|
|
751
741
|
"""Compute val_score, predict_time and cache out-of-fold (OOF) predictions."""
|
|
752
742
|
past_data, known_covariates = val_data.get_model_inputs_for_scoring(
|
|
753
|
-
prediction_length=self.prediction_length, known_covariates_names=self.
|
|
743
|
+
prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
|
|
754
744
|
)
|
|
755
745
|
predict_start_time = time.time()
|
|
756
746
|
oof_predictions = self.predict(past_data, known_covariates=known_covariates, **predict_kwargs)
|
|
757
|
-
self.
|
|
747
|
+
self.cache_oof_predictions(oof_predictions)
|
|
758
748
|
if store_predict_time:
|
|
759
749
|
self.predict_time = time.time() - predict_start_time
|
|
760
750
|
if store_val_score:
|
|
761
751
|
self.val_score = self._score_with_predictions(val_data, oof_predictions)
|
|
762
752
|
|
|
763
|
-
def
|
|
764
|
-
"""Update kwargs passed to model_trial depending on the model configuration.
|
|
765
|
-
|
|
766
|
-
These kwargs need to be updated, for example, by MultiWindowBacktestingModel.
|
|
767
|
-
"""
|
|
768
|
-
return train_fn_kwargs
|
|
769
|
-
|
|
770
|
-
def _is_gpu_available(self) -> bool:
|
|
771
|
-
return False
|
|
772
|
-
|
|
773
|
-
@staticmethod
|
|
774
|
-
def _get_system_resources() -> Dict[str, Any]:
|
|
775
|
-
resource_manager = get_resource_manager()
|
|
776
|
-
system_num_cpus = resource_manager.get_cpu_count()
|
|
777
|
-
system_num_gpus = resource_manager.get_gpu_count()
|
|
778
|
-
return {
|
|
779
|
-
"num_cpus": system_num_cpus,
|
|
780
|
-
"num_gpus": system_num_gpus,
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
def hyperparameter_tune(
|
|
784
|
-
self,
|
|
785
|
-
train_data: TimeSeriesDataFrame,
|
|
786
|
-
val_data: Optional[TimeSeriesDataFrame],
|
|
787
|
-
val_splitter: Any = None,
|
|
788
|
-
default_num_trials: Optional[int] = 1,
|
|
789
|
-
refit_every_n_windows: Optional[int] = 1,
|
|
790
|
-
hyperparameter_tune_kwargs: Union[str, dict] = "auto",
|
|
791
|
-
time_limit: Optional[float] = None,
|
|
792
|
-
) -> Tuple[Dict[str, Any], Any]:
|
|
793
|
-
hpo_executor = self._get_default_hpo_executor()
|
|
794
|
-
hpo_executor.initialize(
|
|
795
|
-
hyperparameter_tune_kwargs, default_num_trials=default_num_trials, time_limit=time_limit
|
|
796
|
-
)
|
|
797
|
-
|
|
798
|
-
self.initialize()
|
|
799
|
-
|
|
800
|
-
# we use k_fold=1 to circumvent autogluon.core logic to manage resources during parallelization
|
|
801
|
-
# of different folds
|
|
802
|
-
# FIXME: we pass in self which currently does not inherit from AbstractModel
|
|
803
|
-
hpo_executor.register_resources(self, k_fold=1, **self._get_system_resources()) # type: ignore
|
|
804
|
-
|
|
805
|
-
time_start = time.time()
|
|
806
|
-
logger.debug(f"\tStarting hyperparameter tuning for {self.name}")
|
|
807
|
-
search_space = self._get_search_space()
|
|
808
|
-
|
|
809
|
-
try:
|
|
810
|
-
hpo_executor.validate_search_space(search_space, self.name)
|
|
811
|
-
except EmptySearchSpace:
|
|
812
|
-
return skip_hpo(self, train_data, val_data, time_limit=hpo_executor.time_limit)
|
|
813
|
-
|
|
814
|
-
train_path, val_path = self._save_with_data(train_data, val_data)
|
|
815
|
-
|
|
816
|
-
train_fn_kwargs = self._get_hpo_train_fn_kwargs(
|
|
817
|
-
model_cls=self.__class__,
|
|
818
|
-
init_params=self.get_params(),
|
|
819
|
-
time_start=time_start,
|
|
820
|
-
time_limit=hpo_executor.time_limit,
|
|
821
|
-
fit_kwargs=dict(
|
|
822
|
-
val_splitter=val_splitter,
|
|
823
|
-
refit_every_n_windows=refit_every_n_windows,
|
|
824
|
-
),
|
|
825
|
-
train_path=train_path,
|
|
826
|
-
val_path=val_path,
|
|
827
|
-
hpo_executor=hpo_executor,
|
|
828
|
-
)
|
|
829
|
-
|
|
830
|
-
minimum_resources = self.get_minimum_resources(is_gpu_available=self._is_gpu_available())
|
|
831
|
-
hpo_context = disable_stdout if isinstance(hpo_executor, RayHpoExecutor) else nullcontext
|
|
832
|
-
|
|
833
|
-
minimum_cpu_per_trial = minimum_resources.get("num_cpus", 1)
|
|
834
|
-
if not isinstance(minimum_cpu_per_trial, int):
|
|
835
|
-
logger.warning(
|
|
836
|
-
f"Minimum number of CPUs per trial for {self.name} is not an integer. "
|
|
837
|
-
f"Setting to 1. Minimum number of CPUs per trial: {minimum_cpu_per_trial}"
|
|
838
|
-
)
|
|
839
|
-
minimum_cpu_per_trial = 1
|
|
840
|
-
|
|
841
|
-
with hpo_context(), warning_filter(): # prevent Ray from outputting its results to stdout with print
|
|
842
|
-
hpo_executor.execute(
|
|
843
|
-
model_trial=model_trial,
|
|
844
|
-
train_fn_kwargs=train_fn_kwargs,
|
|
845
|
-
directory=self.path,
|
|
846
|
-
minimum_cpu_per_trial=minimum_cpu_per_trial,
|
|
847
|
-
minimum_gpu_per_trial=minimum_resources.get("num_gpus", 0),
|
|
848
|
-
model_estimate_memory_usage=None,
|
|
849
|
-
adapter_type="timeseries",
|
|
850
|
-
)
|
|
851
|
-
|
|
852
|
-
assert self.path_root is not None
|
|
853
|
-
hpo_models, analysis = hpo_executor.get_hpo_results(
|
|
854
|
-
model_name=self.name,
|
|
855
|
-
model_path_root=self.path_root,
|
|
856
|
-
time_start=time_start,
|
|
857
|
-
)
|
|
858
|
-
|
|
859
|
-
return hpo_models, analysis
|
|
860
|
-
|
|
861
|
-
@property
|
|
862
|
-
def is_ensemble(self) -> bool:
|
|
863
|
-
"""Return True if the model is an ensemble model or a container of multiple models."""
|
|
864
|
-
return self._get_model_base() is self
|
|
865
|
-
|
|
866
|
-
def _get_default_hpo_executor(self) -> HpoExecutor:
|
|
867
|
-
backend = (
|
|
868
|
-
self._get_model_base()._get_hpo_backend()
|
|
869
|
-
) # If ensemble, will use the base model to determine backend
|
|
870
|
-
if backend == RAY_BACKEND:
|
|
871
|
-
try:
|
|
872
|
-
try_import_ray()
|
|
873
|
-
except Exception as e:
|
|
874
|
-
warning_msg = f"Will use custom hpo logic because ray import failed. Reason: {str(e)}"
|
|
875
|
-
dup_filter.attach_filter_targets(warning_msg)
|
|
876
|
-
logger.warning(warning_msg)
|
|
877
|
-
backend = CUSTOM_BACKEND
|
|
878
|
-
hpo_executor = HpoExecutorFactory.get_hpo_executor(backend)() # type: ignore
|
|
879
|
-
return hpo_executor
|
|
880
|
-
|
|
881
|
-
def _get_model_base(self) -> AbstractTimeSeriesModel:
|
|
882
|
-
return self
|
|
883
|
-
|
|
884
|
-
def _get_hpo_backend(self) -> str:
|
|
885
|
-
"""Choose which backend("ray" or "custom") to use for hpo"""
|
|
886
|
-
if DistributedContext.is_distributed_mode():
|
|
887
|
-
return RAY_BACKEND
|
|
888
|
-
return CUSTOM_BACKEND
|
|
889
|
-
|
|
890
|
-
def _get_search_space(self):
|
|
891
|
-
"""Sets up default search space for HPO. Each hyperparameter which user did not specify is converted from
|
|
892
|
-
default fixed value to default search space.
|
|
893
|
-
"""
|
|
894
|
-
params = self.params.copy()
|
|
895
|
-
return params
|
|
896
|
-
|
|
897
|
-
def _save_with_data(self, train_data, val_data):
|
|
898
|
-
self.set_contexts(os.path.abspath(self.path))
|
|
899
|
-
dataset_train_filename = "dataset_train.pkl"
|
|
900
|
-
train_path = os.path.join(self.path, dataset_train_filename)
|
|
901
|
-
save_pkl.save(path=train_path, object=train_data)
|
|
902
|
-
|
|
903
|
-
dataset_val_filename = "dataset_val.pkl"
|
|
904
|
-
val_path = os.path.join(self.path, dataset_val_filename)
|
|
905
|
-
save_pkl.save(path=val_path, object=val_data)
|
|
906
|
-
return train_path, val_path
|
|
907
|
-
|
|
908
|
-
def preprocess( # type: ignore
|
|
753
|
+
def preprocess(
|
|
909
754
|
self,
|
|
910
755
|
data: TimeSeriesDataFrame,
|
|
911
|
-
known_covariates:
|
|
756
|
+
known_covariates: TimeSeriesDataFrame | None = None,
|
|
912
757
|
is_train: bool = False,
|
|
913
758
|
**kwargs,
|
|
914
|
-
) ->
|
|
759
|
+
) -> tuple[TimeSeriesDataFrame, TimeSeriesDataFrame | None]:
|
|
915
760
|
"""Method that implements model-specific preprocessing logic."""
|
|
916
|
-
# TODO: move to new AbstractModel
|
|
917
761
|
return data, known_covariates
|
|
918
|
-
|
|
919
|
-
def persist(self) -> Self:
|
|
920
|
-
"""Ask the model to persist its assets in memory, i.e., to predict with low latency. In practice
|
|
921
|
-
this is used for pretrained models that have to lazy-load model parameters to device memory at
|
|
922
|
-
prediction time.
|
|
923
|
-
"""
|
|
924
|
-
return self
|
|
925
|
-
|
|
926
|
-
def convert_to_refit_full_via_copy(self) -> Self:
|
|
927
|
-
# save the model as a new model on disk
|
|
928
|
-
previous_name = self.name
|
|
929
|
-
self.rename(self.name + REFIT_FULL_SUFFIX)
|
|
930
|
-
refit_model_path = self.path
|
|
931
|
-
self.save(path=self.path, verbose=False)
|
|
932
|
-
|
|
933
|
-
self.rename(previous_name)
|
|
934
|
-
|
|
935
|
-
refit_model = self.load(path=refit_model_path, verbose=False)
|
|
936
|
-
refit_model.val_score = None
|
|
937
|
-
refit_model.predict_time = None
|
|
938
|
-
|
|
939
|
-
return refit_model
|
|
940
|
-
|
|
941
|
-
def convert_to_refit_full_template(self):
|
|
942
|
-
"""
|
|
943
|
-
After calling this function, returned model should be able to be fit without X_val, y_val using the iterations trained by the original model.
|
|
944
|
-
|
|
945
|
-
Increase max_memory_usage_ratio by 25% to reduce the chance that the refit model will trigger NotEnoughMemoryError and skip training.
|
|
946
|
-
This can happen without the 25% increase since the refit model generally will use more training data and thus require more memory.
|
|
947
|
-
"""
|
|
948
|
-
params = copy.deepcopy(self.get_params())
|
|
949
|
-
|
|
950
|
-
if "hyperparameters" not in params:
|
|
951
|
-
params["hyperparameters"] = dict()
|
|
952
|
-
|
|
953
|
-
if AG_ARGS_FIT not in params["hyperparameters"]:
|
|
954
|
-
params["hyperparameters"][AG_ARGS_FIT] = dict()
|
|
955
|
-
|
|
956
|
-
# TODO: remove
|
|
957
|
-
# Increase memory limit by 25% to avoid memory restrictions during fit
|
|
958
|
-
params["hyperparameters"][AG_ARGS_FIT]["max_memory_usage_ratio"] = (
|
|
959
|
-
params["hyperparameters"][AG_ARGS_FIT].get("max_memory_usage_ratio", 1.0) * 1.25
|
|
960
|
-
)
|
|
961
|
-
|
|
962
|
-
params["hyperparameters"].update(self.params_trained)
|
|
963
|
-
params["name"] = params["name"] + REFIT_FULL_SUFFIX
|
|
964
|
-
template = self.__class__(**params)
|
|
965
|
-
|
|
966
|
-
return template
|
|
967
|
-
|
|
968
|
-
def get_user_params(self) -> dict:
|
|
969
|
-
"""Used to access user-specified parameters for the model before initialization."""
|
|
970
|
-
if self._user_params is None:
|
|
971
|
-
return {}
|
|
972
|
-
else:
|
|
973
|
-
return self._user_params.copy()
|
|
974
|
-
|
|
975
|
-
def _more_tags(self) -> dict:
|
|
976
|
-
"""Encode model properties using tags, similar to sklearn & autogluon.tabular.
|
|
977
|
-
|
|
978
|
-
For more details, see `autogluon.core.models.abstract.AbstractModel._get_tags()` and https://scikit-learn.org/stable/_sources/developers/develop.rst.txt.
|
|
979
|
-
|
|
980
|
-
List of currently supported tags:
|
|
981
|
-
- allow_nan: Can the model handle data with missing values represented by np.nan?
|
|
982
|
-
- can_refit_full: Does it make sense to retrain the model without validation data?
|
|
983
|
-
See `autogluon.core.models.abstract._tags._DEFAULT_TAGS` for more details.
|
|
984
|
-
- can_use_train_data: Can the model use train_data if it's provided to model.fit()?
|
|
985
|
-
- can_use_val_data: Can the model use val_data if it's provided to model.fit()?
|
|
986
|
-
"""
|
|
987
|
-
return {
|
|
988
|
-
"allow_nan": False,
|
|
989
|
-
"can_refit_full": False,
|
|
990
|
-
"can_use_train_data": True,
|
|
991
|
-
"can_use_val_data": False,
|
|
992
|
-
}
|