We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Prediction fails for TransformedTargetForecaster with LTSFNLinearForecaster with a non-relative ForecastingHorizon
**Describe the bug**
<!--
A clear and concise description of what the bug is.
-->
When constructing a TransformedTargetForecaster with a transformer and LTSFNLinearForecaster, prediction fails saying that a different fh has been provided even if that is not the case.
```
Traceback (most recent call last):
  File "bug_report/tmp.py", line 29, in <module>
    pipe.predict(fh=fh)
  File "lib/python3.11/site-packages/sktime/forecasting/base/_base.py", line 431, in predict
    y_pred = self._predict(fh=fh, X=X_inner)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "lib/python3.11/site-packages/sktime/forecasting/compose/_pipeline.py", line 1032, in _predict
    y_pred = self.forecaster_.predict(fh=fh, X=X)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "lib/python3.11/site-packages/sktime/forecasting/base/_base.py", line 424, in predict
    fh = self._check_fh(fh)
         ^^^^^^^^^^^^^^^^^^
  File "lib/python3.11/site-packages/sktime/forecasting/base/_base.py", line 1745, in _check_fh
    raise ValueError(
ValueError: A different forecasting horizon `fh` has been provided from the one seen already in `fit`, in this instance of LTSFNLinearForecaster. If you want to change the forecasting horizon, please re-fit the forecaster. This is because fitting of the forecaster LTSFNLinearForecaster depends on `fh`.
```

**To Reproduce**
<!--
Add a Minimal, Complete, and Verifiable example (for more details, see e.g. https://stackoverflow.com/help/mcve

If the code is too long, feel free to put it in a public gist and link it in the issue: https://gist.github.com
-->

```python
import numpy as np
import pandas as pd
from sktime.forecasting.base import ForecastingHorizon
from sktime.forecasting.ltsf import LTSFNLinearForecaster
from sktime.split import temporal_train_test_split
from sktime.transformations.series.boxcox import LogTransformer

y = pd.DataFrame(
    np.random.random([100, 3]), index=pd.date_range("2023-01-01", periods=100)
)
y_train, y_test = temporal_train_test_split(y, test_size=30)

# TransformedTargetForecaster works with relative ForecastingHorizon
fh = ForecastingHorizon(np.arange(1, 31), is_relative=True)
pipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)
pipe.fit(y_train, fh=fh)
pipe.predict()

# Forecaster works with non-relative ForecastingHorizon
fh = ForecastingHorizon(y_test.index, is_relative=False)
pipe = LTSFNLinearForecaster(seq_len=20, pred_len=30, batch_size=256)
pipe.fit(y_train, fh=fh)
pipe.predict()

# TransformedTargetForecaster fails with non-relative ForecastingHorizon
fh = ForecastingHorizon(y_test.index, is_relative=False)
pipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)
pipe.fit(y_train, fh=fh)
pipe.predict()

# TransformedTargetForecaster fails with non-relative ForecastingHorizon even if ForecastingHorizon is provided to predict
fh = ForecastingHorizon(y_test.index, is_relative=False)
pipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)
pipe.fit(y_train, fh=fh)
pipe.predict(fh=fh)

```

**Expected behavior**
<!--
A clear and concise description of what you expected to happen.
-->
I would expect the TransformedTargetForecaster prediction to work with a non-relative ForecastingHorizon same as when using only the LTSFNLinearForecaster without needing to provide the ForecastingHorizon again.

**Additional context**
<!--
Add any other context about the problem here.
-->
I suspect that it has to do with the conversion of the ForecastingHorizon to relative if it's non-relative and then it thinks that it's not the same ForecastingHorizon anymore.
https://github.com/sktime/sktime/blob/d8574257d62a36930203bcb11f532623869fd7d7/sktime/forecasting/base/adapters/_pytorch.py#L64

**Versions**
<details>

<!--
Please run the following code snippet and paste the output here:

from sktime import show_versions; show_versions()
-->

System:
    python: 3.11.7 (main, Dec 17 2023, 18:26:46) [GCC 11.4.0]
executable: /home/usr/.pyenv/versions/bug_report/bin/python
   machine: Linux-6.5.0-15-generic-x86_64-with-glibc2.35

Python dependencies:
          pip: 23.3.2
       sktime: 0.26.0
      sklearn: 1.4.0
       skbase: 0.7.2
        numpy: 1.26.3
        scipy: 1.12.0
       pandas: 2.1.4
   matplotlib: 3.8.2
       joblib: 1.3.2
        numba: 0.59.0
  statsmodels: 0.14.1
     pmdarima: 2.0.4
statsforecast: 1.6.0
      tsfresh: None
      tslearn: None
        torch: 2.2.0+cpu
   tensorflow: None
tensorflow_probability: None


</details>



<!-- Thanks for contributing! -->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sktime/forecasting/base/adapters/_pytorch.py`
Content:
```
1 from abc import ABC
2 
3 import numpy as np
4 import pandas as pd
5 
6 from sktime.forecasting.base import BaseForecaster
7 from sktime.utils.validation._dependencies import _check_soft_dependencies
8 
9 if _check_soft_dependencies("torch", severity="none"):
10     import torch
11 
12 
13 class BaseDeepNetworkPyTorch(BaseForecaster, ABC):
14     """Abstract base class for deep learning networks using torch.nn."""
15 
16     _tags = {
17         "python_dependencies": "torch",
18         "y_inner_mtype": "pd.DataFrame",
19         "capability:insample": False,
20         "capability:pred_int:insample": False,
21         "scitype:y": "both",
22         "ignores-exogeneous-X": True,
23     }
24 
25     def __init__(
26         self,
27         num_epochs=16,
28         batch_size=8,
29         in_channels=1,
30         individual=False,
31         criterion_kwargs=None,
32         optimizer=None,
33         optimizer_kwargs=None,
34         lr=0.001,
35     ):
36         self.num_epochs = num_epochs
37         self.batch_size = batch_size
38         self.in_channels = in_channels
39         self.individual = individual
40         self.criterion_kwargs = criterion_kwargs
41         self.optimizer = optimizer
42         self.optimizer_kwargs = optimizer_kwargs
43         self.lr = lr
44 
45         super().__init__()
46 
47     def _fit(self, y, fh, X=None):
48         """Fit the network.
49 
50         Changes to state:
51             writes to self._network.state_dict
52 
53         Parameters
54         ----------
55         X : iterable-style or map-style dataset
56             see (https://pytorch.org/docs/stable/data.html) for more information
57         """
58         from sktime.forecasting.base import ForecastingHorizon
59 
60         # save fh and y for prediction later
61         if fh.is_relative:
62             self._fh = fh
63         else:
64             fh = fh.to_relative(self.cutoff)
65             self._fh = fh
66 
67         self._y = y
68 
69         if type(fh) is ForecastingHorizon:
70             self.network = self._build_network(fh._values[-1])
71         else:
72             self.network = self._build_network(fh)
73 
74         if self.criterion:
75             if self.criterion in self.criterions.keys():
76                 if self.criterion_kwargs:
77                     self._criterion = self.criterions[self.criterion](
78                         **self.criterion_kwargs
79                     )
80                 else:
81                     self._criterion = self.criterions[self.criterion]()
82             else:
83                 raise TypeError(
84                     f"Please pass one of {self.criterions.keys()} for `criterion`."
85                 )
86         else:
87             # default criterion
88             self._criterion = torch.nn.MSELoss()
89 
90         if self.optimizer:
91             if self.optimizer in self.optimizers.keys():
92                 if self.optimizer_kwargs:
93                     self._optimizer = self.optimizers[self.optimizer](
94                         self.network.parameters(), lr=self.lr, **self.optimizer_kwargs
95                     )
96                 else:
97                     self._optimizer = self.optimizers[self.optimizer](
98                         self.network.parameters(), lr=self.lr
99                     )
100             else:
101                 raise TypeError(
102                     f"Please pass one of {self.optimizers.keys()} for `optimizer`."
103                 )
104         else:
105             # default optimizer
106             self._optimizer = torch.optim.Adam(self.network.parameters(), lr=self.lr)
107 
108         dataloader = self.build_pytorch_train_dataloader(y)
109         self.network.train()
110 
111         for _ in range(self.num_epochs):
112             for x, y in dataloader:
113                 y_pred = self.network(x)
114                 loss = self._criterion(y_pred, y)
115                 self._optimizer.zero_grad()
116                 loss.backward()
117                 self._optimizer.step()
118 
119     def _predict(self, X=None, fh=None):
120         """Predict with fitted model."""
121         from torch import cat
122 
123         if fh is None:
124             fh = self._fh
125 
126         if max(fh._values) > self.network.pred_len or min(fh._values) < 0:
127             raise ValueError(
128                 f"fh of {fh} passed to {self.__class__.__name__} is not "
129                 "within `pred_len`. Please use a fh that aligns with the `pred_len` of "
130                 "the forecaster."
131             )
132 
133         if X is None:
134             dataloader = self.build_pytorch_pred_dataloader(self._y, fh)
135         else:
136             dataloader = self.build_pytorch_pred_dataloader(X, fh)
137 
138         y_pred = []
139         for x, _ in dataloader:
140             y_pred.append(self.network(x).detach())
141         y_pred = cat(y_pred, dim=0).view(-1, y_pred[0].shape[-1]).numpy()
142         y_pred = y_pred[fh._values.values - 1]
143         y_pred = pd.DataFrame(
144             y_pred, columns=self._y.columns, index=fh.to_absolute_index(self.cutoff)
145         )
146 
147         return y_pred
148 
149     def build_pytorch_train_dataloader(self, y):
150         """Build PyTorch DataLoader for training."""
151         from torch.utils.data import DataLoader
152 
153         if self.custom_dataset_train:
154             if hasattr(self.custom_dataset_train, "build_dataset") and callable(
155                 self.custom_dataset_train.build_dataset
156             ):
157                 self.custom_dataset_train.build_dataset(y)
158                 dataset = self.custom_dataset_train
159             else:
160                 raise NotImplementedError(
161                     "Custom Dataset `build_dataset` method is not available. Please "
162                     f"refer to the {self.__class__.__name__}.build_dataset "
163                     "documentation."
164                 )
165         else:
166             dataset = PyTorchTrainDataset(
167                 y=y,
168                 seq_len=self.network.seq_len,
169                 fh=self._fh._values[-1],
170             )
171 
172         return DataLoader(
173             dataset,
174             self.batch_size,
175         )
176 
177     def build_pytorch_pred_dataloader(self, y, fh):
178         """Build PyTorch DataLoader for prediction."""
179         from torch.utils.data import DataLoader
180 
181         if self.custom_dataset_pred:
182             if hasattr(self.custom_dataset_pred, "build_dataset") and callable(
183                 self.custom_dataset_pred.build_dataset
184             ):
185                 self.custom_dataset_train.build_dataset(y)
186                 dataset = self.custom_dataset_train
187             else:
188                 raise NotImplementedError(
189                     "Custom Dataset `build_dataset` method is not available. Please"
190                     f"refer to the {self.__class__.__name__}.build_dataset"
191                     "documentation."
192                 )
193         else:
194             dataset = PyTorchPredDataset(
195                 y=y[-self.network.seq_len :],
196                 seq_len=self.network.seq_len,
197             )
198 
199         return DataLoader(
200             dataset,
201             self.batch_size,
202         )
203 
204     def get_y_true(self, y):
205         """Get y_true values for validation."""
206         dataloader = self.build_pytorch_pred_dataloader(y)
207         y_true = [y.flatten().numpy() for _, y in dataloader]
208         return np.concatenate(y_true, axis=0)
209 
210 
211 class PyTorchTrainDataset:
212     """Dataset for use in sktime deep learning forecasters."""
213 
214     def __init__(self, y, seq_len, fh):
215         self.y = y.values
216         self.seq_len = seq_len
217         self.fh = fh
218 
219     def __len__(self):
220         """Return length of dataset."""
221         return len(self.y) - self.seq_len - self.fh + 1
222 
223     def __getitem__(self, i):
224         """Return data point."""
225         from torch import from_numpy, tensor
226 
227         return (
228             tensor(self.y[i : i + self.seq_len]).float(),
229             from_numpy(self.y[i + self.seq_len : i + self.seq_len + self.fh]).float(),
230         )
231 
232 
233 class PyTorchPredDataset:
234     """Dataset for use in sktime deep learning forecasters."""
235 
236     def __init__(self, y, seq_len):
237         self.y = y.values
238         self.seq_len = seq_len
239 
240     def __len__(self):
241         """Return length of dataset."""
242         return 1
243 
244     def __getitem__(self, i):
245         """Return data point."""
246         from torch import from_numpy, tensor
247 
248         return (
249             tensor(self.y[i : i + self.seq_len]).float(),
250             from_numpy(self.y[i + self.seq_len : i + self.seq_len]).float(),
251         )
252
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sktime/forecasting/base/adapters/_pytorch.py b/sktime/forecasting/base/adapters/_pytorch.py
--- a/sktime/forecasting/base/adapters/_pytorch.py
+++ b/sktime/forecasting/base/adapters/_pytorch.py
@@ -57,14 +57,7 @@
         """
         from sktime.forecasting.base import ForecastingHorizon
 
-        # save fh and y for prediction later
-        if fh.is_relative:
-            self._fh = fh
-        else:
-            fh = fh.to_relative(self.cutoff)
-            self._fh = fh
-
-        self._y = y
+        fh = fh.to_relative(self.cutoff)
 
         if type(fh) is ForecastingHorizon:
             self.network = self._build_network(fh._values[-1])
@@ -122,6 +115,7 @@
 
         if fh is None:
             fh = self._fh
+        fh = fh.to_relative(self.cutoff)
 
         if max(fh._values) > self.network.pred_len or min(fh._values) < 0:
             raise ValueError(
@@ -166,7 +160,7 @@
             dataset = PyTorchTrainDataset(
                 y=y,
                 seq_len=self.network.seq_len,
-                fh=self._fh._values[-1],
+                fh=self._fh.to_relative(self.cutoff)._values[-1],
             )
 
         return DataLoader(

{"golden_diff": "diff --git a/sktime/forecasting/base/adapters/_pytorch.py b/sktime/forecasting/base/adapters/_pytorch.py\n--- a/sktime/forecasting/base/adapters/_pytorch.py\n+++ b/sktime/forecasting/base/adapters/_pytorch.py\n@@ -57,14 +57,7 @@\n         \"\"\"\n         from sktime.forecasting.base import ForecastingHorizon\n \n-        # save fh and y for prediction later\n-        if fh.is_relative:\n-            self._fh = fh\n-        else:\n-            fh = fh.to_relative(self.cutoff)\n-            self._fh = fh\n-\n-        self._y = y\n+        fh = fh.to_relative(self.cutoff)\n \n         if type(fh) is ForecastingHorizon:\n             self.network = self._build_network(fh._values[-1])\n@@ -122,6 +115,7 @@\n \n         if fh is None:\n             fh = self._fh\n+        fh = fh.to_relative(self.cutoff)\n \n         if max(fh._values) > self.network.pred_len or min(fh._values) < 0:\n             raise ValueError(\n@@ -166,7 +160,7 @@\n             dataset = PyTorchTrainDataset(\n                 y=y,\n                 seq_len=self.network.seq_len,\n-                fh=self._fh._values[-1],\n+                fh=self._fh.to_relative(self.cutoff)._values[-1],\n             )\n \n         return DataLoader(\n", "issue": "[BUG] Prediction fails for TransformedTargetForecaster with LTSFNLinearForecaster with a non-relative ForecastingHorizon\n**Describe the bug**\r\n<!--\r\nA clear and concise description of what the bug is.\r\n-->\r\nWhen constructing a TransformedTargetForecaster with a transformer and LTSFNLinearForecaster, prediction fails saying that a different fh has been provided even if that is not the case.\r\n```\r\nTraceback (most recent call last):\r\n  File \"bug_report/tmp.py\", line 29, in <module>\r\n    pipe.predict(fh=fh)\r\n  File \"lib/python3.11/site-packages/sktime/forecasting/base/_base.py\", line 431, in predict\r\n    y_pred = self._predict(fh=fh, X=X_inner)\r\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"lib/python3.11/site-packages/sktime/forecasting/compose/_pipeline.py\", line 1032, in _predict\r\n    y_pred = self.forecaster_.predict(fh=fh, X=X)\r\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"lib/python3.11/site-packages/sktime/forecasting/base/_base.py\", line 424, in predict\r\n    fh = self._check_fh(fh)\r\n         ^^^^^^^^^^^^^^^^^^\r\n  File \"lib/python3.11/site-packages/sktime/forecasting/base/_base.py\", line 1745, in _check_fh\r\n    raise ValueError(\r\nValueError: A different forecasting horizon `fh` has been provided from the one seen already in `fit`, in this instance of LTSFNLinearForecaster. If you want to change the forecasting horizon, please re-fit the forecaster. This is because fitting of the forecaster LTSFNLinearForecaster depends on `fh`.\r\n```\r\n\r\n**To Reproduce**\r\n<!--\r\nAdd a Minimal, Complete, and Verifiable example (for more details, see e.g. https://stackoverflow.com/help/mcve\r\n\r\nIf the code is too long, feel free to put it in a public gist and link it in the issue: https://gist.github.com\r\n-->\r\n\r\n```python\r\nimport numpy as np\r\nimport pandas as pd\r\nfrom sktime.forecasting.base import ForecastingHorizon\r\nfrom sktime.forecasting.ltsf import LTSFNLinearForecaster\r\nfrom sktime.split import temporal_train_test_split\r\nfrom sktime.transformations.series.boxcox import LogTransformer\r\n\r\ny = pd.DataFrame(\r\n    np.random.random([100, 3]), index=pd.date_range(\"2023-01-01\", periods=100)\r\n)\r\ny_train, y_test = temporal_train_test_split(y, test_size=30)\r\n\r\n# TransformedTargetForecaster works with relative ForecastingHorizon\r\nfh = ForecastingHorizon(np.arange(1, 31), is_relative=True)\r\npipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)\r\npipe.fit(y_train, fh=fh)\r\npipe.predict()\r\n\r\n# Forecaster works with non-relative ForecastingHorizon\r\nfh = ForecastingHorizon(y_test.index, is_relative=False)\r\npipe = LTSFNLinearForecaster(seq_len=20, pred_len=30, batch_size=256)\r\npipe.fit(y_train, fh=fh)\r\npipe.predict()\r\n\r\n# TransformedTargetForecaster fails with non-relative ForecastingHorizon\r\nfh = ForecastingHorizon(y_test.index, is_relative=False)\r\npipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)\r\npipe.fit(y_train, fh=fh)\r\npipe.predict()\r\n\r\n# TransformedTargetForecaster fails with non-relative ForecastingHorizon even if ForecastingHorizon is provided to predict\r\nfh = ForecastingHorizon(y_test.index, is_relative=False)\r\npipe = LogTransformer() * LTSFNLinearForecaster(seq_len=20, pred_len=30)\r\npipe.fit(y_train, fh=fh)\r\npipe.predict(fh=fh)\r\n\r\n```\r\n\r\n**Expected behavior**\r\n<!--\r\nA clear and concise description of what you expected to happen.\r\n-->\r\nI would expect the TransformedTargetForecaster prediction to work with a non-relative ForecastingHorizon same as when using only the LTSFNLinearForecaster without needing to provide the ForecastingHorizon again.\r\n\r\n**Additional context**\r\n<!--\r\nAdd any other context about the problem here.\r\n-->\r\nI suspect that it has to do with the conversion of the ForecastingHorizon to relative if it's non-relative and then it thinks that it's not the same ForecastingHorizon anymore.\r\nhttps://github.com/sktime/sktime/blob/d8574257d62a36930203bcb11f532623869fd7d7/sktime/forecasting/base/adapters/_pytorch.py#L64\r\n\r\n**Versions**\r\n<details>\r\n\r\n<!--\r\nPlease run the following code snippet and paste the output here:\r\n\r\nfrom sktime import show_versions; show_versions()\r\n-->\r\n\r\nSystem:\r\n    python: 3.11.7 (main, Dec 17 2023, 18:26:46) [GCC 11.4.0]\r\nexecutable: /home/usr/.pyenv/versions/bug_report/bin/python\r\n   machine: Linux-6.5.0-15-generic-x86_64-with-glibc2.35\r\n\r\nPython dependencies:\r\n          pip: 23.3.2\r\n       sktime: 0.26.0\r\n      sklearn: 1.4.0\r\n       skbase: 0.7.2\r\n        numpy: 1.26.3\r\n        scipy: 1.12.0\r\n       pandas: 2.1.4\r\n   matplotlib: 3.8.2\r\n       joblib: 1.3.2\r\n        numba: 0.59.0\r\n  statsmodels: 0.14.1\r\n     pmdarima: 2.0.4\r\nstatsforecast: 1.6.0\r\n      tsfresh: None\r\n      tslearn: None\r\n        torch: 2.2.0+cpu\r\n   tensorflow: None\r\ntensorflow_probability: None\r\n\r\n\r\n</details>\r\n\r\n\r\n\r\n<!-- Thanks for contributing! -->\r\n\n", "before_files": [{"content": "from abc import ABC\n\nimport numpy as np\nimport pandas as pd\n\nfrom sktime.forecasting.base import BaseForecaster\nfrom sktime.utils.validation._dependencies import _check_soft_dependencies\n\nif _check_soft_dependencies(\"torch\", severity=\"none\"):\n    import torch\n\n\nclass BaseDeepNetworkPyTorch(BaseForecaster, ABC):\n    \"\"\"Abstract base class for deep learning networks using torch.nn.\"\"\"\n\n    _tags = {\n        \"python_dependencies\": \"torch\",\n        \"y_inner_mtype\": \"pd.DataFrame\",\n        \"capability:insample\": False,\n        \"capability:pred_int:insample\": False,\n        \"scitype:y\": \"both\",\n        \"ignores-exogeneous-X\": True,\n    }\n\n    def __init__(\n        self,\n        num_epochs=16,\n        batch_size=8,\n        in_channels=1,\n        individual=False,\n        criterion_kwargs=None,\n        optimizer=None,\n        optimizer_kwargs=None,\n        lr=0.001,\n    ):\n        self.num_epochs = num_epochs\n        self.batch_size = batch_size\n        self.in_channels = in_channels\n        self.individual = individual\n        self.criterion_kwargs = criterion_kwargs\n        self.optimizer = optimizer\n        self.optimizer_kwargs = optimizer_kwargs\n        self.lr = lr\n\n        super().__init__()\n\n    def _fit(self, y, fh, X=None):\n        \"\"\"Fit the network.\n\n        Changes to state:\n            writes to self._network.state_dict\n\n        Parameters\n        ----------\n        X : iterable-style or map-style dataset\n            see (https://pytorch.org/docs/stable/data.html) for more information\n        \"\"\"\n        from sktime.forecasting.base import ForecastingHorizon\n\n        # save fh and y for prediction later\n        if fh.is_relative:\n            self._fh = fh\n        else:\n            fh = fh.to_relative(self.cutoff)\n            self._fh = fh\n\n        self._y = y\n\n        if type(fh) is ForecastingHorizon:\n            self.network = self._build_network(fh._values[-1])\n        else:\n            self.network = self._build_network(fh)\n\n        if self.criterion:\n            if self.criterion in self.criterions.keys():\n                if self.criterion_kwargs:\n                    self._criterion = self.criterions[self.criterion](\n                        **self.criterion_kwargs\n                    )\n                else:\n                    self._criterion = self.criterions[self.criterion]()\n            else:\n                raise TypeError(\n                    f\"Please pass one of {self.criterions.keys()} for `criterion`.\"\n                )\n        else:\n            # default criterion\n            self._criterion = torch.nn.MSELoss()\n\n        if self.optimizer:\n            if self.optimizer in self.optimizers.keys():\n                if self.optimizer_kwargs:\n                    self._optimizer = self.optimizers[self.optimizer](\n                        self.network.parameters(), lr=self.lr, **self.optimizer_kwargs\n                    )\n                else:\n                    self._optimizer = self.optimizers[self.optimizer](\n                        self.network.parameters(), lr=self.lr\n                    )\n            else:\n                raise TypeError(\n                    f\"Please pass one of {self.optimizers.keys()} for `optimizer`.\"\n                )\n        else:\n            # default optimizer\n            self._optimizer = torch.optim.Adam(self.network.parameters(), lr=self.lr)\n\n        dataloader = self.build_pytorch_train_dataloader(y)\n        self.network.train()\n\n        for _ in range(self.num_epochs):\n            for x, y in dataloader:\n                y_pred = self.network(x)\n                loss = self._criterion(y_pred, y)\n                self._optimizer.zero_grad()\n                loss.backward()\n                self._optimizer.step()\n\n    def _predict(self, X=None, fh=None):\n        \"\"\"Predict with fitted model.\"\"\"\n        from torch import cat\n\n        if fh is None:\n            fh = self._fh\n\n        if max(fh._values) > self.network.pred_len or min(fh._values) < 0:\n            raise ValueError(\n                f\"fh of {fh} passed to {self.__class__.__name__} is not \"\n                \"within `pred_len`. Please use a fh that aligns with the `pred_len` of \"\n                \"the forecaster.\"\n            )\n\n        if X is None:\n            dataloader = self.build_pytorch_pred_dataloader(self._y, fh)\n        else:\n            dataloader = self.build_pytorch_pred_dataloader(X, fh)\n\n        y_pred = []\n        for x, _ in dataloader:\n            y_pred.append(self.network(x).detach())\n        y_pred = cat(y_pred, dim=0).view(-1, y_pred[0].shape[-1]).numpy()\n        y_pred = y_pred[fh._values.values - 1]\n        y_pred = pd.DataFrame(\n            y_pred, columns=self._y.columns, index=fh.to_absolute_index(self.cutoff)\n        )\n\n        return y_pred\n\n    def build_pytorch_train_dataloader(self, y):\n        \"\"\"Build PyTorch DataLoader for training.\"\"\"\n        from torch.utils.data import DataLoader\n\n        if self.custom_dataset_train:\n            if hasattr(self.custom_dataset_train, \"build_dataset\") and callable(\n                self.custom_dataset_train.build_dataset\n            ):\n                self.custom_dataset_train.build_dataset(y)\n                dataset = self.custom_dataset_train\n            else:\n                raise NotImplementedError(\n                    \"Custom Dataset `build_dataset` method is not available. Please \"\n                    f\"refer to the {self.__class__.__name__}.build_dataset \"\n                    \"documentation.\"\n                )\n        else:\n            dataset = PyTorchTrainDataset(\n                y=y,\n                seq_len=self.network.seq_len,\n                fh=self._fh._values[-1],\n            )\n\n        return DataLoader(\n            dataset,\n            self.batch_size,\n        )\n\n    def build_pytorch_pred_dataloader(self, y, fh):\n        \"\"\"Build PyTorch DataLoader for prediction.\"\"\"\n        from torch.utils.data import DataLoader\n\n        if self.custom_dataset_pred:\n            if hasattr(self.custom_dataset_pred, \"build_dataset\") and callable(\n                self.custom_dataset_pred.build_dataset\n            ):\n                self.custom_dataset_train.build_dataset(y)\n                dataset = self.custom_dataset_train\n            else:\n                raise NotImplementedError(\n                    \"Custom Dataset `build_dataset` method is not available. Please\"\n                    f\"refer to the {self.__class__.__name__}.build_dataset\"\n                    \"documentation.\"\n                )\n        else:\n            dataset = PyTorchPredDataset(\n                y=y[-self.network.seq_len :],\n                seq_len=self.network.seq_len,\n            )\n\n        return DataLoader(\n            dataset,\n            self.batch_size,\n        )\n\n    def get_y_true(self, y):\n        \"\"\"Get y_true values for validation.\"\"\"\n        dataloader = self.build_pytorch_pred_dataloader(y)\n        y_true = [y.flatten().numpy() for _, y in dataloader]\n        return np.concatenate(y_true, axis=0)\n\n\nclass PyTorchTrainDataset:\n    \"\"\"Dataset for use in sktime deep learning forecasters.\"\"\"\n\n    def __init__(self, y, seq_len, fh):\n        self.y = y.values\n        self.seq_len = seq_len\n        self.fh = fh\n\n    def __len__(self):\n        \"\"\"Return length of dataset.\"\"\"\n        return len(self.y) - self.seq_len - self.fh + 1\n\n    def __getitem__(self, i):\n        \"\"\"Return data point.\"\"\"\n        from torch import from_numpy, tensor\n\n        return (\n            tensor(self.y[i : i + self.seq_len]).float(),\n            from_numpy(self.y[i + self.seq_len : i + self.seq_len + self.fh]).float(),\n        )\n\n\nclass PyTorchPredDataset:\n    \"\"\"Dataset for use in sktime deep learning forecasters.\"\"\"\n\n    def __init__(self, y, seq_len):\n        self.y = y.values\n        self.seq_len = seq_len\n\n    def __len__(self):\n        \"\"\"Return length of dataset.\"\"\"\n        return 1\n\n    def __getitem__(self, i):\n        \"\"\"Return data point.\"\"\"\n        from torch import from_numpy, tensor\n\n        return (\n            tensor(self.y[i : i + self.seq_len]).float(),\n            from_numpy(self.y[i + self.seq_len : i + self.seq_len]).float(),\n        )\n", "path": "sktime/forecasting/base/adapters/_pytorch.py"}], "after_files": [{"content": "from abc import ABC\n\nimport numpy as np\nimport pandas as pd\n\nfrom sktime.forecasting.base import BaseForecaster\nfrom sktime.utils.validation._dependencies import _check_soft_dependencies\n\nif _check_soft_dependencies(\"torch\", severity=\"none\"):\n    import torch\n\n\nclass BaseDeepNetworkPyTorch(BaseForecaster, ABC):\n    \"\"\"Abstract base class for deep learning networks using torch.nn.\"\"\"\n\n    _tags = {\n        \"python_dependencies\": \"torch\",\n        \"y_inner_mtype\": \"pd.DataFrame\",\n        \"capability:insample\": False,\n        \"capability:pred_int:insample\": False,\n        \"scitype:y\": \"both\",\n        \"ignores-exogeneous-X\": True,\n    }\n\n    def __init__(\n        self,\n        num_epochs=16,\n        batch_size=8,\n        in_channels=1,\n        individual=False,\n        criterion_kwargs=None,\n        optimizer=None,\n        optimizer_kwargs=None,\n        lr=0.001,\n    ):\n        self.num_epochs = num_epochs\n        self.batch_size = batch_size\n        self.in_channels = in_channels\n        self.individual = individual\n        self.criterion_kwargs = criterion_kwargs\n        self.optimizer = optimizer\n        self.optimizer_kwargs = optimizer_kwargs\n        self.lr = lr\n\n        super().__init__()\n\n    def _fit(self, y, fh, X=None):\n        \"\"\"Fit the network.\n\n        Changes to state:\n            writes to self._network.state_dict\n\n        Parameters\n        ----------\n        X : iterable-style or map-style dataset\n            see (https://pytorch.org/docs/stable/data.html) for more information\n        \"\"\"\n        from sktime.forecasting.base import ForecastingHorizon\n\n        fh = fh.to_relative(self.cutoff)\n\n        if type(fh) is ForecastingHorizon:\n            self.network = self._build_network(fh._values[-1])\n        else:\n            self.network = self._build_network(fh)\n\n        if self.criterion:\n            if self.criterion in self.criterions.keys():\n                if self.criterion_kwargs:\n                    self._criterion = self.criterions[self.criterion](\n                        **self.criterion_kwargs\n                    )\n                else:\n                    self._criterion = self.criterions[self.criterion]()\n            else:\n                raise TypeError(\n                    f\"Please pass one of {self.criterions.keys()} for `criterion`.\"\n                )\n        else:\n            # default criterion\n            self._criterion = torch.nn.MSELoss()\n\n        if self.optimizer:\n            if self.optimizer in self.optimizers.keys():\n                if self.optimizer_kwargs:\n                    self._optimizer = self.optimizers[self.optimizer](\n                        self.network.parameters(), lr=self.lr, **self.optimizer_kwargs\n                    )\n                else:\n                    self._optimizer = self.optimizers[self.optimizer](\n                        self.network.parameters(), lr=self.lr\n                    )\n            else:\n                raise TypeError(\n                    f\"Please pass one of {self.optimizers.keys()} for `optimizer`.\"\n                )\n        else:\n            # default optimizer\n            self._optimizer = torch.optim.Adam(self.network.parameters(), lr=self.lr)\n\n        dataloader = self.build_pytorch_train_dataloader(y)\n        self.network.train()\n\n        for _ in range(self.num_epochs):\n            for x, y in dataloader:\n                y_pred = self.network(x)\n                loss = self._criterion(y_pred, y)\n                self._optimizer.zero_grad()\n                loss.backward()\n                self._optimizer.step()\n\n    def _predict(self, X=None, fh=None):\n        \"\"\"Predict with fitted model.\"\"\"\n        from torch import cat\n\n        if fh is None:\n            fh = self._fh\n        fh = fh.to_relative(self.cutoff)\n\n        if max(fh._values) > self.network.pred_len or min(fh._values) < 0:\n            raise ValueError(\n                f\"fh of {fh} passed to {self.__class__.__name__} is not \"\n                \"within `pred_len`. Please use a fh that aligns with the `pred_len` of \"\n                \"the forecaster.\"\n            )\n\n        if X is None:\n            dataloader = self.build_pytorch_pred_dataloader(self._y, fh)\n        else:\n            dataloader = self.build_pytorch_pred_dataloader(X, fh)\n\n        y_pred = []\n        for x, _ in dataloader:\n            y_pred.append(self.network(x).detach())\n        y_pred = cat(y_pred, dim=0).view(-1, y_pred[0].shape[-1]).numpy()\n        y_pred = y_pred[fh._values.values - 1]\n        y_pred = pd.DataFrame(\n            y_pred, columns=self._y.columns, index=fh.to_absolute_index(self.cutoff)\n        )\n\n        return y_pred\n\n    def build_pytorch_train_dataloader(self, y):\n        \"\"\"Build PyTorch DataLoader for training.\"\"\"\n        from torch.utils.data import DataLoader\n\n        if self.custom_dataset_train:\n            if hasattr(self.custom_dataset_train, \"build_dataset\") and callable(\n                self.custom_dataset_train.build_dataset\n            ):\n                self.custom_dataset_train.build_dataset(y)\n                dataset = self.custom_dataset_train\n            else:\n                raise NotImplementedError(\n                    \"Custom Dataset `build_dataset` method is not available. Please \"\n                    f\"refer to the {self.__class__.__name__}.build_dataset \"\n                    \"documentation.\"\n                )\n        else:\n            dataset = PyTorchTrainDataset(\n                y=y,\n                seq_len=self.network.seq_len,\n                fh=self._fh.to_relative(self.cutoff)._values[-1],\n            )\n\n        return DataLoader(\n            dataset,\n            self.batch_size,\n        )\n\n    def build_pytorch_pred_dataloader(self, y, fh):\n        \"\"\"Build PyTorch DataLoader for prediction.\"\"\"\n        from torch.utils.data import DataLoader\n\n        if self.custom_dataset_pred:\n            if hasattr(self.custom_dataset_pred, \"build_dataset\") and callable(\n                self.custom_dataset_pred.build_dataset\n            ):\n                self.custom_dataset_train.build_dataset(y)\n                dataset = self.custom_dataset_train\n            else:\n                raise NotImplementedError(\n                    \"Custom Dataset `build_dataset` method is not available. Please\"\n                    f\"refer to the {self.__class__.__name__}.build_dataset\"\n                    \"documentation.\"\n                )\n        else:\n            dataset = PyTorchPredDataset(\n                y=y[-self.network.seq_len :],\n                seq_len=self.network.seq_len,\n            )\n\n        return DataLoader(\n            dataset,\n            self.batch_size,\n        )\n\n    def get_y_true(self, y):\n        \"\"\"Get y_true values for validation.\"\"\"\n        dataloader = self.build_pytorch_pred_dataloader(y)\n        y_true = [y.flatten().numpy() for _, y in dataloader]\n        return np.concatenate(y_true, axis=0)\n\n\nclass PyTorchTrainDataset:\n    \"\"\"Dataset for use in sktime deep learning forecasters.\"\"\"\n\n    def __init__(self, y, seq_len, fh):\n        self.y = y.values\n        self.seq_len = seq_len\n        self.fh = fh\n\n    def __len__(self):\n        \"\"\"Return length of dataset.\"\"\"\n        return len(self.y) - self.seq_len - self.fh + 1\n\n    def __getitem__(self, i):\n        \"\"\"Return data point.\"\"\"\n        from torch import from_numpy, tensor\n\n        return (\n            tensor(self.y[i : i + self.seq_len]).float(),\n            from_numpy(self.y[i + self.seq_len : i + self.seq_len + self.fh]).float(),\n        )\n\n\nclass PyTorchPredDataset:\n    \"\"\"Dataset for use in sktime deep learning forecasters.\"\"\"\n\n    def __init__(self, y, seq_len):\n        self.y = y.values\n        self.seq_len = seq_len\n\n    def __len__(self):\n        \"\"\"Return length of dataset.\"\"\"\n        return 1\n\n    def __getitem__(self, i):\n        \"\"\"Return data point.\"\"\"\n        from torch import from_numpy, tensor\n\n        return (\n            tensor(self.y[i : i + self.seq_len]).float(),\n            from_numpy(self.y[i + self.seq_len : i + self.seq_len]).float(),\n        )\n", "path": "sktime/forecasting/base/adapters/_pytorch.py"}]}