From a2393b4c9b2555714e0aa200ac0bcdae0e559abd Mon Sep 17 00:00:00 2001 From: Martin Gabdushev <33594071+martins0n@users.noreply.github.com> Date: Thu, 17 Feb 2022 17:44:44 +0300 Subject: [PATCH] [BUG] nn models forecast without inverse_transform (#541) --- CHANGELOG.md | 2 +- etna/models/nn/deepar.py | 1 + etna/models/nn/tft.py | 1 + tests/test_models/nn/test_deepar.py | 41 ++++++++++++++++++++++++++++ tests/test_models/nn/test_tft.py | 42 +++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f76b070d0..e6cedb5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - - -- +- [BUG] nn models make forecast without inverse_transform ([#541](https://github.com/tinkoff-ai/etna/pull/541)) ## [1.6.3] - 2022-02-14 diff --git a/etna/models/nn/deepar.py b/etna/models/nn/deepar.py index 7e0ed816a..1a100dc2c 100644 --- a/etna/models/nn/deepar.py +++ b/etna/models/nn/deepar.py @@ -167,4 +167,5 @@ def forecast(self, ts: TSDataset) -> TSDataset: # shape (segments, encoder_length) ts.loc[:, pd.IndexSlice[:, "target"]] = predicts.T[-len(ts.df) :] + ts.inverse_transform() return ts diff --git a/etna/models/nn/tft.py b/etna/models/nn/tft.py index d76a1d579..b98236647 100644 --- a/etna/models/nn/tft.py +++ b/etna/models/nn/tft.py @@ -174,4 +174,5 @@ def forecast(self, ts: TSDataset) -> TSDataset: # shape (segments, encoder_length) ts.loc[:, pd.IndexSlice[:, "target"]] = predicts.T[-len(ts.df) :] + ts.inverse_transform() return ts diff --git a/tests/test_models/nn/test_deepar.py b/tests/test_models/nn/test_deepar.py index c596987b0..abbf8c0bb 100644 --- a/tests/test_models/nn/test_deepar.py +++ b/tests/test_models/nn/test_deepar.py @@ -9,6 +9,7 @@ from etna.transforms import AddConstTransform from etna.transforms import DateFlagsTransform from etna.transforms import PytorchForecastingTransform +from etna.transforms import StandardScalerTransform def test_fit_wrong_order_transform(weekly_period_df): @@ -68,6 +69,46 @@ def test_deepar_model_run_weekly_overfit(weekly_period_df, horizon): assert mae(ts_test, ts_pred) < 0.2207 +@pytest.mark.long +@pytest.mark.parametrize("horizon", [8]) +def test_deepar_model_run_weekly_overfit_with_scaler(weekly_period_df, horizon): + """ + Given: I have dataframe with 2 segments with weekly seasonality with known future + When: I use scale transformations + Then: I get {horizon} periods per dataset as a forecast and they "the same" as past + """ + + ts_start = sorted(set(weekly_period_df.timestamp))[-horizon] + train, test = ( + weekly_period_df[lambda x: x.timestamp < ts_start], + weekly_period_df[lambda x: x.timestamp >= ts_start], + ) + + ts_train = TSDataset(TSDataset.to_dataset(train), "D") + ts_test = TSDataset(TSDataset.to_dataset(test), "D") + std = StandardScalerTransform(in_column="target") + dft = DateFlagsTransform(day_number_in_week=True, day_number_in_month=False, out_column="regressor_dateflags") + pft = PytorchForecastingTransform( + max_encoder_length=21, + max_prediction_length=horizon, + time_varying_known_reals=["time_idx"], + time_varying_known_categoricals=["regressor_dateflags_day_number_in_week"], + time_varying_unknown_reals=["target"], + target_normalizer=GroupNormalizer(groups=["segment"]), + ) + + ts_train.fit_transform([std, dft, pft]) + + model = DeepARModel(max_epochs=300, learning_rate=[0.1]) + ts_pred = ts_train.make_future(horizon) + model.fit(ts_train) + ts_pred = model.forecast(ts_pred) + + mae = MAE("macro") + + assert mae(ts_test, ts_pred) < 0.2207 + + def test_forecast_without_make_future(weekly_period_df): ts = TSDataset(TSDataset.to_dataset(weekly_period_df), "D") pft = PytorchForecastingTransform( diff --git a/tests/test_models/nn/test_tft.py b/tests/test_models/nn/test_tft.py index b05b987c2..14c8f309a 100644 --- a/tests/test_models/nn/test_tft.py +++ b/tests/test_models/nn/test_tft.py @@ -6,6 +6,7 @@ from etna.transforms import AddConstTransform from etna.transforms import DateFlagsTransform from etna.transforms import PytorchForecastingTransform +from etna.transforms import StandardScalerTransform def test_fit_wrong_order_transform(weekly_period_df): @@ -68,6 +69,47 @@ def test_tft_model_run_weekly_overfit(weekly_period_df, horizon): assert mae(ts_test, ts_pred) < 0.24 +@pytest.mark.long +@pytest.mark.parametrize("horizon", [8]) +def test_tft_model_run_weekly_overfit_with_scaler(weekly_period_df, horizon): + """ + Given: I have dataframe with 2 segments with weekly seasonality with known future + When: I use scale transformations + Then: I get {horizon} periods per dataset as a forecast and they "the same" as past + """ + + ts_start = sorted(set(weekly_period_df.timestamp))[-horizon] + train, test = ( + weekly_period_df[lambda x: x.timestamp < ts_start], + weekly_period_df[lambda x: x.timestamp >= ts_start], + ) + + ts_train = TSDataset(TSDataset.to_dataset(train), "D") + ts_test = TSDataset(TSDataset.to_dataset(test), "D") + std = StandardScalerTransform(in_column="target") + dft = DateFlagsTransform(day_number_in_week=True, day_number_in_month=False, out_column="regressor_dateflag") + pft = PytorchForecastingTransform( + max_encoder_length=21, + min_encoder_length=21, + max_prediction_length=horizon, + time_varying_known_reals=["time_idx"], + time_varying_known_categoricals=["regressor_dateflag_day_number_in_week"], + time_varying_unknown_reals=["target"], + static_categoricals=["segment"], + target_normalizer=None, + ) + + ts_train.fit_transform([std, dft, pft]) + + model = TFTModel(max_epochs=300, learning_rate=[0.1]) + ts_pred = ts_train.make_future(horizon) + model.fit(ts_train) + ts_pred = model.forecast(ts_pred) + + mae = MAE("macro") + assert mae(ts_test, ts_pred) < 0.24 + + def test_forecast_without_make_future(weekly_period_df): ts = TSDataset(TSDataset.to_dataset(weekly_period_df), "D") pft = PytorchForecastingTransform(