autogluon.timeseries 1.2.1b20250416__py3-none-any.whl → 1.2.1b20250418__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.
Files changed (28) hide show
  1. autogluon/timeseries/learner.py +8 -6
  2. autogluon/timeseries/metrics/abstract.py +1 -1
  3. autogluon/timeseries/metrics/point.py +4 -4
  4. autogluon/timeseries/metrics/quantile.py +2 -2
  5. autogluon/timeseries/models/abstract/abstract_timeseries_model.py +123 -204
  6. autogluon/timeseries/models/autogluon_tabular/mlforecast.py +9 -9
  7. autogluon/timeseries/models/chronos/model.py +37 -30
  8. autogluon/timeseries/models/gluonts/abstract_gluonts.py +34 -30
  9. autogluon/timeseries/models/gluonts/torch/models.py +8 -8
  10. autogluon/timeseries/models/local/abstract_local_model.py +1 -1
  11. autogluon/timeseries/models/local/naive.py +2 -2
  12. autogluon/timeseries/models/multi_window/multi_window_model.py +0 -3
  13. autogluon/timeseries/models/presets.py +2 -2
  14. autogluon/timeseries/predictor.py +76 -59
  15. autogluon/timeseries/regressor.py +5 -4
  16. autogluon/timeseries/trainer.py +14 -13
  17. autogluon/timeseries/utils/features.py +5 -2
  18. autogluon/timeseries/utils/forecast.py +13 -8
  19. autogluon/timeseries/version.py +1 -1
  20. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/METADATA +4 -4
  21. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/RECORD +28 -28
  22. /autogluon.timeseries-1.2.1b20250416-py3.9-nspkg.pth → /autogluon.timeseries-1.2.1b20250418-py3.9-nspkg.pth +0 -0
  23. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/LICENSE +0 -0
  24. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/NOTICE +0 -0
  25. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/WHEEL +0 -0
  26. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/namespace_packages.txt +0 -0
  27. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/top_level.txt +0 -0
  28. {autogluon.timeseries-1.2.1b20250416.dist-info → autogluon.timeseries-1.2.1b20250418.dist-info}/zip-safe +0 -0
@@ -12,7 +12,7 @@ from autogluon.timeseries.models.abstract import AbstractTimeSeriesModel
12
12
  from autogluon.timeseries.splitter import AbstractWindowSplitter
13
13
  from autogluon.timeseries.trainer import TimeSeriesTrainer
14
14
  from autogluon.timeseries.utils.features import TimeSeriesFeatureGenerator
15
- from autogluon.timeseries.utils.forecast import get_forecast_horizon_index_ts_dataframe
15
+ from autogluon.timeseries.utils.forecast import make_future_data_frame
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -88,7 +88,7 @@ class TimeSeriesLearner(AbstractLearner):
88
88
  verbosity=kwargs.get("verbosity", 2),
89
89
  skip_model_selection=kwargs.get("skip_model_selection", False),
90
90
  enable_ensemble=kwargs.get("enable_ensemble", True),
91
- metadata=self.feature_generator.covariate_metadata,
91
+ covariate_metadata=self.feature_generator.covariate_metadata,
92
92
  val_splitter=val_splitter,
93
93
  refit_every_n_windows=refit_every_n_windows,
94
94
  cache_predictions=self.cache_predictions,
@@ -148,15 +148,17 @@ class TimeSeriesLearner(AbstractLearner):
148
148
  f"known_covariates are missing information for the following item_ids: {reprlib.repr(missing_item_ids.to_list())}."
149
149
  )
150
150
 
151
- forecast_index = get_forecast_horizon_index_ts_dataframe(
152
- data, prediction_length=self.prediction_length, freq=self.freq
151
+ forecast_index = pd.MultiIndex.from_frame(
152
+ make_future_data_frame(data, prediction_length=self.prediction_length, freq=self.freq)
153
153
  )
154
154
  try:
155
155
  known_covariates = known_covariates.loc[forecast_index] # type: ignore
156
156
  except KeyError:
157
157
  raise ValueError(
158
- f"known_covariates should include the values for prediction_length={self.prediction_length} "
159
- "many time steps into the future."
158
+ "`known_covariates` should include the `item_id` and `timestamp` values covering the forecast horizon "
159
+ "(i.e., the next `prediction_length` time steps following the end of each time series in the input "
160
+ "data). Use `TimeSeriesPredictor.make_future_data_frame` to generate the required `item_id` and "
161
+ "`timestamp` combinations for the `known_covariates`."
160
162
  )
161
163
  return known_covariates
162
164
 
@@ -140,7 +140,7 @@ class TimeSeriesScorer:
140
140
  ) -> None:
141
141
  """Compute auxiliary metrics on past data (before forecast horizon), if the chosen metric requires it.
142
142
 
143
- This method should only be implemented by metrics that rely on historic (in-sample) data, such as Mean Absolute
143
+ This method should only be implemented by metrics that rely on historical (in-sample) data, such as Mean Absolute
144
144
  Scaled Error (MASE) https://en.wikipedia.org/wiki/Mean_absolute_scaled_error.
145
145
 
146
146
  We keep this method separate from :meth:`compute_metric` to avoid redundant computations when fitting ensemble.
@@ -196,13 +196,13 @@ class MAPE(TimeSeriesScorer):
196
196
  class MASE(TimeSeriesScorer):
197
197
  r"""Mean absolute scaled error.
198
198
 
199
- Normalizes the absolute error for each time series by the historic seasonal error of this time series.
199
+ Normalizes the absolute error for each time series by the historical seasonal error of this time series.
200
200
 
201
201
  .. math::
202
202
 
203
203
  \operatorname{MASE} = \frac{1}{N} \frac{1}{H} \sum_{i=1}^{N} \frac{1}{a_i} \sum_{t=T+1}^{T+H} |y_{i,t} - f_{i,t}|
204
204
 
205
- where :math:`a_i` is the historic absolute seasonal error defined as
205
+ where :math:`a_i` is the historical absolute seasonal error defined as
206
206
 
207
207
  .. math::
208
208
 
@@ -255,13 +255,13 @@ class MASE(TimeSeriesScorer):
255
255
  class RMSSE(TimeSeriesScorer):
256
256
  r"""Root mean squared scaled error.
257
257
 
258
- Normalizes the absolute error for each time series by the historic seasonal error of this time series.
258
+ Normalizes the absolute error for each time series by the historical seasonal error of this time series.
259
259
 
260
260
  .. math::
261
261
 
262
262
  \operatorname{RMSSE} = \sqrt{\frac{1}{N} \frac{1}{H} \sum_{i=1}^{N} \frac{1}{s_i} \sum_{t=T+1}^{T+H} (y_{i,t} - f_{i,t})^2}
263
263
 
264
- where :math:`s_i` is the historic squared seasonal error defined as
264
+ where :math:`s_i` is the historical squared seasonal error defined as
265
265
 
266
266
  .. math::
267
267
 
@@ -51,13 +51,13 @@ class SQL(TimeSeriesScorer):
51
51
 
52
52
  Also known as scaled pinball loss.
53
53
 
54
- Normalizes the quantile loss for each time series by the historic seasonal error of this time series.
54
+ Normalizes the quantile loss for each time series by the historical seasonal error of this time series.
55
55
 
56
56
  .. math::
57
57
 
58
58
  \operatorname{SQL} = \frac{1}{N} \frac{1}{H} \sum_{i=1}^{N} \frac{1}{a_i} \sum_{t=T+1}^{T+H} \sum_{q} \rho_q(y_{i,t}, f^q_{i,t})
59
59
 
60
- where :math:`a_i` is the historic absolute seasonal error defined as
60
+ where :math:`a_i` is the historical absolute seasonal error defined as
61
61
 
62
62
  .. math::
63
63
 
@@ -16,7 +16,7 @@ from autogluon.common.loaders import load_pkl
16
16
  from autogluon.common.savers import save_pkl
17
17
  from autogluon.common.utils.resource_utils import get_resource_manager
18
18
  from autogluon.common.utils.utils import setup_outputdir
19
- from autogluon.core.constants import AG_ARG_PREFIX, AG_ARGS_FIT, REFIT_FULL_SUFFIX
19
+ from autogluon.core.constants import AG_ARGS_FIT, REFIT_FULL_SUFFIX
20
20
  from autogluon.core.models import ModelBase
21
21
  from autogluon.core.utils.exceptions import TimeLimitExceeded
22
22
  from autogluon.timeseries.dataset import TimeSeriesDataFrame
@@ -24,90 +24,13 @@ from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_
24
24
  from autogluon.timeseries.regressor import CovariateRegressor, get_covariate_regressor
25
25
  from autogluon.timeseries.transforms import CovariateScaler, TargetScaler, get_covariate_scaler, get_target_scaler
26
26
  from autogluon.timeseries.utils.features import CovariateMetadata
27
- from autogluon.timeseries.utils.forecast import get_forecast_horizon_index_ts_dataframe
27
+ from autogluon.timeseries.utils.forecast import make_future_data_frame
28
28
 
29
29
  from .tunable import TimeSeriesTunable
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
33
33
 
34
- # TODO: refactor and move to util. We do not need to use "params_aux" in time series
35
- def check_and_split_hyperparameters(
36
- params: Optional[Dict[str, Any]] = None,
37
- ag_args_fit: str = AG_ARGS_FIT,
38
- ag_arg_prefix: str = AG_ARG_PREFIX,
39
- ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
40
- """
41
- Given the user-specified hyperparameters, split into `params` and `params_aux`.
42
-
43
- Parameters
44
- ----------
45
- params : Optional[Dict[str, Any]], default = None
46
- The model hyperparameters dictionary
47
- ag_args_fit : str, default = "ag_args_fit"
48
- The params key to look for that contains params_aux.
49
- If the key is present, the value is used for params_aux and popped from params.
50
- If no such key is found, then initialize params_aux as an empty dictionary.
51
- ag_arg_prefix : str, default = "ag."
52
- The key prefix to look for that indicates a parameter is intended for params_aux.
53
- If None, this logic is skipped.
54
- If a key starts with this prefix, it is popped from params and added to params_aux with the prefix removed.
55
- For example:
56
- input: params={'ag.foo': 2, 'abc': 7}, params_aux={'bar': 3}, and ag_arg_prefix='.ag',
57
- output: params={'abc': 7}, params_aux={'bar': 3, 'foo': 2}
58
- In cases where the key is specified multiple times, the value of the key with the prefix will always take priority.
59
- A warning will be logged if a key is present multiple times.
60
- For example, given the most complex scenario:
61
- input: params={'ag.foo': 1, 'foo': 2, 'ag_args_fit': {'ag.foo': 3, 'foo': 4}}
62
- output: params={'foo': 2}, params_aux={'foo': 1}
63
-
64
- Returns
65
- -------
66
- params, params_aux : (Dict[str, Any], Dict[str, Any])
67
- params will contain the native model hyperparameters
68
- params_aux will contain special auxiliary hyperparameters
69
- """
70
- params = copy.deepcopy(params) if params is not None else dict()
71
- assert isinstance(params, dict), f"Invalid dtype of params! Expected dict, but got {type(params)}"
72
- for k in params.keys():
73
- if not isinstance(k, str):
74
- logger.warning(
75
- f"Warning: Specified hyperparameter key is not of type str: {k} (type={type(k)}). "
76
- f"There might be a bug in your configuration."
77
- )
78
-
79
- params_aux = params.pop(ag_args_fit, dict())
80
- if params_aux is None:
81
- params_aux = dict()
82
- assert isinstance(params_aux, dict), f"Invalid dtype of params_aux! Expected dict, but got {type(params_aux)}"
83
- if ag_arg_prefix is not None:
84
- param_aux_keys = list(params_aux.keys())
85
- for k in param_aux_keys:
86
- if isinstance(k, str) and k.startswith(ag_arg_prefix):
87
- k_no_prefix = k[len(ag_arg_prefix) :]
88
- if k_no_prefix in params_aux:
89
- logger.warning(
90
- f'Warning: hyperparameter "{k}" is present '
91
- f'in `ag_args_fit` as both "{k}" and "{k_no_prefix}". '
92
- f'Will use "{k}" and ignore "{k_no_prefix}".'
93
- )
94
- params_aux[k_no_prefix] = params_aux.pop(k)
95
- param_keys = list(params.keys())
96
- for k in param_keys:
97
- if isinstance(k, str) and k.startswith(ag_arg_prefix):
98
- k_no_prefix = k[len(ag_arg_prefix) :]
99
- if k_no_prefix in params_aux:
100
- logger.warning(
101
- f'Warning: hyperparameter "{k}" is present '
102
- f"in both `ag_args_fit` and `hyperparameters`. "
103
- f"Will use `hyperparameters` value."
104
- )
105
- params_aux[k_no_prefix] = params.pop(k)
106
- return params, params_aux
107
-
108
-
109
- # TODO: refactor. remove params_aux, etc. make overrides and abstract
110
- # methods clear, et al.
111
34
  class TimeSeriesModelBase(ModelBase, ABC):
112
35
  """Abstract class for all `Model` objects in autogluon.timeseries.
113
36
 
@@ -126,7 +49,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
126
49
  Name of the subdirectory inside path where model will be saved.
127
50
  The final model directory will be os.path.join(path, name)
128
51
  If None, defaults to the model's class name: self.__class__.__name__
129
- metadata: CovariateMetadata
52
+ covariate_metadata: CovariateMetadata
130
53
  A mapping of different covariate types known to autogluon.timeseries to column names
131
54
  in the data set.
132
55
  eval_metric : Union[str, TimeSeriesScorer], default = "WQL"
@@ -160,7 +83,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
160
83
  hyperparameters: Optional[Dict[str, Any]] = None,
161
84
  freq: Optional[str] = None,
162
85
  prediction_length: int = 1,
163
- metadata: Optional[CovariateMetadata] = None,
86
+ covariate_metadata: Optional[CovariateMetadata] = None,
164
87
  target: str = "target",
165
88
  quantile_levels: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
166
89
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
@@ -171,7 +94,8 @@ class TimeSeriesModelBase(ModelBase, ABC):
171
94
  self.path_root = path
172
95
  if self.path_root is None:
173
96
  path_suffix = self.name
174
- # TODO: Would be ideal to not create dir, but still track that it is unique. However, this isn't possible to do without a global list of used dirs or using UUID.
97
+ # TODO: Would be ideal to not create dir, but still track that it is unique. However, this isn't possible
98
+ # to do without a global list of used dirs or using UUID.
175
99
  path_cur = setup_outputdir(path=None, create_dir=True, path_suffix=path_suffix)
176
100
  self.path_root = path_cur.rsplit(self.name, 1)[0]
177
101
  logger.log(20, f"Warning: No path was specified for model, defaulting to: {self.path_root}")
@@ -181,7 +105,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
181
105
  self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(eval_metric)
182
106
  self.eval_metric_seasonal_period = eval_metric_seasonal_period
183
107
  self.target: str = target
184
- self.metadata = metadata or CovariateMetadata()
108
+ self.covariate_metadata = covariate_metadata or CovariateMetadata()
185
109
 
186
110
  self.freq: Optional[str] = freq
187
111
  self.prediction_length: int = prediction_length
@@ -198,22 +122,15 @@ class TimeSeriesModelBase(ModelBase, ABC):
198
122
  else:
199
123
  self.must_drop_median = False
200
124
 
201
- self._user_params, self._user_params_aux = check_and_split_hyperparameters(hyperparameters)
202
125
  self._oof_predictions: Optional[List[TimeSeriesDataFrame]] = None
203
126
 
204
- self.params: Dict[str, Any] = {}
205
- self.params_aux: Dict[str, Any] = {}
206
- self._init_params_aux()
207
- self._init_params()
208
- self._is_initialized = True
209
-
210
- # TODO: remove the variables below
211
- self.nondefault_params: List[str] = []
127
+ # user provided hyperparameters and extra arguments that are used during model training
128
+ self._hyperparameters, self._extra_ag_args = self._check_and_split_hyperparameters(hyperparameters)
212
129
 
213
130
  self.fit_time: Optional[float] = None # Time taken to fit in seconds (Training data)
214
131
  self.predict_time: Optional[float] = None # Time taken to predict in seconds (Validation data)
215
132
  self.predict_1_time: Optional[float] = (
216
- None # Time taken to predict 1 row of data in seconds (with batch size `predict_1_batch_size` in params_aux)
133
+ None # Time taken to predict 1 row of data in seconds (with batch size `predict_1_batch_size`)
217
134
  )
218
135
  self.val_score: Optional[float] = None # Score with eval_metric (Validation data)
219
136
 
@@ -236,6 +153,44 @@ class TimeSeriesModelBase(ModelBase, ABC):
236
153
  self.path = path_context
237
154
  self.path_root = self.path.rsplit(self.name, 1)[0]
238
155
 
156
+ @classmethod
157
+ def _check_and_split_hyperparameters(
158
+ cls, hyperparameters: Optional[Dict[str, Any]] = None
159
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
160
+ """
161
+ Given the user-specified hyperparameters, split into `hyperparameters` and `extra_ag_args`, intended
162
+ to be used during model initialization.
163
+
164
+ Parameters
165
+ ----------
166
+ hyperparameters : Optional[Dict[str, Any]], default = None
167
+ The model hyperparameters dictionary provided to the model constructor.
168
+
169
+ Returns
170
+ -------
171
+ hyperparameters: Dict[str, Any]
172
+ Native model hyperparameters that are passed into the "inner model" AutoGluon wraps
173
+ extra_ag_args: Dict[str, Any]
174
+ Special auxiliary parameters that modify the model training process used by AutoGluon
175
+ """
176
+ hyperparameters = copy.deepcopy(hyperparameters) if hyperparameters is not None else dict()
177
+ assert isinstance(hyperparameters, dict), (
178
+ f"Invalid dtype for hyperparameters. Expected dict, but got {type(hyperparameters)}"
179
+ )
180
+ for k in hyperparameters.keys():
181
+ if not isinstance(k, str):
182
+ logger.warning(
183
+ f"Warning: Specified hyperparameter key is not of type str: {k} (type={type(k)}). "
184
+ f"There might be a bug in your configuration."
185
+ )
186
+
187
+ extra_ag_args = hyperparameters.pop(AG_ARGS_FIT, {})
188
+ if not isinstance(extra_ag_args, dict):
189
+ raise ValueError(
190
+ f"Invalid hyperparameter type for `{AG_ARGS_FIT}`. Expected dict, but got {type(extra_ag_args)}"
191
+ )
192
+ return hyperparameters, extra_ag_args
193
+
239
194
  def save(self, path: Optional[str] = None, verbose=True) -> str:
240
195
  if path is None:
241
196
  path = self.path
@@ -262,9 +217,6 @@ class TimeSeriesModelBase(ModelBase, ABC):
262
217
  model = load_pkl.load(path=file_path, verbose=verbose)
263
218
  if reset_paths:
264
219
  model.set_contexts(path)
265
- if hasattr(model, "_compiler"):
266
- if model._compiler is not None and not model._compiler.save_in_pkl:
267
- model.model = model._compiler.load(path=path)
268
220
  if load_oof and model._oof_predictions is None:
269
221
  model._oof_predictions = cls.load_oof_predictions(path=path, verbose=verbose)
270
222
  return model
@@ -277,7 +229,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
277
229
  @property
278
230
  def supports_known_covariates(self) -> bool:
279
231
  return (
280
- self._get_model_params().get("covariate_regressor") is not None
232
+ self.get_hyperparameters().get("covariate_regressor") is not None
281
233
  or self.__class__._supports_known_covariates
282
234
  )
283
235
 
@@ -288,7 +240,8 @@ class TimeSeriesModelBase(ModelBase, ABC):
288
240
  @property
289
241
  def supports_static_features(self) -> bool:
290
242
  return (
291
- self._get_model_params().get("covariate_regressor") is not None or self.__class__._supports_static_features
243
+ self.get_hyperparameters().get("covariate_regressor") is not None
244
+ or self.__class__._supports_static_features
292
245
  )
293
246
 
294
247
  def get_oof_predictions(self):
@@ -296,73 +249,32 @@ class TimeSeriesModelBase(ModelBase, ABC):
296
249
  self._oof_predictions = self.load_oof_predictions(self.path)
297
250
  return self._oof_predictions
298
251
 
299
- def _init_params(self):
300
- """Initializes model hyperparameters"""
301
- hyperparameters = self._user_params
302
- self.nondefault_params = []
303
- if hyperparameters is not None:
304
- self.params.update(hyperparameters)
305
- # These are hyperparameters that user has specified.
306
- self.nondefault_params = list(hyperparameters.keys())[:]
307
- self.params_trained = {}
308
-
309
- def _init_params_aux(self):
310
- """
311
- Initializes auxiliary hyperparameters.
312
- These parameters are generally not model specific and can have a wide variety of effects.
313
- For documentation on some of the available options and their defaults, refer to `self._get_default_auxiliary_params`.
314
- """
315
- hyperparameters_aux = self._user_params_aux or {}
316
- default_aux_params = dict(
317
- # ratio of given time_limit to use during fit(). If time_limit == 10 and max_time_limit_ratio=0.3,
318
- # time_limit would be changed to 3.
319
- max_time_limit_ratio=self.default_max_time_limit_ratio,
320
- # max time_limit value during fit(). If the provided time_limit is greater than this value, it will be
321
- # replaced by max_time_limit. Occurs after max_time_limit_ratio is applied.
322
- max_time_limit=None,
323
- )
324
- self.params_aux = {**default_aux_params, **hyperparameters_aux}
325
-
326
252
  def _initialize_transforms_and_regressor(self) -> None:
327
- self.target_scaler = get_target_scaler(self._get_model_params().get("target_scaler"), target=self.target)
253
+ self.target_scaler = get_target_scaler(self.get_hyperparameters().get("target_scaler"), target=self.target)
328
254
  self.covariate_scaler = get_covariate_scaler(
329
- self._get_model_params().get("covariate_scaler"),
330
- covariate_metadata=self.metadata,
255
+ self.get_hyperparameters().get("covariate_scaler"),
256
+ covariate_metadata=self.covariate_metadata,
331
257
  use_static_features=self.supports_static_features,
332
258
  use_known_covariates=self.supports_known_covariates,
333
259
  use_past_covariates=self.supports_past_covariates,
334
260
  )
335
261
  self.covariate_regressor = get_covariate_regressor(
336
- self._get_model_params().get("covariate_regressor"),
262
+ self.get_hyperparameters().get("covariate_regressor"),
337
263
  target=self.target,
338
- covariate_metadata=self.metadata,
264
+ covariate_metadata=self.covariate_metadata,
339
265
  )
340
266
 
341
- def _get_model_params(self) -> dict:
342
- return self.params.copy()
267
+ def _get_default_hyperparameters(self) -> dict:
268
+ return {}
343
269
 
344
- def get_params(self) -> dict:
345
- hyperparameters = self._user_params.copy()
346
- if self._user_params_aux:
347
- hyperparameters[AG_ARGS_FIT] = self._user_params_aux.copy()
348
-
349
- return dict(
350
- path=self.path_root,
351
- name=self.name,
352
- eval_metric=self.eval_metric,
353
- hyperparameters=hyperparameters,
354
- freq=self.freq,
355
- prediction_length=self.prediction_length,
356
- quantile_levels=self.quantile_levels,
357
- metadata=self.metadata,
358
- target=self.target,
359
- )
270
+ def get_hyperparameters(self) -> dict:
271
+ """Get hyperparameters that will be passed to the "inner model" that AutoGluon wraps."""
272
+ return {**self._get_default_hyperparameters(), **self._hyperparameters}
360
273
 
361
274
  def get_info(self) -> dict:
362
275
  """
363
276
  Returns a dictionary of numerous fields describing the model.
364
277
  """
365
- # TODO: Include self.metadata
366
278
  info = {
367
279
  "name": self.name,
368
280
  "model_type": type(self).__name__,
@@ -373,7 +285,8 @@ class TimeSeriesModelBase(ModelBase, ABC):
373
285
  "prediction_length": self.prediction_length,
374
286
  "quantile_levels": self.quantile_levels,
375
287
  "val_score": self.val_score,
376
- "hyperparameters": self.params,
288
+ "hyperparameters": self.get_hyperparameters(),
289
+ "covariate_metadata": self.covariate_metadata.to_dict(),
377
290
  }
378
291
  return info
379
292
 
@@ -438,7 +351,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
438
351
  time steps of each time series.
439
352
  """
440
353
  past_data, known_covariates = data.get_model_inputs_for_scoring(
441
- prediction_length=self.prediction_length, known_covariates_names=self.metadata.known_covariates
354
+ prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
442
355
  )
443
356
  predictions = self.predict(past_data, known_covariates=known_covariates)
444
357
  return self._score_with_predictions(data=data, predictions=predictions, metric=metric)
@@ -452,7 +365,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
452
365
  ) -> None:
453
366
  """Compute val_score, predict_time and cache out-of-fold (OOF) predictions."""
454
367
  past_data, known_covariates = val_data.get_model_inputs_for_scoring(
455
- prediction_length=self.prediction_length, known_covariates_names=self.metadata.known_covariates
368
+ prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
456
369
  )
457
370
  predict_start_time = time.time()
458
371
  oof_predictions = self.predict(past_data, known_covariates=known_covariates, **predict_kwargs)
@@ -495,49 +408,6 @@ class TimeSeriesModelBase(ModelBase, ABC):
495
408
  """
496
409
  return self
497
410
 
498
- def convert_to_refit_full_via_copy(self) -> Self:
499
- # save the model as a new model on disk
500
- previous_name = self.name
501
- self.rename(self.name + REFIT_FULL_SUFFIX)
502
- refit_model_path = self.path
503
- self.save(path=self.path, verbose=False)
504
-
505
- self.rename(previous_name)
506
-
507
- refit_model = self.load(path=refit_model_path, verbose=False)
508
- refit_model.val_score = None
509
- refit_model.predict_time = None
510
-
511
- return refit_model
512
-
513
- def convert_to_refit_full_template(self):
514
- """
515
- After calling this function, returned model should be able to be fit without X_val, y_val using the iterations trained by the original model.
516
-
517
- Increase max_memory_usage_ratio by 25% to reduce the chance that the refit model will trigger NotEnoughMemoryError and skip training.
518
- This can happen without the 25% increase since the refit model generally will use more training data and thus require more memory.
519
- """
520
- params = copy.deepcopy(self.get_params())
521
-
522
- if "hyperparameters" not in params:
523
- params["hyperparameters"] = dict()
524
-
525
- if AG_ARGS_FIT not in params["hyperparameters"]:
526
- params["hyperparameters"][AG_ARGS_FIT] = dict()
527
-
528
- params["hyperparameters"].update(self.params_trained)
529
- params["name"] = params["name"] + REFIT_FULL_SUFFIX
530
- template = self.__class__(**params)
531
-
532
- return template
533
-
534
- def get_user_params(self) -> dict:
535
- """Used to access user-specified parameters for the model before initialization."""
536
- if self._user_params is None:
537
- return {}
538
- else:
539
- return self._user_params.copy()
540
-
541
411
  def _more_tags(self) -> dict:
542
412
  """Encode model properties using tags, similar to sklearn & autogluon.tabular.
543
413
 
@@ -668,10 +538,12 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
668
538
  """
669
539
  pass
670
540
 
671
- # TODO: perform this check inside fit() ?
541
+ # TODO: this check cannot be moved inside fit because of the complex way in which
542
+ # MultiWindowBacktestingModel handles hyperparameter spaces during initialization.
543
+ # Move inside fit() after refactoring MultiWindowBacktestingModel
672
544
  def _check_fit_params(self):
673
545
  # gracefully handle hyperparameter specifications if they are provided to fit instead
674
- if any(isinstance(v, space.Space) for v in self.params.values()):
546
+ if any(isinstance(v, space.Space) for v in self.get_hyperparameters().values()):
675
547
  raise ValueError(
676
548
  "Hyperparameter spaces provided to `fit`. Please provide concrete values "
677
549
  "as hyperparameters when initializing or use `hyperparameter_tune` instead."
@@ -705,9 +577,6 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
705
577
  data is given as a separate forecast item in the dictionary, keyed by the `item_id`s
706
578
  of input items.
707
579
  """
708
- # TODO: the method signature is not aligned with the model interface in general as it allows dict
709
- assert isinstance(data, TimeSeriesDataFrame)
710
-
711
580
  if self.target_scaler is not None:
712
581
  data = self.target_scaler.fit_transform(data)
713
582
  if self.covariate_scaler is not None:
@@ -748,9 +617,59 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
748
617
  predictions = self.target_scaler.inverse_transform(predictions)
749
618
  return predictions
750
619
 
620
+ def convert_to_refit_full_via_copy(self) -> Self:
621
+ # save the model as a new model on disk
622
+ previous_name = self.name
623
+ self.rename(self.name + REFIT_FULL_SUFFIX)
624
+ refit_model_path = self.path
625
+ self.save(path=self.path, verbose=False)
626
+
627
+ self.rename(previous_name)
628
+
629
+ refit_model = self.load(path=refit_model_path, verbose=False)
630
+ refit_model.val_score = None
631
+ refit_model.predict_time = None
632
+
633
+ return refit_model
634
+
635
+ def convert_to_refit_full_template(self):
636
+ """After calling this function, returned model should be able to be fit without `val_data`."""
637
+ params = copy.deepcopy(self.get_params())
638
+
639
+ if "hyperparameters" not in params:
640
+ params["hyperparameters"] = dict()
641
+
642
+ if AG_ARGS_FIT not in params["hyperparameters"]:
643
+ params["hyperparameters"][AG_ARGS_FIT] = dict()
644
+
645
+ params["name"] = params["name"] + REFIT_FULL_SUFFIX
646
+ template = self.__class__(**params)
647
+
648
+ return template
649
+
650
+ def get_params(self) -> dict:
651
+ """Get the constructor parameters required for cloning this model object"""
652
+ hyperparameters = self.get_hyperparameters().copy()
653
+ if self._extra_ag_args:
654
+ hyperparameters[AG_ARGS_FIT] = self._extra_ag_args.copy()
655
+
656
+ return dict(
657
+ path=self.path_root,
658
+ name=self.name,
659
+ eval_metric=self.eval_metric,
660
+ hyperparameters=hyperparameters,
661
+ freq=self.freq,
662
+ prediction_length=self.prediction_length,
663
+ quantile_levels=self.quantile_levels,
664
+ covariate_metadata=self.covariate_metadata,
665
+ target=self.target,
666
+ )
667
+
751
668
  def get_forecast_horizon_index(self, data: TimeSeriesDataFrame) -> pd.MultiIndex:
752
669
  """For each item in the dataframe, get timestamps for the next `prediction_length` time steps into the future."""
753
- return get_forecast_horizon_index_ts_dataframe(data, prediction_length=self.prediction_length, freq=self.freq)
670
+ return pd.MultiIndex.from_frame(
671
+ make_future_data_frame(data, prediction_length=self.prediction_length, freq=self.freq)
672
+ )
754
673
 
755
674
  @abstractmethod
756
675
  def _predict(
@@ -764,8 +683,8 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
764
683
 
765
684
  def _preprocess_time_limit(self, time_limit: float) -> float:
766
685
  original_time_limit = time_limit
767
- max_time_limit_ratio = self.params_aux["max_time_limit_ratio"]
768
- max_time_limit = self.params_aux["max_time_limit"]
686
+ max_time_limit_ratio = self._extra_ag_args.get("max_time_limit_ratio", self.default_max_time_limit_ratio)
687
+ max_time_limit = self._extra_ag_args.get("max_time_limit")
769
688
 
770
689
  time_limit *= max_time_limit_ratio
771
690
 
@@ -788,5 +707,5 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
788
707
  """Sets up default search space for HPO. Each hyperparameter which user did not specify is converted from
789
708
  default fixed value to default search space.
790
709
  """
791
- params = self.params.copy()
710
+ params = self._hyperparameters.copy()
792
711
  return params
@@ -134,8 +134,8 @@ class AbstractMLForecastModel(AbstractTimeSeriesModel):
134
134
  def _get_extra_tabular_init_kwargs(self) -> dict:
135
135
  raise NotImplementedError
136
136
 
137
- def _get_model_params(self) -> dict:
138
- model_params = super()._get_model_params().copy()
137
+ def get_hyperparameters(self) -> dict:
138
+ model_params = super().get_hyperparameters().copy()
139
139
  model_params.setdefault("max_num_items", 20_000)
140
140
  model_params.setdefault("max_num_samples", 1_000_000)
141
141
  model_params.setdefault("tabular_hyperparameters", {"GBM": {}})
@@ -263,7 +263,7 @@ class AbstractMLForecastModel(AbstractTimeSeriesModel):
263
263
  Each row contains unique_id, ds, y, and (optionally) known covariates & static features.
264
264
  """
265
265
  # TODO: Add support for past_covariates
266
- selected_columns = self.metadata.known_covariates.copy()
266
+ selected_columns = self.covariate_metadata.known_covariates.copy()
267
267
  column_name_mapping = {ITEMID: MLF_ITEMID, TIMESTAMP: MLF_TIMESTAMP}
268
268
  if include_target:
269
269
  selected_columns += [self.target]
@@ -297,11 +297,11 @@ class AbstractMLForecastModel(AbstractTimeSeriesModel):
297
297
  self._check_fit_params()
298
298
  fit_start_time = time.time()
299
299
  self._train_target_median = train_data[self.target].median()
300
- for col in self.metadata.known_covariates_real:
300
+ for col in self.covariate_metadata.known_covariates_real:
301
301
  if not set(train_data[col].unique()) == set([0, 1]):
302
302
  self._non_boolean_real_covariates.append(col)
303
303
  # TabularEstimator is passed to MLForecast later to include tuning_data
304
- model_params = self._get_model_params()
304
+ model_params = self.get_hyperparameters()
305
305
 
306
306
  mlforecast_init_args = self._get_mlforecast_init_args(train_data, model_params)
307
307
  self._mlf = MLForecast(models={}, freq=self.freq, **mlforecast_init_args)
@@ -473,8 +473,8 @@ class DirectTabularModel(AbstractMLForecastModel):
473
473
  def is_quantile_model(self) -> bool:
474
474
  return self.eval_metric.needs_quantile
475
475
 
476
- def _get_model_params(self) -> dict:
477
- model_params = super()._get_model_params()
476
+ def get_hyperparameters(self) -> dict:
477
+ model_params = super().get_hyperparameters()
478
478
  model_params.setdefault("target_scaler", "mean_abs")
479
479
  if "differences" not in model_params or model_params["differences"] is None:
480
480
  model_params["differences"] = []
@@ -622,8 +622,8 @@ class RecursiveTabularModel(AbstractMLForecastModel):
622
622
  end of each time series).
623
623
  """
624
624
 
625
- def _get_model_params(self) -> dict:
626
- model_params = super()._get_model_params()
625
+ def get_hyperparameters(self) -> dict:
626
+ model_params = super().get_hyperparameters()
627
627
  model_params.setdefault("target_scaler", "standard")
628
628
  if "differences" not in model_params or model_params["differences"] is None:
629
629
  model_params["differences"] = [get_seasonality(self.freq)]