autogluon.timeseries 1.0.1b20240304__py3-none-any.whl → 1.4.1b20251210__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 +84 -0
- autogluon/timeseries/dataset/ts_dataframe.py +339 -186
- autogluon/timeseries/learner.py +192 -60
- autogluon/timeseries/metrics/__init__.py +55 -11
- autogluon/timeseries/metrics/abstract.py +96 -25
- autogluon/timeseries/metrics/point.py +186 -39
- autogluon/timeseries/metrics/quantile.py +47 -20
- autogluon/timeseries/metrics/utils.py +6 -6
- autogluon/timeseries/models/__init__.py +13 -7
- autogluon/timeseries/models/abstract/__init__.py +2 -2
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +533 -273
- autogluon/timeseries/models/abstract/model_trial.py +10 -10
- autogluon/timeseries/models/abstract/tunable.py +189 -0
- autogluon/timeseries/models/autogluon_tabular/__init__.py +2 -0
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +369 -215
- autogluon/timeseries/models/autogluon_tabular/per_step.py +513 -0
- autogluon/timeseries/models/autogluon_tabular/transforms.py +67 -0
- autogluon/timeseries/models/autogluon_tabular/utils.py +3 -51
- autogluon/timeseries/models/chronos/__init__.py +4 -0
- autogluon/timeseries/models/chronos/chronos2.py +361 -0
- autogluon/timeseries/models/chronos/model.py +738 -0
- autogluon/timeseries/models/chronos/utils.py +369 -0
- autogluon/timeseries/models/ensemble/__init__.py +35 -2
- autogluon/timeseries/models/ensemble/{abstract_timeseries_ensemble.py → abstract.py} +50 -26
- autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
- autogluon/timeseries/models/ensemble/array_based/abstract.py +236 -0
- autogluon/timeseries/models/ensemble/array_based/models.py +73 -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 +167 -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 +162 -0
- autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +40 -0
- autogluon/timeseries/models/ensemble/weighted/basic.py +78 -0
- autogluon/timeseries/models/ensemble/weighted/greedy.py +57 -0
- autogluon/timeseries/models/gluonts/__init__.py +3 -1
- autogluon/timeseries/models/gluonts/abstract.py +583 -0
- autogluon/timeseries/models/gluonts/dataset.py +109 -0
- autogluon/timeseries/models/gluonts/{torch/models.py → models.py} +185 -44
- autogluon/timeseries/models/local/__init__.py +1 -10
- autogluon/timeseries/models/local/abstract_local_model.py +150 -97
- autogluon/timeseries/models/local/naive.py +31 -23
- autogluon/timeseries/models/local/npts.py +6 -2
- autogluon/timeseries/models/local/statsforecast.py +99 -112
- autogluon/timeseries/models/multi_window/multi_window_model.py +99 -40
- 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 +118 -0
- autogluon/timeseries/models/toto/model.py +236 -0
- autogluon/timeseries/predictor.py +826 -305
- autogluon/timeseries/regressor.py +253 -0
- autogluon/timeseries/splitter.py +10 -31
- autogluon/timeseries/trainer/__init__.py +2 -3
- autogluon/timeseries/trainer/ensemble_composer.py +439 -0
- autogluon/timeseries/trainer/model_set_builder.py +256 -0
- autogluon/timeseries/trainer/prediction_cache.py +149 -0
- autogluon/timeseries/trainer/trainer.py +1298 -0
- autogluon/timeseries/trainer/utils.py +17 -0
- autogluon/timeseries/transforms/__init__.py +2 -0
- autogluon/timeseries/transforms/covariate_scaler.py +164 -0
- autogluon/timeseries/transforms/target_scaler.py +149 -0
- autogluon/timeseries/utils/constants.py +10 -0
- autogluon/timeseries/utils/datetime/base.py +38 -20
- autogluon/timeseries/utils/datetime/lags.py +18 -16
- autogluon/timeseries/utils/datetime/seasonality.py +14 -14
- autogluon/timeseries/utils/datetime/time_features.py +17 -14
- autogluon/timeseries/utils/features.py +317 -53
- autogluon/timeseries/utils/forecast.py +31 -17
- autogluon/timeseries/utils/timer.py +173 -0
- autogluon/timeseries/utils/warning_filters.py +44 -6
- autogluon/timeseries/version.py +2 -1
- autogluon.timeseries-1.4.1b20251210-py3.11-nspkg.pth +1 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info}/METADATA +71 -47
- autogluon_timeseries-1.4.1b20251210.dist-info/RECORD +103 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info}/WHEEL +1 -1
- autogluon/timeseries/configs/presets_configs.py +0 -11
- autogluon/timeseries/evaluator.py +0 -6
- autogluon/timeseries/models/ensemble/greedy_ensemble.py +0 -170
- autogluon/timeseries/models/gluonts/abstract_gluonts.py +0 -550
- autogluon/timeseries/models/gluonts/torch/__init__.py +0 -0
- autogluon/timeseries/models/presets.py +0 -325
- autogluon/timeseries/trainer/abstract_trainer.py +0 -1144
- autogluon/timeseries/trainer/auto_trainer.py +0 -74
- autogluon.timeseries-1.0.1b20240304-py3.8-nspkg.pth +0 -1
- autogluon.timeseries-1.0.1b20240304.dist-info/RECORD +0 -58
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info/licenses}/LICENSE +0 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info/licenses}/NOTICE +0 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.0.1b20240304.dist-info → autogluon_timeseries-1.4.1b20251210.dist-info}/zip-safe +0 -0
|
@@ -5,46 +5,33 @@ import os
|
|
|
5
5
|
import pprint
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Literal, Type, cast, overload
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import pandas as pd
|
|
12
12
|
|
|
13
|
-
from autogluon.common.utils.
|
|
14
|
-
|
|
13
|
+
from autogluon.common.utils.log_utils import (
|
|
14
|
+
add_log_to_file,
|
|
15
|
+
set_logger_verbosity,
|
|
16
|
+
warn_if_mlflow_autologging_is_enabled,
|
|
17
|
+
)
|
|
15
18
|
from autogluon.common.utils.system_info import get_ag_system_info
|
|
16
19
|
from autogluon.common.utils.utils import check_saved_predictor_version, setup_outputdir
|
|
17
20
|
from autogluon.core.utils.decorators import apply_presets
|
|
18
21
|
from autogluon.core.utils.loaders import load_pkl, load_str
|
|
19
22
|
from autogluon.core.utils.savers import save_pkl, save_str
|
|
20
23
|
from autogluon.timeseries import __version__ as current_ag_version
|
|
21
|
-
from autogluon.timeseries.configs import
|
|
22
|
-
from autogluon.timeseries.dataset
|
|
23
|
-
from autogluon.timeseries.learner import
|
|
24
|
+
from autogluon.timeseries.configs import get_predictor_presets
|
|
25
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
26
|
+
from autogluon.timeseries.learner import TimeSeriesLearner
|
|
24
27
|
from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_metric
|
|
25
|
-
from autogluon.timeseries.
|
|
26
|
-
from autogluon.timeseries.
|
|
28
|
+
from autogluon.timeseries.trainer import TimeSeriesTrainer
|
|
29
|
+
from autogluon.timeseries.utils.forecast import make_future_data_frame
|
|
27
30
|
|
|
28
31
|
logger = logging.getLogger("autogluon.timeseries")
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
class
|
|
32
|
-
"""Contains deprecated methods from TimeSeriesPredictor that shouldn't show up in API documentation."""
|
|
33
|
-
|
|
34
|
-
@Deprecated(min_version_to_warn="0.8.3", min_version_to_error="1.2", version_to_remove="1.2", new="evaluate")
|
|
35
|
-
def score(self, *args, **kwargs):
|
|
36
|
-
return self.evaluate(*args, **kwargs)
|
|
37
|
-
|
|
38
|
-
@Deprecated(min_version_to_warn="0.8.3", min_version_to_error="1.2", version_to_remove="1.2", new="model_best")
|
|
39
|
-
def get_model_best(self) -> str:
|
|
40
|
-
return self.model_best
|
|
41
|
-
|
|
42
|
-
@Deprecated(min_version_to_warn="0.8.3", min_version_to_error="1.2", version_to_remove="1.2", new="model_names")
|
|
43
|
-
def get_model_names(self) -> str:
|
|
44
|
-
return self.model_names()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
34
|
+
class TimeSeriesPredictor:
|
|
48
35
|
"""AutoGluon ``TimeSeriesPredictor`` predicts future values of multiple related time series.
|
|
49
36
|
|
|
50
37
|
``TimeSeriesPredictor`` provides probabilistic (quantile) multi-step-ahead forecasts for univariate time series.
|
|
@@ -69,7 +56,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
69
56
|
models that predict up to 3 days into the future from the most recent observation.
|
|
70
57
|
freq : str, optional
|
|
71
58
|
Frequency of the time series data (see `pandas documentation <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`_
|
|
72
|
-
for available frequencies). For example, ``"D"`` for daily data or ``"
|
|
59
|
+
for available frequencies). For example, ``"D"`` for daily data or ``"h"`` for hourly data.
|
|
73
60
|
|
|
74
61
|
By default, the predictor will attempt to automatically infer the frequency from the data. This argument should
|
|
75
62
|
only be set in two cases:
|
|
@@ -79,7 +66,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
79
66
|
|
|
80
67
|
If ``freq`` is provided when creating the predictor, all data passed to the predictor will be automatically
|
|
81
68
|
resampled at this frequency.
|
|
82
|
-
eval_metric :
|
|
69
|
+
eval_metric : str | TimeSeriesScorer, default = "WQL"
|
|
83
70
|
Metric by which predictions will be ultimately evaluated on future test data. AutoGluon tunes hyperparameters
|
|
84
71
|
in order to improve this metric on validation data, and ranks models (on validation data) according to this
|
|
85
72
|
metric.
|
|
@@ -105,23 +92,29 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
105
92
|
eval_metric_seasonal_period : int, optional
|
|
106
93
|
Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
|
|
107
94
|
``None``, in which case the seasonal period is computed based on the data frequency.
|
|
108
|
-
|
|
95
|
+
horizon_weight : list[float], optional
|
|
96
|
+
Weight assigned to each time step in the forecast horizon when computing the ``eval_metric``. If provided, this
|
|
97
|
+
must be a list with ``prediction_length`` non-negative values, where at least some values are greater than zero.
|
|
98
|
+
AutoGluon will automatically normalize the weights so that they sum up to ``prediction_length``. By default, all
|
|
99
|
+
time steps in the forecast horizon have the same weight, which is equivalent to setting ``horizon_weight = [1] * prediction_length``.
|
|
100
|
+
|
|
101
|
+
This parameter only affects model selection and ensemble construction; it has no effect on the loss function of
|
|
102
|
+
the individual forecasting models.
|
|
103
|
+
known_covariates_names: list[str], optional
|
|
109
104
|
Names of the covariates that are known in advance for all time steps in the forecast horizon. These are also
|
|
110
105
|
known as dynamic features, exogenous variables, additional regressors or related time series. Examples of such
|
|
111
106
|
covariates include holidays, promotions or weather forecasts.
|
|
112
107
|
|
|
113
|
-
Currently, only numeric (float of integer dtype) are supported.
|
|
114
|
-
|
|
115
108
|
If ``known_covariates_names`` are provided, then:
|
|
116
109
|
|
|
117
|
-
- :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`, :meth:`~autogluon.timeseries.TimeSeriesPredictor.evaluate`, and :meth:`~autogluon.timeseries.TimeSeriesPredictor.leaderboard` will expect a
|
|
110
|
+
- :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`, :meth:`~autogluon.timeseries.TimeSeriesPredictor.evaluate`, and :meth:`~autogluon.timeseries.TimeSeriesPredictor.leaderboard` will expect a dataframe with columns listed in ``known_covariates_names`` (in addition to the ``target`` column).
|
|
118
111
|
- :meth:`~autogluon.timeseries.TimeSeriesPredictor.predict` will expect an additional keyword argument ``known_covariates`` containing the future values of the known covariates in ``TimeSeriesDataFrame`` format.
|
|
119
112
|
|
|
120
|
-
quantile_levels :
|
|
113
|
+
quantile_levels : list[float], optional
|
|
121
114
|
List of increasing decimals that specifies which quantiles should be estimated when making distributional
|
|
122
115
|
forecasts. Defaults to ``[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]``.
|
|
123
116
|
path : str or pathlib.Path, optional
|
|
124
|
-
Path to the directory where models and intermediate outputs will be saved. Defaults to a timestamped folder
|
|
117
|
+
Path to the local directory where models and intermediate outputs will be saved. Defaults to a timestamped folder
|
|
125
118
|
``AutogluonModels/ag-[TIMESTAMP]`` that will be created in the working directory.
|
|
126
119
|
verbosity : int, default = 2
|
|
127
120
|
Verbosity levels range from 0 to 4 and control how much information is printed to stdout. Higher levels
|
|
@@ -131,10 +124,10 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
131
124
|
debug messages from AutoGluon and all logging in dependencies (GluonTS, PyTorch Lightning, AutoGluon-Tabular, etc.)
|
|
132
125
|
log_to_file: bool, default = True
|
|
133
126
|
Whether to save the logs into a file for later reference
|
|
134
|
-
log_file_path:
|
|
127
|
+
log_file_path: str | Path, default = "auto"
|
|
135
128
|
File path to save the logs.
|
|
136
|
-
If auto, logs will be saved under
|
|
137
|
-
Will be ignored if
|
|
129
|
+
If auto, logs will be saved under ``predictor_path/logs/predictor_log.txt``.
|
|
130
|
+
Will be ignored if ``log_to_file`` is set to False
|
|
138
131
|
cache_predictions : bool, default = True
|
|
139
132
|
If True, the predictor will cache and reuse the predictions made by individual models whenever
|
|
140
133
|
:meth:`~autogluon.timeseries.TimeSeriesPredictor.predict`, :meth:`~autogluon.timeseries.TimeSeriesPredictor.leaderboard`,
|
|
@@ -145,32 +138,37 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
145
138
|
Alias for :attr:`target`.
|
|
146
139
|
"""
|
|
147
140
|
|
|
141
|
+
_learner_type = TimeSeriesLearner
|
|
148
142
|
predictor_file_name = "predictor.pkl"
|
|
149
|
-
_predictor_version_file_name = "
|
|
143
|
+
_predictor_version_file_name = "version.txt"
|
|
150
144
|
_predictor_log_file_name = "predictor_log.txt"
|
|
151
145
|
|
|
152
146
|
def __init__(
|
|
153
147
|
self,
|
|
154
|
-
target:
|
|
155
|
-
known_covariates_names:
|
|
148
|
+
target: str | None = None,
|
|
149
|
+
known_covariates_names: list[str] | None = None,
|
|
156
150
|
prediction_length: int = 1,
|
|
157
|
-
freq: str = None,
|
|
158
|
-
eval_metric:
|
|
159
|
-
eval_metric_seasonal_period:
|
|
160
|
-
|
|
151
|
+
freq: str | None = None,
|
|
152
|
+
eval_metric: str | TimeSeriesScorer | None = None,
|
|
153
|
+
eval_metric_seasonal_period: int | None = None,
|
|
154
|
+
horizon_weight: list[float] | None = None,
|
|
155
|
+
path: str | Path | None = None,
|
|
161
156
|
verbosity: int = 2,
|
|
162
157
|
log_to_file: bool = True,
|
|
163
|
-
log_file_path:
|
|
164
|
-
quantile_levels:
|
|
158
|
+
log_file_path: str | Path = "auto",
|
|
159
|
+
quantile_levels: list[float] | None = None,
|
|
165
160
|
cache_predictions: bool = True,
|
|
166
|
-
|
|
167
|
-
learner_kwargs: Optional[dict] = None,
|
|
168
|
-
label: Optional[str] = None,
|
|
161
|
+
label: str | None = None,
|
|
169
162
|
**kwargs,
|
|
170
163
|
):
|
|
171
164
|
self.verbosity = verbosity
|
|
172
165
|
set_logger_verbosity(self.verbosity, logger=logger)
|
|
173
166
|
self.path = setup_outputdir(path)
|
|
167
|
+
if self.path.lower().startswith("s3://"):
|
|
168
|
+
logger.warning(
|
|
169
|
+
"Warning: S3 paths are not supported for the `path` argument in TimeSeriesPredictor. "
|
|
170
|
+
"Use a local path and upload the trained predictor to S3 manually if needed"
|
|
171
|
+
)
|
|
174
172
|
self._setup_log_to_file(log_to_file=log_to_file, log_file_path=log_file_path)
|
|
175
173
|
|
|
176
174
|
self.cache_predictions = cache_predictions
|
|
@@ -190,59 +188,56 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
190
188
|
raise ValueError(f"Target column {self.target} cannot be one of the known covariates.")
|
|
191
189
|
self.known_covariates_names = list(known_covariates_names)
|
|
192
190
|
|
|
193
|
-
self.prediction_length = prediction_length
|
|
191
|
+
self.prediction_length = int(prediction_length)
|
|
194
192
|
# For each validation fold, all time series in training set must have length >= _min_train_length
|
|
195
193
|
self._min_train_length = max(self.prediction_length + 1, 5)
|
|
196
194
|
self.freq = freq
|
|
197
195
|
if self.freq is not None:
|
|
198
|
-
# Standardize frequency string (e.g., "
|
|
199
|
-
|
|
196
|
+
# Standardize frequency string (e.g., "T" -> "min", "Y" -> "YE")
|
|
197
|
+
offset = pd.tseries.frequencies.to_offset(self.freq)
|
|
198
|
+
assert offset is not None
|
|
199
|
+
std_freq = offset.freqstr
|
|
200
200
|
if std_freq != str(self.freq):
|
|
201
201
|
logger.info(f"Frequency '{self.freq}' stored as '{std_freq}'")
|
|
202
202
|
self.freq = std_freq
|
|
203
|
-
self.eval_metric = check_get_evaluation_metric(
|
|
204
|
-
|
|
203
|
+
self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(
|
|
204
|
+
eval_metric,
|
|
205
|
+
prediction_length=prediction_length,
|
|
206
|
+
seasonal_period=eval_metric_seasonal_period,
|
|
207
|
+
horizon_weight=horizon_weight,
|
|
208
|
+
)
|
|
205
209
|
if quantile_levels is None:
|
|
206
210
|
quantile_levels = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
|
|
207
211
|
self.quantile_levels = sorted(quantile_levels)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
target=self.target,
|
|
218
|
-
known_covariates_names=self.known_covariates_names,
|
|
219
|
-
prediction_length=self.prediction_length,
|
|
220
|
-
quantile_levels=self.quantile_levels,
|
|
221
|
-
cache_predictions=self.cache_predictions,
|
|
222
|
-
)
|
|
212
|
+
self._learner: TimeSeriesLearner = self._learner_type(
|
|
213
|
+
path_context=self.path,
|
|
214
|
+
eval_metric=self.eval_metric,
|
|
215
|
+
target=self.target,
|
|
216
|
+
known_covariates_names=self.known_covariates_names,
|
|
217
|
+
prediction_length=self.prediction_length,
|
|
218
|
+
quantile_levels=self.quantile_levels,
|
|
219
|
+
cache_predictions=self.cache_predictions,
|
|
220
|
+
ensemble_model_type=kwargs.pop("ensemble_model_type", None),
|
|
223
221
|
)
|
|
224
|
-
# Using `TimeSeriesLearner` as default argument breaks doc generation with Sphnix
|
|
225
|
-
if learner_type is None:
|
|
226
|
-
learner_type = TimeSeriesLearner
|
|
227
|
-
self._learner: AbstractLearner = learner_type(**learner_kwargs)
|
|
228
|
-
self._learner_type = type(self._learner)
|
|
229
222
|
|
|
230
|
-
if "ignore_time_index" in kwargs:
|
|
231
|
-
raise TypeError(
|
|
232
|
-
"`ignore_time_index` argument to TimeSeriesPredictor.__init__() has been deprecated.\n"
|
|
233
|
-
"If your data has irregular timestamps, please either 1) specify the desired regular frequency when "
|
|
234
|
-
"creating the predictor as `TimeSeriesPredictor(freq=...)` or 2) manually convert timestamps to "
|
|
235
|
-
"regular frequency with `data.convert_frequency(freq=...)`."
|
|
236
|
-
)
|
|
237
223
|
if len(kwargs) > 0:
|
|
238
224
|
for key in kwargs:
|
|
239
225
|
raise TypeError(f"TimeSeriesPredictor.__init__() got an unexpected keyword argument '{key}'")
|
|
240
226
|
|
|
241
227
|
@property
|
|
242
|
-
def _trainer(self) ->
|
|
228
|
+
def _trainer(self) -> TimeSeriesTrainer:
|
|
243
229
|
return self._learner.load_trainer() # noqa
|
|
244
230
|
|
|
245
|
-
|
|
231
|
+
@property
|
|
232
|
+
def is_fit(self) -> bool:
|
|
233
|
+
return self._learner.is_fit
|
|
234
|
+
|
|
235
|
+
def _assert_is_fit(self, method_name: str) -> None:
|
|
236
|
+
"""Check if predictor is fit and raise AssertionError with informative message if not."""
|
|
237
|
+
if not self.is_fit:
|
|
238
|
+
raise AssertionError(f"Predictor is not fit. Call `.fit` before calling `.{method_name}`. ")
|
|
239
|
+
|
|
240
|
+
def _setup_log_to_file(self, log_to_file: bool, log_file_path: str | Path) -> None:
|
|
246
241
|
if log_to_file:
|
|
247
242
|
if log_file_path == "auto":
|
|
248
243
|
log_file_path = os.path.join(self.path, "logs", self._predictor_log_file_name)
|
|
@@ -252,14 +247,14 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
252
247
|
|
|
253
248
|
def _to_data_frame(
|
|
254
249
|
self,
|
|
255
|
-
data:
|
|
250
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
256
251
|
name: str = "data",
|
|
257
|
-
) ->
|
|
252
|
+
) -> TimeSeriesDataFrame:
|
|
258
253
|
if isinstance(data, TimeSeriesDataFrame):
|
|
259
254
|
return data
|
|
260
|
-
elif isinstance(data, (pd.DataFrame, str)):
|
|
255
|
+
elif isinstance(data, (pd.DataFrame, Path, str)):
|
|
261
256
|
try:
|
|
262
|
-
data = TimeSeriesDataFrame(data)
|
|
257
|
+
data = TimeSeriesDataFrame(data) # type: ignore
|
|
263
258
|
except:
|
|
264
259
|
raise ValueError(
|
|
265
260
|
f"Provided {name} of type {type(data)} cannot be automatically converted to a TimeSeriesDataFrame."
|
|
@@ -267,23 +262,23 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
267
262
|
return data
|
|
268
263
|
else:
|
|
269
264
|
raise TypeError(
|
|
270
|
-
f"{name} must be a TimeSeriesDataFrame
|
|
265
|
+
f"{name} must be a TimeSeriesDataFrame, pandas.DataFrame, pathlib.Path or string (path to data) "
|
|
271
266
|
f"but received an object of type {type(data)}."
|
|
272
267
|
)
|
|
273
268
|
|
|
274
269
|
def _check_and_prepare_data_frame(
|
|
275
270
|
self,
|
|
276
|
-
data:
|
|
271
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
277
272
|
name: str = "data",
|
|
278
273
|
) -> TimeSeriesDataFrame:
|
|
279
|
-
"""Ensure that TimeSeriesDataFrame has a sorted index
|
|
274
|
+
"""Ensure that TimeSeriesDataFrame has a sorted index and a valid frequency.
|
|
280
275
|
|
|
281
276
|
If self.freq is None, then self.freq of the predictor will be set to the frequency of the data.
|
|
282
277
|
|
|
283
278
|
Parameters
|
|
284
279
|
----------
|
|
285
|
-
data :
|
|
286
|
-
Data as a
|
|
280
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
281
|
+
Data as a dataframe or path to file storing the data.
|
|
287
282
|
name : str
|
|
288
283
|
Name of the data that will be used in log messages (e.g., 'train_data', 'tuning_data', or 'data').
|
|
289
284
|
|
|
@@ -292,60 +287,77 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
292
287
|
df : TimeSeriesDataFrame
|
|
293
288
|
Preprocessed data in TimeSeriesDataFrame format.
|
|
294
289
|
"""
|
|
295
|
-
df = self._to_data_frame(data, name=name)
|
|
296
|
-
|
|
290
|
+
df: TimeSeriesDataFrame = self._to_data_frame(data, name=name)
|
|
291
|
+
if not pd.api.types.is_numeric_dtype(df[self.target]):
|
|
292
|
+
raise ValueError(f"Target column {name}['{self.target}'] has a non-numeric dtype {df[self.target].dtype}")
|
|
293
|
+
# Assign makes a copy, so future operations can be performed in-place
|
|
294
|
+
df = df.assign(**{self.target: df[self.target].astype("float64")})
|
|
295
|
+
df.replace(to_replace=[float("-inf"), float("inf")], value=float("nan"), inplace=True)
|
|
296
|
+
|
|
297
297
|
# MultiIndex.is_monotonic_increasing checks if index is sorted by ["item_id", "timestamp"]
|
|
298
298
|
if not df.index.is_monotonic_increasing:
|
|
299
299
|
df = df.sort_index()
|
|
300
|
-
df._cached_freq = None # in case frequency was incorrectly cached as IRREGULAR_TIME_INDEX_FREQSTR
|
|
301
300
|
|
|
302
301
|
# Ensure that data has a regular frequency that matches the predictor frequency
|
|
303
302
|
if self.freq is None:
|
|
304
|
-
|
|
303
|
+
try:
|
|
304
|
+
# Use all items for inferring the frequency
|
|
305
|
+
data_freq = df.infer_frequency(num_items=None, raise_if_irregular=True)
|
|
306
|
+
except ValueError:
|
|
305
307
|
raise ValueError(
|
|
306
308
|
f"Frequency of {name} is not provided and cannot be inferred. Please set the expected data "
|
|
307
309
|
f"frequency when creating the predictor with `TimeSeriesPredictor(freq=...)` or ensure that "
|
|
308
310
|
f"the data has a regular time index with `{name}.convert_frequency(freq=...)`"
|
|
309
311
|
)
|
|
310
312
|
else:
|
|
311
|
-
self.freq =
|
|
312
|
-
logger.info(f"Inferred time series frequency: '{
|
|
313
|
+
self.freq = data_freq
|
|
314
|
+
logger.info(f"Inferred time series frequency: '{data_freq}'")
|
|
313
315
|
else:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
+
data_freq = df.infer_frequency(num_items=None)
|
|
317
|
+
if data_freq != self.freq:
|
|
318
|
+
logger.warning(f"{name} with frequency '{data_freq}' has been resampled to frequency '{self.freq}'.")
|
|
316
319
|
df = df.convert_frequency(freq=self.freq)
|
|
317
|
-
|
|
318
|
-
# Fill missing values
|
|
319
|
-
if df.isna().values.any():
|
|
320
|
-
# FIXME: Do not automatically fill NaNs here, handle missing values at the level of individual models.
|
|
321
|
-
# FIXME: Current solution leads to incorrect metric computation if missing values are present
|
|
322
|
-
logger.warning(
|
|
323
|
-
f"{name} contains missing values represented by NaN. "
|
|
324
|
-
f"They have been filled by carrying forward the last valid observation."
|
|
325
|
-
)
|
|
326
|
-
df = df.fill_missing_values()
|
|
327
|
-
if df.isna().values.any():
|
|
328
|
-
raise ValueError(f"Some time series in {name} consist completely of NaN values. Please remove them.")
|
|
329
320
|
return df
|
|
330
321
|
|
|
331
|
-
def
|
|
332
|
-
|
|
333
|
-
|
|
322
|
+
def _check_and_prepare_data_frame_for_evaluation(
|
|
323
|
+
self, data: TimeSeriesDataFrame, cutoff: int | None = None, name: str = "data"
|
|
324
|
+
) -> TimeSeriesDataFrame:
|
|
325
|
+
"""
|
|
326
|
+
Make sure that provided evaluation data includes both historical and future time series values.
|
|
327
|
+
Slices the dataframe based on cutoff, if needed.
|
|
328
|
+
"""
|
|
329
|
+
cutoff = -1 * self.prediction_length if cutoff is None else cutoff
|
|
330
|
+
if not (isinstance(cutoff, int) and cutoff <= -self.prediction_length):
|
|
331
|
+
raise ValueError(f"`cutoff` should be a negative integer <= -prediction_length, got: {cutoff=}")
|
|
332
|
+
|
|
333
|
+
expected_length = -cutoff
|
|
334
|
+
|
|
335
|
+
if data.num_timesteps_per_item().min() <= expected_length:
|
|
336
|
+
var_name = "-cutoff" if expected_length > self.prediction_length else "prediction_length"
|
|
334
337
|
raise ValueError(
|
|
335
|
-
f"Cannot reserve last
|
|
336
|
-
f"time series in {name}. Please make sure that {name} includes both
|
|
337
|
-
f"all time series have length >
|
|
338
|
+
f"Cannot reserve last {expected_length} time steps for evaluation in some "
|
|
339
|
+
f"time series in {name}. Please make sure that {name} includes both historical and future data, and that"
|
|
340
|
+
f"all time series have length > {var_name} (at least {expected_length + 1})"
|
|
338
341
|
)
|
|
339
342
|
|
|
340
|
-
|
|
341
|
-
|
|
343
|
+
if cutoff < -self.prediction_length:
|
|
344
|
+
data = data.slice_by_timestep(None, cutoff + self.prediction_length)
|
|
345
|
+
|
|
346
|
+
return data
|
|
347
|
+
|
|
348
|
+
def _get_dataset_stats(self, data: TimeSeriesDataFrame) -> str:
|
|
342
349
|
ts_lengths = data.num_timesteps_per_item()
|
|
343
|
-
median_length =
|
|
350
|
+
median_length = ts_lengths.median()
|
|
344
351
|
min_length = ts_lengths.min()
|
|
345
352
|
max_length = ts_lengths.max()
|
|
353
|
+
missing_value_fraction = data[self.target].isna().mean()
|
|
354
|
+
if missing_value_fraction > 0:
|
|
355
|
+
missing_value_fraction_str = f" (NaN fraction={missing_value_fraction:.1%})"
|
|
356
|
+
else:
|
|
357
|
+
missing_value_fraction_str = ""
|
|
346
358
|
return (
|
|
347
|
-
f"{len(data)} rows, {data.num_items} time series. "
|
|
348
|
-
f"Median time series length is {median_length} (min={min_length}, max={max_length}). "
|
|
359
|
+
f"{len(data)} rows{missing_value_fraction_str}, {data.num_items} time series. "
|
|
360
|
+
f"Median time series length is {median_length:.0f} (min={min_length}, max={max_length}). "
|
|
349
361
|
)
|
|
350
362
|
|
|
351
363
|
def _reduce_num_val_windows_if_necessary(
|
|
@@ -374,65 +386,72 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
374
386
|
)
|
|
375
387
|
return new_num_val_windows
|
|
376
388
|
|
|
377
|
-
def
|
|
389
|
+
def _filter_useless_train_data(
|
|
378
390
|
self,
|
|
379
391
|
train_data: TimeSeriesDataFrame,
|
|
380
392
|
num_val_windows: int,
|
|
381
393
|
val_step_size: int,
|
|
382
|
-
) ->
|
|
383
|
-
"""Remove time series from train_data that are too short for chosen
|
|
394
|
+
) -> TimeSeriesDataFrame:
|
|
395
|
+
"""Remove time series from train_data that either contain all NaNs or are too short for chosen settings.
|
|
384
396
|
|
|
385
|
-
This method ensures that
|
|
397
|
+
This method ensures that 1) no time series consist of all NaN values and 2) for each validation fold, all train
|
|
398
|
+
series have length >= max(prediction_length + 1, 5).
|
|
386
399
|
|
|
387
|
-
In other words, this method removes from train_data all time series with length less than
|
|
400
|
+
In other words, this method removes from train_data all time series with only NaN values or length less than
|
|
388
401
|
min_train_length + prediction_length + (num_val_windows - 1) * val_step_size
|
|
389
402
|
"""
|
|
390
403
|
min_length = self._min_train_length + self.prediction_length + (num_val_windows - 1) * val_step_size
|
|
391
|
-
|
|
392
404
|
train_lengths = train_data.num_timesteps_per_item()
|
|
393
|
-
|
|
394
|
-
|
|
405
|
+
too_short_items = train_lengths.index[train_lengths < min_length]
|
|
406
|
+
|
|
407
|
+
if len(too_short_items) > 0:
|
|
395
408
|
logger.info(
|
|
396
|
-
f"\tRemoving {len(
|
|
409
|
+
f"\tRemoving {len(too_short_items)} short time series from train_data. Only series with length "
|
|
397
410
|
f">= {min_length} will be used for training."
|
|
398
411
|
)
|
|
399
|
-
|
|
400
|
-
if len(filtered_train_data) == 0:
|
|
401
|
-
raise ValueError(
|
|
402
|
-
f"At least some time series in train_data must have length >= {min_length}. Please provide longer "
|
|
403
|
-
f"time series as train_data or reduce prediction_length, num_val_windows, or val_step_size."
|
|
404
|
-
)
|
|
405
|
-
logger.info(
|
|
406
|
-
f"\tAfter removing short series, train_data has {self._get_dataset_stats(filtered_train_data)}"
|
|
407
|
-
)
|
|
408
|
-
else:
|
|
409
|
-
filtered_train_data = train_data
|
|
412
|
+
train_data = train_data.query("item_id not in @too_short_items")
|
|
410
413
|
|
|
411
|
-
|
|
414
|
+
all_nan_items = train_data.item_ids[
|
|
415
|
+
train_data[self.target].isna().groupby(TimeSeriesDataFrame.ITEMID, sort=False).all()
|
|
416
|
+
]
|
|
417
|
+
if len(all_nan_items) > 0:
|
|
418
|
+
logger.info(f"\tRemoving {len(all_nan_items)} time series consisting of only NaN values from train_data.")
|
|
419
|
+
train_data = train_data.query("item_id not in @all_nan_items")
|
|
420
|
+
|
|
421
|
+
if len(too_short_items) or len(all_nan_items):
|
|
422
|
+
logger.info(f"\tAfter filtering, train_data has {self._get_dataset_stats(train_data)}")
|
|
412
423
|
|
|
413
|
-
|
|
424
|
+
if len(train_data) == 0:
|
|
425
|
+
raise ValueError(
|
|
426
|
+
f"At least some time series in train_data must have >= {min_length} observations. Please provide "
|
|
427
|
+
f"longer time series as train_data or reduce prediction_length, num_val_windows, or val_step_size."
|
|
428
|
+
)
|
|
429
|
+
return train_data
|
|
430
|
+
|
|
431
|
+
@apply_presets(get_predictor_presets())
|
|
414
432
|
def fit(
|
|
415
433
|
self,
|
|
416
|
-
train_data:
|
|
417
|
-
tuning_data:
|
|
418
|
-
time_limit:
|
|
419
|
-
presets:
|
|
420
|
-
hyperparameters:
|
|
421
|
-
hyperparameter_tune_kwargs:
|
|
422
|
-
excluded_model_types:
|
|
434
|
+
train_data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
435
|
+
tuning_data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
|
|
436
|
+
time_limit: int | None = None,
|
|
437
|
+
presets: str | None = None,
|
|
438
|
+
hyperparameters: str | dict[str | Type, Any] | None = None,
|
|
439
|
+
hyperparameter_tune_kwargs: str | dict | None = None,
|
|
440
|
+
excluded_model_types: list[str] | None = None,
|
|
423
441
|
num_val_windows: int = 1,
|
|
424
|
-
val_step_size:
|
|
425
|
-
refit_every_n_windows: int = 1,
|
|
442
|
+
val_step_size: int | None = None,
|
|
443
|
+
refit_every_n_windows: int | None = 1,
|
|
426
444
|
refit_full: bool = False,
|
|
427
445
|
enable_ensemble: bool = True,
|
|
428
|
-
|
|
429
|
-
|
|
446
|
+
skip_model_selection: bool = False,
|
|
447
|
+
random_seed: int | None = 123,
|
|
448
|
+
verbosity: int | None = None,
|
|
430
449
|
) -> "TimeSeriesPredictor":
|
|
431
450
|
"""Fit probabilistic forecasting models to the given time series dataset.
|
|
432
451
|
|
|
433
452
|
Parameters
|
|
434
453
|
----------
|
|
435
|
-
train_data :
|
|
454
|
+
train_data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
436
455
|
Training data in the :class:`~autogluon.timeseries.TimeSeriesDataFrame` format.
|
|
437
456
|
|
|
438
457
|
Time series with length ``<= (num_val_windows + 1) * prediction_length`` will be ignored during training.
|
|
@@ -440,44 +459,39 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
440
459
|
|
|
441
460
|
If ``known_covariates_names`` were specified when creating the predictor, ``train_data`` must include the
|
|
442
461
|
columns listed in ``known_covariates_names`` with the covariates values aligned with the target time series.
|
|
443
|
-
The known covariates must have a numeric (float or integer) dtype.
|
|
444
462
|
|
|
445
463
|
Columns of ``train_data`` except ``target`` and those listed in ``known_covariates_names`` will be
|
|
446
464
|
interpreted as ``past_covariates`` - covariates that are known only in the past.
|
|
447
465
|
|
|
448
|
-
If ``train_data``
|
|
449
|
-
predictor will interpret columns with ``int`` and ``float`` dtypes as continuous (real-valued) features,
|
|
450
|
-
columns with ``object`` and ``str`` dtypes as categorical features, and will ignore the rest of columns.
|
|
466
|
+
If ``train_data`` contains covariates or static features, they will be interpreted as follows:
|
|
451
467
|
|
|
452
|
-
|
|
453
|
-
|
|
468
|
+
* columns with ``int``, ``bool`` and ``float`` dtypes are interpreted as continuous (real-valued) features
|
|
469
|
+
* columns with ``object``, ``str`` and ``category`` dtypes are as interpreted as categorical features
|
|
470
|
+
* columns with other dtypes are ignored
|
|
454
471
|
|
|
455
|
-
|
|
472
|
+
To ensure that the column type is interpreted correctly, please convert it to one of the above dtypes.
|
|
473
|
+
For example, to ensure that column "store_id" with dtype ``int`` is interpreted as a category, change
|
|
474
|
+
its dtype to ``category``::
|
|
456
475
|
|
|
457
|
-
|
|
458
|
-
to a ``TimeSeriesDataFrame``.
|
|
476
|
+
data.static_features["store_id"] = data.static_features["store_id"].astype("category")
|
|
459
477
|
|
|
460
|
-
|
|
478
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
479
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
480
|
+
tuning_data : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
|
|
461
481
|
Data reserved for model selection and hyperparameter tuning, rather than training individual models. Also
|
|
462
482
|
used to compute the validation scores. Note that only the last ``prediction_length`` time steps of each
|
|
463
483
|
time series are used for computing the validation score.
|
|
464
484
|
|
|
465
485
|
If ``tuning_data`` is provided, multi-window backtesting on training data will be disabled, the
|
|
466
|
-
|
|
486
|
+
``num_val_windows`` will be set to ``0``, and ``refit_full`` will be set to ``False``.
|
|
467
487
|
|
|
468
488
|
Leaving this argument empty and letting AutoGluon automatically generate the validation set from
|
|
469
489
|
``train_data`` is a good default.
|
|
470
490
|
|
|
471
|
-
|
|
472
|
-
the columns listed in ``known_covariates_names`` with the covariates values aligned with the target time
|
|
473
|
-
series.
|
|
474
|
-
|
|
475
|
-
If ``train_data`` has past covariates or static features, ``tuning_data`` must have also include them (with
|
|
476
|
-
same columns names and dtypes).
|
|
477
|
-
|
|
478
|
-
If provided data is an instance of pandas DataFrame, AutoGluon will attempt to automatically convert it
|
|
479
|
-
to a ``TimeSeriesDataFrame``.
|
|
491
|
+
The names and dtypes of columns and static features in ``tuning_data`` must match the ``train_data``.
|
|
480
492
|
|
|
493
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
494
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
481
495
|
time_limit : int, optional
|
|
482
496
|
Approximately how long :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit` will run (wall-clock time in
|
|
483
497
|
seconds). If not specified, :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit` will run until all models
|
|
@@ -496,14 +510,22 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
496
510
|
|
|
497
511
|
Available presets:
|
|
498
512
|
|
|
499
|
-
- ``"fast_training"``:
|
|
500
|
-
- ``"medium_quality"``:
|
|
501
|
-
- ``"high_quality"``:
|
|
502
|
-
- ``"best_quality"``: Same models as in ``"high_quality"
|
|
513
|
+
- ``"fast_training"``: Simple statistical and tree-based ML models. These models are fast to train but may not be very accurate.
|
|
514
|
+
- ``"medium_quality"``: Same models as above, plus deep learning models ``TemporalFusionTransformer`` and Chronos-Bolt (small). Produces good forecasts with reasonable training time.
|
|
515
|
+
- ``"high_quality"``: A mix of multiple DL, ML and statistical forecasting models available in AutoGluon that offers the best forecast accuracy. Much more accurate than ``medium_quality``, but takes longer to train.
|
|
516
|
+
- ``"best_quality"``: Same models as in ``"high_quality"``, but performs validation with multiple backtests. Usually better than ``high_quality``, but takes even longer to train.
|
|
517
|
+
|
|
518
|
+
Available presets with the `Chronos-Bolt <https://github.com/amazon-science/chronos-forecasting>`_ model:
|
|
519
|
+
|
|
520
|
+
- ``"bolt_{model_size}"``: where model size is one of ``tiny,mini,small,base``. Uses the Chronos-Bolt pretrained model for zero-shot forecasting.
|
|
521
|
+
See the documentation for ``ChronosModel`` or see `Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_ for more information.
|
|
503
522
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
523
|
+
Exact definitions of these presets can be found in the source code
|
|
524
|
+
[`1 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/presets_configs.py>`_,
|
|
525
|
+
`2 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/models/presets.py>`_].
|
|
526
|
+
|
|
527
|
+
If no ``presets`` are selected, user-provided values for ``hyperparameters`` will be used (defaulting to their
|
|
528
|
+
default values specified below).
|
|
507
529
|
hyperparameters : str or dict, optional
|
|
508
530
|
Determines what models are trained and what hyperparameters are used by each model.
|
|
509
531
|
|
|
@@ -561,7 +583,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
561
583
|
Valid preset values:
|
|
562
584
|
|
|
563
585
|
* "auto": Performs HPO via bayesian optimization search on GluonTS-backed neural forecasting models and
|
|
564
|
-
|
|
586
|
+
random search on other models using local scheduler.
|
|
565
587
|
* "random": Performs HPO via random search.
|
|
566
588
|
|
|
567
589
|
You can also provide a dict to specify searchers and schedulers
|
|
@@ -569,7 +591,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
569
591
|
|
|
570
592
|
* "num_trials": How many HPO trials to run
|
|
571
593
|
* "scheduler": Which scheduler to use. Valid values:
|
|
572
|
-
* "local": Local
|
|
594
|
+
* "local": Local scheduler that schedules trials FIFO
|
|
573
595
|
* "searcher": Which searching algorithm to use. Valid values:
|
|
574
596
|
* "local_random": Uses the "random" searcher
|
|
575
597
|
* "random": Perform random search
|
|
@@ -588,7 +610,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
588
610
|
"scheduler": "local",
|
|
589
611
|
},
|
|
590
612
|
)
|
|
591
|
-
excluded_model_types:
|
|
613
|
+
excluded_model_types: list[str], optional
|
|
592
614
|
Banned subset of model types to avoid training during ``fit()``, even if present in ``hyperparameters``.
|
|
593
615
|
For example, the following code will train all models included in the ``high_quality`` presets except ``DeepAR``::
|
|
594
616
|
|
|
@@ -603,7 +625,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
603
625
|
of time series in ``train_data`` are long enough for the chosen number of backtests.
|
|
604
626
|
|
|
605
627
|
Increasing this parameter increases the training time roughly by a factor of ``num_val_windows // refit_every_n_windows``.
|
|
606
|
-
See
|
|
628
|
+
See ``refit_every_n_windows`` and ``val_step_size`` for details.
|
|
607
629
|
|
|
608
630
|
For example, for ``prediction_length=2``, ``num_val_windows=3`` and ``val_step_size=1`` the folds are::
|
|
609
631
|
|
|
@@ -622,7 +644,11 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
622
644
|
This argument has no effect if ``tuning_data`` is provided.
|
|
623
645
|
refit_every_n_windows: int or None, default = 1
|
|
624
646
|
When performing cross validation, each model will be retrained every ``refit_every_n_windows`` validation
|
|
625
|
-
windows
|
|
647
|
+
windows, where the number of validation windows is specified by ``num_val_windows``. Note that in the
|
|
648
|
+
default setting where ``num_val_windows=1``, this argument has no effect.
|
|
649
|
+
|
|
650
|
+
If set to ``None``, models will only be fit once for the first (oldest) validation window. By default,
|
|
651
|
+
``refit_every_n_windows=1``, i.e., all models will be refit for each validation window.
|
|
626
652
|
refit_full : bool, default = False
|
|
627
653
|
If True, after training is complete, AutoGluon will attempt to re-train all models using all of training
|
|
628
654
|
data (including the data initially reserved for validation). This argument has no effect if ``tuning_data``
|
|
@@ -630,6 +656,10 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
630
656
|
enable_ensemble : bool, default = True
|
|
631
657
|
If True, the ``TimeSeriesPredictor`` will fit a simple weighted ensemble on top of the models specified via
|
|
632
658
|
``hyperparameters``.
|
|
659
|
+
skip_model_selection : bool, default = False
|
|
660
|
+
If True, predictor will not compute the validation score. For example, this argument is useful if we want
|
|
661
|
+
to use the predictor as a wrapper for a single pre-trained model. If set to True, then the ``hyperparameters``
|
|
662
|
+
dict must contain exactly one model without hyperparameter search spaces or an exception will be raised.
|
|
633
663
|
random_seed : int or None, default = 123
|
|
634
664
|
If provided, fixes the seed of the random number generator for all models. This guarantees reproducible
|
|
635
665
|
results for most models (except those trained on GPU because of the non-determinism of GPU operations).
|
|
@@ -639,12 +669,15 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
639
669
|
|
|
640
670
|
"""
|
|
641
671
|
time_start = time.time()
|
|
642
|
-
if self.
|
|
643
|
-
raise AssertionError(
|
|
672
|
+
if self.is_fit:
|
|
673
|
+
raise AssertionError(
|
|
674
|
+
"Predictor is already fit! To fit additional models create a new `TimeSeriesPredictor`."
|
|
675
|
+
)
|
|
644
676
|
|
|
645
677
|
if verbosity is None:
|
|
646
678
|
verbosity = self.verbosity
|
|
647
679
|
set_logger_verbosity(verbosity, logger=logger)
|
|
680
|
+
warn_if_mlflow_autologging_is_enabled(logger=logger)
|
|
648
681
|
|
|
649
682
|
logger.info("Beginning AutoGluon training..." + (f" Time limit = {time_limit}s" if time_limit else ""))
|
|
650
683
|
logger.info(f"AutoGluon will save models to '{self.path}'")
|
|
@@ -658,7 +691,8 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
658
691
|
target=self.target,
|
|
659
692
|
known_covariates_names=self.known_covariates_names,
|
|
660
693
|
eval_metric=self.eval_metric,
|
|
661
|
-
eval_metric_seasonal_period=self.
|
|
694
|
+
eval_metric_seasonal_period=self.eval_metric.seasonal_period,
|
|
695
|
+
horizon_weight=self.eval_metric.horizon_weight,
|
|
662
696
|
quantile_levels=self.quantile_levels,
|
|
663
697
|
freq=self.freq,
|
|
664
698
|
time_limit=time_limit,
|
|
@@ -669,6 +703,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
669
703
|
val_step_size=val_step_size,
|
|
670
704
|
refit_every_n_windows=refit_every_n_windows,
|
|
671
705
|
refit_full=refit_full,
|
|
706
|
+
skip_model_selection=skip_model_selection,
|
|
672
707
|
enable_ensemble=enable_ensemble,
|
|
673
708
|
random_seed=random_seed,
|
|
674
709
|
verbosity=verbosity,
|
|
@@ -691,37 +726,44 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
691
726
|
|
|
692
727
|
if tuning_data is not None:
|
|
693
728
|
tuning_data = self._check_and_prepare_data_frame(tuning_data, name="tuning_data")
|
|
694
|
-
self.
|
|
695
|
-
logger.info(f"Provided tuning_data has {self._get_dataset_stats(
|
|
729
|
+
tuning_data = self._check_and_prepare_data_frame_for_evaluation(tuning_data, name="tuning_data")
|
|
730
|
+
logger.info(f"Provided tuning_data has {self._get_dataset_stats(tuning_data)}")
|
|
696
731
|
# TODO: Use num_val_windows to perform multi-window backtests on tuning_data
|
|
697
|
-
if num_val_windows >
|
|
732
|
+
if num_val_windows > 1:
|
|
698
733
|
logger.warning(
|
|
699
734
|
"\tSetting num_val_windows = 0 (disabling backtesting on train_data) because tuning_data is provided."
|
|
700
735
|
)
|
|
701
|
-
num_val_windows =
|
|
736
|
+
num_val_windows = 1
|
|
702
737
|
|
|
703
738
|
if num_val_windows == 0 and tuning_data is None:
|
|
704
739
|
raise ValueError("Please set num_val_windows >= 1 or provide custom tuning_data")
|
|
705
740
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
741
|
+
if num_val_windows <= 1 and refit_every_n_windows is not None and refit_every_n_windows > 1:
|
|
742
|
+
logger.warning(
|
|
743
|
+
f"\trefit_every_n_windows provided as {refit_every_n_windows} but num_val_windows is set to {num_val_windows}."
|
|
744
|
+
" Refit_every_n_windows will have no effect."
|
|
745
|
+
)
|
|
709
746
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
747
|
+
if not skip_model_selection:
|
|
748
|
+
train_data = self._filter_useless_train_data(
|
|
749
|
+
train_data,
|
|
750
|
+
num_val_windows=0 if tuning_data is not None else num_val_windows,
|
|
751
|
+
val_step_size=val_step_size,
|
|
752
|
+
)
|
|
713
753
|
|
|
714
754
|
time_left = None if time_limit is None else time_limit - (time.time() - time_start)
|
|
715
755
|
self._learner.fit(
|
|
716
756
|
train_data=train_data,
|
|
717
|
-
val_data=tuning_data,
|
|
718
757
|
hyperparameters=hyperparameters,
|
|
758
|
+
val_data=tuning_data,
|
|
719
759
|
hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
|
|
720
760
|
excluded_model_types=excluded_model_types,
|
|
721
761
|
time_limit=time_left,
|
|
722
762
|
verbosity=verbosity,
|
|
723
|
-
|
|
763
|
+
num_val_windows=(num_val_windows,) if isinstance(num_val_windows, int) else num_val_windows,
|
|
764
|
+
val_step_size=val_step_size,
|
|
724
765
|
refit_every_n_windows=refit_every_n_windows,
|
|
766
|
+
skip_model_selection=skip_model_selection,
|
|
725
767
|
enable_ensemble=enable_ensemble,
|
|
726
768
|
random_seed=random_seed,
|
|
727
769
|
)
|
|
@@ -734,40 +776,41 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
734
776
|
self.save()
|
|
735
777
|
return self
|
|
736
778
|
|
|
737
|
-
def model_names(self) ->
|
|
779
|
+
def model_names(self) -> list[str]:
|
|
738
780
|
"""Returns the list of model names trained by this predictor object."""
|
|
781
|
+
self._assert_is_fit("model_names")
|
|
739
782
|
return self._trainer.get_model_names()
|
|
740
783
|
|
|
741
784
|
def predict(
|
|
742
785
|
self,
|
|
743
|
-
data:
|
|
744
|
-
known_covariates:
|
|
745
|
-
model:
|
|
786
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
787
|
+
known_covariates: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
|
|
788
|
+
model: str | None = None,
|
|
746
789
|
use_cache: bool = True,
|
|
747
|
-
random_seed:
|
|
790
|
+
random_seed: int | None = 123,
|
|
748
791
|
) -> TimeSeriesDataFrame:
|
|
749
792
|
"""Return quantile and mean forecasts for the given dataset, starting from the end of each time series.
|
|
750
793
|
|
|
751
794
|
Parameters
|
|
752
795
|
----------
|
|
753
|
-
data :
|
|
754
|
-
|
|
796
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
797
|
+
Historical time series data for which the forecast needs to be made.
|
|
755
798
|
|
|
756
|
-
|
|
757
|
-
|
|
799
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
800
|
+
the predictor.
|
|
758
801
|
|
|
759
|
-
If
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
If provided data is an instance of pandas DataFrame, AutoGluon will attempt to automatically convert it
|
|
763
|
-
to a ``TimeSeriesDataFrame``.
|
|
764
|
-
known_covariates : Union[TimeSeriesDataFrame, pd.DataFrame, str], optional
|
|
802
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
803
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
804
|
+
known_covariates : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
|
|
765
805
|
If ``known_covariates_names`` were specified when creating the predictor, it is necessary to provide the
|
|
766
|
-
values of the known covariates for each time series during the forecast horizon.
|
|
806
|
+
values of the known covariates for each time series during the forecast horizon. Specifically:
|
|
807
|
+
|
|
808
|
+
- Must contain all columns listed in ``known_covariates_names``.
|
|
809
|
+
- Must include all ``item_id`` values present in the input ``data``.
|
|
810
|
+
- Must include ``timestamp`` values for the full forecast horizon (i.e., ``prediction_length`` time steps) following the end of each series in the input ``data``.
|
|
767
811
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
- The ``timestamp`` index must include the values for ``prediction_length`` many time steps into the future from the end of each time series in ``data``
|
|
812
|
+
You can use :meth:`autogluon.timeseries.TimeSeriesPredictor.make_future_data_frame` to generate a template
|
|
813
|
+
containing the required ``item_id`` and ``timestamp`` combinations for the ``known_covariates`` dataframe.
|
|
771
814
|
|
|
772
815
|
See example below.
|
|
773
816
|
model : str, optional
|
|
@@ -808,8 +851,10 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
808
851
|
B 2020-03-04 17.1
|
|
809
852
|
2020-03-05 8.3
|
|
810
853
|
"""
|
|
811
|
-
|
|
812
|
-
|
|
854
|
+
self._assert_is_fit("predict")
|
|
855
|
+
# Save original item_id order to return predictions in the same order as input data
|
|
856
|
+
data = self._to_data_frame(data)
|
|
857
|
+
original_item_id_order = data.item_ids
|
|
813
858
|
data = self._check_and_prepare_data_frame(data)
|
|
814
859
|
if known_covariates is not None:
|
|
815
860
|
known_covariates = self._to_data_frame(known_covariates)
|
|
@@ -820,41 +865,250 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
820
865
|
use_cache=use_cache,
|
|
821
866
|
random_seed=random_seed,
|
|
822
867
|
)
|
|
823
|
-
return predictions.reindex(original_item_id_order, level=ITEMID)
|
|
868
|
+
return cast(TimeSeriesDataFrame, predictions.reindex(original_item_id_order, level=TimeSeriesDataFrame.ITEMID))
|
|
869
|
+
|
|
870
|
+
@overload
|
|
871
|
+
def backtest_predictions(
|
|
872
|
+
self,
|
|
873
|
+
data: TimeSeriesDataFrame | None = None,
|
|
874
|
+
*,
|
|
875
|
+
model: str | None = None,
|
|
876
|
+
num_val_windows: int | None = None,
|
|
877
|
+
val_step_size: int | None = None,
|
|
878
|
+
use_cache: bool = True,
|
|
879
|
+
) -> list[TimeSeriesDataFrame]: ...
|
|
880
|
+
|
|
881
|
+
@overload
|
|
882
|
+
def backtest_predictions(
|
|
883
|
+
self,
|
|
884
|
+
data: TimeSeriesDataFrame | None = None,
|
|
885
|
+
*,
|
|
886
|
+
model: list[str],
|
|
887
|
+
num_val_windows: int | None = None,
|
|
888
|
+
val_step_size: int | None = None,
|
|
889
|
+
use_cache: bool = True,
|
|
890
|
+
) -> dict[str, list[TimeSeriesDataFrame]]: ...
|
|
891
|
+
|
|
892
|
+
def backtest_predictions(
|
|
893
|
+
self,
|
|
894
|
+
data: TimeSeriesDataFrame | None = None,
|
|
895
|
+
*,
|
|
896
|
+
model: str | list[str] | None = None,
|
|
897
|
+
num_val_windows: int | None = None,
|
|
898
|
+
val_step_size: int | None = None,
|
|
899
|
+
use_cache: bool = True,
|
|
900
|
+
) -> list[TimeSeriesDataFrame] | dict[str, list[TimeSeriesDataFrame]]:
|
|
901
|
+
"""Return predictions for multiple validation windows.
|
|
902
|
+
|
|
903
|
+
When ``data=None``, returns the predictions that were saved during training. Otherwise, generates new
|
|
904
|
+
predictions by splitting ``data`` into multiple windows using an expanding window strategy.
|
|
905
|
+
|
|
906
|
+
The corresponding target values for each window can be obtained using
|
|
907
|
+
:meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_targets`.
|
|
908
|
+
|
|
909
|
+
Parameters
|
|
910
|
+
----------
|
|
911
|
+
data : TimeSeriesDataFrame, optional
|
|
912
|
+
Time series data to generate predictions for. If ``None``, returns the predictions that were saved
|
|
913
|
+
during training on ``train_data``.
|
|
914
|
+
|
|
915
|
+
If provided, all time series in ``data`` must have length at least
|
|
916
|
+
``prediction_length + (num_val_windows - 1) * val_step_size + 1``.
|
|
917
|
+
|
|
918
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
919
|
+
the predictor.
|
|
920
|
+
model : str, list[str], or None, default = None
|
|
921
|
+
Name of the model(s) to generate predictions with. By default, the best model during training
|
|
922
|
+
(with highest validation score) will be used.
|
|
923
|
+
|
|
924
|
+
- If ``str``: Returns predictions for a single model as a list.
|
|
925
|
+
- If ``list[str]``: Returns predictions for multiple models as a dict mapping model names to lists.
|
|
926
|
+
- If ``None``: Uses the best model.
|
|
927
|
+
num_val_windows : int, optional
|
|
928
|
+
Number of validation windows to generate. If ``None``, uses the ``num_val_windows`` value from training
|
|
929
|
+
configuration when ``data=None``, otherwise defaults to 1.
|
|
930
|
+
|
|
931
|
+
For example, with ``prediction_length=2``, ``num_val_windows=3``, and ``val_step_size=1``, the validation
|
|
932
|
+
windows are::
|
|
933
|
+
|
|
934
|
+
|-------------------|
|
|
935
|
+
| x x x x x y y - - |
|
|
936
|
+
| x x x x x x y y - |
|
|
937
|
+
| x x x x x x x y y |
|
|
938
|
+
|
|
939
|
+
where ``x`` denotes training time steps and ``y`` denotes validation time steps for each window.
|
|
940
|
+
val_step_size : int, optional
|
|
941
|
+
Number of time steps between the start of consecutive validation windows. If ``None``, defaults to
|
|
942
|
+
``prediction_length``.
|
|
943
|
+
use_cache : bool, default = True
|
|
944
|
+
If True, will attempt to use cached predictions. If False, cached predictions will be ignored.
|
|
945
|
+
This argument is ignored if ``cache_predictions`` was set to False when creating the ``TimeSeriesPredictor``.
|
|
946
|
+
|
|
947
|
+
Returns
|
|
948
|
+
-------
|
|
949
|
+
list[TimeSeriesDataFrame] or dict[str, list[TimeSeriesDataFrame]]
|
|
950
|
+
Predictions for each validation window.
|
|
951
|
+
|
|
952
|
+
- If ``model`` is a ``str`` or ``None``: Returns a list of length ``num_val_windows``, where each element
|
|
953
|
+
contains the predictions for one validation window.
|
|
954
|
+
- If ``model`` is a ``list[str]``: Returns a dict mapping each model name to a list of predictions for
|
|
955
|
+
each validation window.
|
|
956
|
+
|
|
957
|
+
Examples
|
|
958
|
+
--------
|
|
959
|
+
Make predictions on new data with the best model
|
|
960
|
+
|
|
961
|
+
>>> predictor.backtest_predictions(test_data, num_val_windows=2)
|
|
962
|
+
|
|
963
|
+
Load validation predictions for all models that were saved during training
|
|
964
|
+
|
|
965
|
+
>>> predictor.backtest_predictions(model=predictor.model_names())
|
|
966
|
+
|
|
967
|
+
See Also
|
|
968
|
+
--------
|
|
969
|
+
backtest_targets
|
|
970
|
+
Return target values aligned with predictions.
|
|
971
|
+
evaluate
|
|
972
|
+
Evaluate forecast accuracy on a hold-out set.
|
|
973
|
+
predict
|
|
974
|
+
Generate forecasts for future time steps.
|
|
975
|
+
"""
|
|
976
|
+
self._assert_is_fit("backtest_predictions")
|
|
977
|
+
if data is not None:
|
|
978
|
+
data = self._check_and_prepare_data_frame(data)
|
|
979
|
+
|
|
980
|
+
if model is None:
|
|
981
|
+
model_names = [self.model_best]
|
|
982
|
+
elif isinstance(model, str):
|
|
983
|
+
model_names = [model]
|
|
984
|
+
else:
|
|
985
|
+
model_names = model
|
|
986
|
+
|
|
987
|
+
result = self._learner.backtest_predictions(
|
|
988
|
+
data=data,
|
|
989
|
+
model_names=model_names,
|
|
990
|
+
num_val_windows=num_val_windows,
|
|
991
|
+
val_step_size=val_step_size,
|
|
992
|
+
use_cache=use_cache,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
if isinstance(model, list):
|
|
996
|
+
return result
|
|
997
|
+
else:
|
|
998
|
+
return result[model_names[0]]
|
|
999
|
+
|
|
1000
|
+
def backtest_targets(
|
|
1001
|
+
self,
|
|
1002
|
+
data: TimeSeriesDataFrame | None = None,
|
|
1003
|
+
*,
|
|
1004
|
+
num_val_windows: int | None = None,
|
|
1005
|
+
val_step_size: int | None = None,
|
|
1006
|
+
) -> list[TimeSeriesDataFrame]:
|
|
1007
|
+
"""Return target values for each validation window.
|
|
1008
|
+
|
|
1009
|
+
Returns the actual target values corresponding to each validation window used in
|
|
1010
|
+
:meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`. The returned targets are aligned
|
|
1011
|
+
with the predictions, making it easy to compute custom evaluation metrics or analyze forecast errors.
|
|
1012
|
+
|
|
1013
|
+
Parameters
|
|
1014
|
+
----------
|
|
1015
|
+
data : TimeSeriesDataFrame, optional
|
|
1016
|
+
Time series data to extract targets from. If ``None``, returns the targets from the validation windows
|
|
1017
|
+
used during training.
|
|
1018
|
+
|
|
1019
|
+
If provided, all time series in ``data`` must have length at least
|
|
1020
|
+
``prediction_length + (num_val_windows - 1) * val_step_size + 1``.
|
|
1021
|
+
|
|
1022
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
1023
|
+
the predictor.
|
|
1024
|
+
num_val_windows : int, optional
|
|
1025
|
+
Number of validation windows to extract targets for. If ``None``, uses the ``num_val_windows`` value from
|
|
1026
|
+
training configuration when ``data=None``, otherwise defaults to 1.
|
|
1027
|
+
|
|
1028
|
+
This should match the ``num_val_windows`` argument passed to
|
|
1029
|
+
:meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`.
|
|
1030
|
+
val_step_size : int, optional
|
|
1031
|
+
Number of time steps between the start of consecutive validation windows. If ``None``, defaults to
|
|
1032
|
+
``prediction_length``.
|
|
1033
|
+
|
|
1034
|
+
This should match the ``val_step_size`` argument passed to
|
|
1035
|
+
:meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`.
|
|
1036
|
+
|
|
1037
|
+
Returns
|
|
1038
|
+
-------
|
|
1039
|
+
list[TimeSeriesDataFrame]
|
|
1040
|
+
Target values for each validation window. Returns a list of length ``num_val_windows``,
|
|
1041
|
+
where each element contains the full time series data for one validation window.
|
|
1042
|
+
Each dataframe includes both historical context and the last ``prediction_length`` time steps
|
|
1043
|
+
that represent the target values to compare against predictions.
|
|
1044
|
+
|
|
1045
|
+
The returned targets are aligned with the output of
|
|
1046
|
+
:meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`, so ``targets[i]`` corresponds
|
|
1047
|
+
to ``predictions[i]`` for the i-th validation window.
|
|
1048
|
+
|
|
1049
|
+
See Also
|
|
1050
|
+
--------
|
|
1051
|
+
backtest_predictions
|
|
1052
|
+
Return predictions for multiple validation windows.
|
|
1053
|
+
evaluate
|
|
1054
|
+
Evaluate forecast accuracy on a hold-out set.
|
|
1055
|
+
"""
|
|
1056
|
+
self._assert_is_fit("backtest_targets")
|
|
1057
|
+
if data is not None:
|
|
1058
|
+
data = self._check_and_prepare_data_frame(data)
|
|
1059
|
+
return self._learner.backtest_targets(
|
|
1060
|
+
data=data,
|
|
1061
|
+
num_val_windows=num_val_windows,
|
|
1062
|
+
val_step_size=val_step_size,
|
|
1063
|
+
)
|
|
824
1064
|
|
|
825
1065
|
def evaluate(
|
|
826
1066
|
self,
|
|
827
|
-
data:
|
|
828
|
-
model:
|
|
829
|
-
metrics:
|
|
1067
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
1068
|
+
model: str | None = None,
|
|
1069
|
+
metrics: str | TimeSeriesScorer | list[str | TimeSeriesScorer] | None = None,
|
|
1070
|
+
cutoff: int | None = None,
|
|
830
1071
|
display: bool = False,
|
|
831
1072
|
use_cache: bool = True,
|
|
832
|
-
) ->
|
|
1073
|
+
) -> dict[str, float]:
|
|
833
1074
|
"""Evaluate the forecast accuracy for given dataset.
|
|
834
1075
|
|
|
835
1076
|
This method measures the forecast accuracy using the last ``self.prediction_length`` time steps of each time
|
|
836
1077
|
series in ``data`` as a hold-out set.
|
|
837
1078
|
|
|
1079
|
+
.. note::
|
|
1080
|
+
Metrics are always reported in 'higher is better' format.
|
|
1081
|
+
This means that metrics such as MASE or MAPE will be multiplied by -1, so their values will be negative.
|
|
1082
|
+
This is necessary to avoid the user needing to know the metric to understand if higher is better when
|
|
1083
|
+
looking at the evaluation results.
|
|
1084
|
+
|
|
838
1085
|
Parameters
|
|
839
1086
|
----------
|
|
840
|
-
data :
|
|
841
|
-
The data to evaluate the best model on.
|
|
842
|
-
|
|
1087
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
1088
|
+
The data to evaluate the best model on. If a ``cutoff`` is not provided, the last ``prediction_length``
|
|
1089
|
+
time steps of each time series in ``data`` will be held out for prediction and forecast accuracy will
|
|
1090
|
+
be calculated on these time steps. When a ``cutoff`` is provided, the ``-cutoff``-th to the
|
|
1091
|
+
``-cutoff + prediction_length``-th time steps of each time series are used for evaluation.
|
|
843
1092
|
|
|
844
|
-
|
|
845
|
-
|
|
1093
|
+
Must include both historical and future data (i.e., length of all time series in ``data`` must be at least
|
|
1094
|
+
``prediction_length + 1``, if ``cutoff`` is not provided, ``-cutoff + 1`` otherwise).
|
|
846
1095
|
|
|
847
|
-
|
|
848
|
-
|
|
1096
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
1097
|
+
the predictor.
|
|
849
1098
|
|
|
850
|
-
If provided data is
|
|
851
|
-
|
|
1099
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
1100
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
852
1101
|
model : str, optional
|
|
853
1102
|
Name of the model that you would like to evaluate. By default, the best model during training
|
|
854
1103
|
(with highest validation score) will be used.
|
|
855
|
-
metrics : str, TimeSeriesScorer or
|
|
1104
|
+
metrics : str, TimeSeriesScorer or list[str | TimeSeriesScorer], optional
|
|
856
1105
|
Metric or a list of metrics to compute scores with. Defaults to ``self.eval_metric``. Supports both
|
|
857
1106
|
metric names as strings and custom metrics based on TimeSeriesScorer.
|
|
1107
|
+
cutoff : int, optional
|
|
1108
|
+
A *negative* integer less than or equal to ``-1 * prediction_length`` denoting the time step in ``data``
|
|
1109
|
+
where the forecast evaluation starts, i.e., time series are evaluated from the ``-cutoff``-th to the
|
|
1110
|
+
``-cutoff + prediction_length``-th time step. Defaults to ``-1 * prediction_length``, using the last
|
|
1111
|
+
``prediction_length`` time steps of each time series for evaluation.
|
|
858
1112
|
display : bool, default = False
|
|
859
1113
|
If True, the scores will be printed.
|
|
860
1114
|
use_cache : bool, default = True
|
|
@@ -863,29 +1117,185 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
863
1117
|
|
|
864
1118
|
Returns
|
|
865
1119
|
-------
|
|
866
|
-
scores_dict :
|
|
1120
|
+
scores_dict : dict[str, float]
|
|
867
1121
|
Dictionary where keys = metrics, values = performance along each metric. For consistency, error metrics
|
|
868
1122
|
will have their signs flipped to obey this convention. For example, negative MAPE values will be reported.
|
|
869
1123
|
To get the ``eval_metric`` score, do ``output[predictor.eval_metric.name]``.
|
|
870
1124
|
"""
|
|
1125
|
+
self._assert_is_fit("evaluate")
|
|
871
1126
|
data = self._check_and_prepare_data_frame(data)
|
|
872
|
-
self.
|
|
1127
|
+
data = self._check_and_prepare_data_frame_for_evaluation(data, cutoff=cutoff)
|
|
1128
|
+
|
|
873
1129
|
scores_dict = self._learner.evaluate(data, model=model, metrics=metrics, use_cache=use_cache)
|
|
874
1130
|
if display:
|
|
875
1131
|
logger.info("Evaluations on test data:")
|
|
876
1132
|
logger.info(json.dumps(scores_dict, indent=4))
|
|
877
1133
|
return scores_dict
|
|
878
1134
|
|
|
1135
|
+
def feature_importance(
|
|
1136
|
+
self,
|
|
1137
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
|
|
1138
|
+
model: str | None = None,
|
|
1139
|
+
metric: str | TimeSeriesScorer | None = None,
|
|
1140
|
+
features: list[str] | None = None,
|
|
1141
|
+
time_limit: float | None = None,
|
|
1142
|
+
method: Literal["naive", "permutation"] = "permutation",
|
|
1143
|
+
subsample_size: int = 50,
|
|
1144
|
+
num_iterations: int | None = None,
|
|
1145
|
+
random_seed: int | None = 123,
|
|
1146
|
+
relative_scores: bool = False,
|
|
1147
|
+
include_confidence_band: bool = True,
|
|
1148
|
+
confidence_level: float = 0.99,
|
|
1149
|
+
) -> pd.DataFrame:
|
|
1150
|
+
"""
|
|
1151
|
+
Calculates feature importance scores for the given model via replacing each feature by a shuffled version of the same feature
|
|
1152
|
+
(also known as permutation feature importance) or by assigning a constant value representing the median or mode of the feature,
|
|
1153
|
+
and computing the relative decrease in the model's predictive performance.
|
|
1154
|
+
|
|
1155
|
+
A feature's importance score represents the performance drop that results when the model makes predictions on a perturbed copy
|
|
1156
|
+
of the data where this feature's values have been randomly shuffled across rows. A feature score of 0.01 would indicate that the
|
|
1157
|
+
predictive performance dropped by 0.01 when the feature was randomly shuffled or replaced. The higher the score a feature has,
|
|
1158
|
+
the more important it is to the model's performance.
|
|
1159
|
+
|
|
1160
|
+
If a feature has a negative score, this means that the feature is likely harmful to the final model, and a model trained with
|
|
1161
|
+
the feature removed would be expected to achieve a better predictive performance. Note that calculating feature importance can
|
|
1162
|
+
be a computationally expensive process, particularly if the model uses many features. In many cases, this can take longer than
|
|
1163
|
+
the original model training. Roughly, this will equal to the number of features in the data multiplied by ``num_iterations``
|
|
1164
|
+
(or, 1 when ``method="naive"``) and time taken when ``evaluate()`` is called on a dataset with ``subsample_size``.
|
|
1165
|
+
|
|
1166
|
+
Parameters
|
|
1167
|
+
----------
|
|
1168
|
+
data : TimeSeriesDataFrame, pd.DataFrame, Path or str, optional
|
|
1169
|
+
The data to evaluate feature importances on. The last ``prediction_length`` time steps of the data set, for each
|
|
1170
|
+
item, will be held out for prediction and forecast accuracy will be calculated on these time steps.
|
|
1171
|
+
More accurate feature importances will be obtained from new data that was held-out during ``fit()``.
|
|
1172
|
+
|
|
1173
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
1174
|
+
the predictor.
|
|
1175
|
+
|
|
1176
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
1177
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
1178
|
+
|
|
1179
|
+
If ``data`` is not provided, then validation (tuning) data provided during training (or the held out data used for
|
|
1180
|
+
validation if ``tuning_data`` was not explicitly provided ``fit()``) will be used.
|
|
1181
|
+
model : str, optional
|
|
1182
|
+
Name of the model that you would like to evaluate. By default, the best model during training
|
|
1183
|
+
(with highest validation score) will be used.
|
|
1184
|
+
metric : str or TimeSeriesScorer, optional
|
|
1185
|
+
Metric to be used for computing feature importance. If None, the ``eval_metric`` specified during initialization of
|
|
1186
|
+
the ``TimeSeriesPredictor`` will be used.
|
|
1187
|
+
features : list[str], optional
|
|
1188
|
+
List of feature names that feature importances are calculated for and returned. By default, all feature importances
|
|
1189
|
+
will be returned.
|
|
1190
|
+
method : {"permutation", "naive"}, default = "permutation"
|
|
1191
|
+
Method to be used for computing feature importance.
|
|
1192
|
+
|
|
1193
|
+
* ``naive``: computes feature importance by replacing the values of each feature by a constant value and computing
|
|
1194
|
+
feature importances as the relative improvement in the evaluation metric. The constant value is the median for
|
|
1195
|
+
real-valued features and the mode for categorical features, for both covariates and static features, obtained from the
|
|
1196
|
+
feature values in ``data`` provided.
|
|
1197
|
+
* ``permutation``: computes feature importance by naively shuffling the values of the feature across different items
|
|
1198
|
+
and time steps. Each feature is shuffled for ``num_iterations`` times and feature importances are computed as the
|
|
1199
|
+
relative improvement in the evaluation metric. Refer to https://explained.ai/rf-importance/ for an explanation of
|
|
1200
|
+
permutation importance.
|
|
1201
|
+
|
|
1202
|
+
subsample_size : int, default = 50
|
|
1203
|
+
The number of items to sample from ``data`` when computing feature importance. Larger values increase the accuracy of
|
|
1204
|
+
the feature importance scores. Runtime linearly scales with ``subsample_size``.
|
|
1205
|
+
time_limit : float, optional
|
|
1206
|
+
Time in seconds to limit the calculation of feature importance. If None, feature importance will calculate without early stopping.
|
|
1207
|
+
If ``method="permutation"``, a minimum of 1 full shuffle set will always be evaluated. If a shuffle set evaluation takes longer than
|
|
1208
|
+
``time_limit``, the method will take the length of a shuffle set evaluation to return regardless of the ``time_limit``.
|
|
1209
|
+
num_iterations : int, optional
|
|
1210
|
+
The number of different iterations of the data that are evaluated. If ``method="permutation"``, this will be interpreted
|
|
1211
|
+
as the number of shuffle sets (equivalent to ``num_shuffle_sets`` in :meth:`TabularPredictor.feature_importance`). If ``method="naive"``, the
|
|
1212
|
+
constant replacement approach is repeated for ``num_iterations`` times, and a different subsample of data (of size ``subsample_size``) will
|
|
1213
|
+
be taken in each iteration.
|
|
1214
|
+
Default is 1 for ``method="naive"`` and 5 for ``method="permutation"``. The value will be ignored if ``method="naive"`` and the subsample
|
|
1215
|
+
size is greater than the number of items in ``data`` as additional iterations will be redundant.
|
|
1216
|
+
Larger values will increase the quality of the importance evaluation.
|
|
1217
|
+
It is generally recommended to increase ``subsample_size`` before increasing ``num_iterations``.
|
|
1218
|
+
Runtime scales linearly with ``num_iterations``.
|
|
1219
|
+
random_seed : int or None, default = 123
|
|
1220
|
+
If provided, fixes the seed of the random number generator for all models. This guarantees reproducible
|
|
1221
|
+
results for feature importance.
|
|
1222
|
+
relative_scores : bool, default = False
|
|
1223
|
+
By default, this method will return expected average *absolute* improvement in the eval metric due to the feature. If True, then
|
|
1224
|
+
the statistics will be computed over the *relative* (percentage) improvements.
|
|
1225
|
+
include_confidence_band: bool, default = True
|
|
1226
|
+
If True, returned DataFrame will include two additional columns specifying confidence interval for the true underlying importance value of
|
|
1227
|
+
each feature. Increasing ``subsample_size`` and ``num_iterations`` will tighten the confidence interval.
|
|
1228
|
+
confidence_level: float, default = 0.99
|
|
1229
|
+
This argument is only considered when ``include_confidence_band=True``, and can be used to specify the confidence level used
|
|
1230
|
+
for constructing confidence intervals. For example, if ``confidence_level`` is set to 0.99, then the returned DataFrame will include
|
|
1231
|
+
columns ``p99_high`` and ``p99_low`` which indicates that the true feature importance will be between ``p99_high`` and ``p99_low`` 99% of
|
|
1232
|
+
the time (99% confidence interval). More generally, if ``confidence_level`` = 0.XX, then the columns containing the XX% confidence interval
|
|
1233
|
+
will be named ``pXX_high`` and ``pXX_low``.
|
|
1234
|
+
|
|
1235
|
+
Returns
|
|
1236
|
+
-------
|
|
1237
|
+
:class:`pd.DataFrame` of feature importance scores with 2 columns:
|
|
1238
|
+
index: The feature name.
|
|
1239
|
+
'importance': The estimated feature importance score.
|
|
1240
|
+
'stddev': The standard deviation of the feature importance score. If NaN, then not enough ``num_iterations`` were used.
|
|
1241
|
+
"""
|
|
1242
|
+
self._assert_is_fit("feature_importance")
|
|
1243
|
+
if data is not None:
|
|
1244
|
+
data = self._check_and_prepare_data_frame(data)
|
|
1245
|
+
data = self._check_and_prepare_data_frame_for_evaluation(data)
|
|
1246
|
+
|
|
1247
|
+
fi_df = self._learner.get_feature_importance(
|
|
1248
|
+
data=data,
|
|
1249
|
+
model=model,
|
|
1250
|
+
metric=metric,
|
|
1251
|
+
features=features,
|
|
1252
|
+
time_limit=time_limit,
|
|
1253
|
+
method=method,
|
|
1254
|
+
subsample_size=subsample_size,
|
|
1255
|
+
num_iterations=num_iterations,
|
|
1256
|
+
random_seed=random_seed,
|
|
1257
|
+
relative_scores=relative_scores,
|
|
1258
|
+
include_confidence_band=include_confidence_band,
|
|
1259
|
+
confidence_level=confidence_level,
|
|
1260
|
+
)
|
|
1261
|
+
return fi_df
|
|
1262
|
+
|
|
879
1263
|
@classmethod
|
|
880
1264
|
def _load_version_file(cls, path: str) -> str:
|
|
1265
|
+
"""
|
|
1266
|
+
Loads the version file that is part of the saved predictor artifact.
|
|
1267
|
+
|
|
1268
|
+
Parameters
|
|
1269
|
+
----------
|
|
1270
|
+
path: str
|
|
1271
|
+
The path that would be used to load the predictor via `predictor.load(path)`
|
|
1272
|
+
|
|
1273
|
+
Returns
|
|
1274
|
+
-------
|
|
1275
|
+
The version of AutoGluon used to fit the predictor, as a string.
|
|
1276
|
+
|
|
1277
|
+
"""
|
|
881
1278
|
version_file_path = os.path.join(path, cls._predictor_version_file_name)
|
|
882
|
-
|
|
1279
|
+
try:
|
|
1280
|
+
version = load_str.load(path=version_file_path)
|
|
1281
|
+
except:
|
|
1282
|
+
# Loads the old version file used in `autogluon.timeseries<=1.1.0`, named `__version__`.
|
|
1283
|
+
# This file name was changed because Kaggle does not allow uploading files named `__version__`.
|
|
1284
|
+
version_file_path = os.path.join(path, "__version__")
|
|
1285
|
+
version = load_str.load(path=version_file_path)
|
|
883
1286
|
return version
|
|
884
1287
|
|
|
885
1288
|
@classmethod
|
|
886
|
-
def load(cls, path:
|
|
1289
|
+
def load(cls, path: str | Path, require_version_match: bool = True) -> "TimeSeriesPredictor":
|
|
887
1290
|
"""Load an existing ``TimeSeriesPredictor`` from given ``path``.
|
|
888
1291
|
|
|
1292
|
+
.. warning::
|
|
1293
|
+
|
|
1294
|
+
:meth:`autogluon.timeseries.TimeSeriesPredictor.load` uses ``pickle`` module implicitly, which is known to
|
|
1295
|
+
be insecure. It is possible to construct malicious pickle data which will execute arbitrary code during
|
|
1296
|
+
unpickling. Never load data that could have come from an untrusted source, or that could have been tampered
|
|
1297
|
+
with. **Only load data you trust.**
|
|
1298
|
+
|
|
889
1299
|
Parameters
|
|
890
1300
|
----------
|
|
891
1301
|
path : str or pathlib.Path
|
|
@@ -907,14 +1317,18 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
907
1317
|
"""
|
|
908
1318
|
if not path:
|
|
909
1319
|
raise ValueError("`path` cannot be None or empty in load().")
|
|
910
|
-
path
|
|
1320
|
+
path = setup_outputdir(path, warn_if_exist=False)
|
|
1321
|
+
|
|
1322
|
+
predictor_path = Path(path) / cls.predictor_file_name
|
|
1323
|
+
if not predictor_path.exists():
|
|
1324
|
+
raise FileNotFoundError(f"No such file '{predictor_path}'")
|
|
911
1325
|
|
|
912
1326
|
try:
|
|
913
1327
|
version_saved = cls._load_version_file(path=path)
|
|
914
1328
|
except:
|
|
915
1329
|
logger.warning(
|
|
916
1330
|
f'WARNING: Could not find version file at "{os.path.join(path, cls._predictor_version_file_name)}".\n'
|
|
917
|
-
f"This means that the predictor was fit in
|
|
1331
|
+
f"This means that the predictor was fit in an AutoGluon version `<=0.7.0`."
|
|
918
1332
|
)
|
|
919
1333
|
version_saved = "Unknown (Likely <=0.7.0)"
|
|
920
1334
|
|
|
@@ -926,13 +1340,13 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
926
1340
|
)
|
|
927
1341
|
|
|
928
1342
|
logger.info(f"Loading predictor from path {path}")
|
|
929
|
-
learner =
|
|
930
|
-
predictor = load_pkl.load(path=
|
|
1343
|
+
learner = cls._learner_type.load(path)
|
|
1344
|
+
predictor = load_pkl.load(path=str(predictor_path))
|
|
931
1345
|
predictor._learner = learner
|
|
932
1346
|
predictor.path = learner.path
|
|
933
1347
|
return predictor
|
|
934
1348
|
|
|
935
|
-
def _save_version_file(self):
|
|
1349
|
+
def _save_version_file(self) -> None:
|
|
936
1350
|
version_file_contents = current_ag_version
|
|
937
1351
|
version_file_path = os.path.join(self.path, self._predictor_version_file_name)
|
|
938
1352
|
save_str.save(path=version_file_path, data=version_file_contents, verbose=False)
|
|
@@ -944,43 +1358,87 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
944
1358
|
(we do not recommend modifying the Predictor object yourself as it tracks many trained models).
|
|
945
1359
|
"""
|
|
946
1360
|
tmp_learner = self._learner
|
|
947
|
-
self._learner = None
|
|
1361
|
+
self._learner = None # type: ignore
|
|
948
1362
|
save_pkl.save(path=os.path.join(tmp_learner.path, self.predictor_file_name), object=self)
|
|
949
1363
|
self._learner = tmp_learner
|
|
950
1364
|
self._save_version_file()
|
|
951
1365
|
|
|
952
|
-
def info(self) ->
|
|
1366
|
+
def info(self) -> dict[str, Any]:
|
|
953
1367
|
"""Returns a dictionary of objects each describing an attribute of the training process and trained models."""
|
|
954
1368
|
return self._learner.get_info(include_model_info=True)
|
|
955
1369
|
|
|
956
1370
|
@property
|
|
957
1371
|
def model_best(self) -> str:
|
|
958
1372
|
"""Returns the name of the best model from trainer."""
|
|
1373
|
+
self._assert_is_fit("model_best")
|
|
959
1374
|
if self._trainer.model_best is not None:
|
|
960
1375
|
models = self._trainer.get_model_names()
|
|
961
1376
|
if self._trainer.model_best in models:
|
|
962
1377
|
return self._trainer.model_best
|
|
963
1378
|
return self._trainer.get_model_best()
|
|
964
1379
|
|
|
1380
|
+
def persist(self, models: Literal["all", "best"] | list[str] = "best", with_ancestors: bool = True) -> list[str]:
|
|
1381
|
+
"""Persist models in memory for reduced inference latency. This is particularly important if the models are being used for online
|
|
1382
|
+
inference where low latency is critical. If models are not persisted in memory, they are loaded from disk every time they are
|
|
1383
|
+
asked to make predictions. This is especially cumbersome for large deep learning based models which have to be loaded into
|
|
1384
|
+
accelerator (e.g., GPU) memory each time.
|
|
1385
|
+
|
|
1386
|
+
Parameters
|
|
1387
|
+
----------
|
|
1388
|
+
models : list of str or str, default = 'best'
|
|
1389
|
+
Model names of models to persist.
|
|
1390
|
+
If 'best' then the model with the highest validation score is persisted (this is the model used for prediction by default).
|
|
1391
|
+
If 'all' then all models are persisted. Valid models are listed in this ``predictor`` by calling ``predictor.model_names()``.
|
|
1392
|
+
with_ancestors : bool, default = True
|
|
1393
|
+
If True, all ancestor models of the provided models will also be persisted.
|
|
1394
|
+
If False, ensemble models will not have the models they depend on persisted unless those models were specified in ``models``.
|
|
1395
|
+
This will slow down inference as the ancestor models will still need to be loaded from disk for each predict call.
|
|
1396
|
+
Only relevant for ensemble models.
|
|
1397
|
+
|
|
1398
|
+
Returns
|
|
1399
|
+
-------
|
|
1400
|
+
list_of_models : list[str]
|
|
1401
|
+
List of persisted model names.
|
|
1402
|
+
"""
|
|
1403
|
+
self._assert_is_fit("persist")
|
|
1404
|
+
return self._learner.persist_trainer(models=models, with_ancestors=with_ancestors)
|
|
1405
|
+
|
|
1406
|
+
def unpersist(self) -> list[str]:
|
|
1407
|
+
"""Unpersist models in memory for reduced memory usage. If models are not persisted in memory, they are loaded from
|
|
1408
|
+
disk every time they are asked to make predictions.
|
|
1409
|
+
|
|
1410
|
+
Note: Another way to reset the predictor and unpersist models is to reload the predictor from disk
|
|
1411
|
+
via ``predictor = TimeSeriesPredictor.load(predictor.path)``.
|
|
1412
|
+
|
|
1413
|
+
Returns
|
|
1414
|
+
-------
|
|
1415
|
+
list_of_models : list[str]
|
|
1416
|
+
List of unpersisted model names.
|
|
1417
|
+
"""
|
|
1418
|
+
return self._learner.unpersist_trainer()
|
|
1419
|
+
|
|
965
1420
|
def leaderboard(
|
|
966
1421
|
self,
|
|
967
|
-
data:
|
|
1422
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
|
|
1423
|
+
cutoff: int | None = None,
|
|
1424
|
+
extra_info: bool = False,
|
|
1425
|
+
extra_metrics: list[str | TimeSeriesScorer] | None = None,
|
|
968
1426
|
display: bool = False,
|
|
969
1427
|
use_cache: bool = True,
|
|
970
1428
|
**kwargs,
|
|
971
1429
|
) -> pd.DataFrame:
|
|
972
1430
|
"""Return a leaderboard showing the performance of every trained model, the output is a
|
|
973
|
-
pandas
|
|
1431
|
+
pandas dataframe with columns:
|
|
974
1432
|
|
|
975
1433
|
* ``model``: The name of the model.
|
|
976
1434
|
* ``score_test``: The test score of the model on ``data``, if provided. Computed according to ``eval_metric``.
|
|
977
1435
|
* ``score_val``: The validation score of the model using the internal validation data. Computed according to ``eval_metric``.
|
|
978
1436
|
|
|
979
1437
|
.. note::
|
|
980
|
-
Metrics
|
|
1438
|
+
Metrics are always reported in 'higher is better' format.
|
|
981
1439
|
This means that metrics such as MASE or MAPE will be multiplied by -1, so their values will be negative.
|
|
982
1440
|
This is necessary to avoid the user needing to know the metric to understand if higher is better when
|
|
983
|
-
looking at leaderboard.
|
|
1441
|
+
looking at the leaderboard.
|
|
984
1442
|
|
|
985
1443
|
* ``pred_time_val``: Time taken by the model to predict on the validation data set
|
|
986
1444
|
* ``fit_time_marginal``: The fit time required to train the model (ignoring base models for ensembles).
|
|
@@ -989,19 +1447,35 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
989
1447
|
|
|
990
1448
|
Parameters
|
|
991
1449
|
----------
|
|
992
|
-
data :
|
|
993
|
-
dataset used for additional evaluation.
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
to
|
|
1004
|
-
|
|
1450
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
|
|
1451
|
+
dataset used for additional evaluation. Must include both historical and future data (i.e., length of all
|
|
1452
|
+
time series in ``data`` must be at least ``prediction_length + 1``, if ``cutoff`` is not provided,
|
|
1453
|
+
``-cutoff + 1`` otherwise).
|
|
1454
|
+
|
|
1455
|
+
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
1456
|
+
the predictor.
|
|
1457
|
+
|
|
1458
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
1459
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
1460
|
+
cutoff : int, optional
|
|
1461
|
+
A *negative* integer less than or equal to ``-1 * prediction_length`` denoting the time step in ``data``
|
|
1462
|
+
where the forecast evaluation starts, i.e., time series are evaluated from the ``-cutoff``-th to the
|
|
1463
|
+
``-cutoff + prediction_length``-th time step. Defaults to ``-1 * prediction_length``, using the last
|
|
1464
|
+
``prediction_length`` time steps of each time series for evaluation.
|
|
1465
|
+
extra_info : bool, default = False
|
|
1466
|
+
If True, the leaderboard will contain an additional column ``hyperparameters`` with the hyperparameters used
|
|
1467
|
+
by each model during training. An empty dictionary ``{}`` means that the model was trained with default
|
|
1468
|
+
hyperparameters.
|
|
1469
|
+
extra_metrics : list[str | TimeSeriesScorer], optional
|
|
1470
|
+
A list of metrics to calculate scores for and include in the output DataFrame.
|
|
1471
|
+
|
|
1472
|
+
Only valid when ``data`` is specified. The scores refer to the scores on ``data`` (same data as used to
|
|
1473
|
+
calculate the ``score_test`` column).
|
|
1474
|
+
|
|
1475
|
+
This list can contain any values which would also be valid for ``eval_metric`` when creating a :class:`~autogluon.timeseries.TimeSeriesPredictor`.
|
|
1476
|
+
|
|
1477
|
+
For each provided ``metric``, a column with name ``str(metric)`` will be added to the leaderboard, containing
|
|
1478
|
+
the value of the metric computed on ``data``.
|
|
1005
1479
|
display : bool, default = False
|
|
1006
1480
|
If True, the leaderboard DataFrame will be printed.
|
|
1007
1481
|
use_cache : bool, default = True
|
|
@@ -1014,6 +1488,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1014
1488
|
The leaderboard containing information on all models and in order of best model to worst in terms of
|
|
1015
1489
|
test performance.
|
|
1016
1490
|
"""
|
|
1491
|
+
self._assert_is_fit("leaderboard")
|
|
1017
1492
|
if "silent" in kwargs:
|
|
1018
1493
|
# keep `silent` logic for backwards compatibility
|
|
1019
1494
|
assert isinstance(kwargs["silent"], bool)
|
|
@@ -1021,17 +1496,62 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1021
1496
|
if len(kwargs) > 0:
|
|
1022
1497
|
for key in kwargs:
|
|
1023
1498
|
raise TypeError(f"TimeSeriesPredictor.leaderboard() got an unexpected keyword argument '{key}'")
|
|
1499
|
+
if data is None and extra_metrics is not None:
|
|
1500
|
+
raise ValueError("`extra_metrics` is only valid when `data` is specified.")
|
|
1501
|
+
if data is None and cutoff is not None:
|
|
1502
|
+
raise ValueError("`cutoff` is only valid when `data` is specified.")
|
|
1024
1503
|
|
|
1025
1504
|
if data is not None:
|
|
1026
1505
|
data = self._check_and_prepare_data_frame(data)
|
|
1027
|
-
self.
|
|
1028
|
-
|
|
1506
|
+
data = self._check_and_prepare_data_frame_for_evaluation(data, cutoff=cutoff)
|
|
1507
|
+
|
|
1508
|
+
leaderboard = self._learner.leaderboard(
|
|
1509
|
+
data, extra_info=extra_info, extra_metrics=extra_metrics, use_cache=use_cache
|
|
1510
|
+
)
|
|
1029
1511
|
if display:
|
|
1030
1512
|
with pd.option_context("display.max_rows", None, "display.max_columns", None, "display.width", 1000):
|
|
1031
1513
|
print(leaderboard)
|
|
1032
1514
|
return leaderboard
|
|
1033
1515
|
|
|
1034
|
-
def
|
|
1516
|
+
def make_future_data_frame(self, data: TimeSeriesDataFrame | pd.DataFrame | Path | str) -> pd.DataFrame:
|
|
1517
|
+
"""Generate a dataframe with the ``item_id`` and ``timestamp`` values corresponding to the forecast horizon.
|
|
1518
|
+
|
|
1519
|
+
Parameters
|
|
1520
|
+
----------
|
|
1521
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
1522
|
+
Historical time series data.
|
|
1523
|
+
|
|
1524
|
+
Returns
|
|
1525
|
+
-------
|
|
1526
|
+
forecast_horizon : pd.DataFrame
|
|
1527
|
+
Data frame with columns ``item_id`` and ``timestamp`` corresponding to the forecast horizon. For each item ID
|
|
1528
|
+
in ``data``, ``forecast_horizon`` will contain the timestamps for the next ``prediction_length`` time steps,
|
|
1529
|
+
following the end of each series in the input data.
|
|
1530
|
+
|
|
1531
|
+
Examples
|
|
1532
|
+
--------
|
|
1533
|
+
>>> print(data)
|
|
1534
|
+
target
|
|
1535
|
+
item_id timestamp
|
|
1536
|
+
A 2024-01-01 0
|
|
1537
|
+
2024-01-02 1
|
|
1538
|
+
2024-01-03 2
|
|
1539
|
+
B 2024-04-07 3
|
|
1540
|
+
2024-04-08 4
|
|
1541
|
+
>>> predictor = TimeSeriesPredictor(prediction_length=2, freq="D")
|
|
1542
|
+
>>> print(predictor.make_future_data_frame(data))
|
|
1543
|
+
item_id timestamp
|
|
1544
|
+
0 A 2024-01-04
|
|
1545
|
+
0 A 2024-01-05
|
|
1546
|
+
1 B 2024-04-09
|
|
1547
|
+
1 B 2024-04-10
|
|
1548
|
+
"""
|
|
1549
|
+
if self.freq is None:
|
|
1550
|
+
raise ValueError("Please fit the predictor before calling `make_future_data_frame`")
|
|
1551
|
+
data = self._check_and_prepare_data_frame(data)
|
|
1552
|
+
return make_future_data_frame(data, prediction_length=self.prediction_length, freq=self.freq)
|
|
1553
|
+
|
|
1554
|
+
def fit_summary(self, verbosity: int = 1) -> dict[str, Any]:
|
|
1035
1555
|
"""Output summary of information about models produced during
|
|
1036
1556
|
:meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`.
|
|
1037
1557
|
|
|
@@ -1042,10 +1562,11 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1042
1562
|
|
|
1043
1563
|
Returns
|
|
1044
1564
|
-------
|
|
1045
|
-
summary_dict :
|
|
1565
|
+
summary_dict : dict[str, Any]
|
|
1046
1566
|
Dict containing various detailed information. We do not recommend directly printing this dict as it may
|
|
1047
1567
|
be very large.
|
|
1048
1568
|
"""
|
|
1569
|
+
self._assert_is_fit("fit_summary")
|
|
1049
1570
|
# TODO: HPO-specific information currently not reported in fit_summary
|
|
1050
1571
|
# TODO: Revisit after ray tune integration
|
|
1051
1572
|
|
|
@@ -1066,7 +1587,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1066
1587
|
model_hyperparams = {}
|
|
1067
1588
|
for model_name in self.model_names():
|
|
1068
1589
|
model_obj = self._trainer.load_model(model_name)
|
|
1069
|
-
model_hyperparams[model_name] = model_obj.
|
|
1590
|
+
model_hyperparams[model_name] = model_obj.get_hyperparameters()
|
|
1070
1591
|
|
|
1071
1592
|
results["model_hyperparams"] = model_hyperparams
|
|
1072
1593
|
results["leaderboard"] = self._learner.leaderboard()
|
|
@@ -1081,7 +1602,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1081
1602
|
print("****************** End of fit() summary ******************")
|
|
1082
1603
|
return results
|
|
1083
1604
|
|
|
1084
|
-
def refit_full(self, model: str = "all", set_best_to_refit_full: bool = True) ->
|
|
1605
|
+
def refit_full(self, model: str = "all", set_best_to_refit_full: bool = True) -> dict[str, str]:
|
|
1085
1606
|
"""Retrain model on all of the data (training + validation).
|
|
1086
1607
|
|
|
1087
1608
|
This method can only be used if no ``tuning_data`` was passed to :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`.
|
|
@@ -1106,6 +1627,7 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1106
1627
|
``predictor.predict(data)`` is called will be the refit_full version instead of the original version of the
|
|
1107
1628
|
model. Has no effect if ``model`` is not the best model.
|
|
1108
1629
|
"""
|
|
1630
|
+
self._assert_is_fit("refit_full")
|
|
1109
1631
|
logger.warning(
|
|
1110
1632
|
"\tWARNING: refit_full functionality for TimeSeriesPredictor is experimental "
|
|
1111
1633
|
"and is not yet supported by all models."
|
|
@@ -1143,40 +1665,38 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1143
1665
|
)
|
|
1144
1666
|
return refit_full_dict
|
|
1145
1667
|
|
|
1146
|
-
def __dir__(self) -> List[str]:
|
|
1147
|
-
# This hides method from IPython autocomplete, but not VSCode autocomplete
|
|
1148
|
-
deprecated = ["score", "get_model_best", "get_model_names"]
|
|
1149
|
-
return [d for d in super().__dir__() if d not in deprecated]
|
|
1150
|
-
|
|
1151
1668
|
def _simulation_artifact(self, test_data: TimeSeriesDataFrame) -> dict:
|
|
1152
1669
|
"""[Advanced] Computes and returns the necessary information to perform offline ensemble simulation."""
|
|
1153
1670
|
|
|
1154
1671
|
def select_target(ts_df: TimeSeriesDataFrame) -> TimeSeriesDataFrame:
|
|
1155
1672
|
ts_df = ts_df.copy()
|
|
1156
1673
|
ts_df.static_features = None
|
|
1157
|
-
return ts_df[[self.target]]
|
|
1674
|
+
return cast(TimeSeriesDataFrame, ts_df[[self.target]])
|
|
1158
1675
|
|
|
1159
1676
|
test_data = self._check_and_prepare_data_frame(test_data)
|
|
1160
|
-
self.
|
|
1677
|
+
test_data = self._check_and_prepare_data_frame_for_evaluation(test_data, name="test_data")
|
|
1161
1678
|
test_data = self._learner.feature_generator.transform(test_data)
|
|
1162
1679
|
|
|
1163
1680
|
trainer = self._trainer
|
|
1164
1681
|
train_data = trainer.load_train_data()
|
|
1165
1682
|
val_data = trainer.load_val_data()
|
|
1166
|
-
|
|
1167
|
-
pred_proba_dict_val:
|
|
1168
|
-
|
|
1683
|
+
base_model_names = trainer.get_model_names(layer=0)
|
|
1684
|
+
pred_proba_dict_val: dict[str, list[TimeSeriesDataFrame]] = {
|
|
1685
|
+
model_name: trainer._get_model_oof_predictions(model_name)
|
|
1686
|
+
for model_name in base_model_names
|
|
1687
|
+
if "_FULL" not in model_name
|
|
1169
1688
|
}
|
|
1170
1689
|
|
|
1171
1690
|
past_data, known_covariates = test_data.get_model_inputs_for_scoring(
|
|
1172
|
-
prediction_length=self.prediction_length,
|
|
1691
|
+
prediction_length=self.prediction_length,
|
|
1692
|
+
known_covariates_names=trainer.covariate_metadata.known_covariates,
|
|
1173
1693
|
)
|
|
1174
|
-
pred_proba_dict_test
|
|
1175
|
-
|
|
1694
|
+
pred_proba_dict_test, _ = trainer.get_model_pred_dict(
|
|
1695
|
+
base_model_names, data=past_data, known_covariates=known_covariates
|
|
1176
1696
|
)
|
|
1177
1697
|
|
|
1178
|
-
y_val:
|
|
1179
|
-
select_target(df) for df in trainer.
|
|
1698
|
+
y_val: list[TimeSeriesDataFrame] = [
|
|
1699
|
+
select_target(df) for df in trainer._get_validation_windows(train_data=train_data, val_data=val_data)
|
|
1180
1700
|
]
|
|
1181
1701
|
y_test: TimeSeriesDataFrame = select_target(test_data)
|
|
1182
1702
|
|
|
@@ -1188,34 +1708,35 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1188
1708
|
target=self.target,
|
|
1189
1709
|
prediction_length=self.prediction_length,
|
|
1190
1710
|
eval_metric=self.eval_metric.name,
|
|
1191
|
-
eval_metric_seasonal_period=self.
|
|
1711
|
+
eval_metric_seasonal_period=self.eval_metric.seasonal_period,
|
|
1712
|
+
horizon_weight=self.eval_metric.horizon_weight,
|
|
1192
1713
|
quantile_levels=self.quantile_levels,
|
|
1193
1714
|
)
|
|
1194
1715
|
return simulation_dict
|
|
1195
1716
|
|
|
1196
1717
|
def plot(
|
|
1197
1718
|
self,
|
|
1198
|
-
data:
|
|
1199
|
-
predictions:
|
|
1200
|
-
quantile_levels:
|
|
1201
|
-
item_ids:
|
|
1719
|
+
data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
|
|
1720
|
+
predictions: TimeSeriesDataFrame | None = None,
|
|
1721
|
+
quantile_levels: list[float] | None = None,
|
|
1722
|
+
item_ids: list[str | int] | None = None,
|
|
1202
1723
|
max_num_item_ids: int = 8,
|
|
1203
|
-
max_history_length:
|
|
1204
|
-
point_forecast_column:
|
|
1205
|
-
matplotlib_rc_params:
|
|
1724
|
+
max_history_length: int | None = None,
|
|
1725
|
+
point_forecast_column: str | None = None,
|
|
1726
|
+
matplotlib_rc_params: dict | None = None,
|
|
1206
1727
|
):
|
|
1207
|
-
"""Plot
|
|
1728
|
+
"""Plot historical time series values and the forecasts.
|
|
1208
1729
|
|
|
1209
1730
|
Parameters
|
|
1210
1731
|
----------
|
|
1211
|
-
data :
|
|
1732
|
+
data : TimeSeriesDataFrame | pd.DataFrame | Path | str
|
|
1212
1733
|
Observed time series data.
|
|
1213
1734
|
predictions : TimeSeriesDataFrame, optional
|
|
1214
1735
|
Predictions generated by calling :meth:`~autogluon.timeseries.TimeSeriesPredictor.predict`.
|
|
1215
|
-
quantile_levels :
|
|
1736
|
+
quantile_levels : list[float], optional
|
|
1216
1737
|
Quantile levels for which to plot the prediction intervals. Defaults to lowest & highest quantile levels
|
|
1217
1738
|
available in ``predictions``.
|
|
1218
|
-
item_ids :
|
|
1739
|
+
item_ids : list[str | int], optional
|
|
1219
1740
|
If provided, plots will only be generated for time series with these item IDs. By default (if set to
|
|
1220
1741
|
``None``), item IDs are selected randomly. In either case, plots are generated for at most
|
|
1221
1742
|
``max_num_item_ids`` time series.
|
|
@@ -1227,8 +1748,8 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1227
1748
|
Name of the column in ``predictions`` that will be plotted as the point forecast. Defaults to ``"0.5"``,
|
|
1228
1749
|
if this column is present in ``predictions``, otherwise ``"mean"``.
|
|
1229
1750
|
matplotlib_rc_params : dict, optional
|
|
1230
|
-
Dictionary describing the plot style that will be passed to
|
|
1231
|
-
See
|
|
1751
|
+
Dictionary describing the plot style that will be passed to `matplotlib.pyplot.rc_context <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.rc_context.html>`_.
|
|
1752
|
+
See `matplotlib documentation <https://matplotlib.org/stable/users/explain/customizing.html#the-default-matplotlibrc-file>`_ for the list of available options.
|
|
1232
1753
|
"""
|
|
1233
1754
|
import matplotlib.pyplot as plt
|
|
1234
1755
|
|
|
@@ -1291,14 +1812,14 @@ class TimeSeriesPredictor(TimeSeriesPredictorDeprecatedMixin):
|
|
|
1291
1812
|
ax.plot(ts, label="Observed", color="C0")
|
|
1292
1813
|
|
|
1293
1814
|
if predictions is not None:
|
|
1294
|
-
forecast = predictions.loc[item_id]
|
|
1815
|
+
forecast: pd.DataFrame = predictions.loc[item_id] # type: ignore
|
|
1295
1816
|
point_forecast = forecast[point_forecast_column]
|
|
1296
1817
|
ax.plot(point_forecast, color="C1", label="Forecast")
|
|
1297
1818
|
if quantile_levels is not None:
|
|
1298
1819
|
for q in quantile_levels:
|
|
1299
1820
|
ax.fill_between(forecast.index, point_forecast, forecast[str(q)], color="C1", alpha=0.2)
|
|
1300
1821
|
if len(axes) > len(item_ids):
|
|
1301
|
-
axes[len(item_ids)].set_axis_off()
|
|
1302
|
-
handles, labels = axes[0].get_legend_handles_labels()
|
|
1822
|
+
axes[len(item_ids)].set_axis_off() # type: ignore
|
|
1823
|
+
handles, labels = axes[0].get_legend_handles_labels() # type: ignore
|
|
1303
1824
|
fig.legend(handles, labels, bbox_to_anchor=(0.5, 0.0), ncols=len(handles))
|
|
1304
1825
|
return fig
|