patchfm 3.2.0__tar.gz → 3.3.0__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: 3.2.0
3
+ Version: 3.3.0
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 = "3.2.0"
3
+ version = "3.3.0"
4
4
  authors = [
5
5
  { name="Samy-Melwan Vilhes", email="samy-melwan.vilhes@insa-rouen.fr" },
6
6
  ]
@@ -5,13 +5,13 @@ from dataclasses import asdict, dataclass, field
5
5
  class PatchFMConfig:
6
6
  max_seq_len: int = 1024
7
7
  patch_len: int = 32
8
- d_model: int = 2048
9
- n_heads: int = 32
10
- n_layers_encoder: int = 6
8
+ d_model: int = 1024
9
+ n_heads: int = 16
10
+ n_layers_encoder: int = 20
11
11
  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
12
 
13
- compile: bool = True
14
- full_leakage: bool = True
13
+ compile: bool = False
14
+ full_leakage: bool = False
15
15
 
16
16
  def __getitem__(self, key):
17
17
  return getattr(self, key)
@@ -4,7 +4,8 @@ import torch.nn as nn
4
4
  from einops import rearrange
5
5
 
6
6
  from patchfm.inference.modules import (CausalRevIN, PatchFM, ResidualBlock,
7
- SeqTypeConverter, TransformerEncoder)
7
+ SeqTypeConverter, TransformerEncoder,
8
+ quantile_widths, monotone_quantiles)
8
9
 
9
10
 
10
11
  # --- Forecaster Model ---
@@ -22,6 +23,9 @@ class Forecaster(nn.Module):
22
23
  self.n_quantiles = len(self.quantiles)
23
24
  self.max_patches = self.max_seq_len // self.patch_len
24
25
 
26
+ widths, self.median_idx = quantile_widths(self.quantiles)
27
+ self.register_buffer("quantile_widths", widths, persistent=False)
28
+
25
29
  if config["full_leakage"]:
26
30
  print("Loading full leakage model from HuggingFace Hub...")
27
31
  model_name = "vilhess/PatchFM-Leakage"
@@ -48,7 +52,7 @@ class Forecaster(nn.Module):
48
52
  self.revin = CausalRevIN()
49
53
  self.proj_embedding = ResidualBlock(
50
54
  in_dim=self.patch_len,
51
- hid_dim=4 * self.d_model,
55
+ hid_dim=self.d_model,
52
56
  out_dim=self.d_model
53
57
  )
54
58
  self.transformer_encoder = TransformerEncoder(
@@ -103,6 +107,19 @@ class Forecaster(nn.Module):
103
107
  x = x[:, -1:, :] # Keep only the last patch for autoregressive forecasting
104
108
 
105
109
  forecasting = self.proj_output(x)
110
+ forecasting = rearrange(
111
+ forecasting,
112
+ "b 1 (pl q) -> b 1 pl q",
113
+ pl=self.patch_len,
114
+ q=self.n_quantiles,
115
+ )
116
+ forecasting = monotone_quantiles(
117
+ forecasting, self.quantile_widths, self.median_idx, width_factor=2.0
118
+ )
119
+ forecasting = rearrange(
120
+ forecasting,
121
+ "b 1 pl q -> b 1 (pl q)",
122
+ )
106
123
  forecasting = self.revin(forecasting, mode="denorm")
107
124
 
108
125
  # Reshape to (bs, patch_num, patch_len, n_quantiles)
@@ -122,6 +139,19 @@ class Forecaster(nn.Module):
122
139
  x = self.transformer_encoder(x)
123
140
  x = x[:, -1:, :] # Keep only the last patch for autoregressive forecasting
124
141
  forecasting = self.proj_output(x)
142
+ forecasting = rearrange(
143
+ forecasting,
144
+ "b 1 (pl q) -> b 1 pl q",
145
+ pl=self.patch_len,
146
+ q=self.n_quantiles,
147
+ )
148
+ forecasting = monotone_quantiles(
149
+ forecasting, self.quantile_widths, self.median_idx, width_factor=2.0
150
+ )
151
+ forecasting = rearrange(
152
+ forecasting,
153
+ "b 1 pl q -> b 1 (pl q)",
154
+ )
125
155
  forecasting = self.revin(forecasting, mode="denorm")
126
156
 
127
157
  # Reshape to (bs*n_quantiles, patch_num, patch_len, n_quantiles)
@@ -87,9 +87,8 @@ def nanstd(o, dim, keepdim=False):
87
87
  return std
88
88
 
89
89
  class CausalRevIN(nn.Module):
90
- def __init__(self, eps=1e-5):
90
+ def __init__(self):
91
91
  super().__init__()
92
- self.eps = eps
93
92
  self.cached_mean = None
94
93
  self.cached_std = None
95
94
 
@@ -159,7 +158,7 @@ class CausalRevIN(nn.Module):
159
158
  self.cached_cumsum_x2 = cumsum_x2[:, -1:, :]
160
159
 
161
160
  var = (cumsum_x2 - 2 * mean * cumsum_x + counts * mean**2) / counts
162
- std = torch.sqrt(var + 1e-5)
161
+ std = torch.sqrt(var.clamp_min(0)).clamp_min(1e-3)
163
162
 
164
163
  return mean, std
165
164
 
@@ -205,9 +204,10 @@ class MultiHeadAttention(nn.Module):
205
204
  super().__init__()
206
205
  assert d_model%n_heads==0, f"d_model ({d_model}) must be divisible by n_heads ({n_heads})"
207
206
 
208
- self.WQ = nn.Linear(d_model, d_model)
209
- self.WK = nn.Linear(d_model, d_model)
210
- self.WV = nn.Linear(d_model, d_model)
207
+ self.WQ = nn.Linear(d_model, d_model, bias=False)
208
+ self.WK = nn.Linear(d_model, d_model, bias=False)
209
+ self.WV = nn.Linear(d_model, d_model, bias=False)
210
+ self.gate = nn.Linear(d_model, d_model, bias=False)
211
211
 
212
212
  self.out_proj = nn.Linear(d_model, d_model)
213
213
 
@@ -238,6 +238,8 @@ class MultiHeadAttention(nn.Module):
238
238
  is_causal = False
239
239
  offset += (context - 1)
240
240
 
241
+ gated = self.gate(q)
242
+
241
243
  q = self.WQ(q).reshape(bs, -1, self.n_heads, self.head_dim).transpose(1, 2)
242
244
  k = self.WK(k).reshape(bs, -1, self.n_heads, self.head_dim).transpose(1, 2)
243
245
  v = self.WV(v).reshape(bs, -1, self.n_heads, self.head_dim).transpose(1, 2)
@@ -261,9 +263,10 @@ class MultiHeadAttention(nn.Module):
261
263
  k = self.k_norm(k)
262
264
  q = self.per_dim_scale(q)
263
265
 
264
- values = nn.functional.scaled_dot_product_attention(q, k, v, is_causal=is_causal)
266
+ values = nn.functional.scaled_dot_product_attention(q, k, v, is_causal=is_causal, scale=1.0)
265
267
 
266
268
  values = values.transpose(1, 2).reshape(bs, -1, dim)
269
+ values = gated.sigmoid() * values
267
270
  values = self.out_proj(values)
268
271
  return values
269
272
 
@@ -293,9 +296,9 @@ class FeedForward(nn.Module):
293
296
  class TransformerEncoderLayer(nn.Module):
294
297
  def __init__(self, d_model, n_heads, last=False):
295
298
  super().__init__()
296
- self.ln1 = nn.LayerNorm(d_model)
299
+ self.ln1 = nn.RMSNorm(d_model)
297
300
  self.attn = MultiHeadAttention(d_model=d_model, n_heads=n_heads, last=last)
298
- self.ln2 = nn.LayerNorm(d_model)
301
+ self.ln2 = nn.RMSNorm(d_model)
299
302
  self.ff = FeedForward(d_model=d_model)
300
303
 
301
304
  def forward(self, x):
@@ -314,12 +317,34 @@ class TransformerEncoder(nn.Module):
314
317
  ]
315
318
  )
316
319
  self.layers.append(TransformerEncoderLayer(d_model=d_model, n_heads=n_heads, last=True))
317
- self.norm = nn.LayerNorm(d_model)
320
+ self.norm = nn.RMSNorm(d_model)
318
321
 
319
322
  def forward(self, x):
320
323
  for layer in self.layers:
321
324
  x = layer(x)
322
325
  return self.norm(x)
326
+
327
+ def quantile_widths(quantiles):
328
+ m = quantiles.index(0.5)
329
+ widths = [0.0] * len(quantiles)
330
+ for i in range(m + 1, len(quantiles)):
331
+ widths[i] = quantiles[i] - quantiles[i - 1]
332
+ for i in range(m):
333
+ widths[i] = quantiles[i + 1] - quantiles[i]
334
+ return torch.tensor(widths), m
335
+
336
+
337
+ def monotone_quantiles(raw, widths, m, width_factor=2.0):
338
+ inc = width_factor * widths.to(raw.dtype) * torch.nn.functional.softplus(raw)
339
+ median = raw[..., m : m + 1]
340
+ up = torch.cumsum(inc[..., m + 1 :], dim=-1)
341
+ down = torch.flip(
342
+ torch.cumsum(torch.flip(inc[..., :m], dims=[-1]), dim=-1), dims=[-1]
343
+ )
344
+ big = torch.finfo(raw.dtype).max
345
+ return torch.cat([median - down, median, median + up], dim=-1).clamp(
346
+ min=-big, max=big
347
+ )
323
348
 
324
349
  class PatchFM(nn.Module, PyTorchModelHubMixin):
325
350
  def __init__(self, config):
@@ -337,7 +362,7 @@ class PatchFM(nn.Module, PyTorchModelHubMixin):
337
362
  self.revin = CausalRevIN()
338
363
  self.proj_embedding = ResidualBlock(
339
364
  in_dim=self.patch_len,
340
- hid_dim=4 * self.d_model,
365
+ hid_dim=self.d_model,
341
366
  out_dim=self.d_model
342
367
  )
343
368
  self.transformer_encoder = TransformerEncoder(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchfm
3
- Version: 3.2.0
3
+ Version: 3.3.0
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