-
Notifications
You must be signed in to change notification settings - Fork 1k
Imp/tsmixer basic #2555
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: master
Are you sure you want to change the base?
Imp/tsmixer basic #2555
Changes from 20 commits
e12a751
1bd17da
34e431f
bb72922
1934928
c5de724
b8a2e88
c6c7e97
79612f0
6cdc9db
171dc34
f9797a3
e528f85
133547e
5afff01
3a392a3
ebe02d1
0a90f24
2674e1c
2bf09ce
245ae09
f9c0d15
51e2b11
c86b559
e6647c0
1483096
97c83b2
bf912d9
46cf888
c374724
67a89f1
ef1a322
0dd0c30
a4891e2
a46645a
c5a5fe4
af74b5b
8550314
df935cc
2c156f5
694ca4a
94c6d71
f31af84
784e9ea
7ca964b
176dd5c
7ad36bd
552c92b
83264d4
1e48108
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 |
|---|---|---|
|
|
@@ -267,6 +267,7 @@ def __init__( | |
| super().__init__() | ||
|
|
||
| mixing_input = input_dim | ||
|
|
||
| if static_cov_dim != 0: | ||
| self.feature_mixing_static = _FeatureMixing( | ||
| sequence_length=sequence_length, | ||
|
|
@@ -328,6 +329,7 @@ def __init__( | |
| dropout: float, | ||
| norm_type: Union[str, nn.Module], | ||
| normalize_before: bool, | ||
| project_first_layer: bool = True, | ||
| **kwargs, | ||
| ) -> None: | ||
| """ | ||
|
|
@@ -362,18 +364,28 @@ def __init__( | |
| Type of normalization to use. | ||
| normalize_before | ||
| Whether to apply normalization before or after mixing. | ||
| project_first_layer | ||
| Whether to project to the output time dimension at the first layer (default), | ||
| or at the end of the module. False is recommended if there are | ||
| no future covariates, while True is recommended if there are | ||
| important future covariates. | ||
| """ | ||
| super().__init__(**kwargs) | ||
| self.input_dim = input_dim | ||
| self.output_dim = output_dim | ||
| self.future_cov_dim = future_cov_dim | ||
| self.static_cov_dim = static_cov_dim | ||
| self.nr_params = nr_params | ||
| self.project_first_layer = project_first_layer | ||
|
|
||
| self.sequence_length = ( | ||
| self.output_chunk_length if project_first_layer else self.input_chunk_length | ||
| ) | ||
|
|
||
| if activation not in ACTIVATIONS: | ||
| raise_log( | ||
| ValueError( | ||
| f"Invalid `activation={activation}`. Must be on of {ACTIVATIONS}." | ||
| f"Invalid `activation={activation}`. Must be one of {ACTIVATIONS}." | ||
| ), | ||
| logger=logger, | ||
| ) | ||
|
|
@@ -383,7 +395,7 @@ def __init__( | |
| if norm_type not in NORMS: | ||
| raise_log( | ||
| ValueError( | ||
| f"Invalid `norm_type={norm_type}`. Must be on of {NORMS}." | ||
| f"Invalid `norm_type={norm_type}`. Must be one of {NORMS}." | ||
| ), | ||
| logger=logger, | ||
| ) | ||
|
|
@@ -402,24 +414,33 @@ def __init__( | |
| "normalize_before": normalize_before, | ||
| } | ||
|
|
||
| # Projects from the input time dimension to the output time dimension | ||
| self.fc_hist = nn.Linear(self.input_chunk_length, self.output_chunk_length) | ||
|
|
||
| # Projects from the output time dimension to the input time dimension | ||
| # (if we are keeping the input time dimension/project_first_layer=False) | ||
| if not self.project_first_layer: | ||
| self.fc_future = nn.Linear( | ||
| self.output_chunk_length, self.input_chunk_length | ||
| ) | ||
|
|
||
| self.feature_mixing_hist = _FeatureMixing( | ||
| sequence_length=self.output_chunk_length, | ||
| sequence_length=self.sequence_length, | ||
| input_dim=input_dim + past_cov_dim + future_cov_dim, | ||
| output_dim=hidden_size, | ||
| **mixer_params, | ||
| ) | ||
| if future_cov_dim: | ||
| self.feature_mixing_future = _FeatureMixing( | ||
| sequence_length=self.output_chunk_length, | ||
| sequence_length=self.sequence_length, | ||
| input_dim=future_cov_dim, | ||
| output_dim=hidden_size, | ||
| **mixer_params, | ||
| ) | ||
| else: | ||
| self.feature_mixing_future = None | ||
| self.conditional_mixer = self._build_mixer( | ||
| prediction_length=self.output_chunk_length, | ||
| sequence_length=self.sequence_length, | ||
| num_blocks=num_blocks, | ||
| hidden_size=hidden_size, | ||
| future_cov_dim=future_cov_dim, | ||
|
|
@@ -430,7 +451,7 @@ def __init__( | |
|
|
||
| @staticmethod | ||
| def _build_mixer( | ||
| prediction_length: int, | ||
| sequence_length: int, | ||
| num_blocks: int, | ||
| hidden_size: int, | ||
| future_cov_dim: int, | ||
|
|
@@ -448,7 +469,7 @@ def _build_mixer( | |
| layer = _ConditionalMixerLayer( | ||
| input_dim=input_dim_block, | ||
| output_dim=hidden_size, | ||
| sequence_length=prediction_length, | ||
| sequence_length=sequence_length, | ||
| static_cov_dim=static_cov_dim, | ||
| **kwargs, | ||
| ) | ||
|
|
@@ -480,6 +501,7 @@ def forward( | |
| # B: batch size | ||
| # L: input chunk length | ||
| # T: output chunk length | ||
| # SL: Residual block time dimension (T if project_first_layer, L otherwise) | ||
| # C: target components | ||
| # P: past cov features | ||
| # F: future cov features | ||
|
|
@@ -491,31 +513,62 @@ def forward( | |
| # `x`: (B, L, H), `x_future`: (B, T, F), `x_static`: (B, C or 1, S) | ||
| x, x_future, x_static = x_in | ||
|
|
||
| # swap feature and time dimensions (B, L, H) -> (B, H, L) | ||
| x = _time_to_feature(x) | ||
| # linear transformations to horizon (B, H, L) -> (B, H, T) | ||
| x = self.fc_hist(x) | ||
| # (B, H, T) -> (B, T, H) | ||
| x = _time_to_feature(x) | ||
|
|
||
| # feature mixing for historical features (B, T, H) -> (B, T, H_S) | ||
| # If project_first_layer, decoder style model with residual blocks in output time dimension | ||
| # (B, L, H) -> (B, SL, H) | ||
| if self.project_first_layer: | ||
| # swap feature and time dimensions (B, L, H) -> (B, H, L) | ||
| x = _time_to_feature(x) | ||
| # linear transformations to SL (T in this case) | ||
| # (B, H, L) -> (B, H, SL) | ||
| x = self.fc_hist(x) | ||
| # Transpose back | ||
| # (B, H, T) -> (B, T, H) | ||
| x = _time_to_feature(x) | ||
|
|
||
| # Otherwise, encoder-style model with residual blocks in input time dimension | ||
| # In the original paper this was not implimented for future covariates, | ||
|
eschibli marked this conversation as resolved.
Outdated
|
||
| # but rather than ignoring them or raising an error we remap them to the input time dimension. | ||
| # Suboptimal but may be useful in some cases. | ||
| elif self.future_cov_dim: | ||
|
Contributor
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. to make it a bit more intuitive, I would move this code below, inside the
Contributor
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. This implementation is out-of-date with the current version of the PR, and I think the current version makes more sense.
Contributor
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. I set it up this way originally because the TSMixer performance test was failing if I didn't generate the layers in exactly the same order. In this PR I have relaxed the tolerance as it wasn't stable. |
||
| # swap feature and time dimensions (B, L, F) -> (B, F, L) | ||
| x_future = _time_to_feature(x_future) | ||
| # linear transformations to SL (L in this case) | ||
| # (B, F, T) -> (B, F, SL) | ||
| x_future = self.fc_future(x_future) | ||
| # Transpose back (B, L, F) -> (B, F, L) | ||
| x_future = _time_to_feature(x_future) | ||
|
|
||
| # feature mixing for historical features (B, SL, H) -> (B, SL, H_S) | ||
| x = self.feature_mixing_hist(x) | ||
| if self.future_cov_dim: | ||
| # feature mixing for future features (B, T, F) -> (B, T, H_S) | ||
| # feature mixing for future features (B, SL, F) -> (B, SL, H_S) | ||
| x_future = self.feature_mixing_future(x_future) | ||
| # (B, T, H_S) + (B, T, H_S) -> (B, T, 2*H_S) | ||
| # (B, SL, H_S) + (B, SL, H_S) -> (B, T, 2*H_S) | ||
| x = torch.cat([x, x_future], dim=-1) | ||
|
|
||
| if self.static_cov_dim: | ||
| # (B, C, S) -> (B, 1, C * S) | ||
| x_static = x_static.reshape(x_static.shape[0], 1, -1) | ||
| # repeat to match horizon (B, 1, C * S) -> (B, T, C * S) | ||
| x_static = x_static.repeat(1, self.output_chunk_length, 1) | ||
| # repeat to match time dim: (B, 1, C * S) -> (B, SL, C * S) | ||
| x_static = x_static.repeat(1, self.sequence_length, 1) | ||
|
|
||
| for mixing_layer in self.conditional_mixer: | ||
| # conditional mixer layers with static covariates (B, T, 2 * H_S), (B, T, C * S) -> (B, T, H_S) | ||
| # conditional mixer layers with static covariates (B, SL, 2 * H_S), (B, SL, C * S) -> (B, SL, H_S) | ||
| x = mixing_layer(x, x_static=x_static) | ||
|
|
||
| # If we are in the input time dimension, we need to project to the output time dimension. | ||
| # The original paper did not a fc_out layer (as hidden_size == output_dim) | ||
|
eschibli marked this conversation as resolved.
Outdated
|
||
| # (so we needed to decide where to put it) | ||
| # We put the projection first as it as while both operations may be very compressive, | ||
| # we felt it more likely that output_dim << hidden_size than output_chunk_length << input_chunk_length. | ||
| if not self.project_first_layer: | ||
| # (B, SL, H_S) -> (B, H_S, SL) | ||
| x = _time_to_feature(x) | ||
| # (B, H_S, SL) -> (B, H_S, T) | ||
| x = self.fc_hist(x) | ||
| # (B, H_S, T) -> (B, T, H_S) | ||
| x = _time_to_feature(x) | ||
|
|
||
| # linear transformation to generate the forecast (B, T, H_S) -> (B, T, C * N_P) | ||
| x = self.fc_out(x) | ||
| # (B, T, C * N_P) -> (B, T, C, N_P) | ||
|
|
@@ -537,6 +590,7 @@ def __init__( | |
| norm_type: Union[str, nn.Module] = "LayerNorm", | ||
| normalize_before: bool = False, | ||
| use_static_covariates: bool = True, | ||
| project_first_layer: bool = True, | ||
| **kwargs, | ||
| ) -> None: | ||
| """Time-Series Mixer (TSMixer): An All-MLP Architecture for Time Series. | ||
|
|
@@ -591,6 +645,10 @@ def __init__( | |
| `"LayerNormNoBias", "LayerNorm", "TimeBatchNorm2d"`. Otherwise, must be a custom `nn.Module`. | ||
| normalize_before | ||
| Whether to apply layer normalization before or after mixer layer. | ||
| project_first_layer | ||
| Whether to project to the output time dimension at the first layer (default), or at the end of the module. | ||
| Projecting last is recommended if there are no future covariates, while projecting first is recommended if | ||
| there are important future covariates. | ||
| use_static_covariates | ||
| Whether the model should use static covariate information in case the input `series` passed to ``fit()`` | ||
| contain static covariates. If ``True``, and static covariates are available at fitting time, will enforce | ||
|
|
@@ -774,6 +832,7 @@ def encode_year(idx): | |
| self.normalize_before = normalize_before | ||
| self.norm_type = norm_type | ||
| self.hidden_size = hidden_size | ||
| self.project_first_layer = project_first_layer | ||
| self._considers_static_covariates = use_static_covariates | ||
|
|
||
| def _create_model(self, train_sample: MixedCovariatesTrainTensorType) -> nn.Module: | ||
|
|
@@ -825,6 +884,7 @@ def _create_model(self, train_sample: MixedCovariatesTrainTensorType) -> nn.Modu | |
| dropout=self.dropout, | ||
| norm_type=self.norm_type, | ||
| normalize_before=self.normalize_before, | ||
| project_first_layer=self.project_first_layer, | ||
| **self.pl_module_params, | ||
| ) | ||
|
|
||
|
|
||
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.
self.fc_histis not used anymore (I guess replaced by the newencoder_to_decoder?). We should remove one of the two