-
Notifications
You must be signed in to change notification settings - Fork 885
[ENH] Add more train_test_split strategies
#2087
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
base: main
Are you sure you want to change the base?
Changes from 35 commits
12c5e57
d4e48b3
455472c
1758ec3
29734d7
263ae38
d089be4
90d345a
af59b4c
35bf4c3
aae1f96
9248866
ab5b4d0
b623d59
573d1fb
2d0456c
7e5c849
7e3ae57
67ba8ee
2295183
d482898
3be0d0a
3694122
b6ef270
8e22de7
ac4a762
54ff0ab
5017d01
2ee716e
a4a47b4
84a5bca
d71376d
a6f3a24
54b5de8
a87d7e4
5144273
f68b441
15543a9
ab5d862
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -309,6 +309,8 @@ def __init__( | |
| batch_size: int = 32, | ||
| num_workers: int = 0, | ||
| train_val_test_split: tuple[float, float, float] = (0.7, 0.15, 0.15), | ||
| split_strategy: str = "random", | ||
| temporal_cutoffs: dict[str, float] | None = None, | ||
| collate_fn: Callable | None = None, | ||
| **kwargs, | ||
| ) -> None: | ||
|
|
@@ -323,6 +325,8 @@ def __init__( | |
| self.batch_size = batch_size | ||
| self.num_workers = num_workers | ||
| self.train_val_test_split = train_val_test_split | ||
| self.split_strategy = split_strategy | ||
| self.temporal_cutoffs = temporal_cutoffs | ||
| self.collate_fn = ( | ||
| collate_fn if collate_fn is not None else self.__class__.collate_fn | ||
| ) # noqa: E501 | ||
|
|
@@ -695,51 +699,92 @@ def setup(self, stage: str | None = None) -> None: | |
| "Please provide a non-empty dataset." | ||
| ) | ||
|
|
||
| # this is a very rudimentary way to handle the splits when | ||
| # the dataset is of size equal to 1 or 2. | ||
| self._indices = torch.randperm(total_series) | ||
| if total_series == 1: | ||
| self._train_indices = self._indices | ||
| self._val_indices = self._indices | ||
| self._test_indices = self._indices | ||
| elif total_series == 2: | ||
| self._train_indices = self._indices[0:1] | ||
| self._val_indices = self._indices[1:2] | ||
| self._test_indices = self._indices[1:2] | ||
| else: | ||
| self._train_size = int(self.train_val_test_split[0] * total_series) | ||
| self._val_size = int(self.train_val_test_split[1] * total_series) | ||
|
|
||
| self._train_indices = self._indices[: self._train_size] | ||
| self._val_indices = self._indices[ | ||
| self._train_size : self._train_size + self._val_size | ||
| ] | ||
| from pytorch_forecasting.data.splitters import ( | ||
| random_series_split, | ||
| stratified_series_split, | ||
| temporal_window_split, | ||
| ) | ||
|
|
||
| self._test_indices = self._indices[ | ||
| self._train_size + self._val_size : total_series | ||
| ] | ||
| if self.split_strategy in ["random", "group"]: | ||
| self._train_indices, self._val_indices, self._test_indices = ( | ||
| random_series_split(total_series, self.train_val_test_split) | ||
| ) | ||
| elif self.split_strategy == "stratified": | ||
| self._train_indices, self._val_indices, self._test_indices = ( | ||
| stratified_series_split( | ||
| self.time_series_dataset, | ||
| target_idx=0, | ||
| train_val_test_split=self.train_val_test_split, | ||
| ) | ||
| ) | ||
| elif self.split_strategy == "temporal": | ||
| self._train_indices = torch.arange(total_series) | ||
| self._val_indices = torch.arange(total_series) | ||
| self._test_indices = torch.arange(total_series) | ||
| else: | ||
| raise ValueError(f"Unknown split_strategy: {self.split_strategy}") | ||
|
|
||
| if stage == "fit" or stage is None: | ||
| if not hasattr(self, "_train_dataset") or not hasattr(self, "_val_dataset"): | ||
| self._train_windows = self._create_windows(self._train_indices) | ||
| self._val_windows = self._create_windows(self._val_indices) | ||
| if not hasattr(self, "_train_windows") or not hasattr(self, "_val_windows"): | ||
| if self.split_strategy == "temporal": | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you provide a visual example (or a snippet of code) with let say:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Diagram :
Code : For the Temporal split, the filtering happens at the Window level. The DataModule assigns ALL series indices to Train, Val, and Test. It generates ALL possible windows for the entire dataset, and then passes that massive list of windows to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice for the representation, it is much easier now to understand :-)
We can still have this splitting methodology, but I won't call it |
||
| all_windows = self._create_windows(self._train_indices) | ||
|
|
||
| series_timestamps = {} | ||
| for idx in self._train_indices: | ||
| series_idx = ( | ||
| idx.item() if isinstance(idx, torch.Tensor) else idx | ||
| ) | ||
| sample = self.time_series_dataset[series_idx] | ||
| series_timestamps[series_idx] = sample["t"] | ||
|
|
||
| t_win, v_win, te_win = temporal_window_split( | ||
| all_windows, | ||
| self.train_val_test_split, | ||
| series_timestamps, | ||
| self.temporal_cutoffs, | ||
| ) | ||
|
|
||
| self._train_windows, self._val_windows, self._test_windows = ( | ||
| t_win, | ||
| v_win, | ||
| te_win, | ||
| ) | ||
| else: | ||
| self._train_windows = self._create_windows(self._train_indices) | ||
| self._val_windows = self._create_windows(self._val_indices) | ||
|
|
||
| self.train_dataset = _TslibDataset( | ||
| dataset=self.time_series_dataset, | ||
| data_module=self, | ||
| windows=self._train_windows, | ||
| add_relative_time_idx=self.add_relative_time_idx, | ||
| ) | ||
|
|
||
| self.val_dataset = _TslibDataset( | ||
| dataset=self.time_series_dataset, | ||
| data_module=self, | ||
| windows=self._val_windows, | ||
| add_relative_time_idx=self.add_relative_time_idx, | ||
| ) | ||
|
|
||
| elif stage == "test": | ||
| if not hasattr(self, "_test_dataset"): | ||
| self._test_windows = self._create_windows(self._test_indices) | ||
| if not hasattr(self, "_test_windows") or self.test_dataset is None: | ||
| if self.split_strategy == "temporal": | ||
| all_windows = self._create_windows(torch.arange(total_series)) | ||
|
|
||
| series_timestamps = {} | ||
| for idx in range(total_series): | ||
| sample = self.time_series_dataset[idx] | ||
| series_timestamps[idx] = sample["t"] | ||
|
|
||
| _, _, self._test_windows = temporal_window_split( | ||
| all_windows, | ||
| self.train_val_test_split, | ||
| series_timestamps, | ||
| self.temporal_cutoffs, | ||
| ) | ||
|
|
||
| else: | ||
| self._test_windows = self._create_windows(self._test_indices) | ||
|
|
||
| self.test_dataset = _TslibDataset( | ||
| dataset=self.time_series_dataset, | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add all the split statergies here