patchfm 2.2.1__tar.gz → 2.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchfm
3
- Version: 2.2.1
3
+ Version: 2.2.5
4
4
  Summary: a Foundation Model for Univariate Time Series Forecasting
5
5
  Author-email: Samy-Melwan Vilhes <samy-melwan.vilhes@insa-rouen.fr>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "patchfm"
3
- version = "2.2.1"
3
+ version = "2.2.5"
4
4
  authors = [
5
5
  { name="Samy-Melwan Vilhes", email="samy-melwan.vilhes@insa-rouen.fr" },
6
6
  ]
@@ -8,6 +8,7 @@ class PatchFMConfig:
8
8
  d_model: int = 2048
9
9
  n_heads: int = 32
10
10
  n_layers_encoder: int = 6
11
+ use_xsa: bool = True
11
12
  quantiles: list[float] = field(default_factory=lambda: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
12
13
 
13
14
  compile: bool = True
@@ -18,12 +18,18 @@ class Forecaster(nn.Module):
18
18
  self.d_model = config["d_model"]
19
19
  self.n_heads = config["n_heads"]
20
20
  self.n_layers_encoder = config["n_layers_encoder"]
21
+ self.use_xsa = config["use_xsa"]
21
22
  self.quantiles = config["quantiles"]
22
23
  self.n_quantiles = len(self.quantiles)
23
24
  self.max_patches = self.max_seq_len // self.patch_len
24
25
 
25
- print("Loading base model from HuggingFace Hub...")
26
- base_model = PatchFM.from_pretrained("vilhess/PatchFM-CausalRevIN-asinh")
26
+ if not self.use_xsa:
27
+ print("Loading base model from HuggingFace Hub...")
28
+ base_model = PatchFM.from_pretrained("vilhess/PatchFM")
29
+ else:
30
+ print("Loading XSA model from HuggingFace Hub...")
31
+ base_model = PatchFM.from_pretrained("vilhess/PatchFM-XSA")
32
+
27
33
  self._init_from_base(base_model)
28
34
 
29
35
  self.eval()
@@ -46,7 +52,8 @@ class Forecaster(nn.Module):
46
52
  self.transformer_encoder = TransformerEncoder(
47
53
  d_model=self.d_model,
48
54
  n_heads=self.n_heads,
49
- n_layers=self.n_layers_encoder
55
+ n_layers=self.n_layers_encoder,
56
+ use_xsa=self.use_xsa
50
57
  )
51
58
  self.proj_output = ResidualBlock(
52
59
  in_dim=self.d_model,
@@ -69,15 +76,7 @@ class Forecaster(nn.Module):
69
76
  # Default horizon = patch_len
70
77
  forecast_horizon = forecast_horizon or self.patch_len
71
78
 
72
- x = self.converter.convert(x)
73
- assert x.ndim in (1, 2), f"Input dimension must be 1D (time) or 2D (batch, time), got {x.ndim}D."
74
-
75
- batch_dim=True
76
- if x.ndim != 2:
77
- x = x.unsqueeze(0)
78
- batch_dim=False
79
79
  bs, ws = x.size()
80
-
81
80
  x = x.to(self.device)
82
81
 
83
82
  if ws > self.max_seq_len:
@@ -153,26 +152,39 @@ class Forecaster(nn.Module):
153
152
  pred_median = torch.zeros_like(pred_median)
154
153
  pred_quantiles = torch.zeros_like(pred_quantiles)
155
154
 
156
- if not batch_dim:
157
- pred_median = pred_median.squeeze(0)
158
- pred_quantiles = pred_quantiles.squeeze(0)
159
-
160
- pred_median, pred_quantiles = self.converter.deconvert(pred_median, pred_quantiles)
161
155
  return pred_median, pred_quantiles
162
156
 
163
157
  def __call__(self, context: torch.Tensor, forecast_horizon: int | None = None, quantiles: list[float] | None = None, flip_equivariance: bool = False) -> torch.Tensor:
158
+
159
+ x = self.converter.convert(context)
160
+ assert x.ndim in (1, 2), f"Input dimension must be 1D (time) or 2D (batch, time), got {x.ndim}D."
161
+
162
+ batch_dim=True
163
+ if x.ndim != 2:
164
+ x = x.unsqueeze(0)
165
+ batch_dim=False
166
+
167
+ bs, ws = x.size()
168
+
164
169
  if flip_equivariance:
165
170
  print("Flip equivariance enabled: forecast = (f(x) - f(-x)) / 2. This requires multiplying by 2 the batch size (Reverso: Efficient Time Series Foundation Models for Zero-shot Forecasting).")
166
171
  bs = context.size(0)
167
- context_flipped = -context
168
- concat_context = torch.cat([context, context_flipped], dim=0)
169
- pred_median_full, pred_quantiles_full = self.auto_regressive_quantile_decoding(concat_context, forecast_horizon, quantiles)
172
+ x_flipped = -x
173
+ concat_x = torch.cat([x, x_flipped], dim=0)
174
+ pred_median_full, pred_quantiles_full = self.auto_regressive_quantile_decoding(concat_x, forecast_horizon, quantiles)
170
175
  pred_median, pred_quantiles = pred_median_full[:bs], pred_quantiles_full[:bs]
171
176
  pred_median2, pred_quantiles2 = pred_median_full[bs:], pred_quantiles_full[bs:]
172
177
  pred_median = (pred_median - pred_median2) / 2
173
178
  pred_quantiles = (pred_quantiles - flip_last_dim(pred_quantiles2)) / 2
174
179
  else:
175
- pred_median, pred_quantiles = self.auto_regressive_quantile_decoding(context, forecast_horizon, quantiles)
180
+ pred_median, pred_quantiles = self.auto_regressive_quantile_decoding(x, forecast_horizon, quantiles)
181
+
182
+ if not batch_dim:
183
+ pred_median = pred_median.squeeze(0)
184
+ pred_quantiles = pred_quantiles.squeeze(0)
185
+
186
+ pred_median, pred_quantiles = self.converter.deconvert(pred_median, pred_quantiles)
187
+
176
188
  return pred_median, pred_quantiles
177
189
 
178
190
  def clear_cache(self):
@@ -185,7 +185,7 @@ class ResidualBlock(nn.Module):
185
185
  return out
186
186
 
187
187
  class MultiHeadAttention(nn.Module):
188
- def __init__(self, d_model, n_heads, last=False):
188
+ def __init__(self, d_model, n_heads, last=False, use_xsa=False):
189
189
  super().__init__()
190
190
  assert d_model%n_heads==0, f"d_model ({d_model}) must be divisible by n_heads ({n_heads})"
191
191
 
@@ -204,7 +204,8 @@ class MultiHeadAttention(nn.Module):
204
204
  self.v_cache = None
205
205
 
206
206
  self.last = last
207
-
207
+ self.use_xsa = use_xsa
208
+
208
209
  def forward(self, q):
209
210
  bs, context, dim = q.size()
210
211
  offset = 0
@@ -239,6 +240,13 @@ class MultiHeadAttention(nn.Module):
239
240
 
240
241
  values = nn.functional.scaled_dot_product_attention(q, k, v, is_causal=is_causal)
241
242
 
243
+ if self.use_xsa:
244
+ if v.size(2) != values.size(2):
245
+ v = v[:, :, -values.size(2) :, :]
246
+
247
+ vn = torch.nn.functional.normalize(v, dim=-1)
248
+ values = values - (values * vn).sum(dim=-1, keepdim=True) * vn
249
+
242
250
  values = values.transpose(1, 2).reshape(bs, -1, dim)
243
251
  values = self.out_proj(values)
244
252
  return values
@@ -267,10 +275,10 @@ class FeedForward(nn.Module):
267
275
 
268
276
 
269
277
  class TransformerEncoderLayer(nn.Module):
270
- def __init__(self, d_model, n_heads, last=False):
278
+ def __init__(self, d_model, n_heads, last=False, use_xsa=False):
271
279
  super().__init__()
272
280
  self.ln1 = nn.LayerNorm(d_model)
273
- self.attn = MultiHeadAttention(d_model=d_model, n_heads=n_heads, last=last)
281
+ self.attn = MultiHeadAttention(d_model=d_model, n_heads=n_heads, last=last, use_xsa=use_xsa)
274
282
  self.ln2 = nn.LayerNorm(d_model)
275
283
  self.ff = FeedForward(d_model=d_model)
276
284
 
@@ -281,15 +289,15 @@ class TransformerEncoderLayer(nn.Module):
281
289
  return out
282
290
 
283
291
  class TransformerEncoder(nn.Module):
284
- def __init__(self, d_model, n_heads, n_layers):
292
+ def __init__(self, d_model, n_heads, n_layers, use_xsa=False):
285
293
  super().__init__()
286
294
  self.layers = nn.ModuleList(
287
295
  [
288
- TransformerEncoderLayer(d_model=d_model, n_heads=n_heads)
296
+ TransformerEncoderLayer(d_model=d_model, n_heads=n_heads, use_xsa=use_xsa)
289
297
  for _ in range(n_layers-1)
290
298
  ]
291
299
  )
292
- self.layers.append(TransformerEncoderLayer(d_model=d_model, n_heads=n_heads, last=True))
300
+ self.layers.append(TransformerEncoderLayer(d_model=d_model, n_heads=n_heads, last=True, use_xsa=use_xsa))
293
301
  self.norm = nn.LayerNorm(d_model)
294
302
 
295
303
  def forward(self, x):
@@ -306,6 +314,7 @@ class PatchFM(nn.Module, PyTorchModelHubMixin):
306
314
  self.d_model = config["d_model"]
307
315
  self.n_heads = config["n_heads"]
308
316
  self.n_layers_encoder = config["n_layers_encoder"]
317
+ self.use_xsa = config["use_xsa"]
309
318
  self.quantiles = config["quantiles"]
310
319
  self.n_quantiles = len(self.quantiles)
311
320
 
@@ -319,7 +328,8 @@ class PatchFM(nn.Module, PyTorchModelHubMixin):
319
328
  self.transformer_encoder = TransformerEncoder(
320
329
  d_model=self.d_model,
321
330
  n_heads=self.n_heads,
322
- n_layers=self.n_layers_encoder
331
+ n_layers=self.n_layers_encoder,
332
+ use_xsa=self.use_xsa
323
333
  )
324
334
  self.proj_output = ResidualBlock(
325
335
  in_dim=self.d_model,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchfm
3
- Version: 2.2.1
3
+ Version: 2.2.5
4
4
  Summary: a Foundation Model for Univariate Time Series Forecasting
5
5
  Author-email: Samy-Melwan Vilhes <samy-melwan.vilhes@insa-rouen.fr>
6
6
  License: MIT License
File without changes
File without changes
File without changes
File without changes