-
Notifications
You must be signed in to change notification settings - Fork 885
[ENH] Units_v2 Model added
#2165
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 8 commits
930f855
0f4faf4
43dd89d
2d46f32
e9cd22f
c8c81a9
61e4d29
ee65d40
7e13f3b
8900902
5701122
4152064
9efb9df
e17fb97
78b7bd2
a7c0172
08f46b0
49c27f8
349c726
9f192ed
24f57c9
858a146
bc89293
18afcbb
4327f59
1fa26b3
8e7919f
7eb7fce
0dc7a8d
26e8290
013bd15
96c229e
36f2e6a
dccfe8c
3c3b662
e069a29
874e477
0de97fc
038d8c4
30b0aab
aca98b1
81fc45a
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 |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| """ | ||
| UniTS layer abstractions. | ||
| """ | ||
|
|
||
| from pytorch_forecasting.layers._units._units import ( | ||
| _PatchEmbedding, | ||
| _PositionalEncoding, | ||
| _TransformerBlock, | ||
| ) | ||
|
|
||
| __all__ = ["_PatchEmbedding", "_PositionalEncoding", "_TransformerBlock"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| """ | ||
| Core Neural Network Layers for the UniTS architecture. | ||
| """ | ||
|
|
||
| import math | ||
|
|
||
| import torch | ||
| import torch.nn as nn | ||
|
|
||
|
|
||
| class _PatchEmbedding(nn.Module): | ||
|
Member
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. would it make sense to add it to
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. Yes I think it would make complete sense if we'd place it there in a file called _patch_embedding.py, as the |
||
| """ | ||
| Project strided patches of a multivariate time series into d_model space. | ||
|
|
||
| Uses channel-independent patching: each channel's patches are projected | ||
| separately with a shared Linear(patch_len, d_model), then averaged across | ||
| channels to match the UniTS paper's channel-independent approach. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| patch_len : int | ||
| Length of each patch window in time steps. | ||
| stride : int | ||
| Stride between consecutive patches. | ||
| d_model : int | ||
| Output embedding dimension. | ||
| dropout : float | ||
| Dropout probability. | ||
| """ | ||
|
|
||
| def __init__(self, patch_len: int, stride: int, d_model: int, dropout: float = 0.1): | ||
| super().__init__() | ||
| self.patch_len = patch_len | ||
| self.stride = stride | ||
| self.projection = nn.Linear(patch_len, d_model) | ||
| self.drop = nn.Dropout(dropout) | ||
|
|
||
| def forward(self, x: torch.Tensor) -> torch.Tensor: | ||
| """ | ||
| Parameters | ||
| ---------- | ||
| x : torch.Tensor | ||
| Shape (batch, seq_len, n_channels). | ||
|
|
||
| Returns | ||
| ------- | ||
| torch.Tensor | ||
| Shape (batch, num_patches, d_model). | ||
| """ | ||
| patches = x.unfold(dimension=1, size=self.patch_len, step=self.stride) | ||
| B, num_patches, C, P = patches.shape | ||
| patches = patches.permute(0, 2, 1, 3).contiguous().view(B * C, num_patches, P) | ||
| emb = self.drop(self.projection(patches)) | ||
| emb = emb.view(B, C, num_patches, self.projection.out_features) | ||
|
|
||
| # Channel independence: average across channels as per UniTS logic | ||
| return emb.mean(dim=1) | ||
|
|
||
|
|
||
| class _PositionalEncoding(nn.Module): | ||
|
Member
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. it could go to
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. Actually its just a misinterpretation, it is an embedding not an encoder. We already have a PositionalEmbedding class in |
||
| """ | ||
| Sinusoidal positional encoding. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| d_model : int | ||
| Embedding dimension. | ||
| max_len : int | ||
| Maximum sequence length. | ||
| dropout : float | ||
| Dropout probability. | ||
| """ | ||
|
|
||
| def __init__(self, d_model: int, max_len: int = 512, dropout: float = 0.1): | ||
| super().__init__() | ||
| self.drop = nn.Dropout(dropout) | ||
| pe = torch.zeros(max_len, d_model) | ||
| position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) | ||
| half = d_model // 2 | ||
| div_term = torch.exp( | ||
| torch.arange(0, half, dtype=torch.float) * (-math.log(10000.0) / d_model) | ||
| ) | ||
| pe[:, 0::2] = torch.sin(position * div_term[: pe[:, 0::2].size(1)]) | ||
| pe[:, 1::2] = torch.cos(position * div_term[: pe[:, 1::2].size(1)]) | ||
| self.register_buffer("pe", pe.unsqueeze(0)) | ||
|
|
||
| def forward(self, x: torch.Tensor) -> torch.Tensor: | ||
| return self.drop(x + self.pe[:, : x.size(1), :]) | ||
|
|
||
|
|
||
| class _TransformerBlock(nn.Module): | ||
|
Member
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. should we add it to
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. I think creating a new |
||
| """ | ||
| Pre-norm transformer encoder block (MHSA + FFN). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| d_model : int | ||
| Model dimension. | ||
| n_heads : int | ||
| Number of attention heads. | ||
| d_ff : int | ||
| Feed-forward hidden dimension. | ||
| dropout : float | ||
| Dropout probability. | ||
| """ | ||
|
|
||
| def __init__(self, d_model: int, n_heads: int, d_ff: int, dropout: float = 0.1): | ||
| super().__init__() | ||
| self.norm1 = nn.LayerNorm(d_model) | ||
| self.norm2 = nn.LayerNorm(d_model) | ||
| self.attn = nn.MultiheadAttention( | ||
| embed_dim=d_model, num_heads=n_heads, dropout=dropout, batch_first=True | ||
| ) | ||
| self.ff = nn.Sequential( | ||
| nn.Linear(d_model, d_ff), | ||
| nn.GELU(), | ||
| nn.Dropout(dropout), | ||
| nn.Linear(d_ff, d_model), | ||
| nn.Dropout(dropout), | ||
| ) | ||
|
|
||
| def forward(self, x: torch.Tensor) -> torch.Tensor: | ||
| normed = self.norm1(x) | ||
| attn_out, _ = self.attn(normed, normed, normed) | ||
| x = x + attn_out | ||
| x = x + self.ff(self.norm2(x)) | ||
| return x | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| """ | ||
| UniTS: Unified Time Series Model for time series forecasting. | ||
| """ | ||
|
|
||
| from pytorch_forecasting.models.units._units_pkg_v2 import UniTS_pkg_v2 | ||
| from pytorch_forecasting.models.units._units_v2 import UniTS | ||
|
|
||
| __all__ = ["UniTS", "UniTS_pkg_v2"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """ | ||
| Packages container for UniTS model. | ||
| """ | ||
|
|
||
| from pytorch_forecasting.base._base_pkg import Base_pkg | ||
|
|
||
|
|
||
| class UniTS_pkg_v2(Base_pkg): | ||
| """ | ||
| UniTS: Unified Time Series Model. | ||
| Reference: https://arxiv.org/abs/2403.00131 | ||
| """ | ||
|
|
||
| _tags = { | ||
| "info:name": "UniTS", | ||
| "authors": ["Muhammad-Rebaal", "sohamukute"], | ||
| "capability:exogenous": True, | ||
| "capability:multivariate": True, | ||
| "capability:pred_int": False, | ||
| "capability:flexible_history_length": False, | ||
|
Member
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 think some tags are missing here. PLease look at the extension-templates
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. Updated |
||
| } | ||
|
|
||
| @classmethod | ||
| def get_cls(cls): | ||
| from pytorch_forecasting.models.units._units_v2 import UniTS | ||
|
|
||
| return UniTS | ||
|
|
||
| @classmethod | ||
| def get_datamodule_cls(cls): | ||
| from pytorch_forecasting.data._tslib_data_module import TslibDataModule | ||
|
Member
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. the path for the data modules have changed. Please use |
||
|
|
||
| return TslibDataModule | ||
|
Member
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. Is this a
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. Yes, this is a tslib model. However, we are not using the |
||
|
|
||
| @classmethod | ||
| def get_test_train_params(cls): | ||
| """Return testing parameter settings for the trainer. | ||
|
|
||
| Returns | ||
| ------- | ||
| params : dict or list of dict, default = {} | ||
| Parameters to create testing instances of the class. | ||
| Each dict are parameters to construct an "interesting" test instance, i.e., | ||
| ``MyClass(**params)`` or ``MyClass(**params[i])`` creates a valid test | ||
| instance. ``create_test_instance`` uses the first (or only) dictionary in | ||
| ``params``. | ||
| """ | ||
| params = [ | ||
| {}, | ||
| { | ||
|
phoeenniixx marked this conversation as resolved.
|
||
| "patch_len": 8, | ||
| "stride": 4, | ||
|
Member
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. please add some loss functions as well here - Is this model only compatible with point prediction losses, or can it also handle quantile and distribution losses?
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. In the paper the model it is mentioned only about point prediction losses but we can extend it so I extend it to both quantile and distribution losses. |
||
| }, | ||
| { | ||
| "d_model": 32, | ||
| "n_heads": 4, | ||
| "patch_len": 8, | ||
| "stride": 4, | ||
| }, | ||
| { | ||
| "patch_len": 8, | ||
| "stride": 4, | ||
| "datamodule_cfg": {"context_length": 16, "prediction_length": 4}, | ||
| }, | ||
| ] | ||
|
|
||
| base_dm_cfg = {"context_length": 16, "prediction_length": 4} | ||
|
|
||
| for param in params: | ||
| merged = base_dm_cfg.copy() | ||
| merged.update(param.get("datamodule_cfg", {})) | ||
| param["datamodule_cfg"] = merged | ||
|
|
||
| return 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.
what is the use of this folder?
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.
The model file already imports directly from the canonical locations. I'll remove the
_unitslayer folder entirely.