Skip to content

Commit

Permalink
Add supports_multivariate property to ForecastingModel (#1848)
Browse files Browse the repository at this point in the history
* Add supports_multivariate property to ForecastingModel

* Fix formatting in RegressionEnsembleModel

* Add forgotten supports_multivariate property to FourTheta model

* SImplify supports_multivariate in RegressionEnsembleModel

* Fix formatting

* Update darts/models/forecasting/forecasting_model.py

* Update darts/models/forecasting/exponential_smoothing.py

* Apply suggestions from code review

Co-authored-by: madtoinou <32447896+madtoinou@users.noreply.github.com>

* Apply suggestions from code review

Uniform handling & documentation of supports_multivariate by RegressionModel class

Co-authored-by: Dennis Bader <dennis.bader@gmx.ch>

* Update CHANGELOG.md

---------

Co-authored-by: madtoinou <32447896+madtoinou@users.noreply.github.com>
Co-authored-by: Dennis Bader <dennis.bader@gmx.ch>
  • Loading branch information
3 people committed Jul 5, 2023
1 parent 6d373f4 commit 4c3812b
Show file tree
Hide file tree
Showing 29 changed files with 147 additions and 10 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ but cannot always guarantee backwards compatibility. Changes that may **break co
**Improved**
- General model improvements:
- Added support for `PathLike` to the `save()` and `load()` functions of all non-deep learning based models. [#1754](https://github.com/unit8co/darts/pull/1754) by [Simon Sudrich](https://github.com/sudrich).
- Improved efficiency of `historical_forecasts()` and `backtest()` for all models giving significant process time reduction for larger number of predict iterations and series. [#1801](https://github.com/unit8co/darts/pull/1801) by [Dennis Bader](https://github.com/dennisbader).
- Improved efficiency of `historical_forecasts()` and `backtest()` for all models giving significant process time reduction for larger number of predict iterations and series. [#1801](https://github.com/unit8co/darts/pull/1801) by [Dennis Bader](https://github.com/dennisbader).
- Added model property `ForecastingModel.supports_multivariate` to indicate whether the model supports multivariate forecasting. [#1848](https://github.com/unit8co/darts/pull/1848) by [Felix Divo](https://github.com/felixdivo).
- Improvements to `EnsembleModel`:
- Model creation parameter `forecasting_models` now supports a mix of `LocalForecastingModel` and `GlobalForecastingModel` (single `TimeSeries` training/inference only, due to the local models). [#1745](https://github.com/unit8co/darts/pull/1745) by [Antoine Madrona](https://github.com/madtoinou).
- Future and past covariates can now be used even if `forecasting_models` have different covariates support. The covariates passed to `fit()`/`predict()` are used only by models that support it. [#1745](https://github.com/unit8co/darts/pull/1745) by [Antoine Madrona](https://github.com/madtoinou).
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def __init__(
self._random_state = None
np.random.seed(random_state if random_state is not None else 0)

@property
def supports_multivariate(self) -> bool:
return False

def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None):
super()._fit(series, future_covariates)

Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/auto_arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def __init__(
self.model = PmdAutoARIMA(*autoarima_args, **autoarima_kwargs)
self.trend = self.model.trend

@property
def supports_multivariate(self) -> bool:
return False

def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None):
super()._fit(series, future_covariates)
self._assert_univariate(series)
Expand Down
16 changes: 16 additions & 0 deletions darts/models/forecasting/baselines.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def __init__(self):
super().__init__()
self.mean_val = None

@property
def supports_multivariate(self) -> bool:
return True

def fit(self, series: TimeSeries):
super().fit(series)

Expand Down Expand Up @@ -59,6 +63,10 @@ def __init__(self, K: int = 1):
self.last_k_vals = None
self.K = K

@property
def supports_multivariate(self) -> bool:
return True

@property
def min_train_series_length(self):
return max(self.K, 3)
Expand Down Expand Up @@ -91,6 +99,10 @@ def __init__(self):
"""
super().__init__()

@property
def supports_multivariate(self) -> bool:
return True

def fit(self, series: TimeSeries):
super().fit(series)
assert series.n_samples == 1, "This model expects deterministic time series"
Expand Down Expand Up @@ -125,6 +137,10 @@ def __init__(self, input_chunk_length: int = 1):
self.input_chunk_length = input_chunk_length
self.rolling_window = None

@property
def supports_multivariate(self) -> bool:
return True

@property
def min_train_series_length(self):
return self.input_chunk_length
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/block_rnn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ def __init__(
self.n_rnn_layers = n_rnn_layers
self.dropout = dropout

@property
def supports_multivariate(self) -> bool:
return True

def _create_model(self, train_sample: Tuple[torch.Tensor]) -> torch.nn.Module:
# samples are made of (past_target, past_covariates, future_target)
input_dim = train_sample[0].shape[1] + (
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/croston.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ def __init__(

self.version = version

@property
def supports_multivariate(self) -> bool:
return False

def _fit(self, series: TimeSeries, future_covariates: Optional[TimeSeries] = None):
super()._fit(series, future_covariates)
self._assert_univariate(series)
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/dlinear.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ def _create_model(
**self.pl_module_params,
)

@property
def supports_multivariate(self) -> bool:
return True

@property
def supports_static_covariates(self) -> bool:
return True
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/ensemble_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ def _models_are_probabilistic(self) -> bool:
def _is_probabilistic(self) -> bool:
return self._models_are_probabilistic()

@property
def supports_multivariate(self) -> bool:
return all([model.supports_multivariate for model in self.models])

@property
def supports_past_covariates(self) -> bool:
return any([model.supports_past_covariates for model in self.models])
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/exponential_smoothing.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ def predict(self, n, num_samples=1, verbose: bool = False):

return self._build_forecast_series(forecast)

@property
def supports_multivariate(self) -> bool:
return False

def _is_probabilistic(self) -> bool:
return True

Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/fft.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ def __init__(
self.trend = trend
self.trend_poly_degree = trend_poly_degree

@property
def supports_multivariate(self) -> bool:
return False

def _exp_trend(self, x) -> Callable:
"""Helper function, used to make FFT model pickable."""
return np.exp(self.trend_coefficients[1]) * np.exp(
Expand Down
19 changes: 13 additions & 6 deletions darts/models/forecasting/forecasting_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,21 @@ def _supports_non_retrainable_historical_forecasts(self) -> bool:
return False

@property
def supports_past_covariates(self):
@abstractmethod
def supports_multivariate(self) -> bool:
"""
Whether the model considers more than one variate in the time series.
"""

@property
def supports_past_covariates(self) -> bool:
"""
Whether model supports past covariates
"""
return "past_covariates" in inspect.signature(self.fit).parameters.keys()

@property
def supports_future_covariates(self):
def supports_future_covariates(self) -> bool:
"""
Whether model supports future covariates
"""
Expand All @@ -216,28 +223,28 @@ def supports_static_covariates(self) -> bool:
return False

@property
def uses_past_covariates(self):
def uses_past_covariates(self) -> bool:
"""
Whether the model uses past covariates, once fitted.
"""
return self._uses_past_covariates

@property
def uses_future_covariates(self):
def uses_future_covariates(self) -> bool:
"""
Whether the model uses future covariates, once fitted.
"""
return self._uses_future_covariates

@property
def uses_static_covariates(self):
def uses_static_covariates(self) -> bool:
"""
Whether the model uses static covariates, once fitted.
"""
return self._uses_static_covariates

@property
def considers_static_covariates(self):
def considers_static_covariates(self) -> bool:
"""
Whether the model considers static covariates, if there are any.
"""
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/kalman_forecaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,9 @@ def _predict(

return filtered_series[-n:]

@property
def supports_multivariate(self) -> bool:
return True

def _is_probabilistic(self) -> bool:
return True
4 changes: 4 additions & 0 deletions darts/models/forecasting/nbeats.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,10 @@ def __init__(
if isinstance(layer_widths, int):
self.layer_widths = [layer_widths] * self.num_stacks

@property
def supports_multivariate(self) -> bool:
return True

def _create_model(self, train_sample: Tuple[torch.Tensor]) -> torch.nn.Module:
# samples are made of (past_target, past_covariates, future_target)
input_dim = train_sample[0].shape[1] + (
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/nhits.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ def __init__(
if isinstance(layer_widths, int):
self.layer_widths = [layer_widths] * self.num_stacks

@property
def supports_multivariate(self) -> bool:
return True

@staticmethod
def _prepare_pooling_downsampling(
pooling_kernel_sizes, n_freq_downsample, in_len, out_len, num_blocks, num_stacks
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/nlinear.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,10 @@ def _create_model(self, train_sample: Tuple[torch.Tensor]) -> torch.nn.Module:
**self.pl_module_params,
)

@property
def supports_multivariate(self) -> bool:
return True

@property
def supports_static_covariates(self) -> bool:
return True
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/prophet_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ def _generate_predict_df(
)
return predict_df

@property
def supports_multivariate(self) -> bool:
return False

def _is_probabilistic(self) -> bool:
return True

Expand Down
11 changes: 8 additions & 3 deletions darts/models/forecasting/regression_ensemble_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __init__(
f"{regression_model.lags}",
)

self.regression_model = regression_model
self.regression_model: RegressionModel = regression_model
self.train_n_points = regression_train_n_points

def _split_multi_ts_sequence(
Expand All @@ -115,7 +115,6 @@ def fit(
past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None,
future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None,
):

super().fit(
series, past_covariates=past_covariates, future_covariates=future_covariates
)
Expand Down Expand Up @@ -189,7 +188,6 @@ def ensemble(
series: Optional[Sequence[TimeSeries]] = None,
num_samples: int = 1,
) -> Union[TimeSeries, Sequence[TimeSeries]]:

is_single_series = isinstance(series, TimeSeries) or series is None
predictions = series2seq(predictions)
series = series2seq(series) if series is not None else [None]
Expand Down Expand Up @@ -219,6 +217,13 @@ def extreme_lags(
extreme_lags_ = super().extreme_lags
return (extreme_lags_[0] - self.train_n_points,) + extreme_lags_[1:]

@property
def supports_multivariate(self) -> bool:
return (
super().supports_multivariate
and self.regression_model.supports_multivariate
)

def _is_probabilistic(self) -> bool:
"""
A RegressionEnsembleModel is probabilistic if its regression
Expand Down
8 changes: 8 additions & 0 deletions darts/models/forecasting/regression_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ def extreme_lags(
max_future_cov_lag,
)

@property
def supports_multivariate(self) -> bool:
"""
If available, uses `model`'s native multivariate support. If not available, obtains multivariate support by
wrapping the univariate model in a `sklearn.multioutput.MultiOutputRegressor`.
"""
return True

@property
def min_train_series_length(self) -> int:
return max(
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/rnn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ def _verify_train_dataset_type(self, train_dataset: TrainingDataset):
"RNNModel requires a shifted training dataset with shift=1.",
)

@property
def supports_multivariate(self) -> bool:
return True

@property
def min_train_series_length(self) -> int:
return self.training_length + 1
4 changes: 4 additions & 0 deletions darts/models/forecasting/sf_auto_arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ def _predict(

return self._build_forecast_series(samples)

@property
def supports_multivariate(self) -> bool:
return False

@property
def min_train_series_length(self) -> int:
return 10
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/sf_auto_ces.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def predict(

return self._build_forecast_series(mu)

@property
def supports_multivariate(self) -> bool:
return False

@property
def min_train_series_length(self) -> int:
return 10
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/sf_auto_ets.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ def _predict(
samples = mu
return self._build_forecast_series(samples)

@property
def supports_multivariate(self) -> bool:
return False

@property
def min_train_series_length(self) -> int:
return 10
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/sf_auto_theta.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def predict(

return self._build_forecast_series(samples)

@property
def supports_multivariate(self) -> bool:
return False

@property
def min_train_series_length(self) -> int:
return 10
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/tbats_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ def predict(self, n, num_samples=1, verbose: bool = False):

return self._build_forecast_series(samples)

@property
def supports_multivariate(self) -> bool:
return False

def _is_probabilistic(self) -> bool:
return True

Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/tcn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ def __init__(
self.dropout = dropout
self.weight_norm = weight_norm

@property
def supports_multivariate(self) -> bool:
return True

def _create_model(self, train_sample: Tuple[torch.Tensor]) -> torch.nn.Module:
# samples are made of (past_target, past_covariates, future_target)
input_dim = train_sample[0].shape[1] + (
Expand Down
4 changes: 4 additions & 0 deletions darts/models/forecasting/tft_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,10 @@ def _build_inference_dataset(
use_static_covariates=self.uses_static_covariates,
)

@property
def supports_multivariate(self) -> bool:
return True

@property
def supports_static_covariates(self) -> bool:
return True
Expand Down

0 comments on commit 4c3812b

Please sign in to comment.