autogluon.timeseries 1.2.1b20250224__py3-none-any.whl → 1.4.1b20251215__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of autogluon.timeseries might be problematic. Click here for more details.

Files changed (108) hide show
  1. autogluon/timeseries/configs/__init__.py +3 -2
  2. autogluon/timeseries/configs/hyperparameter_presets.py +62 -0
  3. autogluon/timeseries/configs/predictor_presets.py +106 -0
  4. autogluon/timeseries/dataset/ts_dataframe.py +256 -141
  5. autogluon/timeseries/learner.py +86 -52
  6. autogluon/timeseries/metrics/__init__.py +42 -8
  7. autogluon/timeseries/metrics/abstract.py +89 -19
  8. autogluon/timeseries/metrics/point.py +142 -53
  9. autogluon/timeseries/metrics/quantile.py +46 -21
  10. autogluon/timeseries/metrics/utils.py +4 -4
  11. autogluon/timeseries/models/__init__.py +8 -2
  12. autogluon/timeseries/models/abstract/__init__.py +2 -2
  13. autogluon/timeseries/models/abstract/abstract_timeseries_model.py +361 -592
  14. autogluon/timeseries/models/abstract/model_trial.py +2 -1
  15. autogluon/timeseries/models/abstract/tunable.py +189 -0
  16. autogluon/timeseries/models/autogluon_tabular/__init__.py +2 -0
  17. autogluon/timeseries/models/autogluon_tabular/mlforecast.py +282 -194
  18. autogluon/timeseries/models/autogluon_tabular/per_step.py +513 -0
  19. autogluon/timeseries/models/autogluon_tabular/transforms.py +25 -18
  20. autogluon/timeseries/models/chronos/__init__.py +2 -1
  21. autogluon/timeseries/models/chronos/chronos2.py +361 -0
  22. autogluon/timeseries/models/chronos/model.py +219 -138
  23. autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +81 -50
  24. autogluon/timeseries/models/ensemble/__init__.py +37 -2
  25. autogluon/timeseries/models/ensemble/abstract.py +107 -0
  26. autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
  27. autogluon/timeseries/models/ensemble/array_based/abstract.py +240 -0
  28. autogluon/timeseries/models/ensemble/array_based/models.py +185 -0
  29. autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +12 -0
  30. autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +88 -0
  31. autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
  32. autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +94 -0
  33. autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +107 -0
  34. autogluon/timeseries/models/ensemble/ensemble_selection.py +167 -0
  35. autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
  36. autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
  37. autogluon/timeseries/models/ensemble/weighted/abstract.py +45 -0
  38. autogluon/timeseries/models/ensemble/weighted/basic.py +91 -0
  39. autogluon/timeseries/models/ensemble/weighted/greedy.py +62 -0
  40. autogluon/timeseries/models/gluonts/__init__.py +1 -1
  41. autogluon/timeseries/models/gluonts/{abstract_gluonts.py → abstract.py} +148 -208
  42. autogluon/timeseries/models/gluonts/dataset.py +109 -0
  43. autogluon/timeseries/models/gluonts/{torch/models.py → models.py} +38 -22
  44. autogluon/timeseries/models/local/__init__.py +0 -7
  45. autogluon/timeseries/models/local/abstract_local_model.py +71 -74
  46. autogluon/timeseries/models/local/naive.py +13 -9
  47. autogluon/timeseries/models/local/npts.py +9 -2
  48. autogluon/timeseries/models/local/statsforecast.py +52 -36
  49. autogluon/timeseries/models/multi_window/multi_window_model.py +65 -45
  50. autogluon/timeseries/models/registry.py +64 -0
  51. autogluon/timeseries/models/toto/__init__.py +3 -0
  52. autogluon/timeseries/models/toto/_internal/__init__.py +9 -0
  53. autogluon/timeseries/models/toto/_internal/backbone/__init__.py +3 -0
  54. autogluon/timeseries/models/toto/_internal/backbone/attention.py +196 -0
  55. autogluon/timeseries/models/toto/_internal/backbone/backbone.py +262 -0
  56. autogluon/timeseries/models/toto/_internal/backbone/distribution.py +70 -0
  57. autogluon/timeseries/models/toto/_internal/backbone/kvcache.py +136 -0
  58. autogluon/timeseries/models/toto/_internal/backbone/rope.py +89 -0
  59. autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
  60. autogluon/timeseries/models/toto/_internal/backbone/scaler.py +305 -0
  61. autogluon/timeseries/models/toto/_internal/backbone/transformer.py +333 -0
  62. autogluon/timeseries/models/toto/_internal/dataset.py +165 -0
  63. autogluon/timeseries/models/toto/_internal/forecaster.py +423 -0
  64. autogluon/timeseries/models/toto/dataloader.py +108 -0
  65. autogluon/timeseries/models/toto/hf_pretrained_model.py +200 -0
  66. autogluon/timeseries/models/toto/model.py +249 -0
  67. autogluon/timeseries/predictor.py +685 -297
  68. autogluon/timeseries/regressor.py +94 -44
  69. autogluon/timeseries/splitter.py +8 -32
  70. autogluon/timeseries/trainer/__init__.py +3 -0
  71. autogluon/timeseries/trainer/ensemble_composer.py +444 -0
  72. autogluon/timeseries/trainer/model_set_builder.py +256 -0
  73. autogluon/timeseries/trainer/prediction_cache.py +149 -0
  74. autogluon/timeseries/{trainer.py → trainer/trainer.py} +387 -390
  75. autogluon/timeseries/trainer/utils.py +17 -0
  76. autogluon/timeseries/transforms/__init__.py +2 -13
  77. autogluon/timeseries/transforms/covariate_scaler.py +34 -40
  78. autogluon/timeseries/transforms/target_scaler.py +37 -20
  79. autogluon/timeseries/utils/constants.py +10 -0
  80. autogluon/timeseries/utils/datetime/lags.py +3 -5
  81. autogluon/timeseries/utils/datetime/seasonality.py +1 -3
  82. autogluon/timeseries/utils/datetime/time_features.py +2 -2
  83. autogluon/timeseries/utils/features.py +70 -47
  84. autogluon/timeseries/utils/forecast.py +19 -14
  85. autogluon/timeseries/utils/timer.py +173 -0
  86. autogluon/timeseries/utils/warning_filters.py +4 -2
  87. autogluon/timeseries/version.py +1 -1
  88. autogluon.timeseries-1.4.1b20251215-py3.11-nspkg.pth +1 -0
  89. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/METADATA +49 -36
  90. autogluon_timeseries-1.4.1b20251215.dist-info/RECORD +103 -0
  91. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/WHEEL +1 -1
  92. autogluon/timeseries/configs/presets_configs.py +0 -79
  93. autogluon/timeseries/evaluator.py +0 -6
  94. autogluon/timeseries/models/chronos/pipeline/__init__.py +0 -11
  95. autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
  96. autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -585
  97. autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -518
  98. autogluon/timeseries/models/ensemble/abstract_timeseries_ensemble.py +0 -78
  99. autogluon/timeseries/models/ensemble/greedy_ensemble.py +0 -170
  100. autogluon/timeseries/models/gluonts/torch/__init__.py +0 -0
  101. autogluon/timeseries/models/presets.py +0 -360
  102. autogluon.timeseries-1.2.1b20250224-py3.9-nspkg.pth +0 -1
  103. autogluon.timeseries-1.2.1b20250224.dist-info/RECORD +0 -68
  104. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/LICENSE +0 -0
  105. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info/licenses}/NOTICE +0 -0
  106. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/namespace_packages.txt +0 -0
  107. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/top_level.txt +0 -0
  108. {autogluon.timeseries-1.2.1b20250224.dist-info → autogluon_timeseries-1.4.1b20251215.dist-info}/zip-safe +0 -0
@@ -0,0 +1,109 @@
1
+ from typing import Any, Iterator, Type
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from gluonts.dataset.common import Dataset as GluonTSDataset
6
+ from gluonts.dataset.field_names import FieldName
7
+
8
+ from autogluon.timeseries.dataset import TimeSeriesDataFrame
9
+ from autogluon.timeseries.utils.datetime import norm_freq_str
10
+
11
+
12
+ class SimpleGluonTSDataset(GluonTSDataset):
13
+ """Wrapper for TimeSeriesDataFrame that is compatible with the GluonTS Dataset API."""
14
+
15
+ def __init__(
16
+ self,
17
+ target_df: TimeSeriesDataFrame,
18
+ freq: str,
19
+ target_column: str = "target",
20
+ feat_static_cat: np.ndarray | None = None,
21
+ feat_static_real: np.ndarray | None = None,
22
+ feat_dynamic_cat: np.ndarray | None = None,
23
+ feat_dynamic_real: np.ndarray | None = None,
24
+ past_feat_dynamic_cat: np.ndarray | None = None,
25
+ past_feat_dynamic_real: np.ndarray | None = None,
26
+ includes_future: bool = False,
27
+ prediction_length: int | None = None,
28
+ ):
29
+ assert target_df is not None
30
+ # Convert TimeSeriesDataFrame to pd.Series for faster processing
31
+ self.target_array = target_df[target_column].to_numpy(np.float32)
32
+ self.feat_static_cat = self._astype(feat_static_cat, dtype=np.int64)
33
+ self.feat_static_real = self._astype(feat_static_real, dtype=np.float32)
34
+ self.feat_dynamic_cat = self._astype(feat_dynamic_cat, dtype=np.int64)
35
+ self.feat_dynamic_real = self._astype(feat_dynamic_real, dtype=np.float32)
36
+ self.past_feat_dynamic_cat = self._astype(past_feat_dynamic_cat, dtype=np.int64)
37
+ self.past_feat_dynamic_real = self._astype(past_feat_dynamic_real, dtype=np.float32)
38
+ self.freq = self._get_freq_for_period(freq)
39
+
40
+ # Necessary to compute indptr for known_covariates at prediction time
41
+ self.includes_future = includes_future
42
+ self.prediction_length = prediction_length
43
+
44
+ # Replace inefficient groupby ITEMID with indptr that stores start:end of each time series
45
+ self.item_ids = target_df.item_ids
46
+ self.indptr = target_df.get_indptr()
47
+ self.start_timestamps = target_df.index[self.indptr[:-1]].to_frame(index=False)[TimeSeriesDataFrame.TIMESTAMP]
48
+ assert len(self.item_ids) == len(self.start_timestamps)
49
+
50
+ @staticmethod
51
+ def _astype(array: np.ndarray | None, dtype: Type[np.generic]) -> np.ndarray | None:
52
+ if array is None:
53
+ return None
54
+ else:
55
+ return array.astype(dtype)
56
+
57
+ @staticmethod
58
+ def _get_freq_for_period(freq: str) -> str:
59
+ """Convert freq to format compatible with pd.Period.
60
+
61
+ For example, ME freq must be converted to M when creating a pd.Period.
62
+ """
63
+ offset = pd.tseries.frequencies.to_offset(freq)
64
+ assert offset is not None
65
+ freq_name = norm_freq_str(offset)
66
+ if freq_name == "SME":
67
+ # Replace unsupported frequency "SME" with "2W"
68
+ return "2W"
69
+ elif freq_name == "bh":
70
+ # Replace unsupported frequency "bh" with dummy value "Y"
71
+ return "Y"
72
+ else:
73
+ freq_name_for_period = {"YE": "Y", "QE": "Q", "ME": "M"}.get(freq_name, freq_name)
74
+ return f"{offset.n}{freq_name_for_period}"
75
+
76
+ def __len__(self):
77
+ return len(self.indptr) - 1 # noqa
78
+
79
+ def __iter__(self) -> Iterator[dict[str, Any]]:
80
+ for j in range(len(self.indptr) - 1):
81
+ start_idx = self.indptr[j]
82
+ end_idx = self.indptr[j + 1]
83
+ # GluonTS expects item_id to be a string
84
+ ts = {
85
+ FieldName.ITEM_ID: str(self.item_ids[j]),
86
+ FieldName.START: pd.Period(self.start_timestamps.iloc[j], freq=self.freq),
87
+ FieldName.TARGET: self.target_array[start_idx:end_idx],
88
+ }
89
+ if self.feat_static_cat is not None:
90
+ ts[FieldName.FEAT_STATIC_CAT] = self.feat_static_cat[j]
91
+ if self.feat_static_real is not None:
92
+ ts[FieldName.FEAT_STATIC_REAL] = self.feat_static_real[j]
93
+ if self.past_feat_dynamic_cat is not None:
94
+ ts[FieldName.PAST_FEAT_DYNAMIC_CAT] = self.past_feat_dynamic_cat[start_idx:end_idx].T
95
+ if self.past_feat_dynamic_real is not None:
96
+ ts[FieldName.PAST_FEAT_DYNAMIC_REAL] = self.past_feat_dynamic_real[start_idx:end_idx].T
97
+
98
+ # Dynamic features that may extend into the future
99
+ if self.includes_future:
100
+ assert self.prediction_length is not None, (
101
+ "Prediction length must be provided if includes_future is True"
102
+ )
103
+ start_idx = start_idx + j * self.prediction_length
104
+ end_idx = end_idx + (j + 1) * self.prediction_length
105
+ if self.feat_dynamic_cat is not None:
106
+ ts[FieldName.FEAT_DYNAMIC_CAT] = self.feat_dynamic_cat[start_idx:end_idx].T
107
+ if self.feat_dynamic_real is not None:
108
+ ts[FieldName.FEAT_DYNAMIC_REAL] = self.feat_dynamic_real[start_idx:end_idx].T
109
+ yield ts
@@ -3,17 +3,18 @@ Module including wrappers for PyTorch implementations of models in GluonTS
3
3
  """
4
4
 
5
5
  import logging
6
- from typing import Any, Dict, Type
6
+ from typing import Any, Type
7
7
 
8
8
  from gluonts.model.estimator import Estimator as GluonTSEstimator
9
9
 
10
- from autogluon.timeseries.models.gluonts.abstract_gluonts import AbstractGluonTSModel
11
10
  from autogluon.timeseries.utils.datetime import (
12
11
  get_lags_for_frequency,
13
12
  get_seasonality,
14
13
  get_time_features_for_frequency,
15
14
  )
16
15
 
16
+ from .abstract import AbstractGluonTSModel
17
+
17
18
  # NOTE: We avoid imports for torch and lightning.pytorch at the top level and hide them inside class methods.
18
19
  # This is done to skip these imports during multiprocessing (which may cause bugs)
19
20
 
@@ -59,7 +60,7 @@ class DeepARModel(AbstractGluonTSModel):
59
60
  Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
60
61
  scaling: bool, default = True
61
62
  If True, mean absolute scaling will be applied to each *context window* during training & prediction.
62
- Note that this is different from the `target_scaler` that is applied to the *entire time series*.
63
+ Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
63
64
  max_epochs : int, default = 100
64
65
  Number of epochs the model will be trained for
65
66
  batch_size : int, default = 64
@@ -80,6 +81,8 @@ class DeepARModel(AbstractGluonTSModel):
80
81
 
81
82
  # TODO: Replace "scaling: bool" with "window_scaler": {"mean_abs", None} for consistency?
82
83
 
84
+ ag_priority = 40
85
+
83
86
  _supports_known_covariates = True
84
87
  _supports_static_features = True
85
88
 
@@ -88,13 +91,13 @@ class DeepARModel(AbstractGluonTSModel):
88
91
 
89
92
  return DeepAREstimator
90
93
 
91
- def _get_estimator_init_args(self) -> Dict[str, Any]:
94
+ def _get_estimator_init_args(self) -> dict[str, Any]:
92
95
  init_kwargs = super()._get_estimator_init_args()
93
96
  init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
94
97
  init_kwargs["num_feat_static_real"] = self.num_feat_static_real
95
98
  init_kwargs["cardinality"] = self.feat_static_cat_cardinality
96
99
  init_kwargs["num_feat_dynamic_real"] = self.num_feat_dynamic_real
97
- init_kwargs.setdefault("lags_seq", get_lags_for_frequency(self.freq))
100
+ init_kwargs.setdefault("lags_seq", get_lags_for_frequency(self.freq)) # type: ignore
98
101
  init_kwargs.setdefault("time_features", get_time_features_for_frequency(self.freq))
99
102
  return init_kwargs
100
103
 
@@ -110,7 +113,7 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
110
113
  ----------------
111
114
  context_length : int, default = max(10, 2 * prediction_length)
112
115
  Number of time units that condition the predictions
113
- hidden_dimensions: List[int], default = [20, 20]
116
+ hidden_dimensions: list[int], default = [20, 20]
114
117
  Size of hidden layers in the feedforward network
115
118
  distr_output : gluonts.torch.distributions.Output, default = StudentTOutput()
116
119
  Distribution output object that defines how the model output is converted to a forecast, and how the loss is computed.
@@ -118,7 +121,7 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
118
121
  Whether to use batch normalization
119
122
  mean_scaling : bool, default = True
120
123
  If True, mean absolute scaling will be applied to each *context window* during training & prediction.
121
- Note that this is different from the `target_scaler` that is applied to the *entire time series*.
124
+ Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
122
125
  max_epochs : int, default = 100
123
126
  Number of epochs the model will be trained for
124
127
  batch_size : int, default = 64
@@ -137,6 +140,8 @@ class SimpleFeedForwardModel(AbstractGluonTSModel):
137
140
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
138
141
  """
139
142
 
143
+ ag_priority = 10
144
+
140
145
  def _get_estimator_class(self) -> Type[GluonTSEstimator]:
141
146
  from gluonts.torch.model.simple_feedforward import SimpleFeedForwardEstimator
142
147
 
@@ -198,6 +203,9 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
198
203
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
199
204
  """
200
205
 
206
+ ag_priority = 45
207
+ ag_model_aliases = ["TFT"]
208
+
201
209
  _supports_known_covariates = True
202
210
  _supports_past_covariates = True
203
211
  _supports_cat_covariates = True
@@ -208,12 +216,12 @@ class TemporalFusionTransformerModel(AbstractGluonTSModel):
208
216
 
209
217
  return TemporalFusionTransformerEstimator
210
218
 
211
- def _get_default_params(self):
212
- return super()._get_default_params() | {
219
+ def _get_default_hyperparameters(self):
220
+ return super()._get_default_hyperparameters() | {
213
221
  "context_length": min(512, max(64, 2 * self.prediction_length)),
214
222
  }
215
223
 
216
- def _get_estimator_init_args(self) -> Dict[str, Any]:
224
+ def _get_estimator_init_args(self) -> dict[str, Any]:
217
225
  init_kwargs = super()._get_estimator_init_args()
218
226
  if self.num_feat_dynamic_real > 0:
219
227
  init_kwargs["dynamic_dims"] = [self.num_feat_dynamic_real]
@@ -260,7 +268,7 @@ class DLinearModel(AbstractGluonTSModel):
260
268
  Scaling applied to each *context window* during training & prediction.
261
269
  One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
262
270
 
263
- Note that this is different from the `target_scaler` that is applied to the *entire time series*.
271
+ Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
264
272
  max_epochs : int, default = 100
265
273
  Number of epochs the model will be trained for
266
274
  batch_size : int, default = 64
@@ -281,8 +289,10 @@ class DLinearModel(AbstractGluonTSModel):
281
289
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
282
290
  """
283
291
 
284
- def _get_default_params(self):
285
- return super()._get_default_params() | {
292
+ ag_priority = 10
293
+
294
+ def _get_default_hyperparameters(self):
295
+ return super()._get_default_hyperparameters() | {
286
296
  "context_length": 96,
287
297
  }
288
298
 
@@ -324,7 +334,7 @@ class PatchTSTModel(AbstractGluonTSModel):
324
334
  Scaling applied to each *context window* during training & prediction.
325
335
  One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
326
336
 
327
- Note that this is different from the `target_scaler` that is applied to the *entire time series*.
337
+ Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
328
338
  max_epochs : int, default = 100
329
339
  Number of epochs the model will be trained for
330
340
  batch_size : int, default = 64
@@ -339,6 +349,8 @@ class PatchTSTModel(AbstractGluonTSModel):
339
349
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
340
350
  """
341
351
 
352
+ ag_priority = 30
353
+
342
354
  _supports_known_covariates = True
343
355
 
344
356
  def _get_estimator_class(self) -> Type[GluonTSEstimator]:
@@ -346,10 +358,10 @@ class PatchTSTModel(AbstractGluonTSModel):
346
358
 
347
359
  return PatchTSTEstimator
348
360
 
349
- def _get_default_params(self):
350
- return super()._get_default_params() | {"context_length": 96, "patch_len": 16}
361
+ def _get_default_hyperparameters(self):
362
+ return super()._get_default_hyperparameters() | {"context_length": 96, "patch_len": 16}
351
363
 
352
- def _get_estimator_init_args(self) -> Dict[str, Any]:
364
+ def _get_estimator_init_args(self) -> dict[str, Any]:
353
365
  init_kwargs = super()._get_estimator_init_args()
354
366
  init_kwargs["num_feat_dynamic_real"] = self.num_feat_dynamic_real
355
367
  return init_kwargs
@@ -415,6 +427,8 @@ class WaveNetModel(AbstractGluonTSModel):
415
427
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
416
428
  """
417
429
 
430
+ ag_priority = 25
431
+
418
432
  _supports_known_covariates = True
419
433
  _supports_static_features = True
420
434
  default_num_samples: int = 100
@@ -424,7 +438,7 @@ class WaveNetModel(AbstractGluonTSModel):
424
438
 
425
439
  return WaveNetEstimator
426
440
 
427
- def _get_estimator_init_args(self) -> Dict[str, Any]:
441
+ def _get_estimator_init_args(self) -> dict[str, Any]:
428
442
  init_kwargs = super()._get_estimator_init_args()
429
443
  init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
430
444
  init_kwargs["num_feat_static_real"] = self.num_feat_static_real
@@ -488,7 +502,7 @@ class TiDEModel(AbstractGluonTSModel):
488
502
  Scaling applied to each *context window* during training & prediction.
489
503
  One of ``"mean"`` (mean absolute scaling), ``"std"`` (standardization), ``None`` (no scaling).
490
504
 
491
- Note that this is different from the `target_scaler` that is applied to the *entire time series*.
505
+ Note that this is different from the ``target_scaler`` that is applied to the *entire time series*.
492
506
  max_epochs : int, default = 100
493
507
  Number of epochs the model will be trained for
494
508
  batch_size : int, default = 256
@@ -507,6 +521,8 @@ class TiDEModel(AbstractGluonTSModel):
507
521
  If True, ``lightning_logs`` directory will NOT be removed after the model finished training.
508
522
  """
509
523
 
524
+ ag_priority = 30
525
+
510
526
  _supports_known_covariates = True
511
527
  _supports_static_features = True
512
528
 
@@ -515,8 +531,8 @@ class TiDEModel(AbstractGluonTSModel):
515
531
 
516
532
  return TiDEEstimator
517
533
 
518
- def _get_default_params(self):
519
- return super()._get_default_params() | {
534
+ def _get_default_hyperparameters(self):
535
+ return super()._get_default_hyperparameters() | {
520
536
  "context_length": min(512, max(64, 2 * self.prediction_length)),
521
537
  "encoder_hidden_dim": 64,
522
538
  "decoder_hidden_dim": 64,
@@ -531,7 +547,7 @@ class TiDEModel(AbstractGluonTSModel):
531
547
  "batch_size": 256,
532
548
  }
533
549
 
534
- def _get_estimator_init_args(self) -> Dict[str, Any]:
550
+ def _get_estimator_init_args(self) -> dict[str, Any]:
535
551
  init_kwargs = super()._get_estimator_init_args()
536
552
  init_kwargs["num_feat_static_cat"] = self.num_feat_static_cat
537
553
  init_kwargs["num_feat_static_real"] = self.num_feat_static_real
@@ -1,5 +1,3 @@
1
- import joblib.externals.loky
2
-
3
1
  from .naive import AverageModel, NaiveModel, SeasonalAverageModel, SeasonalNaiveModel
4
2
  from .npts import NPTSModel
5
3
  from .statsforecast import (
@@ -15,8 +13,3 @@ from .statsforecast import (
15
13
  ThetaModel,
16
14
  ZeroModel,
17
15
  )
18
-
19
- # By default, joblib w/ loky backend kills processes that take >300MB of RAM assuming that this is caused by a memory
20
- # leak. This leads to problems for some memory-hungry models like AutoARIMA/Theta.
21
- # This monkey patch removes this undesired behavior
22
- joblib.externals.loky.process_executor._MAX_MEMORY_LEAK_SIZE = int(3e10)
@@ -1,26 +1,24 @@
1
1
  import logging
2
2
  import time
3
- from multiprocessing import TimeoutError, cpu_count
4
- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
3
+ from multiprocessing import TimeoutError
4
+ from typing import Any, Callable
5
5
 
6
6
  import numpy as np
7
7
  import pandas as pd
8
- from joblib import Parallel, delayed
8
+ from joblib import Parallel, cpu_count, delayed
9
9
  from scipy.stats import norm
10
10
 
11
11
  from autogluon.core.utils.exceptions import TimeLimitExceeded
12
- from autogluon.timeseries.dataset.ts_dataframe import ITEMID, TimeSeriesDataFrame
12
+ from autogluon.timeseries.dataset import TimeSeriesDataFrame
13
+ from autogluon.timeseries.metrics import TimeSeriesScorer
13
14
  from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
15
+ from autogluon.timeseries.utils.constants import AG_DEFAULT_N_JOBS
14
16
  from autogluon.timeseries.utils.datetime import get_seasonality
15
17
  from autogluon.timeseries.utils.warning_filters import warning_filter
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
21
 
20
- # We use the same default n_jobs across AG-TS to ensure that Joblib reuses the process pool
21
- AG_DEFAULT_N_JOBS = max(int(cpu_count() * 0.5), 1)
22
-
23
-
24
22
  class AbstractLocalModel(AbstractTimeSeriesModel):
25
23
  """Abstract class for local forecasting models that are trained separately for each time series.
26
24
 
@@ -28,50 +26,31 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
28
26
 
29
27
  Attributes
30
28
  ----------
31
- allowed_local_model_args : List[str]
29
+ allowed_local_model_args
32
30
  Argument that can be passed to the underlying local model.
33
- default_n_jobs : Union[int, float]
34
- Default number of CPU cores used to train models. If float, this fraction of CPU cores will be used.
35
- default_max_ts_length : Optional[int]
31
+ default_max_ts_length
36
32
  If not None, only the last ``max_ts_length`` time steps of each time series will be used to train the model.
37
33
  This significantly speeds up fitting and usually leads to no change in accuracy.
38
- init_time_in_seconds : int
34
+ init_time_in_seconds
39
35
  Time that it takes to initialize the model in seconds (e.g., because of JIT compilation by Numba).
40
36
  If time_limit is below this number, model won't be trained.
41
37
  """
42
38
 
43
- allowed_local_model_args: List[str] = []
44
- default_n_jobs: Union[int, float] = AG_DEFAULT_N_JOBS
45
- default_max_ts_length: Optional[int] = 2500
39
+ allowed_local_model_args: list[str] = []
40
+ default_max_ts_length: int | None = 2500
46
41
  default_max_time_limit_ratio = 1.0
47
42
  init_time_in_seconds: int = 0
48
43
 
49
44
  def __init__(
50
45
  self,
51
- freq: Optional[str] = None,
46
+ freq: str | None = None,
52
47
  prediction_length: int = 1,
53
- path: Optional[str] = None,
54
- name: Optional[str] = None,
55
- eval_metric: str = None,
56
- hyperparameters: Dict[str, Any] = None,
48
+ path: str | None = None,
49
+ name: str | None = None,
50
+ eval_metric: str | TimeSeriesScorer | None = None,
51
+ hyperparameters: dict[str, Any] | None = None,
57
52
  **kwargs, # noqa
58
53
  ):
59
- if hyperparameters is None:
60
- hyperparameters = {}
61
- else:
62
- hyperparameters = hyperparameters.copy()
63
- # TODO: Replace with 'num_cpus' argument passed to fit (after predictor API is changed)
64
- n_jobs = hyperparameters.pop("n_jobs", self.default_n_jobs)
65
- if isinstance(n_jobs, float) and 0 < n_jobs <= 1:
66
- self.n_jobs = max(int(cpu_count() * n_jobs), 1)
67
- elif isinstance(n_jobs, int):
68
- self.n_jobs = n_jobs
69
- else:
70
- raise ValueError(f"n_jobs must be a float between 0 and 1 or an integer (received n_jobs = {n_jobs})")
71
- # Default values, potentially overridden inside _fit()
72
- self.use_fallback_model = hyperparameters.pop("use_fallback_model", True)
73
- self.max_ts_length = hyperparameters.pop("max_ts_length", self.default_max_ts_length)
74
-
75
54
  super().__init__(
76
55
  path=path,
77
56
  freq=freq,
@@ -82,12 +61,12 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
82
61
  **kwargs,
83
62
  )
84
63
 
85
- self._local_model_args: Dict[str, Any] = None
86
- self._seasonal_period: Optional[int] = None
87
- self._dummy_forecast: Optional[pd.DataFrame] = None
64
+ self._local_model_args: dict[str, Any]
65
+ self._seasonal_period: int
66
+ self._dummy_forecast: pd.DataFrame
88
67
 
89
68
  @property
90
- def allowed_hyperparameters(self) -> List[str]:
69
+ def allowed_hyperparameters(self) -> list[str]:
91
70
  return (
92
71
  super().allowed_hyperparameters
93
72
  + ["use_fallback_model", "max_ts_length", "n_jobs"]
@@ -97,40 +76,42 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
97
76
  def preprocess(
98
77
  self,
99
78
  data: TimeSeriesDataFrame,
100
- known_covariates: Optional[TimeSeriesDataFrame] = None,
79
+ known_covariates: TimeSeriesDataFrame | None = None,
101
80
  is_train: bool = False,
102
81
  **kwargs,
103
- ) -> Tuple[TimeSeriesDataFrame, Optional[TimeSeriesDataFrame]]:
82
+ ) -> tuple[TimeSeriesDataFrame, TimeSeriesDataFrame | None]:
104
83
  if not self._get_tags()["allow_nan"]:
105
84
  data = data.fill_missing_values()
106
85
  return data, known_covariates
107
86
 
108
- def _fit(self, train_data: TimeSeriesDataFrame, time_limit: Optional[int] = None, **kwargs):
87
+ def _get_default_hyperparameters(self) -> dict:
88
+ return {
89
+ "n_jobs": AG_DEFAULT_N_JOBS,
90
+ "use_fallback_model": True,
91
+ "max_ts_length": self.default_max_ts_length,
92
+ }
93
+
94
+ @staticmethod
95
+ def _compute_n_jobs(n_jobs: int | float) -> int:
96
+ if isinstance(n_jobs, float) and 0 < n_jobs <= 1:
97
+ return max(int(cpu_count() * n_jobs), 1)
98
+ elif isinstance(n_jobs, int):
99
+ return n_jobs
100
+ else:
101
+ raise ValueError(f"n_jobs must be a float between 0 and 1 or an integer (received n_jobs = {n_jobs})")
102
+
103
+ def _fit(self, train_data: TimeSeriesDataFrame, time_limit: int | None = None, **kwargs):
109
104
  self._check_fit_params()
110
105
 
111
106
  if time_limit is not None and time_limit < self.init_time_in_seconds:
112
107
  raise TimeLimitExceeded
113
108
 
114
- # Initialize parameters passed to each local model
115
- raw_local_model_args = self._get_model_params().copy()
116
-
117
- unused_local_model_args = []
118
109
  local_model_args = {}
119
- # TODO: Move filtering logic to AbstractTimeSeriesModel
120
- for key, value in raw_local_model_args.items():
110
+ for key, value in self.get_hyperparameters().items():
121
111
  if key in self.allowed_local_model_args:
122
112
  local_model_args[key] = value
123
- elif key in self.allowed_hyperparameters:
124
- # Quietly ignore params in self.allowed_hyperparameters - they are used by AbstractTimeSeriesModel
125
- pass
126
- else:
127
- unused_local_model_args.append(key)
128
113
 
129
- if len(unused_local_model_args):
130
- logger.warning(
131
- f"{self.name} ignores following hyperparameters: {unused_local_model_args}. "
132
- f"See the docstring of {self.name} for the list of supported hyperparameters."
133
- )
114
+ self._log_unused_hyperparameters(extra_allowed_hyperparameters=self.allowed_local_model_args)
134
115
 
135
116
  if "seasonal_period" not in local_model_args or local_model_args["seasonal_period"] is None:
136
117
  local_model_args["seasonal_period"] = get_seasonality(self.freq)
@@ -141,35 +122,46 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
141
122
  self._dummy_forecast = self._get_dummy_forecast(train_data)
142
123
  return self
143
124
 
144
- def _get_dummy_forecast(self, train_data: TimeSeriesDataFrame) -> pd.DataFrame:
125
+ def _get_dummy_forecast(self, train_data: TimeSeriesDataFrame, max_num_rows: int = 20_000) -> pd.DataFrame:
145
126
  agg_functions = ["mean"] + [get_quantile_function(q) for q in self.quantile_levels]
146
- stats_marginal = train_data[self.target].agg(agg_functions)
127
+ target_series = train_data[self.target]
128
+ if len(target_series) > max_num_rows:
129
+ target_series = target_series.sample(max_num_rows, replace=True)
130
+ stats_marginal = target_series.agg(agg_functions)
147
131
  stats_repeated = np.tile(stats_marginal.values, [self.prediction_length, 1])
148
132
  return pd.DataFrame(stats_repeated, columns=stats_marginal.index)
149
133
 
150
- def _update_local_model_args(self, local_model_args: Dict[str, Any]) -> Dict[str, Any]:
134
+ def _update_local_model_args(self, local_model_args: dict[str, Any]) -> dict[str, Any]:
151
135
  return local_model_args
152
136
 
153
137
  def _predict(self, data: TimeSeriesDataFrame, **kwargs) -> TimeSeriesDataFrame:
154
- if self.max_ts_length is not None:
155
- logger.debug(f"Shortening all time series to at most {self.max_ts_length}")
156
- data = data.groupby(level=ITEMID, sort=False).tail(self.max_ts_length)
138
+ model_params = self.get_hyperparameters()
139
+ max_ts_length = model_params["max_ts_length"]
140
+ if max_ts_length is not None:
141
+ logger.debug(f"Shortening all time series to at most {max_ts_length}")
142
+ data = data.slice_by_timestep(-max_ts_length, None)
157
143
 
158
- df = pd.DataFrame(data).reset_index(level=ITEMID)
159
- all_series = (ts for _, ts in df.groupby(by=ITEMID, as_index=False, sort=False)[self.target])
144
+ indptr = data.get_indptr()
145
+ target_series = data[self.target].droplevel(level=TimeSeriesDataFrame.ITEMID)
146
+ all_series = (target_series[indptr[i] : indptr[i + 1]] for i in range(len(indptr) - 1))
160
147
 
161
148
  # timeout ensures that no individual job takes longer than time_limit
162
149
  # TODO: a job started late may still exceed time_limit - how to prevent that?
163
150
  time_limit = kwargs.get("time_limit")
164
- timeout = None if self.n_jobs == 1 else time_limit
151
+ # TODO: Take into account num_cpus once the TimeSeriesPredictor API is updated
152
+ n_jobs = self._compute_n_jobs(model_params["n_jobs"])
153
+ timeout = None if n_jobs == 1 else time_limit
165
154
  # end_time ensures that no new jobs are started after time_limit is exceeded
166
155
  end_time = None if time_limit is None else time.time() + time_limit
167
- executor = Parallel(self.n_jobs, timeout=timeout)
156
+ executor = Parallel(n_jobs=n_jobs, timeout=timeout)
168
157
 
169
158
  try:
170
159
  with warning_filter():
171
160
  predictions_with_flags = executor(
172
- delayed(self._predict_wrapper)(ts, end_time=end_time) for ts in all_series
161
+ delayed(self._predict_wrapper)(
162
+ ts, use_fallback_model=model_params["use_fallback_model"], end_time=end_time
163
+ )
164
+ for ts in all_series
173
165
  )
174
166
  except TimeoutError:
175
167
  raise TimeLimitExceeded
@@ -185,7 +177,12 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
185
177
  predictions_df.index = self.get_forecast_horizon_index(data)
186
178
  return TimeSeriesDataFrame(predictions_df)
187
179
 
188
- def _predict_wrapper(self, time_series: pd.Series, end_time: Optional[float] = None) -> Tuple[pd.DataFrame, bool]:
180
+ def _predict_wrapper(
181
+ self,
182
+ time_series: pd.Series,
183
+ use_fallback_model: bool,
184
+ end_time: float | None = None,
185
+ ) -> tuple[pd.DataFrame, bool]:
189
186
  if end_time is not None and time.time() >= end_time:
190
187
  raise TimeLimitExceeded
191
188
 
@@ -201,7 +198,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
201
198
  if not np.isfinite(result.values).all():
202
199
  raise RuntimeError("Forecast contains NaN or Inf values.")
203
200
  except Exception:
204
- if self.use_fallback_model:
201
+ if use_fallback_model:
205
202
  result = seasonal_naive_forecast(
206
203
  target=time_series.values.ravel(),
207
204
  prediction_length=self.prediction_length,
@@ -222,7 +219,7 @@ class AbstractLocalModel(AbstractTimeSeriesModel):
222
219
 
223
220
 
224
221
  def seasonal_naive_forecast(
225
- target: np.ndarray, prediction_length: int, quantile_levels: List[float], seasonal_period: int
222
+ target: np.ndarray, prediction_length: int, quantile_levels: list[float], seasonal_period: int
226
223
  ) -> pd.DataFrame:
227
224
  """Generate seasonal naive forecast, predicting the last observed value from the same period."""
228
225