Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/neural prophet #1436

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b139ee7
First draft
BlazejNowickiU8 Dec 6, 2022
2422e47
Allow multivariate time series
BlazejNowickiU8 Dec 8, 2022
906de4c
Add examples and improve conversion
BlazejNowickiU8 Dec 12, 2022
f9937d0
Attempt at global model with past covariates
BlazejNowickiU8 Dec 13, 2022
b4f2f18
Add past covariates
BlazejNowickiU8 Dec 15, 2022
bed3700
Add future covariates
BlazejNowickiU8 Dec 19, 2022
1d293fe
Update requirements
BlazejNowickiU8 Dec 19, 2022
4bacbe4
Merge branch 'master' into feat/neural-prophet
hrzn Dec 19, 2022
2b6c875
Merge branch 'master' into feat/neural-prophet
BlazejNowicki Dec 23, 2022
67ea1d9
Test with newer version
BlazejNowickiU8 Dec 23, 2022
f7f2ad4
Merge branch 'feat/neural-prophet' of github.com:unit8co/darts into f…
BlazejNowickiU8 Dec 23, 2022
b180e87
Test rollback
BlazejNowickiU8 Dec 23, 2022
632b850
Manually add tensorboardX
BlazejNowickiU8 Dec 23, 2022
14f215a
Remove unused imports
BlazejNowickiU8 Dec 23, 2022
00ef06e
Merge branch 'master' into feat/neural-prophet
piaz97 Jan 19, 2023
75c5888
Merge branch 'master' into feat/neural-prophet
hrzn Jan 23, 2023
163269f
Merge branch 'master' into feat/neural-prophet
hrzn Jan 24, 2023
7b41571
Merge branch 'master' into feat/neural-prophet
hrzn Jan 31, 2023
15c8f9a
Merge branch 'master' into feat/neural-prophet
BlazejNowickiU8 Mar 7, 2023
78c5e76
Require neural prophet with updated requirements
BlazejNowickiU8 Mar 7, 2023
4116e17
Revert changes from the notebooks
BlazejNowickiU8 Mar 8, 2023
8c449b1
Add model import in module init file
BlazejNowickiU8 Mar 8, 2023
35b88a6
Add docstring
BlazejNowickiU8 Mar 8, 2023
94db772
Merge branch 'master' into feat/neural-prophet
BlazejNowicki Mar 8, 2023
3a6d8cc
Merge branch 'master' into feat/neural-prophet
dennisbader Mar 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions darts/models/forecasting/neural_prophet_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
"""
Copy link
Contributor

@hrzn hrzn Jan 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few generic comments about the PR:

  • So far I think that the pinned requirement on Pytorch Lightning will be a no-go for the moment for us unfortunately.
  • Could you import the model from darts/models/__init__.py ?
  • Could you stash your changes to the two notebooks?
  • Your model implementation is missing an abstract method implementation (_model_encoder_settings). But this can wait (don't spend more time on this until the dependency situation is figured out).

Neural Prophet
------------
"""

import warnings
from typing import Optional, Sequence, Union

import neuralprophet
import pandas as pd
from neuralprophet.utils import fcst_df_to_latest_forecast

from darts.logging import raise_if_not
from darts.models.forecasting.forecasting_model import ForecastingModel
from darts.timeseries import TimeSeries, concatenate


class NeuralProphet(ForecastingModel):
def __init__(self, n_lags: int = 0, n_forecasts: int = 1, **kwargs):
super().__init__()
# TODO improve passing arguments to the model

raise_if_not(n_lags >= 0, "Argument n_lags should be a non-negative integer")

self.n_lags = n_lags
self.n_forecasts = n_forecasts
self.model = neuralprophet.NeuralProphet(
n_lags=n_lags, n_forecasts=n_forecasts, **kwargs
)

def fit(
self,
series: TimeSeries,
past_covariates: Optional[TimeSeries] = None,
future_covariates: Optional[TimeSeries] = None,
) -> "NeuralProphet":
super().fit(series)

raise_if_not(
series.has_datetime_index,
"NeuralProphet model is limited to TimeSeries indexed with DatetimeIndex",
)

raise_if_not(
past_covariates is None or self.n_lags > 0,
"Past covariates are only supported when auto-regression is enabled (n_lags > 1)",
)

self.training_series = series
fit_df = self._convert_ts_to_df(series)

if past_covariates is not None:
fit_df = self._add_past_covariates(self.model, fit_df, past_covariates)

if future_covariates is not None:
fit_df = self._add_future_covariates(self.model, fit_df, future_covariates)
self.future_components = future_covariates.components
else:
self.future_components = None

with warnings.catch_warnings():
self.model.fit(fit_df, freq=series.freq_str)

self.fit_df = fit_df
return self

def predict(
self,
n: int,
future_covariates: Optional[TimeSeries] = None,
num_samples: int = 1,
verbose: bool = False,
) -> Union[TimeSeries, Sequence[TimeSeries]]:
super().predict(n, num_samples)

raise_if_not(
self.n_lags == 0 or n <= self.n_forecasts,
"Auto-regression has been enabled. `n` must be smaller than or equal to"
"`n_forecasts` parameter in the constructor.",
)

self._future_covariates_checks(future_covariates)

regressors_df = (
self._future_covariates_df(future_covariates)
if self.future_components is not None
else None
)

future_df = self.model.make_future_dataframe(
df=self.fit_df, regressors_df=regressors_df, periods=n
)

with warnings.catch_warnings():
forecast_df = self.model.predict(future_df)

return self._convert_df_to_ts(
forecast_df,
self.training_series.end_time(),
self.training_series.components,
)

def _convert_ts_to_df(self, series: TimeSeries) -> pd.DataFrame:
"""Convert TimeSeries to pandas DataFrame format required by Neural Prophet"""
dfs = [] # ID y

for component in series.components:
component_df = (
series[component]
.pd_dataframe(copy=False)
.reset_index(names=["ds"])
.filter(items=["ds", component])
.rename(columns={component: "y"})
)
component_df["ID"] = component
dfs.append(component_df)

return pd.concat(dfs).copy(deep=True)

def _add_past_covariates(
self,
model: neuralprophet.NeuralProphet,
df: pd.DataFrame,
covariates: TimeSeries,
):
df = self._add_covariate(df, covariates)
model.add_lagged_regressor(names=list(covariates.components))
return df

def _add_future_covariates(
self,
model: neuralprophet.NeuralProphet,
df: pd.DataFrame,
covariates: TimeSeries,
):
df = self._add_covariate(df, covariates)
for component in covariates.components:
model.add_future_regressor(name=component)

return df

def _add_covariate(
self,
df: pd.DataFrame,
covariates: TimeSeries,
) -> pd.DataFrame:
"""Convert past covariates from TimeSeries and add them to DataFrame"""

raise_if_not(
self.training_series.freq == covariates.freq,
"Covariate TimeSeries has to have the same frequency as the TimeSeries that model is fitted on.",
)

raise_if_not(
covariates.start_time() <= self.training_series.start_time()
and self.training_series.end_time() <= covariates.end_time(),
"Covaraite TimeSeries has to span across all TimeSeries that model is fitted on",
)

for component in covariates.components:
covariate_df = (
covariates[component]
.pd_dataframe(copy=False)
.reset_index(names=["ds"])
.filter(items=["ds", component])
)

df = df.merge(covariate_df, how="left", on="ds")

return df

def _convert_df_to_ts(self, forecast: pd.DataFrame, last_train_date, components):
groups = []
for component in components:
if self.n_lags == 0:
# output format is different when AR is not enabled
groups.append(
forecast[
(forecast["ID"] == component)
& (forecast["ds"] > last_train_date)
]
.filter(items=["ds", "yhat1"])
.rename(columns={"yhat1": component})
)
else:
df = fcst_df_to_latest_forecast(
forecast[(forecast["ID"] == component)],
quantiles=[0.5],
n_last=1,
)
groups.append(
df[df["ds"] > last_train_date]
.filter(items=["ds", "origin-0"])
.rename(columns={"origin-0": component})
)

return concatenate(
[TimeSeries.from_dataframe(group, time_col="ds") for group in groups],
axis=1,
)

def _future_covariates_df(self, series: TimeSeries) -> pd.DataFrame:
component_dfs = []
for component in series.components:
component_dfs.append(series[component].pd_dataframe())

return pd.concat(component_dfs, axis=1).reset_index(names=["ds"])

def _future_covariates_checks(self, future_covariates: Optional[TimeSeries]):
raise_if_not(
self.future_components is None
or (
future_covariates is not None
and set(self.future_components) == set(future_covariates.components)
),
f"Missing future covariate TimeSeries. Model was trained with {self.future_components} "
"future components",
)

raise_if_not(
self.future_components is None
or future_covariates.freq == self.training_series.freq,
"Invalid frequency in future covariate TimeSeries",
)

def uses_future_covariates(self):
return True

def __str__(self):
return "Neural Prophet"
10 changes: 7 additions & 3 deletions examples/06-Transformer-examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"from sklearn.preprocessing import MinMaxScaler\n",
"from tqdm import tqdm_notebook as tqdm\n",
"\n",
"from tensorboardX import SummaryWriter\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from darts import TimeSeries\n",
Expand Down Expand Up @@ -567,7 +566,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.9.15 ('prophet')",
"language": "python",
"name": "python3"
},
Expand All @@ -581,7 +580,12 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
"version": "3.9.15"
},
"vscode": {
"interpreter": {
"hash": "2f14e79e1646dc5b749c3dc6e0dfef5e568c2efea6b930caf0398818dd8806ea"
}
}
},
"nbformat": 4,
Expand Down
10 changes: 7 additions & 3 deletions examples/08-DeepAR-examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"from sklearn.preprocessing import MinMaxScaler\n",
"from tqdm import tqdm_notebook as tqdm\n",
"\n",
"from tensorboardX import SummaryWriter\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from darts import TimeSeries\n",
Expand Down Expand Up @@ -370,7 +369,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.9.15 ('prophet')",
"language": "python",
"name": "python3"
},
Expand All @@ -384,7 +383,12 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
"version": "3.9.15"
},
"vscode": {
"interpreter": {
"hash": "2f14e79e1646dc5b749c3dc6e0dfef5e568c2efea6b930caf0398818dd8806ea"
}
}
},
"nbformat": 4,
Expand Down
Loading