From 83bba235e504cc705423d420541a1618a166075f Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Tue, 25 Jul 2023 16:42:57 +0200 Subject: [PATCH 01/17] add global_forecasting_model_wrapper.py --- darts/models/__init__.py | 3 + .../global_forecasting_model_wrapper.py | 183 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 darts/models/forecasting/global_forecasting_model_wrapper.py diff --git a/darts/models/__init__.py b/darts/models/__init__.py index 52ef4d31bf..87e7eb5a3c 100644 --- a/darts/models/__init__.py +++ b/darts/models/__init__.py @@ -18,6 +18,9 @@ ) from darts.models.forecasting.exponential_smoothing import ExponentialSmoothing from darts.models.forecasting.fft import FFT +from darts.models.forecasting.global_forecasting_model_wrapper import ( + GlobalForecastingModelWrapper, +) from darts.models.forecasting.kalman_forecaster import KalmanForecaster from darts.models.forecasting.linear_regression_model import LinearRegressionModel from darts.models.forecasting.random_forest import RandomForest diff --git a/darts/models/forecasting/global_forecasting_model_wrapper.py b/darts/models/forecasting/global_forecasting_model_wrapper.py new file mode 100644 index 0000000000..73df90db67 --- /dev/null +++ b/darts/models/forecasting/global_forecasting_model_wrapper.py @@ -0,0 +1,183 @@ +""" +Regression ensemble model +------------------------- + +An ensemble model which uses a regression model to compute the ensemble forecast. +""" +from typing import List, Optional, Sequence, Tuple, Union + +from darts.logging import get_logger +from darts.models.forecasting.forecasting_model import ( + FutureCovariatesLocalForecastingModel, + GlobalForecastingModel, + TransferableFutureCovariatesLocalForecastingModel, +) +from darts.timeseries import TimeSeries, concatenate +from darts.utils.utils import seq2series, series2seq + +logger = get_logger(__name__) + + +class GlobalForecastingModelWrapper(GlobalForecastingModel): + def __init__(self, model: FutureCovariatesLocalForecastingModel): + """ + Use a regression model for ensembling individual models' predictions using the stacking technique [1]_. + + The provided regression model must implement ``fit()`` and ``predict()`` methods + (e.g. scikit-learn regression models). Note that here the regression model is used to learn how to + best ensemble the individual forecasting models' forecasts. It is not the same usage of regression + as in :class:`RegressionModel`, where the regression model is used to produce forecasts based on the + lagged series. + + If `future_covariates` or `past_covariates` are provided at training or inference time, + they will be passed only to the forecasting models supporting them. + + The regression model does not leverage the covariates passed to ``fit()`` and ``predict()``. + + Parameters + ---------- + forecasting_models + List of forecasting models whose predictions to ensemble + regression_train_n_points + The number of points to use to train the regression model + regression_model + Any regression model with ``predict()`` and ``fit()`` methods (e.g. from scikit-learn) + Default: ``darts.model.LinearRegressionModel(fit_intercept=False)`` + + .. note:: + if `regression_model` is probabilistic, the `RegressionEnsembleModel` will also be probabilistic. + .. + regression_train_num_samples + Number of prediction samples from each forecasting model to train the regression model (samples are + averaged). Should be set to 1 for deterministic models. Default: 1. + + .. note:: + if `forecasting_models` contains a mix of probabilistic and deterministic models, + `regression_train_num_samples will be passed only to the probabilistic ones. + .. + regression_train_samples_reduction + If `forecasting models` are probabilistic and `regression_train_num_samples` > 1, method used to + reduce the samples before passing them to the regression model. Possible values: "mean", "median" + or float value corresponding to the desired quantile. Default: "median" + show_warnings + Whether to show warnings related to forecasting_models covariates support. + References + ---------- + .. [1] D. H. Wolpert, “Stacked generalization”, Neural Networks, vol. 5, no. 2, pp. 241–259, Jan. 1992 + """ + super().__init__() + + self.base_model: FutureCovariatesLocalForecastingModel = model + self._trained_models: List[List[FutureCovariatesLocalForecastingModel]] = [] + + def fit( + self, + series: Union[TimeSeries, Sequence[TimeSeries]], + past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, + future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, + ): + self._trained_models = [] + super().fit(series) + + for multivariate_ts in series2seq(series): + components = self._split_multivariate(multivariate_ts) + if self.supports_future_covariates: + series_models = [ + self.base_model.untrained_model().fit(c, future_covariates) + for c in components + ] + else: + series_models = [ + self.base_model.untrained_model().fit(c) for c in components + ] + self._trained_models.append(series_models) + + return self + + def predict( + self, + n: int, + series: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, + past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, + future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, + num_samples: int = 1, + verbose: bool = False, + ) -> Union[TimeSeries, Sequence[TimeSeries]]: + result = [] + for ts_models in self._trained_models: + if self.supports_future_covariates: + predictions = [ + model.predict(n=n, future_covariates=future_covariates) + for model in ts_models + ] + else: + predictions = [model.predict(n=n) for model in ts_models] + multivariate_series = concatenate(predictions, axis=1) + result.append(multivariate_series) + return seq2series(result) + + def _split_multivariate(self, time_series: TimeSeries): + """ " split multivariate TimeSeries into a list of univariate TimeSeries""" + return [ + time_series.univariate_component(i) for i in range(time_series.n_components) + ] + + @property + def extreme_lags( + self, + ) -> Tuple[ + Optional[int], + Optional[int], + Optional[int], + Optional[int], + Optional[int], + Optional[int], + ]: + return self.base_model.extreme_lags + + @property + def _model_encoder_settings( + self, + ) -> Tuple[ + Optional[int], + Optional[int], + bool, + bool, + Optional[List[int]], + Optional[List[int]], + ]: + return None, None, False, self.supports_future_covariates, None, None + + @property + def supports_multivariate(self) -> bool: + return True + + @property + def supports_past_covariates(self) -> bool: + return False + + @property + def supports_future_covariates(self) -> bool: + return self.base_model.supports_future_covariates + + @property + def supports_static_covariates(self) -> bool: + return False + + def _is_probabilistic(self) -> bool: + """ + A RegressionEnsembleModel is probabilistic if its regression + model is probabilistic (ensembling layer) + """ + return self.base_model._is_probabilistic() + + def _supports_non_retrainable_historical_forecasts(self) -> bool: + return isinstance( + self.base_model, TransferableFutureCovariatesLocalForecastingModel + ) + + @property + def _supress_generate_predict_encoding(self) -> bool: + return isinstance( + self.base_model, TransferableFutureCovariatesLocalForecastingModel + ) From 08999fcdf0371003d1321d97fb96068710c2546f Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Tue, 25 Jul 2023 16:43:28 +0200 Subject: [PATCH 02/17] add tests --- .../test_global_forecasting_model_wrapper.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py diff --git a/darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py new file mode 100644 index 0000000000..84ba52bbad --- /dev/null +++ b/darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py @@ -0,0 +1,116 @@ +import copy + +from darts import TimeSeries +from darts.logging import get_logger +from darts.models import ( + ARIMA, + BATS, + FFT, + TBATS, + AutoARIMA, + Croston, + ExponentialSmoothing, + FourTheta, + GlobalForecastingModelWrapper, + KalmanForecaster, + NaiveMean, + NaiveMovingAverage, + NaiveSeasonal, + Prophet, + StatsForecastAutoCES, + StatsForecastAutoTheta, + Theta, +) +from darts.tests.base_test_class import DartsBaseTestClass +from darts.utils import timeseries_generation as tg + +logger = get_logger(__name__) + +local_models = [ + NaiveMean(), + NaiveMovingAverage(5), + NaiveSeasonal(), + ExponentialSmoothing(), + StatsForecastAutoTheta(season_length=12), + StatsForecastAutoCES(season_length=12, model="Z"), + Theta(1), + FourTheta(1), + FFT(trend="poly"), + TBATS(use_trend=True, use_arma_errors=True, use_box_cox=True), + BATS(use_trend=True, use_arma_errors=True, use_box_cox=True), +] + +future_covariates_models = [ + Prophet(), + Croston(), + AutoARIMA(), + ARIMA(12, 1, 1), + KalmanForecaster(), +] + + +class GlobalForecastingModelTestCase(DartsBaseTestClass): + RANDOM_SEED = 42 + + ts_length = 50 + n_pred = 5 + + univariate = tg.gaussian_timeseries(length=ts_length, mean=50) + multivariate = univariate.stack(univariate) + + future_covariates = tg.gaussian_timeseries(length=ts_length + n_pred, mean=50) + + def test_fit_predict_local_models(self): + for model in local_models: + self._test_predict_with_base_model(model, None) + + def test_fit_predict_local_future_covariates_models(self): + for model in future_covariates_models: + self._test_predict_with_base_model(model, self.future_covariates) + + def test_encoders_support(self): + add_encoders = { + "position": {"future": ["relative"]}, + } + + # test some models that support encoders + for model_object in future_covariates_models: + # test once with user supplied covariates, and once without + for fc in [self.future_covariates, None]: + model_params = { + k: vals + for k, vals in copy.deepcopy(model_object.model_params).items() + } + model_params["add_encoders"] = add_encoders + model = model_object.__class__(**model_params) + + # Test models with user supplied covariates + model.fit(self.univariate, future_covariates=fc) + + prediction = model.predict(self.n_pred, future_covariates=fc) + self.assertTrue(len(prediction) == self.n_pred) + + def _test_predict_with_base_model(self, model, future_covariates): + series_univariate = self.univariate + series_multivariate = self.multivariate + + combinations = [ + series_univariate, + [series_univariate] * 2, + series_multivariate, + [series_multivariate] * 2, + ] + + for combination in combinations: + preds = self.trained_model_predictions( + model, self.n_pred, combination, future_covariates + ) + if isinstance(combination, TimeSeries): + self.assertTrue(isinstance(preds, TimeSeries)) + else: + self.assertTrue(isinstance(preds, list) and len(preds) == 2) + + def trained_model_predictions(self, base_model, n, series, future_covariates): + model = GlobalForecastingModelWrapper(base_model) + model.fit(series, future_covariates=future_covariates) + return model.predict(n=n, series=series, future_covariates=future_covariates) From a079c8ec0aa254413ed90cf43aebc46e9f1c3eba Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Tue, 25 Jul 2023 16:53:49 +0200 Subject: [PATCH 03/17] add comments --- .../global_forecasting_model_wrapper.py | 50 +++---------------- 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/darts/models/forecasting/global_forecasting_model_wrapper.py b/darts/models/forecasting/global_forecasting_model_wrapper.py index 73df90db67..40afe25773 100644 --- a/darts/models/forecasting/global_forecasting_model_wrapper.py +++ b/darts/models/forecasting/global_forecasting_model_wrapper.py @@ -21,49 +21,15 @@ class GlobalForecastingModelWrapper(GlobalForecastingModel): def __init__(self, model: FutureCovariatesLocalForecastingModel): """ - Use a regression model for ensembling individual models' predictions using the stacking technique [1]_. + Wrapper around LocalForecastingModel allowing it to act like a GlobalForecastingModel - The provided regression model must implement ``fit()`` and ``predict()`` methods - (e.g. scikit-learn regression models). Note that here the regression model is used to learn how to - best ensemble the individual forecasting models' forecasts. It is not the same usage of regression - as in :class:`RegressionModel`, where the regression model is used to produce forecasts based on the - lagged series. - - If `future_covariates` or `past_covariates` are provided at training or inference time, - they will be passed only to the forecasting models supporting them. - - The regression model does not leverage the covariates passed to ``fit()`` and ``predict()``. + A copy of the provided model will be trained on each of the components of the provided series separately. + The model doesn't use series supplied during predict() and instead predicts on the series it trained on. Parameters ---------- - forecasting_models - List of forecasting models whose predictions to ensemble - regression_train_n_points - The number of points to use to train the regression model - regression_model - Any regression model with ``predict()`` and ``fit()`` methods (e.g. from scikit-learn) - Default: ``darts.model.LinearRegressionModel(fit_intercept=False)`` - - .. note:: - if `regression_model` is probabilistic, the `RegressionEnsembleModel` will also be probabilistic. - .. - regression_train_num_samples - Number of prediction samples from each forecasting model to train the regression model (samples are - averaged). Should be set to 1 for deterministic models. Default: 1. - - .. note:: - if `forecasting_models` contains a mix of probabilistic and deterministic models, - `regression_train_num_samples will be passed only to the probabilistic ones. - .. - regression_train_samples_reduction - If `forecasting models` are probabilistic and `regression_train_num_samples` > 1, method used to - reduce the samples before passing them to the regression model. Possible values: "mean", "median" - or float value corresponding to the desired quantile. Default: "median" - show_warnings - Whether to show warnings related to forecasting_models covariates support. - References - ---------- - .. [1] D. H. Wolpert, “Stacked generalization”, Neural Networks, vol. 5, no. 2, pp. 241–259, Jan. 1992 + model + Model used to predict individual components """ super().__init__() @@ -117,7 +83,7 @@ def predict( return seq2series(result) def _split_multivariate(self, time_series: TimeSeries): - """ " split multivariate TimeSeries into a list of univariate TimeSeries""" + """split multivariate TimeSeries into a list of univariate TimeSeries""" return [ time_series.univariate_component(i) for i in range(time_series.n_components) ] @@ -166,8 +132,8 @@ def supports_static_covariates(self) -> bool: def _is_probabilistic(self) -> bool: """ - A RegressionEnsembleModel is probabilistic if its regression - model is probabilistic (ensembling layer) + A GlobalForecastingModelWrappers is probabilistic if the base_model + is probabilistic """ return self.base_model._is_probabilistic() From d652e3a1b5f34fae98ad2f33a718e0ea387f6c09 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Wed, 2 Aug 2023 13:00:12 +0200 Subject: [PATCH 04/17] Switch to being a multivariate model wrapper instead of a global model wrapper --- darts/models/__init__.py | 6 +- ...multivariate_forecasting_model_wrapper.py} | 108 +++++++++--------- ...multivariate_forecasting_model_wrapper.py} | 20 +--- 3 files changed, 64 insertions(+), 70 deletions(-) rename darts/models/forecasting/{global_forecasting_model_wrapper.py => multivariate_forecasting_model_wrapper.py} (58%) rename darts/tests/models/forecasting/{test_global_forecasting_model_wrapper.py => test_multivariate_forecasting_model_wrapper.py} (80%) diff --git a/darts/models/__init__.py b/darts/models/__init__.py index e02d67670b..2650c5f57f 100644 --- a/darts/models/__init__.py +++ b/darts/models/__init__.py @@ -18,11 +18,11 @@ ) from darts.models.forecasting.exponential_smoothing import ExponentialSmoothing from darts.models.forecasting.fft import FFT -from darts.models.forecasting.global_forecasting_model_wrapper import ( - GlobalForecastingModelWrapper, -) from darts.models.forecasting.kalman_forecaster import KalmanForecaster from darts.models.forecasting.linear_regression_model import LinearRegressionModel +from darts.models.forecasting.multivariate_forecasting_model_wrapper import ( + MultivariateForecastingModelWrapper, +) from darts.models.forecasting.random_forest import RandomForest from darts.models.forecasting.regression_ensemble_model import RegressionEnsembleModel from darts.models.forecasting.regression_model import RegressionModel diff --git a/darts/models/forecasting/global_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py similarity index 58% rename from darts/models/forecasting/global_forecasting_model_wrapper.py rename to darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 40afe25773..47f1ce27e0 100644 --- a/darts/models/forecasting/global_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -4,22 +4,70 @@ An ensemble model which uses a regression model to compute the ensemble forecast. """ -from typing import List, Optional, Sequence, Tuple, Union +from typing import List, Optional, Tuple from darts.logging import get_logger from darts.models.forecasting.forecasting_model import ( FutureCovariatesLocalForecastingModel, - GlobalForecastingModel, + LocalForecastingModel, TransferableFutureCovariatesLocalForecastingModel, ) from darts.timeseries import TimeSeries, concatenate -from darts.utils.utils import seq2series, series2seq +from darts.utils.utils import seq2series logger = get_logger(__name__) -class GlobalForecastingModelWrapper(GlobalForecastingModel): - def __init__(self, model: FutureCovariatesLocalForecastingModel): +class MultivariateForecastingModelWrapper(FutureCovariatesLocalForecastingModel): + def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None): + super()._fit(series, future_covariates) + self._trained_models = [] + + series = seq2series(series) + components = self._split_multivariate(series) + if self.supports_future_covariates: + self._trained_models = [ + self.base_model.untrained_model().fit(c, future_covariates) + for c in components + ] + else: + self._trained_models = [ + self.base_model.untrained_model().fit(c) for c in components + ] + + return self + + def predict( + self, + n: int, + series: Optional[TimeSeries] = None, + future_covariates: Optional[TimeSeries] = None, + num_samples: int = 1, + **kwargs, + ) -> TimeSeries: + # we override `predict()` to pass a non-None `series`, so that historic_future_covariates + # will be passed to `_predict()` (some future covariates local models require it ex. Kalman) + return self._predict(n, future_covariates, num_samples, **kwargs) + + def _predict( + self, + n: int, + future_covariates: Optional[TimeSeries] = None, + num_samples: int = 1, + verbose: bool = False, + **kwargs, + ) -> TimeSeries: + predictions = [ + model.predict(n=n, future_covariates=future_covariates) + if isinstance(model, FutureCovariatesLocalForecastingModel) + else model.predict(n=n) + for model in self._trained_models + ] + + multivariate_series = concatenate(predictions, axis=1) + return multivariate_series + + def __init__(self, model: LocalForecastingModel): """ Wrapper around LocalForecastingModel allowing it to act like a GlobalForecastingModel @@ -33,54 +81,8 @@ def __init__(self, model: FutureCovariatesLocalForecastingModel): """ super().__init__() - self.base_model: FutureCovariatesLocalForecastingModel = model - self._trained_models: List[List[FutureCovariatesLocalForecastingModel]] = [] - - def fit( - self, - series: Union[TimeSeries, Sequence[TimeSeries]], - past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, - future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, - ): - self._trained_models = [] - super().fit(series) - - for multivariate_ts in series2seq(series): - components = self._split_multivariate(multivariate_ts) - if self.supports_future_covariates: - series_models = [ - self.base_model.untrained_model().fit(c, future_covariates) - for c in components - ] - else: - series_models = [ - self.base_model.untrained_model().fit(c) for c in components - ] - self._trained_models.append(series_models) - - return self - - def predict( - self, - n: int, - series: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, - past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, - future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None, - num_samples: int = 1, - verbose: bool = False, - ) -> Union[TimeSeries, Sequence[TimeSeries]]: - result = [] - for ts_models in self._trained_models: - if self.supports_future_covariates: - predictions = [ - model.predict(n=n, future_covariates=future_covariates) - for model in ts_models - ] - else: - predictions = [model.predict(n=n) for model in ts_models] - multivariate_series = concatenate(predictions, axis=1) - result.append(multivariate_series) - return seq2series(result) + self.base_model: LocalForecastingModel = model + self._trained_models: List[LocalForecastingModel] = [] def _split_multivariate(self, time_series: TimeSeries): """split multivariate TimeSeries into a list of univariate TimeSeries""" diff --git a/darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py similarity index 80% rename from darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py rename to darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py index 84ba52bbad..263c52f62f 100644 --- a/darts/tests/models/forecasting/test_global_forecasting_model_wrapper.py +++ b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py @@ -11,8 +11,8 @@ Croston, ExponentialSmoothing, FourTheta, - GlobalForecastingModelWrapper, KalmanForecaster, + MultivariateForecastingModelWrapper, NaiveMean, NaiveMovingAverage, NaiveSeasonal, @@ -49,7 +49,7 @@ ] -class GlobalForecastingModelTestCase(DartsBaseTestClass): +class MultivariateForecastingModelWrapperTestCase(DartsBaseTestClass): RANDOM_SEED = 42 ts_length = 50 @@ -84,11 +84,7 @@ def test_encoders_support(self): model_params["add_encoders"] = add_encoders model = model_object.__class__(**model_params) - # Test models with user supplied covariates - model.fit(self.univariate, future_covariates=fc) - - prediction = model.predict(self.n_pred, future_covariates=fc) - self.assertTrue(len(prediction) == self.n_pred) + self._test_predict_with_base_model(model, fc) def _test_predict_with_base_model(self, model, future_covariates): series_univariate = self.univariate @@ -96,21 +92,17 @@ def _test_predict_with_base_model(self, model, future_covariates): combinations = [ series_univariate, - [series_univariate] * 2, series_multivariate, - [series_multivariate] * 2, ] for combination in combinations: preds = self.trained_model_predictions( model, self.n_pred, combination, future_covariates ) - if isinstance(combination, TimeSeries): - self.assertTrue(isinstance(preds, TimeSeries)) - else: - self.assertTrue(isinstance(preds, list) and len(preds) == 2) + self.assertTrue(isinstance(preds, TimeSeries)) + self.assertTrue(preds.n_components == combination.n_components) def trained_model_predictions(self, base_model, n, series, future_covariates): - model = GlobalForecastingModelWrapper(base_model) + model = MultivariateForecastingModelWrapper(base_model) model.fit(series, future_covariates=future_covariates) return model.predict(n=n, series=series, future_covariates=future_covariates) From ba39d5598f7a000ec61142fe5b490501f2b4086c Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Fri, 25 Aug 2023 23:10:38 +0200 Subject: [PATCH 05/17] refactor --- .../multivariate_forecasting_model_wrapper.py | 70 ++++++++----------- darts/timeseries.py | 7 ++ 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 47f1ce27e0..2ed8200f51 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -1,8 +1,8 @@ """ -Regression ensemble model +Multivariate forecasting model wrapper ------------------------- -An ensemble model which uses a regression model to compute the ensemble forecast. +A wrapper around local forecasting models which allows forecasting multiple components """ from typing import List, Optional, Tuple @@ -12,27 +12,42 @@ LocalForecastingModel, TransferableFutureCovariatesLocalForecastingModel, ) -from darts.timeseries import TimeSeries, concatenate +from darts.timeseries import TimeSeries, concatenate, split_multivariate from darts.utils.utils import seq2series logger = get_logger(__name__) class MultivariateForecastingModelWrapper(FutureCovariatesLocalForecastingModel): + def __init__(self, model: LocalForecastingModel): + """ + Wrapper around LocalForecastingModel allowing it to predict multivariate TimeSeries. + + A copy of the provided model will be trained on each of the components of the provided series separately. + Parameters + ---------- + model + Model used to predict individual components + """ + super().__init__() + + self.model: LocalForecastingModel = model + self._trained_models: List[LocalForecastingModel] = [] + def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None): super()._fit(series, future_covariates) self._trained_models = [] series = seq2series(series) - components = self._split_multivariate(series) + components = split_multivariate(series) if self.supports_future_covariates: self._trained_models = [ - self.base_model.untrained_model().fit(c, future_covariates) + self.model.untrained_model().fit(c, future_covariates) for c in components ] else: self._trained_models = [ - self.base_model.untrained_model().fit(c) for c in components + self.model.untrained_model().fit(c) for c in components ] return self @@ -67,29 +82,6 @@ def _predict( multivariate_series = concatenate(predictions, axis=1) return multivariate_series - def __init__(self, model: LocalForecastingModel): - """ - Wrapper around LocalForecastingModel allowing it to act like a GlobalForecastingModel - - A copy of the provided model will be trained on each of the components of the provided series separately. - The model doesn't use series supplied during predict() and instead predicts on the series it trained on. - - Parameters - ---------- - model - Model used to predict individual components - """ - super().__init__() - - self.base_model: LocalForecastingModel = model - self._trained_models: List[LocalForecastingModel] = [] - - def _split_multivariate(self, time_series: TimeSeries): - """split multivariate TimeSeries into a list of univariate TimeSeries""" - return [ - time_series.univariate_component(i) for i in range(time_series.n_components) - ] - @property def extreme_lags( self, @@ -101,7 +93,7 @@ def extreme_lags( Optional[int], Optional[int], ]: - return self.base_model.extreme_lags + return self.model.extreme_lags @property def _model_encoder_settings( @@ -122,30 +114,26 @@ def supports_multivariate(self) -> bool: @property def supports_past_covariates(self) -> bool: - return False + return self.model.supports_past_covariates @property def supports_future_covariates(self) -> bool: - return self.base_model.supports_future_covariates + return self.model.supports_future_covariates @property def supports_static_covariates(self) -> bool: - return False + return self.model.supports_static_covariates def _is_probabilistic(self) -> bool: """ - A GlobalForecastingModelWrappers is probabilistic if the base_model + A MultivariateForecastingModelWrapper is probabilistic if the base_model is probabilistic """ - return self.base_model._is_probabilistic() + return self.model._is_probabilistic def _supports_non_retrainable_historical_forecasts(self) -> bool: - return isinstance( - self.base_model, TransferableFutureCovariatesLocalForecastingModel - ) + return isinstance(self.model, TransferableFutureCovariatesLocalForecastingModel) @property def _supress_generate_predict_encoding(self) -> bool: - return isinstance( - self.base_model, TransferableFutureCovariatesLocalForecastingModel - ) + return isinstance(self.model, TransferableFutureCovariatesLocalForecastingModel) diff --git a/darts/timeseries.py b/darts/timeseries.py index 2c54268d2c..7e66088378 100644 --- a/darts/timeseries.py +++ b/darts/timeseries.py @@ -5242,3 +5242,10 @@ def concatenate( ) return TimeSeries.from_xarray(da_concat, fill_missing_dates=False) + + +def split_multivariate(time_series: TimeSeries): + """split multivariate TimeSeries into a list of univariate TimeSeries""" + return [ + time_series.univariate_component(i) for i in range(time_series.n_components) + ] From 48cb1567818714205494a347beaf1666fe339870 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Fri, 25 Aug 2023 23:11:15 +0200 Subject: [PATCH 06/17] add missing property decorator --- .../models/forecasting/multivariate_forecasting_model_wrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 2ed8200f51..3c53c0d6a5 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -124,6 +124,7 @@ def supports_future_covariates(self) -> bool: def supports_static_covariates(self) -> bool: return self.model.supports_static_covariates + @property def _is_probabilistic(self) -> bool: """ A MultivariateForecastingModelWrapper is probabilistic if the base_model From fb1c06b00e2eb72551a5f0191fe2efc171ec07b2 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Sun, 27 Aug 2023 12:09:42 +0200 Subject: [PATCH 07/17] update tests --- .../test_multivariate_forecasting_model_wrapper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py index 263c52f62f..e48f7840aa 100644 --- a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py +++ b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py @@ -21,7 +21,6 @@ StatsForecastAutoTheta, Theta, ) -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -49,7 +48,7 @@ ] -class MultivariateForecastingModelWrapperTestCase(DartsBaseTestClass): +class TestMultivariateForecastingModelWrapper: RANDOM_SEED = 42 ts_length = 50 @@ -99,8 +98,8 @@ def _test_predict_with_base_model(self, model, future_covariates): preds = self.trained_model_predictions( model, self.n_pred, combination, future_covariates ) - self.assertTrue(isinstance(preds, TimeSeries)) - self.assertTrue(preds.n_components == combination.n_components) + assert isinstance(preds, TimeSeries) + assert preds.n_components == combination.n_components def trained_model_predictions(self, base_model, n, series, future_covariates): model = MultivariateForecastingModelWrapper(base_model) From 5a0e32a43723f09ad89a757d201ee782f451715a Mon Sep 17 00:00:00 2001 From: Felix Divo Date: Fri, 12 Jan 2024 18:17:35 +0100 Subject: [PATCH 08/17] Improve testing code for MultivariateForecastingModelWrapper --- .../multivariate_forecasting_model_wrapper.py | 2 +- ..._multivariate_forecasting_model_wrapper.py | 82 ++++++++++++------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 3c53c0d6a5..f0d4acb42a 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -74,7 +74,7 @@ def _predict( ) -> TimeSeries: predictions = [ model.predict(n=n, future_covariates=future_covariates) - if isinstance(model, FutureCovariatesLocalForecastingModel) + if model.supports_future_covariates else model.predict(n=n) for model in self._trained_models ] diff --git a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py index e48f7840aa..23a607d7c2 100644 --- a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py +++ b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py @@ -1,5 +1,7 @@ import copy +import pytest + from darts import TimeSeries from darts.logging import get_logger from darts.models import ( @@ -55,53 +57,71 @@ class TestMultivariateForecastingModelWrapper: n_pred = 5 univariate = tg.gaussian_timeseries(length=ts_length, mean=50) - multivariate = univariate.stack(univariate) + multivariate = univariate.stack(tg.gaussian_timeseries(length=ts_length, mean=20)) future_covariates = tg.gaussian_timeseries(length=ts_length + n_pred, mean=50) - def test_fit_predict_local_models(self): - for model in local_models: - self._test_predict_with_base_model(model, None) + @pytest.mark.parametrize("model", local_models) + def test_fit_predict_local_models(self, model): + self._test_predict_with_base_model(model) - def test_fit_predict_local_future_covariates_models(self): - for model in future_covariates_models: - self._test_predict_with_base_model(model, self.future_covariates) + @pytest.mark.parametrize("model", future_covariates_models) + def test_fit_predict_local_future_covariates_models(self, model): + self._test_predict_with_base_model(model, self.future_covariates) - def test_encoders_support(self): + @pytest.mark.parametrize("model_object", future_covariates_models) + def test_encoders_support(self, model_object): add_encoders = { "position": {"future": ["relative"]}, } - # test some models that support encoders - for model_object in future_covariates_models: - # test once with user supplied covariates, and once without - for fc in [self.future_covariates, None]: - model_params = { - k: vals - for k, vals in copy.deepcopy(model_object.model_params).items() - } - model_params["add_encoders"] = add_encoders - model = model_object.__class__(**model_params) - - self._test_predict_with_base_model(model, fc) - - def _test_predict_with_base_model(self, model, future_covariates): - series_univariate = self.univariate - series_multivariate = self.multivariate - - combinations = [ - series_univariate, - series_multivariate, - ] - - for combination in combinations: + # test once with user supplied covariates, and once without + for fc in [self.future_covariates, None]: + model_params = { + k: vals for k, vals in copy.deepcopy(model_object.model_params).items() + } + model_params["add_encoders"] = add_encoders + model = model_object.__class__(**model_params) + + self._test_predict_with_base_model(model, fc) + + def _test_predict_with_base_model(self, model, future_covariates=None): + for combination in [self.univariate, self.multivariate]: preds = self.trained_model_predictions( model, self.n_pred, combination, future_covariates ) assert isinstance(preds, TimeSeries) assert preds.n_components == combination.n_components + # Make sure that the compound prediction is the same as the individual predictions + individual_preds = self.trained_individual_model_predictions( + model, self.n_pred, combination, future_covariates + ) + for component in range(combination.n_components): + assert ( + preds.univariate_component(component) == individual_preds[component] + ) + def trained_model_predictions(self, base_model, n, series, future_covariates): model = MultivariateForecastingModelWrapper(base_model) model.fit(series, future_covariates=future_covariates) return model.predict(n=n, series=series, future_covariates=future_covariates) + + def trained_individual_model_predictions( + self, base_model, n, series, future_covariates + ): + predictions = [] + for component in range(series.n_components): + single_series = series.univariate_component(component) + + model = base_model.untrained_model() + if model.supports_future_covariates: + model.fit(single_series, future_covariates=future_covariates) + predictions.append( + model.predict(n=n, future_covariates=future_covariates) + ) + else: + model.fit(single_series) + predictions.append(model.predict(n=n)) + + return predictions From bf64cff4eed78791fd73528780fdfeba85b6e670 Mon Sep 17 00:00:00 2001 From: JanFidor <66260538+JanFidor@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:16:57 +0100 Subject: [PATCH 09/17] Expand description Co-authored-by: madtoinou <32447896+madtoinou@users.noreply.github.com> --- .../forecasting/multivariate_forecasting_model_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index f0d4acb42a..303c8d61ad 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -2,7 +2,7 @@ Multivariate forecasting model wrapper ------------------------- -A wrapper around local forecasting models which allows forecasting multiple components +A wrapper around local forecasting models to enable multivariate series training and forecasting. One model is trained for each component of the target series, independently of the others hence ignoring the potential interactions between its components. """ from typing import List, Optional, Tuple From aea93b169c3bf98879375e0b2c2fe44480fa603c Mon Sep 17 00:00:00 2001 From: JanFidor <66260538+JanFidor@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:20:00 +0100 Subject: [PATCH 10/17] Refactor fit loop to make it more intuitive Co-authored-by: madtoinou <32447896+madtoinou@users.noreply.github.com> --- .../multivariate_forecasting_model_wrapper.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 303c8d61ad..5e36915621 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -39,16 +39,13 @@ def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = Non self._trained_models = [] series = seq2series(series) - components = split_multivariate(series) - if self.supports_future_covariates: - self._trained_models = [ - self.model.untrained_model().fit(c, future_covariates) - for c in components - ] - else: - self._trained_models = [ - self.model.untrained_model().fit(c) for c in components - ] + for comp in series.components: + self._trained_models.append( + self.model.untrained_model().fit( + series=series.univariate_component(comp), + future_covariates=future_covariates if self.supports_future_covariates else None + ) + ) return self From 05af0cad9990913a6308be9e54af1cffc06c5575 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Mon, 5 Feb 2024 22:35:34 +0100 Subject: [PATCH 11/17] delete function and rewrite docstring --- .../multivariate_forecasting_model_wrapper.py | 40 ++++++++++++------- darts/timeseries.py | 7 ---- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 5e36915621..8dba7a6637 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -2,17 +2,19 @@ Multivariate forecasting model wrapper ------------------------- -A wrapper around local forecasting models to enable multivariate series training and forecasting. One model is trained for each component of the target series, independently of the others hence ignoring the potential interactions between its components. +A wrapper around local forecasting models to enable multivariate series training and forecasting. One model is trained +for each component of the target series, independently of the others hence ignoring the potential interactions between +its components. """ from typing import List, Optional, Tuple -from darts.logging import get_logger +from darts.logging import get_logger, raise_if_not from darts.models.forecasting.forecasting_model import ( FutureCovariatesLocalForecastingModel, LocalForecastingModel, TransferableFutureCovariatesLocalForecastingModel, ) -from darts.timeseries import TimeSeries, concatenate, split_multivariate +from darts.timeseries import TimeSeries, concatenate from darts.utils.utils import seq2series logger = get_logger(__name__) @@ -21,10 +23,10 @@ class MultivariateForecastingModelWrapper(FutureCovariatesLocalForecastingModel): def __init__(self, model: LocalForecastingModel): """ - Wrapper around LocalForecastingModel allowing it to predict multivariate TimeSeries. + Wrapper for univariate LocalForecastingModel to enable multivariate series training and forecasting. - A copy of the provided model will be trained on each of the components of the provided series separately. - Parameters + A copy of the provided model will be trained independently on each component of the target series, ignoring the + potential interactions. ---------- model Model used to predict individual components @@ -43,7 +45,9 @@ def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = Non self._trained_models.append( self.model.untrained_model().fit( series=series.univariate_component(comp), - future_covariates=future_covariates if self.supports_future_covariates else None + future_covariates=future_covariates + if self.supports_future_covariates + else None, ) ) @@ -57,8 +61,6 @@ def predict( num_samples: int = 1, **kwargs, ) -> TimeSeries: - # we override `predict()` to pass a non-None `series`, so that historic_future_covariates - # will be passed to `_predict()` (some future covariates local models require it ex. Kalman) return self._predict(n, future_covariates, num_samples, **kwargs) def _predict( @@ -70,14 +72,24 @@ def _predict( **kwargs, ) -> TimeSeries: predictions = [ - model.predict(n=n, future_covariates=future_covariates) - if model.supports_future_covariates - else model.predict(n=n) + model.predict( + n=n, + future_covariates=future_covariates + if self.supports_future_covariates + else None, + ) for model in self._trained_models ] - multivariate_series = concatenate(predictions, axis=1) - return multivariate_series + raise_if_not( + len(predictions) == len(self._trained_models), + "Prediction contains {} components but {} models were fitted".format( + len(predictions), + len(self._trained_models), + ), + ) + + return concatenate(predictions, axis=1) @property def extreme_lags( diff --git a/darts/timeseries.py b/darts/timeseries.py index 5a4035b11b..30d5aac716 100644 --- a/darts/timeseries.py +++ b/darts/timeseries.py @@ -5427,10 +5427,3 @@ def _finite_rows_boundaries( last_finite_row = len(finite_rows) - finite_rows[::-1].argmax() - 1 return first_finite_row, last_finite_row - - -def split_multivariate(time_series: TimeSeries): - """split multivariate TimeSeries into a list of univariate TimeSeries""" - return [ - time_series.univariate_component(i) for i in range(time_series.n_components) - ] From 37226d30320e877b7c378523a6d8034533be35bb Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Wed, 7 Feb 2024 18:34:14 +0100 Subject: [PATCH 12/17] fix future_covariates error --- .../multivariate_forecasting_model_wrapper.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 8dba7a6637..5bc2865d83 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -42,14 +42,15 @@ def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = Non series = seq2series(series) for comp in series.components: - self._trained_models.append( + comp = series.univariate_component(comp) + component_model = ( self.model.untrained_model().fit( - series=series.univariate_component(comp), - future_covariates=future_covariates - if self.supports_future_covariates - else None, + series=comp, future_covariates=future_covariates ) + if self.supports_future_covariates + else self.model.untrained_model().fit(series=comp) ) + self._trained_models.append(component_model) return self @@ -72,12 +73,9 @@ def _predict( **kwargs, ) -> TimeSeries: predictions = [ - model.predict( - n=n, - future_covariates=future_covariates - if self.supports_future_covariates - else None, - ) + model.predict(n=n, future_covariates=future_covariates) + if self.supports_future_covariates + else model.predict(n=n) for model in self._trained_models ] From 908b1efed0e5ec9ffd95a3a5cf57f81e65678f82 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Thu, 18 Jul 2024 00:38:36 +0200 Subject: [PATCH 13/17] parametrize test --- ..._multivariate_forecasting_model_wrapper.py | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py index 23a607d7c2..36cc54d4ef 100644 --- a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py +++ b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py @@ -62,12 +62,14 @@ class TestMultivariateForecastingModelWrapper: future_covariates = tg.gaussian_timeseries(length=ts_length + n_pred, mean=50) @pytest.mark.parametrize("model", local_models) - def test_fit_predict_local_models(self, model): - self._test_predict_with_base_model(model) + @pytest.mark.parametrize("series", [univariate, multivariate]) + def test_fit_predict_local_models(self, model, series): + self._test_predict_with_base_model(model, series) @pytest.mark.parametrize("model", future_covariates_models) - def test_fit_predict_local_future_covariates_models(self, model): - self._test_predict_with_base_model(model, self.future_covariates) + @pytest.mark.parametrize("series", [univariate, multivariate]) + def test_fit_predict_local_future_covariates_models(self, model, series): + self._test_predict_with_base_model(model, series, self.future_covariates) @pytest.mark.parametrize("model_object", future_covariates_models) def test_encoders_support(self, model_object): @@ -85,22 +87,21 @@ def test_encoders_support(self, model_object): self._test_predict_with_base_model(model, fc) - def _test_predict_with_base_model(self, model, future_covariates=None): - for combination in [self.univariate, self.multivariate]: - preds = self.trained_model_predictions( - model, self.n_pred, combination, future_covariates - ) - assert isinstance(preds, TimeSeries) - assert preds.n_components == combination.n_components - - # Make sure that the compound prediction is the same as the individual predictions - individual_preds = self.trained_individual_model_predictions( - model, self.n_pred, combination, future_covariates - ) - for component in range(combination.n_components): - assert ( - preds.univariate_component(component) == individual_preds[component] - ) + def _test_predict_with_base_model( + self, model, series: TimeSeries, future_covariates=None + ): + preds = self.trained_model_predictions( + model, self.n_pred, series, future_covariates + ) + assert isinstance(preds, TimeSeries) + assert preds.n_components == series.n_components + + # Make sure that the compound prediction is the same as the individual predictions + individual_preds = self.trained_individual_model_predictions( + model, self.n_pred, series, future_covariates + ) + for component in range(series.n_components): + assert preds.univariate_component(component) == individual_preds[component] def trained_model_predictions(self, base_model, n, series, future_covariates): model = MultivariateForecastingModelWrapper(base_model) From f7ca592117adae261589e134033c4317c3cbd2d8 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Thu, 18 Jul 2024 00:40:21 +0200 Subject: [PATCH 14/17] lint --- .../forecasting/multivariate_forecasting_model_wrapper.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 5bc2865d83..2c6545beed 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -6,6 +6,7 @@ for each component of the target series, independently of the others hence ignoring the potential interactions between its components. """ + from typing import List, Optional, Tuple from darts.logging import get_logger, raise_if_not @@ -81,10 +82,7 @@ def _predict( raise_if_not( len(predictions) == len(self._trained_models), - "Prediction contains {} components but {} models were fitted".format( - len(predictions), - len(self._trained_models), - ), + f"Prediction contains {len(predictions)} components but {len(self._trained_models)} models were fitted", ) return concatenate(predictions, axis=1) From 7386d9e8f6557346469c95af9639d84e55589653 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Thu, 18 Jul 2024 00:52:59 +0200 Subject: [PATCH 15/17] add to __all__ init.py --- darts/models/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/darts/models/__init__.py b/darts/models/__init__.py index 325fd6fcd3..1556ebfb91 100644 --- a/darts/models/__init__.py +++ b/darts/models/__init__.py @@ -146,6 +146,7 @@ "GlobalNaiveDrift", "GlobalNaiveDrift", "GlobalNaiveSeasonal", + "MultivariateForecastingModelWrapper", "NBEATSModel", "NHiTSModel", "NLinearModel", From 219b93380e80dcf86da981f4d0e8fd98c9df0603 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Thu, 18 Jul 2024 18:53:17 +0200 Subject: [PATCH 16/17] update import --- .../forecasting/multivariate_forecasting_model_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py index 2c6545beed..fa996298cd 100644 --- a/darts/models/forecasting/multivariate_forecasting_model_wrapper.py +++ b/darts/models/forecasting/multivariate_forecasting_model_wrapper.py @@ -16,7 +16,7 @@ TransferableFutureCovariatesLocalForecastingModel, ) from darts.timeseries import TimeSeries, concatenate -from darts.utils.utils import seq2series +from darts.utils.ts_utils import seq2series logger = get_logger(__name__) From 40e87d335082d0ff4ec73b79dc3fbbbdaa2b5132 Mon Sep 17 00:00:00 2001 From: Jan Fidor Date: Thu, 18 Jul 2024 19:51:45 +0200 Subject: [PATCH 17/17] parametrize encoder support --- ..._multivariate_forecasting_model_wrapper.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py index 36cc54d4ef..d9721b7abc 100644 --- a/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py +++ b/darts/tests/models/forecasting/test_multivariate_forecasting_model_wrapper.py @@ -72,24 +72,25 @@ def test_fit_predict_local_future_covariates_models(self, model, series): self._test_predict_with_base_model(model, series, self.future_covariates) @pytest.mark.parametrize("model_object", future_covariates_models) - def test_encoders_support(self, model_object): + @pytest.mark.parametrize("series", [univariate, multivariate]) + @pytest.mark.parametrize("future_covariates", [future_covariates, None]) + def test_encoders_support(self, model_object, series, future_covariates): add_encoders = { "position": {"future": ["relative"]}, } - # test once with user supplied covariates, and once without - for fc in [self.future_covariates, None]: - model_params = { - k: vals for k, vals in copy.deepcopy(model_object.model_params).items() - } - model_params["add_encoders"] = add_encoders - model = model_object.__class__(**model_params) + model_params = { + k: vals for k, vals in copy.deepcopy(model_object.model_params).items() + } + model_params["add_encoders"] = add_encoders + model = model_object.__class__(**model_params) - self._test_predict_with_base_model(model, fc) + self._test_predict_with_base_model(model, series, future_covariates) def _test_predict_with_base_model( self, model, series: TimeSeries, future_covariates=None ): + print(type(series), isinstance(series, TimeSeries)) preds = self.trained_model_predictions( model, self.n_pred, series, future_covariates ) @@ -105,6 +106,7 @@ def _test_predict_with_base_model( def trained_model_predictions(self, base_model, n, series, future_covariates): model = MultivariateForecastingModelWrapper(base_model) + print(series) model.fit(series, future_covariates=future_covariates) return model.predict(n=n, series=series, future_covariates=future_covariates)