autogluon.timeseries 1.1.2b20241109__py3-none-any.whl → 1.1.2b20241112__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autogluon/timeseries/dataset/ts_dataframe.py +5 -1
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +73 -5
- autogluon/timeseries/models/chronos/model.py +67 -38
- autogluon/timeseries/models/chronos/pipeline/__init__.py +11 -0
- autogluon/timeseries/models/chronos/pipeline/base.py +146 -0
- autogluon/timeseries/models/chronos/{pipeline.py → pipeline/chronos.py} +66 -102
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +511 -0
- autogluon/timeseries/models/chronos/{utils.py → pipeline/utils.py} +37 -1
- autogluon/timeseries/models/gluonts/abstract_gluonts.py +1 -0
- autogluon/timeseries/models/gluonts/torch/models.py +3 -0
- autogluon/timeseries/models/local/abstract_local_model.py +4 -1
- autogluon/timeseries/models/local/statsforecast.py +3 -0
- autogluon/timeseries/models/multi_window/multi_window_model.py +5 -0
- autogluon/timeseries/predictor.py +1 -1
- autogluon/timeseries/regressor.py +146 -0
- autogluon/timeseries/transforms/scaler.py +1 -1
- autogluon/timeseries/utils/warning_filters.py +20 -0
- autogluon/timeseries/version.py +1 -1
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/METADATA +5 -5
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/RECORD +27 -23
- /autogluon.timeseries-1.1.2b20241109-py3.8-nspkg.pth → /autogluon.timeseries-1.1.2b20241112-py3.8-nspkg.pth +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/LICENSE +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/NOTICE +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/WHEEL +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.1.2b20241109.dist-info → autogluon.timeseries-1.1.2b20241112.dist-info}/zip-safe +0 -0
@@ -921,7 +921,11 @@ class TimeSeriesDataFrame(pd.DataFrame, TimeSeriesDataFrameDeprecatedMixin):
|
|
921
921
|
test_data : TimeSeriesDataFrame
|
922
922
|
Test portion of the data. Contains the slice ``[:end_idx]`` of each time series in the original dataset.
|
923
923
|
"""
|
924
|
-
|
924
|
+
df = self
|
925
|
+
if not df.index.is_monotonic_increasing:
|
926
|
+
logger.warning("Sorting the dataframe index before generating the train/test split.")
|
927
|
+
df = df.sort_index()
|
928
|
+
test_data = df.slice_by_timestep(None, end_index)
|
925
929
|
train_data = test_data.slice_by_timestep(None, -prediction_length)
|
926
930
|
|
927
931
|
if suffix is not None:
|
@@ -5,6 +5,8 @@ import time
|
|
5
5
|
from contextlib import nullcontext
|
6
6
|
from typing import Dict, List, Optional, Union
|
7
7
|
|
8
|
+
import pandas as pd
|
9
|
+
|
8
10
|
from autogluon.common import space
|
9
11
|
from autogluon.common.loaders import load_pkl
|
10
12
|
from autogluon.common.savers import save_pkl
|
@@ -13,8 +15,10 @@ from autogluon.core.hpo.executors import HpoExecutor, RayHpoExecutor
|
|
13
15
|
from autogluon.core.models import AbstractModel
|
14
16
|
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
15
17
|
from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_metric
|
18
|
+
from autogluon.timeseries.regressor import CovariateRegressor
|
16
19
|
from autogluon.timeseries.transforms import LocalTargetScaler, get_target_scaler_from_name
|
17
20
|
from autogluon.timeseries.utils.features import CovariateMetadata
|
21
|
+
from autogluon.timeseries.utils.forecast import get_forecast_horizon_index_ts_dataframe
|
18
22
|
from autogluon.timeseries.utils.warning_filters import disable_stdout, warning_filter
|
19
23
|
|
20
24
|
from .model_trial import model_trial, skip_hpo
|
@@ -164,6 +168,8 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
164
168
|
def _initialize(self, **kwargs) -> None:
|
165
169
|
self._init_params_aux()
|
166
170
|
self._init_params()
|
171
|
+
self.target_scaler = self._create_target_scaler()
|
172
|
+
self.covariate_regressor = self._create_covariate_regressor()
|
167
173
|
|
168
174
|
def _compute_fit_metadata(self, val_data: TimeSeriesDataFrame = None, **kwargs):
|
169
175
|
fit_metadata = dict(
|
@@ -208,7 +214,11 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
208
214
|
return info
|
209
215
|
|
210
216
|
def fit(
|
211
|
-
self,
|
217
|
+
self,
|
218
|
+
train_data: TimeSeriesDataFrame,
|
219
|
+
val_data: Optional[TimeSeriesDataFrame] = None,
|
220
|
+
time_limit: Optional[float] = None,
|
221
|
+
**kwargs,
|
212
222
|
) -> "AbstractTimeSeriesModel":
|
213
223
|
"""Fit timeseries model.
|
214
224
|
|
@@ -243,22 +253,33 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
243
253
|
model: AbstractTimeSeriesModel
|
244
254
|
The fitted model object
|
245
255
|
"""
|
256
|
+
start_time = time.monotonic()
|
246
257
|
self.initialize(**kwargs)
|
247
|
-
self.target_scaler = self._create_target_scaler()
|
248
258
|
if self.target_scaler is not None:
|
249
259
|
train_data = self.target_scaler.fit_transform(train_data)
|
250
260
|
|
261
|
+
if self.covariate_regressor is not None:
|
262
|
+
train_data = self.covariate_regressor.fit_transform(
|
263
|
+
train_data,
|
264
|
+
time_limit=0.5 * time_limit if time_limit is not None else None,
|
265
|
+
)
|
266
|
+
|
251
267
|
train_data = self.preprocess(train_data, is_train=True)
|
252
268
|
if self._get_tags()["can_use_val_data"] and val_data is not None:
|
253
269
|
if self.target_scaler is not None:
|
254
270
|
val_data = self.target_scaler.transform(val_data)
|
271
|
+
if self.covariate_regressor is not None:
|
272
|
+
val_data = self.covariate_regressor.transform(val_data)
|
255
273
|
val_data = self.preprocess(val_data, is_train=False)
|
256
|
-
|
274
|
+
|
275
|
+
if time_limit is not None:
|
276
|
+
time_limit = time_limit - (time.monotonic() - start_time)
|
277
|
+
return super().fit(train_data=train_data, val_data=val_data, time_limit=time_limit, **kwargs)
|
257
278
|
|
258
279
|
@property
|
259
280
|
def allowed_hyperparameters(self) -> List[str]:
|
260
281
|
"""List of hyperparameters allowed by the model."""
|
261
|
-
return ["target_scaler"]
|
282
|
+
return ["target_scaler", "covariate_regressor"]
|
262
283
|
|
263
284
|
def _create_target_scaler(self) -> Optional[LocalTargetScaler]:
|
264
285
|
"""Create a LocalTargetScaler object based on the value of the `target_scaler` hyperparameter."""
|
@@ -269,6 +290,32 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
269
290
|
else:
|
270
291
|
return None
|
271
292
|
|
293
|
+
def _create_covariate_regressor(self) -> Optional[CovariateRegressor]:
|
294
|
+
"""Create a CovariateRegressor object based on the value of the `covariate_regressor` hyperparameter."""
|
295
|
+
covariate_regressor = self._get_model_params().get("covariate_regressor")
|
296
|
+
if covariate_regressor is not None:
|
297
|
+
if len(self.metadata.known_covariates + self.metadata.static_features) == 0:
|
298
|
+
logger.debug(
|
299
|
+
"Skipping CovariateRegressor since the dataset contains no covariates or static features."
|
300
|
+
)
|
301
|
+
return None
|
302
|
+
else:
|
303
|
+
if isinstance(covariate_regressor, str):
|
304
|
+
return CovariateRegressor(covariate_regressor, target=self.target, metadata=self.metadata)
|
305
|
+
elif isinstance(covariate_regressor, CovariateRegressor):
|
306
|
+
logger.warning(
|
307
|
+
"Using a custom CovariateRegressor object is experimental functionality that may break in the future!"
|
308
|
+
)
|
309
|
+
covariate_regressor.target = self.target
|
310
|
+
covariate_regressor.metadata = self.metadata
|
311
|
+
return covariate_regressor
|
312
|
+
else:
|
313
|
+
raise ValueError(
|
314
|
+
f"Invalid value for covariate_regressor {covariate_regressor} of type {type(covariate_regressor)}"
|
315
|
+
)
|
316
|
+
else:
|
317
|
+
return None
|
318
|
+
|
272
319
|
def _fit(
|
273
320
|
self,
|
274
321
|
train_data: TimeSeriesDataFrame,
|
@@ -324,11 +371,19 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
324
371
|
"""
|
325
372
|
if self.target_scaler is not None:
|
326
373
|
data = self.target_scaler.fit_transform(data)
|
374
|
+
if self.covariate_regressor is not None:
|
375
|
+
data = self.covariate_regressor.fit_transform(data)
|
327
376
|
|
328
377
|
data = self.preprocess(data, is_train=False)
|
329
378
|
known_covariates = self.preprocess_known_covariates(known_covariates)
|
379
|
+
|
380
|
+
# FIXME: Set self.covariate_regressor=None so to avoid copying it across processes during _predict
|
381
|
+
# FIXME: The clean solution is to convert all methods executed in parallel to @classmethod
|
382
|
+
covariate_regressor = self.covariate_regressor
|
383
|
+
self.covariate_regressor = None
|
330
384
|
predictions = self._predict(data=data, known_covariates=known_covariates, **kwargs)
|
331
|
-
|
385
|
+
self.covariate_regressor = covariate_regressor
|
386
|
+
|
332
387
|
# "0.5" might be missing from the quantiles if self is a wrapper (MultiWindowBacktestingModel or ensemble)
|
333
388
|
if "0.5" in predictions.columns:
|
334
389
|
if self.eval_metric.optimized_by_median:
|
@@ -336,6 +391,19 @@ class AbstractTimeSeriesModel(AbstractModel):
|
|
336
391
|
if self.must_drop_median:
|
337
392
|
predictions = predictions.drop("0.5", axis=1)
|
338
393
|
|
394
|
+
if self.covariate_regressor is not None:
|
395
|
+
if known_covariates is None:
|
396
|
+
forecast_index = get_forecast_horizon_index_ts_dataframe(
|
397
|
+
data, prediction_length=self.prediction_length, freq=self.freq
|
398
|
+
)
|
399
|
+
known_covariates = pd.DataFrame(index=forecast_index, dtype="float32")
|
400
|
+
|
401
|
+
predictions = self.covariate_regressor.inverse_transform(
|
402
|
+
predictions,
|
403
|
+
known_covariates=known_covariates,
|
404
|
+
static_features=data.static_features,
|
405
|
+
)
|
406
|
+
|
339
407
|
if self.target_scaler is not None:
|
340
408
|
predictions = self.target_scaler.inverse_transform(predictions)
|
341
409
|
return predictions
|
@@ -9,9 +9,9 @@ from autogluon.common.loaders import load_pkl
|
|
9
9
|
from autogluon.timeseries.dataset.ts_dataframe import TimeSeriesDataFrame
|
10
10
|
from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
|
11
11
|
from autogluon.timeseries.utils.forecast import get_forecast_horizon_index_ts_dataframe
|
12
|
-
from autogluon.timeseries.utils.warning_filters import warning_filter
|
12
|
+
from autogluon.timeseries.utils.warning_filters import disable_duplicate_logs, warning_filter
|
13
13
|
|
14
|
-
logger = logging.getLogger(
|
14
|
+
logger = logging.getLogger("autogluon.timeseries.models.chronos")
|
15
15
|
|
16
16
|
|
17
17
|
# allowed HuggingFace model paths with custom parameter definitions
|
@@ -41,6 +41,21 @@ MODEL_CONFIGS = {
|
|
41
41
|
"default_torch_dtype": "bfloat16",
|
42
42
|
"default_batch_size": 8,
|
43
43
|
},
|
44
|
+
"chronos-bolt-mini": {
|
45
|
+
"num_gpus": 0,
|
46
|
+
"default_torch_dtype": "auto",
|
47
|
+
"default_batch_size": 256,
|
48
|
+
},
|
49
|
+
"chronos-bolt-small": {
|
50
|
+
"num_gpus": 0,
|
51
|
+
"default_torch_dtype": "auto",
|
52
|
+
"default_batch_size": 256,
|
53
|
+
},
|
54
|
+
"chronos-bolt-base": {
|
55
|
+
"num_gpus": 0,
|
56
|
+
"default_torch_dtype": "auto",
|
57
|
+
"default_batch_size": 256,
|
58
|
+
},
|
44
59
|
}
|
45
60
|
|
46
61
|
|
@@ -50,22 +65,29 @@ MODEL_ALIASES = {
|
|
50
65
|
"small": "autogluon/chronos-t5-small",
|
51
66
|
"base": "autogluon/chronos-t5-base",
|
52
67
|
"large": "autogluon/chronos-t5-large",
|
68
|
+
"bolt-mini": "autogluon/chronos-bolt-mini",
|
69
|
+
"bolt-small": "autogluon/chronos-bolt-small",
|
70
|
+
"bolt-base": "autogluon/chronos-bolt-base",
|
53
71
|
}
|
54
72
|
|
55
73
|
|
56
74
|
class ChronosModel(AbstractTimeSeriesModel):
|
57
|
-
"""Chronos pretrained time series forecasting models
|
58
|
-
`ChronosModel <https://github.com/amazon-science/chronos-forecasting/blob/main/src/chronos/chronos.py>`_ implementation
|
75
|
+
"""Chronos pretrained time series forecasting models. Models can be based on the original
|
76
|
+
`ChronosModel <https://github.com/amazon-science/chronos-forecasting/blob/main/src/chronos/chronos.py>`_ implementation,
|
77
|
+
as well as a newer family of Chronos-Bolt models which are capable of much faster inference.
|
59
78
|
|
60
|
-
Chronos is family of pretrained models, based on the T5 family, with number of parameters ranging between
|
61
|
-
The full collection of Chronos models is available on
|
79
|
+
The original Chronos is a family of pretrained models, based on the T5 family, with number of parameters ranging between
|
80
|
+
8M and 710M. The full collection of Chronos models is available on
|
62
81
|
`Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_. For Chronos small,
|
63
|
-
base, and large variants a GPU is required to perform inference efficiently.
|
64
|
-
|
65
|
-
|
66
|
-
which are treated as tokens, effectively performing regression by classification. This results in a simple and flexible framework
|
82
|
+
base, and large variants a GPU is required to perform inference efficiently. Chronos takes a minimalistic approach to
|
83
|
+
pretraining time series models, by discretizing time series data directly into bins which are treated as tokens,
|
84
|
+
effectively performing regression by classification. This results in a simple and flexible framework
|
67
85
|
for using any language model in the context of time series forecasting. See [Ansari2024]_ for more information.
|
68
86
|
|
87
|
+
The newer Chronos-Bolt variants enable much faster inference by first "patching" the time series. The resulting
|
88
|
+
time series is then fed into a T5 model for forecasting. The Chronos-Bolt variants are capable of much faster inference,
|
89
|
+
and can all run on CPUs. Chronos-Bolt models are also available on Hugging Face <https://huggingface.co/autogluon/>`_.
|
90
|
+
|
69
91
|
References
|
70
92
|
----------
|
71
93
|
.. [Ansari2024] Ansari, Abdul Fatir, Stella, Lorenzo et al.
|
@@ -79,7 +101,8 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
79
101
|
Model path used for the model, i.e., a HuggingFace transformers ``name_or_path``. Can be a
|
80
102
|
compatible model name on HuggingFace Hub or a local path to a model directory. Original
|
81
103
|
Chronos models (i.e., ``autogluon/chronos-t5-{model_size}``) can be specified with aliases
|
82
|
-
``tiny``, ``mini`` , ``small``, ``base``, and ``large``.
|
104
|
+
``tiny``, ``mini`` , ``small``, ``base``, and ``large``. Chronos-Bolt models can be specified
|
105
|
+
with ``bolt-mini``, ``bolt-small``, and ``bolt-base``.
|
83
106
|
batch_size : int, default = 16
|
84
107
|
Size of batches used during inference
|
85
108
|
num_samples : int, default = 20
|
@@ -90,11 +113,15 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
90
113
|
context_length : int or None, default = None
|
91
114
|
The context length to use in the model. Shorter context lengths will decrease model accuracy, but result
|
92
115
|
in faster inference. If None, the model will infer context length from the data set length at inference
|
93
|
-
time, but set it to a maximum of
|
116
|
+
time, but set it to a maximum of 2048. Note that this is only the context length used to pass data into
|
117
|
+
the model. Individual model implementations may have different context lengths specified in their configuration,
|
118
|
+
and may truncate the context further. For example, original Chronos models have a context length of 512, but
|
119
|
+
Chronos-Bolt models handle contexts up to 2048.
|
94
120
|
optimization_strategy : {None, "onnx", "openvino"}, default = None
|
95
121
|
Optimization strategy to use for inference on CPUs. If None, the model will use the default implementation.
|
96
122
|
If `onnx`, the model will be converted to ONNX and the inference will be performed using ONNX. If ``openvino``,
|
97
|
-
inference will be performed with the model compiled to OpenVINO.
|
123
|
+
inference will be performed with the model compiled to OpenVINO. These optimizations are only available for
|
124
|
+
the original set of Chronos models, and not in Chronos-Bolt where they are not needed.
|
98
125
|
torch_dtype : torch.dtype or {"auto", "bfloat16", "float32", "float64"}, default = "auto"
|
99
126
|
Torch data type for model weights, provided to ``from_pretrained`` method of Hugging Face AutoModels. If
|
100
127
|
original Chronos models are specified and the model size is ``small``, ``base``, or ``large``, the
|
@@ -107,7 +134,7 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
107
134
|
# default number of samples for prediction
|
108
135
|
default_num_samples: int = 20
|
109
136
|
default_model_path = "autogluon/chronos-t5-small"
|
110
|
-
maximum_context_length =
|
137
|
+
maximum_context_length = 2048
|
111
138
|
|
112
139
|
def __init__(
|
113
140
|
self,
|
@@ -159,7 +186,7 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
159
186
|
**kwargs,
|
160
187
|
)
|
161
188
|
|
162
|
-
self.model_pipeline: Optional[Any] = None # of type
|
189
|
+
self.model_pipeline: Optional[Any] = None # of type BaseChronosPipeline
|
163
190
|
self.time_limit: Optional[float] = None
|
164
191
|
|
165
192
|
def save(self, path: str = None, verbose: bool = True) -> str:
|
@@ -218,8 +245,8 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
218
245
|
minimum_resources["num_gpus"] = self.min_num_gpus
|
219
246
|
return minimum_resources
|
220
247
|
|
221
|
-
def load_model_pipeline(self
|
222
|
-
from .pipeline import
|
248
|
+
def load_model_pipeline(self):
|
249
|
+
from .pipeline import BaseChronosPipeline
|
223
250
|
|
224
251
|
gpu_available = self._is_gpu_available()
|
225
252
|
|
@@ -232,18 +259,17 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
232
259
|
|
233
260
|
device = self.device or ("cuda" if gpu_available else "cpu")
|
234
261
|
|
235
|
-
pipeline =
|
262
|
+
pipeline = BaseChronosPipeline.from_pretrained(
|
236
263
|
self.model_path,
|
237
264
|
device_map=device,
|
238
|
-
optimization_strategy=self.optimization_strategy,
|
239
265
|
torch_dtype=self.torch_dtype,
|
240
|
-
|
266
|
+
optimization_strategy=self.optimization_strategy,
|
241
267
|
)
|
242
268
|
|
243
269
|
self.model_pipeline = pipeline
|
244
270
|
|
245
271
|
def persist(self) -> "ChronosModel":
|
246
|
-
self.load_model_pipeline(
|
272
|
+
self.load_model_pipeline()
|
247
273
|
return self
|
248
274
|
|
249
275
|
def _fit(
|
@@ -263,7 +289,7 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
263
289
|
num_workers: int = 0,
|
264
290
|
time_limit: Optional[float] = None,
|
265
291
|
):
|
266
|
-
from .utils import ChronosInferenceDataLoader, ChronosInferenceDataset, timeout_callback
|
292
|
+
from .pipeline.utils import ChronosInferenceDataLoader, ChronosInferenceDataset, timeout_callback
|
267
293
|
|
268
294
|
chronos_dataset = ChronosInferenceDataset(
|
269
295
|
target_df=data,
|
@@ -290,6 +316,9 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
290
316
|
# and use that to determine the context length of the model. If the context length is specified
|
291
317
|
# during initialization, this is always used. If not, the context length is set to the longest
|
292
318
|
# item length. The context length is always capped by self.maximum_context_length.
|
319
|
+
# Note that this is independent of the model's own context length set in the model's config file.
|
320
|
+
# For example, if the context_length is set to 2048 here but the model expects context length
|
321
|
+
# (according to its config.json file) of 512, it will further truncate the series during inference.
|
293
322
|
context_length = self.context_length or min(
|
294
323
|
data.num_timesteps_per_item().max(),
|
295
324
|
self.maximum_context_length,
|
@@ -300,7 +329,7 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
300
329
|
|
301
330
|
if self.model_pipeline is None:
|
302
331
|
# load model pipeline to device memory
|
303
|
-
self.load_model_pipeline(
|
332
|
+
self.load_model_pipeline()
|
304
333
|
|
305
334
|
inference_data_loader = self._get_inference_data_loader(
|
306
335
|
data=data,
|
@@ -308,28 +337,28 @@ class ChronosModel(AbstractTimeSeriesModel):
|
|
308
337
|
context_length=context_length,
|
309
338
|
time_limit=kwargs.get("time_limit"),
|
310
339
|
)
|
340
|
+
|
311
341
|
self.model_pipeline.model.eval()
|
312
|
-
with torch.inference_mode():
|
313
|
-
|
314
|
-
|
342
|
+
with torch.inference_mode(), disable_duplicate_logs(logger):
|
343
|
+
batch_quantiles, batch_means = [], []
|
344
|
+
for batch in inference_data_loader:
|
345
|
+
qs, mn = self.model_pipeline.predict_quantiles(
|
315
346
|
batch,
|
316
347
|
prediction_length=self.prediction_length,
|
348
|
+
quantile_levels=self.quantile_levels,
|
317
349
|
num_samples=self.num_samples,
|
318
|
-
limit_prediction_length=False,
|
319
350
|
)
|
320
|
-
.
|
321
|
-
.
|
322
|
-
.numpy()
|
323
|
-
for batch in inference_data_loader
|
324
|
-
]
|
325
|
-
|
326
|
-
samples = np.concatenate(prediction_samples, axis=0).swapaxes(1, 2).reshape(-1, self.num_samples)
|
327
|
-
|
328
|
-
mean = samples.mean(axis=-1, keepdims=True)
|
329
|
-
quantiles = np.quantile(samples, self.quantile_levels, axis=-1).T
|
351
|
+
batch_quantiles.append(qs.numpy())
|
352
|
+
batch_means.append(mn.numpy())
|
330
353
|
|
331
354
|
df = pd.DataFrame(
|
332
|
-
np.concatenate(
|
355
|
+
np.concatenate(
|
356
|
+
[
|
357
|
+
np.concatenate(batch_means, axis=0).reshape(-1, 1),
|
358
|
+
np.concatenate(batch_quantiles, axis=0).reshape(-1, len(self.quantile_levels)),
|
359
|
+
],
|
360
|
+
axis=1,
|
361
|
+
),
|
333
362
|
columns=["mean"] + [str(q) for q in self.quantile_levels],
|
334
363
|
index=get_forecast_horizon_index_ts_dataframe(data, self.prediction_length, freq=self.freq),
|
335
364
|
)
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Authors: Lorenzo Stella <stellalo@amazon.com>, Caner Turkmen <atturkm@amazon.com>
|
2
|
+
|
3
|
+
from enum import Enum
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Dict, List, Optional, Tuple, Union
|
6
|
+
|
7
|
+
import torch
|
8
|
+
|
9
|
+
from .utils import left_pad_and_stack_1D
|
10
|
+
|
11
|
+
|
12
|
+
class ForecastType(Enum):
|
13
|
+
SAMPLES = "samples"
|
14
|
+
QUANTILES = "quantiles"
|
15
|
+
|
16
|
+
|
17
|
+
class PipelineRegistry(type):
|
18
|
+
REGISTRY: Dict[str, "PipelineRegistry"] = {}
|
19
|
+
|
20
|
+
def __new__(cls, name, bases, attrs):
|
21
|
+
"""See, https://github.com/faif/python-patterns."""
|
22
|
+
new_cls = type.__new__(cls, name, bases, attrs)
|
23
|
+
if name is not None:
|
24
|
+
cls.REGISTRY[name] = new_cls
|
25
|
+
if aliases := attrs.get("_aliases"):
|
26
|
+
for alias in aliases:
|
27
|
+
cls.REGISTRY[alias] = new_cls
|
28
|
+
return new_cls
|
29
|
+
|
30
|
+
|
31
|
+
class BaseChronosPipeline(metaclass=PipelineRegistry):
|
32
|
+
forecast_type: ForecastType
|
33
|
+
dtypes = {
|
34
|
+
"bfloat16": torch.bfloat16,
|
35
|
+
"float32": torch.float32,
|
36
|
+
"float64": torch.float64,
|
37
|
+
}
|
38
|
+
|
39
|
+
def _prepare_and_validate_context(self, context: Union[torch.Tensor, List[torch.Tensor]]):
|
40
|
+
if isinstance(context, list):
|
41
|
+
context = left_pad_and_stack_1D(context)
|
42
|
+
assert isinstance(context, torch.Tensor)
|
43
|
+
if context.ndim == 1:
|
44
|
+
context = context.unsqueeze(0)
|
45
|
+
assert context.ndim == 2
|
46
|
+
|
47
|
+
return context
|
48
|
+
|
49
|
+
def predict(
|
50
|
+
self,
|
51
|
+
context: Union[torch.Tensor, List[torch.Tensor]],
|
52
|
+
prediction_length: Optional[int] = None,
|
53
|
+
**kwargs,
|
54
|
+
):
|
55
|
+
"""
|
56
|
+
Get forecasts for the given time series.
|
57
|
+
|
58
|
+
Parameters
|
59
|
+
----------
|
60
|
+
context
|
61
|
+
Input series. This is either a 1D tensor, or a list
|
62
|
+
of 1D tensors, or a 2D tensor whose first dimension
|
63
|
+
is batch. In the latter case, use left-padding with
|
64
|
+
``torch.nan`` to align series of different lengths.
|
65
|
+
prediction_length
|
66
|
+
Time steps to predict. Defaults to a model-dependent
|
67
|
+
value if not given.
|
68
|
+
|
69
|
+
Returns
|
70
|
+
-------
|
71
|
+
forecasts
|
72
|
+
Tensor containing forecasts. The layout and meaning
|
73
|
+
of the forecasts values depends on ``self.forecast_type``.
|
74
|
+
"""
|
75
|
+
raise NotImplementedError()
|
76
|
+
|
77
|
+
def predict_quantiles(
|
78
|
+
self, context: torch.Tensor, prediction_length: int, quantile_levels: List[float], **kwargs
|
79
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
80
|
+
"""
|
81
|
+
Get quantile and mean forecasts for given time series. All
|
82
|
+
predictions are returned on the CPU.
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
context
|
87
|
+
Input series. This is either a 1D tensor, or a list
|
88
|
+
of 1D tensors, or a 2D tensor whose first dimension
|
89
|
+
is batch. In the latter case, use left-padding with
|
90
|
+
``torch.nan`` to align series of different lengths.
|
91
|
+
prediction_length
|
92
|
+
Time steps to predict. Defaults to a model-dependent
|
93
|
+
value if not given.
|
94
|
+
quantile_levels: List[float]
|
95
|
+
Quantile levels to compute
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
quantiles
|
100
|
+
Tensor containing quantile forecasts. Shape
|
101
|
+
(batch_size, prediction_length, num_quantiles)
|
102
|
+
mean
|
103
|
+
Tensor containing mean (point) forecasts. Shape
|
104
|
+
(batch_size, prediction_length)
|
105
|
+
"""
|
106
|
+
raise NotImplementedError()
|
107
|
+
|
108
|
+
@classmethod
|
109
|
+
def from_pretrained(
|
110
|
+
cls,
|
111
|
+
pretrained_model_name_or_path: Union[str, Path],
|
112
|
+
*model_args,
|
113
|
+
force=False,
|
114
|
+
**kwargs,
|
115
|
+
):
|
116
|
+
"""
|
117
|
+
Load the model, either from a local path or from the HuggingFace Hub.
|
118
|
+
Supports the same arguments as ``AutoConfig`` and ``AutoModel``
|
119
|
+
from ``transformers``.
|
120
|
+
|
121
|
+
When a local path is provided, supports both a folder or a .tar.gz archive.
|
122
|
+
"""
|
123
|
+
from transformers import AutoConfig
|
124
|
+
|
125
|
+
if str(pretrained_model_name_or_path).startswith("s3://"):
|
126
|
+
from .utils import cache_model_from_s3
|
127
|
+
|
128
|
+
local_model_path = cache_model_from_s3(str(pretrained_model_name_or_path), force=force)
|
129
|
+
return cls.from_pretrained(local_model_path, *model_args, **kwargs)
|
130
|
+
|
131
|
+
torch_dtype = kwargs.get("torch_dtype", "auto")
|
132
|
+
if torch_dtype != "auto" and isinstance(torch_dtype, str):
|
133
|
+
kwargs["torch_dtype"] = cls.dtypes[torch_dtype]
|
134
|
+
|
135
|
+
config = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
136
|
+
is_valid_config = hasattr(config, "chronos_pipeline_class") or hasattr(config, "chronos_config")
|
137
|
+
|
138
|
+
if not is_valid_config:
|
139
|
+
raise ValueError("Not a Chronos config file")
|
140
|
+
|
141
|
+
pipeline_class_name = getattr(config, "chronos_pipeline_class", "ChronosPipeline")
|
142
|
+
class_ = PipelineRegistry.REGISTRY.get(pipeline_class_name)
|
143
|
+
if class_ is None:
|
144
|
+
raise ValueError(f"Trying to load unknown pipeline class: {pipeline_class_name}")
|
145
|
+
|
146
|
+
return class_.from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
|