dfm-python 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl
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.
- dfm_python/encoder/vae.py +16 -2
- dfm_python/models/ddfm.py +67 -10
- dfm_python/models/dfm.py +163 -55
- dfm_python/models/mcmc.py +5 -4
- {dfm_python-0.5.2.dist-info → dfm_python-0.5.3.dist-info}/METADATA +2 -2
- {dfm_python-0.5.2.dist-info → dfm_python-0.5.3.dist-info}/RECORD +8 -8
- {dfm_python-0.5.2.dist-info → dfm_python-0.5.3.dist-info}/WHEEL +0 -0
- {dfm_python-0.5.2.dist-info → dfm_python-0.5.3.dist-info}/licenses/LICENSE +0 -0
dfm_python/encoder/vae.py
CHANGED
|
@@ -82,13 +82,27 @@ if _has_torch:
|
|
|
82
82
|
# Build layers
|
|
83
83
|
prev_dim = input_dim
|
|
84
84
|
for hidden_dim in hidden_dims:
|
|
85
|
-
|
|
85
|
+
layer = nn.Linear(prev_dim, hidden_dim)
|
|
86
|
+
# Initialize weights using Xavier/Kaiming initialization for better training stability
|
|
87
|
+
# Use Kaiming for ReLU, Xavier for tanh/sigmoid
|
|
88
|
+
if activation == 'relu':
|
|
89
|
+
nn.init.kaiming_normal_(layer.weight, mode='fan_in', nonlinearity='relu')
|
|
90
|
+
else:
|
|
91
|
+
nn.init.xavier_normal_(layer.weight, gain=1.0)
|
|
92
|
+
# Initialize bias to small values
|
|
93
|
+
if layer.bias is not None:
|
|
94
|
+
nn.init.constant_(layer.bias, 0.0)
|
|
95
|
+
self.layers.append(layer)
|
|
86
96
|
if use_batch_norm:
|
|
87
97
|
self.batch_norms.append(nn.BatchNorm1d(hidden_dim))
|
|
88
98
|
prev_dim = hidden_dim
|
|
89
99
|
|
|
90
|
-
# Output layer (no activation
|
|
100
|
+
# Output layer (linear, no activation)
|
|
101
|
+
# Use smaller initialization for output layer to prevent large initial factors
|
|
91
102
|
self.output_layer = nn.Linear(prev_dim, output_dim)
|
|
103
|
+
nn.init.xavier_normal_(self.output_layer.weight, gain=0.1) # Smaller gain for output
|
|
104
|
+
if self.output_layer.bias is not None:
|
|
105
|
+
nn.init.constant_(self.output_layer.bias, 0.0)
|
|
92
106
|
|
|
93
107
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
94
108
|
"""Forward pass through encoder.
|
dfm_python/models/ddfm.py
CHANGED
|
@@ -308,6 +308,10 @@ class DDFM(BaseFactorModel):
|
|
|
308
308
|
decay_learning_rate: bool = True,
|
|
309
309
|
min_obs_pretrain: int = 50,
|
|
310
310
|
mult_epoch_pretrain: int = 1,
|
|
311
|
+
loss_function: str = 'mse',
|
|
312
|
+
huber_delta: float = 1.0,
|
|
313
|
+
weight_decay: float = 0.0,
|
|
314
|
+
grad_clip_val: float = 1.0,
|
|
311
315
|
**kwargs
|
|
312
316
|
):
|
|
313
317
|
"""Initialize DDFM instance.
|
|
@@ -349,6 +353,19 @@ class DDFM(BaseFactorModel):
|
|
|
349
353
|
mult_epoch_pretrain : int, default 1
|
|
350
354
|
Multiplier for number of epochs during pre-training
|
|
351
355
|
Display progress every 'disp' iterations
|
|
356
|
+
loss_function : str, default 'mse'
|
|
357
|
+
Loss function for training ('mse', 'huber').
|
|
358
|
+
'mse': Mean squared error (default, matches original DDFM)
|
|
359
|
+
'huber': Huber loss (more robust to outliers)
|
|
360
|
+
huber_delta : float, default 1.0
|
|
361
|
+
Delta parameter for Huber loss (only used if loss_function='huber').
|
|
362
|
+
Controls the transition point between quadratic and linear regions.
|
|
363
|
+
weight_decay : float, default 0.0
|
|
364
|
+
Weight decay (L2 regularization) for optimizer. Helps prevent overfitting to linear features.
|
|
365
|
+
Recommended: 1e-5 to 1e-3 for deeper encoders or when encoder collapses to linear behavior.
|
|
366
|
+
grad_clip_val : float, default 1.0
|
|
367
|
+
Maximum gradient norm for gradient clipping. Prevents training instability.
|
|
368
|
+
Set to 0.0 to disable gradient clipping.
|
|
352
369
|
seed : int, optional
|
|
353
370
|
Random seed for reproducibility
|
|
354
371
|
**kwargs
|
|
@@ -383,6 +400,22 @@ class DDFM(BaseFactorModel):
|
|
|
383
400
|
self.decay_learning_rate = decay_learning_rate
|
|
384
401
|
self.min_obs_pretrain = min_obs_pretrain
|
|
385
402
|
self.mult_epoch_pretrain = mult_epoch_pretrain
|
|
403
|
+
self.loss_function = loss_function.lower()
|
|
404
|
+
self.huber_delta = huber_delta
|
|
405
|
+
self.weight_decay = weight_decay
|
|
406
|
+
self.grad_clip_val = grad_clip_val
|
|
407
|
+
|
|
408
|
+
# Validate loss function
|
|
409
|
+
if self.loss_function not in ['mse', 'huber']:
|
|
410
|
+
raise ValueError(
|
|
411
|
+
f"DDFM initialization failed: loss_function must be 'mse' or 'huber', got '{loss_function}'"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Validate gradient clipping value
|
|
415
|
+
if self.grad_clip_val < 0.0:
|
|
416
|
+
raise ValueError(
|
|
417
|
+
f"DDFM initialization failed: grad_clip_val must be >= 0.0, got {grad_clip_val}"
|
|
418
|
+
)
|
|
386
419
|
|
|
387
420
|
# Determine number of factors
|
|
388
421
|
# DDFM does not use block structure - num_factors is specified directly
|
|
@@ -586,7 +619,9 @@ class DDFM(BaseFactorModel):
|
|
|
586
619
|
|
|
587
620
|
# Clip input data to prevent extreme values that cause NaN
|
|
588
621
|
# Clip to reasonable range: -10 to 10 standard deviations
|
|
589
|
-
|
|
622
|
+
# For deeper networks, use slightly tighter clipping to improve stability
|
|
623
|
+
clip_range = 8.0 if len(self.encoder_layers) > 2 else 10.0
|
|
624
|
+
data_clipped = torch.clamp(data, min=-clip_range, max=clip_range)
|
|
590
625
|
|
|
591
626
|
# Forward pass
|
|
592
627
|
reconstructed = self.forward(data_clipped)
|
|
@@ -603,8 +638,23 @@ class DDFM(BaseFactorModel):
|
|
|
603
638
|
# Compute loss with missing data masking
|
|
604
639
|
mask = torch.isfinite(target)
|
|
605
640
|
target_clean = torch.where(mask, target, torch.zeros_like(target))
|
|
606
|
-
|
|
607
|
-
|
|
641
|
+
|
|
642
|
+
# Use specified loss function
|
|
643
|
+
if self.loss_function == 'huber':
|
|
644
|
+
# Huber loss: more robust to outliers
|
|
645
|
+
# L_delta(a) = 0.5 * a^2 if |a| <= delta, else delta * (|a| - 0.5 * delta)
|
|
646
|
+
diff = target_clean - reconstructed
|
|
647
|
+
abs_diff = torch.abs(diff)
|
|
648
|
+
huber_loss = torch.where(
|
|
649
|
+
abs_diff <= self.huber_delta,
|
|
650
|
+
0.5 * diff ** 2,
|
|
651
|
+
self.huber_delta * (abs_diff - 0.5 * self.huber_delta)
|
|
652
|
+
)
|
|
653
|
+
loss = torch.sum(huber_loss * mask) / (torch.sum(mask) + 1e-8)
|
|
654
|
+
else:
|
|
655
|
+
# MSE loss (default)
|
|
656
|
+
squared_diff = (target_clean - reconstructed) ** 2
|
|
657
|
+
loss = torch.sum(squared_diff * mask) / (torch.sum(mask) + 1e-8)
|
|
608
658
|
|
|
609
659
|
# Handle NaN/Inf in loss
|
|
610
660
|
if not torch.isfinite(loss):
|
|
@@ -614,6 +664,9 @@ class DDFM(BaseFactorModel):
|
|
|
614
664
|
# Log metrics
|
|
615
665
|
self.log('train_loss', loss, on_step=True, on_epoch=True, prog_bar=True)
|
|
616
666
|
|
|
667
|
+
# Note: Gradient clipping is handled automatically by Lightning trainer if gradient_clip_val is set
|
|
668
|
+
# The grad_clip_val parameter is used in pre_train() and MCMC training for manual training loops
|
|
669
|
+
|
|
617
670
|
return loss
|
|
618
671
|
|
|
619
672
|
|
|
@@ -697,7 +750,8 @@ class DDFM(BaseFactorModel):
|
|
|
697
750
|
|
|
698
751
|
optimizer = torch.optim.Adam(
|
|
699
752
|
list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
|
700
|
-
lr=self.learning_rate
|
|
753
|
+
lr=self.learning_rate,
|
|
754
|
+
weight_decay=self.weight_decay
|
|
701
755
|
)
|
|
702
756
|
|
|
703
757
|
if self.decay_learning_rate:
|
|
@@ -750,7 +804,8 @@ class DDFM(BaseFactorModel):
|
|
|
750
804
|
|
|
751
805
|
optimizer = torch.optim.Adam(
|
|
752
806
|
list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
|
753
|
-
lr=lr
|
|
807
|
+
lr=lr,
|
|
808
|
+
weight_decay=self.weight_decay
|
|
754
809
|
)
|
|
755
810
|
|
|
756
811
|
return optimizer
|
|
@@ -842,7 +897,8 @@ class DDFM(BaseFactorModel):
|
|
|
842
897
|
# Create optimizer for pre-training
|
|
843
898
|
optimizer = torch.optim.Adam(
|
|
844
899
|
list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
|
845
|
-
lr=self.learning_rate
|
|
900
|
+
lr=self.learning_rate,
|
|
901
|
+
weight_decay=self.weight_decay
|
|
846
902
|
)
|
|
847
903
|
|
|
848
904
|
# Pre-train for epochs * mult_epoch_pretrain
|
|
@@ -889,10 +945,11 @@ class DDFM(BaseFactorModel):
|
|
|
889
945
|
loss.backward()
|
|
890
946
|
|
|
891
947
|
# Gradient clipping for stability
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
948
|
+
if self.grad_clip_val > 0.0:
|
|
949
|
+
torch.nn.utils.clip_grad_norm_(
|
|
950
|
+
list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
|
951
|
+
max_norm=self.grad_clip_val
|
|
952
|
+
)
|
|
896
953
|
|
|
897
954
|
optimizer.step()
|
|
898
955
|
|
dfm_python/models/dfm.py
CHANGED
|
@@ -23,11 +23,15 @@ from ..config import (
|
|
|
23
23
|
MergedConfigSource,
|
|
24
24
|
)
|
|
25
25
|
from ..config.results import DFMResult, FitParams
|
|
26
|
+
from ..config.utils import get_agg_structure, get_tent_weights, FREQUENCY_HIERARCHY, TENT_WEIGHTS_LOOKUP
|
|
26
27
|
from ..logger import get_logger
|
|
27
28
|
from ..ssm.em import EMAlgorithm, EMStepParams
|
|
28
29
|
from ..ssm.kalman import KalmanFilter
|
|
29
30
|
from .base import BaseFactorModel
|
|
30
31
|
|
|
32
|
+
# Frequency to integer mapping for tensor conversion
|
|
33
|
+
_FREQ_TO_INT = {'d': 1, 'w': 2, 'm': 3, 'q': 4, 'sa': 5, 'a': 6}
|
|
34
|
+
|
|
31
35
|
if TYPE_CHECKING:
|
|
32
36
|
from omegaconf import DictConfig
|
|
33
37
|
from ..lightning import DFMDataModule
|
|
@@ -90,6 +94,7 @@ class DFM(BaseFactorModel):
|
|
|
90
94
|
nan_method: int = 2,
|
|
91
95
|
nan_k: int = 3,
|
|
92
96
|
tent_weights_dict: Optional[dict] = None,
|
|
97
|
+
mixed_freq: bool = False,
|
|
93
98
|
**kwargs
|
|
94
99
|
):
|
|
95
100
|
"""Initialize DFM instance.
|
|
@@ -108,6 +113,12 @@ class DFM(BaseFactorModel):
|
|
|
108
113
|
Missing data handling method
|
|
109
114
|
nan_k : int, default 3
|
|
110
115
|
Spline interpolation order
|
|
116
|
+
tent_weights_dict : dict, optional
|
|
117
|
+
[DEPRECATED] Optional tent weights to control aggregation. Use `mixed_freq` parameter instead.
|
|
118
|
+
Kept for backward compatibility only.
|
|
119
|
+
mixed_freq : bool, default False
|
|
120
|
+
If True, use tent kernels for mixed-frequency data. If False, treat all series as clock frequency.
|
|
121
|
+
When True, raises ValueError if any frequency pair is not in TENT_WEIGHTS_LOOKUP.
|
|
111
122
|
**kwargs
|
|
112
123
|
Additional arguments passed to BaseFactorModel
|
|
113
124
|
"""
|
|
@@ -120,8 +131,20 @@ class DFM(BaseFactorModel):
|
|
|
120
131
|
self.max_iter = max_iter
|
|
121
132
|
self.nan_method = nan_method
|
|
122
133
|
self.nan_k = nan_k
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
self.mixed_freq = mixed_freq
|
|
135
|
+
|
|
136
|
+
# Legacy tent_weights_dict parameter (deprecated - use mixed_freq instead)
|
|
137
|
+
# Only used when mixed_freq=False for backward compatibility
|
|
138
|
+
self._legacy_tent_weights_dict = tent_weights_dict
|
|
139
|
+
|
|
140
|
+
# Mixed frequency parameters (set during initialize_from_data)
|
|
141
|
+
self._em_R_mat = None
|
|
142
|
+
self._em_q = None
|
|
143
|
+
self._em_nQ = 0
|
|
144
|
+
self._em_tent_weights_dict = None
|
|
145
|
+
self._em_frequencies = None
|
|
146
|
+
self._em_i_idio = None
|
|
147
|
+
self._em_idio_chain_lengths = None
|
|
125
148
|
|
|
126
149
|
# Determine number of factors
|
|
127
150
|
if num_factors is None:
|
|
@@ -181,6 +204,53 @@ class DFM(BaseFactorModel):
|
|
|
181
204
|
# Parameters will be initialized during fit_em() or first training step
|
|
182
205
|
pass
|
|
183
206
|
|
|
207
|
+
def _create_em_step_params(
|
|
208
|
+
self,
|
|
209
|
+
y: torch.Tensor,
|
|
210
|
+
device: torch.device,
|
|
211
|
+
dtype: torch.dtype
|
|
212
|
+
) -> EMStepParams:
|
|
213
|
+
"""Create EM step parameters using stored mixed frequency parameters.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
y : torch.Tensor
|
|
218
|
+
Data tensor (N x T)
|
|
219
|
+
device : torch.device
|
|
220
|
+
Device for tensors
|
|
221
|
+
dtype : torch.dtype
|
|
222
|
+
Data type for tensors
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
EMStepParams
|
|
227
|
+
EM step parameters
|
|
228
|
+
"""
|
|
229
|
+
clock = getattr(self.config, 'clock', 'm')
|
|
230
|
+
N = y.shape[0]
|
|
231
|
+
|
|
232
|
+
return EMStepParams(
|
|
233
|
+
y=y,
|
|
234
|
+
A=self.A,
|
|
235
|
+
C=self.C,
|
|
236
|
+
Q=self.Q,
|
|
237
|
+
R=self.R,
|
|
238
|
+
Z_0=self.Z_0,
|
|
239
|
+
V_0=self.V_0,
|
|
240
|
+
r=self.r.to(device),
|
|
241
|
+
p=self.p,
|
|
242
|
+
R_mat=self._em_R_mat,
|
|
243
|
+
q=self._em_q,
|
|
244
|
+
nQ=self._em_nQ,
|
|
245
|
+
i_idio=self._em_i_idio if self._em_i_idio is not None else torch.ones(N, device=device, dtype=dtype),
|
|
246
|
+
blocks=self.blocks.to(device),
|
|
247
|
+
tent_weights_dict=self._em_tent_weights_dict,
|
|
248
|
+
clock=clock,
|
|
249
|
+
frequencies=self._em_frequencies,
|
|
250
|
+
idio_chain_lengths=self._em_idio_chain_lengths if self._em_idio_chain_lengths is not None else torch.zeros(N, device=device, dtype=dtype),
|
|
251
|
+
config=self.config
|
|
252
|
+
)
|
|
253
|
+
|
|
184
254
|
def initialize_from_data(self, X: torch.Tensor) -> None:
|
|
185
255
|
"""Initialize parameters from data using PCA and OLS.
|
|
186
256
|
|
|
@@ -190,22 +260,100 @@ class DFM(BaseFactorModel):
|
|
|
190
260
|
Standardized data (T x N)
|
|
191
261
|
"""
|
|
192
262
|
opt_nan = {'method': self.nan_method, 'k': self.nan_k}
|
|
263
|
+
clock = getattr(self.config, 'clock', 'm')
|
|
193
264
|
|
|
194
|
-
#
|
|
265
|
+
# Handle mixed_freq parameter
|
|
266
|
+
if self.mixed_freq:
|
|
267
|
+
# Use tent kernels for mixed-frequency data
|
|
268
|
+
agg_structure = get_agg_structure(self.config, clock=clock)
|
|
269
|
+
|
|
270
|
+
# Validate that all required frequency pairs are in TENT_WEIGHTS_LOOKUP
|
|
271
|
+
frequencies_list = [s.frequency for s in self.config.series]
|
|
272
|
+
frequencies_set = set(frequencies_list)
|
|
273
|
+
clock_hierarchy = FREQUENCY_HIERARCHY.get(clock, 3)
|
|
274
|
+
|
|
275
|
+
missing_pairs = []
|
|
276
|
+
for freq in frequencies_set:
|
|
277
|
+
freq_hierarchy = FREQUENCY_HIERARCHY.get(freq, 3)
|
|
278
|
+
if freq_hierarchy > clock_hierarchy:
|
|
279
|
+
# This frequency is slower than clock, needs tent kernel
|
|
280
|
+
tent_w = get_tent_weights(freq, clock)
|
|
281
|
+
if tent_w is None:
|
|
282
|
+
missing_pairs.append((freq, clock))
|
|
283
|
+
|
|
284
|
+
if missing_pairs:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"mixed_freq=True but the following frequency pairs are not in TENT_WEIGHTS_LOOKUP: {missing_pairs}. "
|
|
287
|
+
f"Available pairs: {list(TENT_WEIGHTS_LOOKUP.keys())}. "
|
|
288
|
+
f"Either add the missing pairs to TENT_WEIGHTS_LOOKUP or set mixed_freq=False."
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Convert tent_weights to torch tensors
|
|
292
|
+
tent_weights_dict = {k: torch.tensor(v, dtype=torch.float32, device=X.device)
|
|
293
|
+
for k, v in agg_structure['tent_weights'].items()}
|
|
294
|
+
|
|
295
|
+
# Get R_mat and q from first structure (if any)
|
|
296
|
+
R_mat = None
|
|
297
|
+
q = None
|
|
298
|
+
if agg_structure['structures']:
|
|
299
|
+
first_structure = list(agg_structure['structures'].values())[0]
|
|
300
|
+
R_mat = torch.tensor(first_structure[0], dtype=torch.float32, device=X.device)
|
|
301
|
+
q = torch.tensor(first_structure[1], dtype=torch.float32, device=X.device)
|
|
302
|
+
|
|
303
|
+
# Create frequencies array
|
|
304
|
+
frequencies_array = np.array(frequencies_list, dtype=object)
|
|
305
|
+
|
|
306
|
+
# Count slower-frequency series
|
|
307
|
+
nQ = sum(1 for freq in frequencies_list
|
|
308
|
+
if FREQUENCY_HIERARCHY.get(freq, 3) > clock_hierarchy)
|
|
309
|
+
|
|
310
|
+
# Compute i_idio (1 for clock frequency, 0 for slower frequencies)
|
|
311
|
+
i_idio = torch.tensor([1 if freq == clock else 0 for freq in frequencies_list],
|
|
312
|
+
dtype=torch.float32, device=X.device)
|
|
313
|
+
else:
|
|
314
|
+
# Treat all series as clock frequency (unified frequency)
|
|
315
|
+
tent_weights_dict = self._legacy_tent_weights_dict
|
|
316
|
+
R_mat = None
|
|
317
|
+
q = None
|
|
318
|
+
frequencies_array = None
|
|
319
|
+
nQ = 0
|
|
320
|
+
i_idio = torch.ones(X.shape[1], dtype=torch.float32, device=X.device)
|
|
321
|
+
|
|
322
|
+
# Convert frequencies to torch tensor
|
|
323
|
+
frequencies_tensor = None
|
|
324
|
+
if frequencies_array is not None:
|
|
325
|
+
frequencies_tensor = torch.tensor(
|
|
326
|
+
[_FREQ_TO_INT.get(f, 3) for f in frequencies_array],
|
|
327
|
+
dtype=torch.int32,
|
|
328
|
+
device=X.device
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Store for reuse in EM steps
|
|
332
|
+
self._em_R_mat = R_mat
|
|
333
|
+
self._em_q = q
|
|
334
|
+
self._em_nQ = nQ
|
|
335
|
+
self._em_tent_weights_dict = tent_weights_dict
|
|
336
|
+
self._em_frequencies = frequencies_tensor
|
|
337
|
+
self._em_i_idio = i_idio
|
|
338
|
+
|
|
339
|
+
# Idiosyncratic chain lengths (currently unused, set to zeros)
|
|
340
|
+
self._em_idio_chain_lengths = torch.zeros(X.shape[1], dtype=torch.int32, device=X.device)
|
|
341
|
+
|
|
342
|
+
# Initialize parameters using EM algorithm
|
|
195
343
|
A, C, Q, R, Z_0, V_0 = self.em.initialize_parameters(
|
|
196
344
|
X,
|
|
197
345
|
r=self.r.to(X.device),
|
|
198
346
|
p=self.p,
|
|
199
347
|
blocks=self.blocks.to(X.device),
|
|
200
348
|
opt_nan=opt_nan,
|
|
201
|
-
R_mat=
|
|
202
|
-
q=
|
|
203
|
-
nQ=
|
|
204
|
-
i_idio=
|
|
205
|
-
clock=
|
|
206
|
-
tent_weights_dict=
|
|
207
|
-
frequencies=
|
|
208
|
-
idio_chain_lengths=
|
|
349
|
+
R_mat=R_mat,
|
|
350
|
+
q=q,
|
|
351
|
+
nQ=nQ,
|
|
352
|
+
i_idio=i_idio,
|
|
353
|
+
clock=clock,
|
|
354
|
+
tent_weights_dict=tent_weights_dict,
|
|
355
|
+
frequencies=frequencies_tensor,
|
|
356
|
+
idio_chain_lengths=self._em_idio_chain_lengths,
|
|
209
357
|
config=self.config
|
|
210
358
|
)
|
|
211
359
|
|
|
@@ -267,28 +415,8 @@ class DFM(BaseFactorModel):
|
|
|
267
415
|
# EM expects y as (N x T), but data is (T x N)
|
|
268
416
|
y = data.T # (N x T)
|
|
269
417
|
|
|
270
|
-
# Create EM step parameters
|
|
271
|
-
em_params =
|
|
272
|
-
y=y,
|
|
273
|
-
A=self.A,
|
|
274
|
-
C=self.C,
|
|
275
|
-
Q=self.Q,
|
|
276
|
-
R=self.R,
|
|
277
|
-
Z_0=self.Z_0,
|
|
278
|
-
V_0=self.V_0,
|
|
279
|
-
r=self.r.to(y.device),
|
|
280
|
-
p=self.p,
|
|
281
|
-
R_mat=None,
|
|
282
|
-
q=None,
|
|
283
|
-
nQ=0,
|
|
284
|
-
i_idio=torch.ones(y.shape[0], device=y.device, dtype=y.dtype),
|
|
285
|
-
blocks=self.blocks.to(y.device),
|
|
286
|
-
tent_weights_dict=self.tent_weights_dict,
|
|
287
|
-
clock=getattr(self.config, 'clock', 'm'),
|
|
288
|
-
frequencies=None,
|
|
289
|
-
idio_chain_lengths=torch.zeros(y.shape[0], device=y.device, dtype=y.dtype),
|
|
290
|
-
config=self.config
|
|
291
|
-
)
|
|
418
|
+
# Create EM step parameters using stored mixed frequency parameters
|
|
419
|
+
em_params = self._create_em_step_params(y, y.device, y.dtype)
|
|
292
420
|
|
|
293
421
|
# Perform EM step - use self.em(...) instead of em_step(...)
|
|
294
422
|
C_new, R_new, A_new, Q_new, Z_0_new, V_0_new, loglik = self.em(em_params)
|
|
@@ -390,28 +518,8 @@ class DFM(BaseFactorModel):
|
|
|
390
518
|
|
|
391
519
|
# EM loop
|
|
392
520
|
while num_iter < self.max_iter and not converged:
|
|
393
|
-
# Create EM step parameters
|
|
394
|
-
em_params =
|
|
395
|
-
y=y,
|
|
396
|
-
A=self.A,
|
|
397
|
-
C=self.C,
|
|
398
|
-
Q=self.Q,
|
|
399
|
-
R=self.R,
|
|
400
|
-
Z_0=self.Z_0,
|
|
401
|
-
V_0=self.V_0,
|
|
402
|
-
r=self.r.to(device),
|
|
403
|
-
p=self.p,
|
|
404
|
-
R_mat=None,
|
|
405
|
-
q=None,
|
|
406
|
-
nQ=0,
|
|
407
|
-
i_idio=torch.ones(y.shape[0], device=device, dtype=dtype),
|
|
408
|
-
blocks=self.blocks.to(device),
|
|
409
|
-
tent_weights_dict={},
|
|
410
|
-
clock=getattr(self.config, 'clock', 'm'),
|
|
411
|
-
frequencies=None,
|
|
412
|
-
idio_chain_lengths=torch.zeros(y.shape[0], device=device, dtype=dtype),
|
|
413
|
-
config=self.config
|
|
414
|
-
)
|
|
521
|
+
# Create EM step parameters using stored mixed frequency parameters
|
|
522
|
+
em_params = self._create_em_step_params(y, device, dtype)
|
|
415
523
|
|
|
416
524
|
# Perform EM step - use self.em(...) instead of em_step(...)
|
|
417
525
|
C_new, R_new, A_new, Q_new, Z_0_new, V_0_new, loglik = self.em(em_params)
|
dfm_python/models/mcmc.py
CHANGED
|
@@ -347,10 +347,11 @@ class DDFMMCMCTrainer:
|
|
|
347
347
|
loss = torch.sum(squared_diff) / (torch.sum(mask) + 1e-8)
|
|
348
348
|
loss.backward()
|
|
349
349
|
# Gradient clipping to prevent NaN and improve stability
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
350
|
+
if self.model.grad_clip_val > 0.0:
|
|
351
|
+
torch.nn.utils.clip_grad_norm_(
|
|
352
|
+
list(self.model.encoder.parameters()) + list(self.model.decoder.parameters()),
|
|
353
|
+
max_norm=self.model.grad_clip_val
|
|
354
|
+
)
|
|
354
355
|
optimizer.step()
|
|
355
356
|
# Extract factors from this sample
|
|
356
357
|
x_sample_tensor = torch.tensor(x_sim_den[i, :, :], device=device, dtype=dtype)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
7
7
|
Project-URL: Repository, https://github.com/yourusername/dfm-python
|
|
8
8
|
Project-URL: Issues, https://github.com/yourusername/dfm-python/issues
|
|
9
|
-
Author:
|
|
9
|
+
Author: Minkey Chang
|
|
10
10
|
License: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: dfm,dynamic-factor-model,econometrics,forecasting,nowcasting,time-series
|
|
@@ -11,7 +11,7 @@ dfm_python/data/utils.py,sha256=5aJFrQ09I3oda4KNut1dXVt-9GYMGouT5lDUpfUEb8Q,1330
|
|
|
11
11
|
dfm_python/encoder/__init__.py,sha256=ljPTXq04cpOsGXNnma2EJ447kPFlytT6_mToLyKok7w,850
|
|
12
12
|
dfm_python/encoder/base.py,sha256=MOSn_bsQP-GVonGprJymQq-_Q6tmCQex8VOWUJjyxNA,3029
|
|
13
13
|
dfm_python/encoder/pca.py,sha256=FsvCqXD0AuASxymV2-5oAM2QVv_syhSyL3CmxVOXxgM,10584
|
|
14
|
-
dfm_python/encoder/vae.py,sha256=
|
|
14
|
+
dfm_python/encoder/vae.py,sha256=dmM243XrLedzwZ-4CdI3W8A9LH591JnlBjd72eHvXvs,22122
|
|
15
15
|
dfm_python/lightning/__init__.py,sha256=_2DFZnMX3BOybuZZ76VJJ8gmMLZ7tFyO3yyN-A4_ECI,1247
|
|
16
16
|
dfm_python/lightning/data_module.py,sha256=cZuBdGbk6meaeiw9ZPGkcsUW4v25ZP_km-u0Pkoh5bg,34815
|
|
17
17
|
dfm_python/lightning/scaling.py,sha256=-8zYrBm3y4_Ke0ODh1-SJnC0pWef3ZJT6GRv8owo2o8,16997
|
|
@@ -21,9 +21,9 @@ dfm_python/logger/logger.py,sha256=1YzsUnvAnAK9CTcfttdSIMtgFvylO13LjQPMEsSs9xc,5
|
|
|
21
21
|
dfm_python/logger/train_logger.py,sha256=9VYhzwNYH1Rn7IQipKTY5wmVJcX1n6HDgnUZH0hChPY,17732
|
|
22
22
|
dfm_python/models/__init__.py,sha256=3JsYZOqDGkl1eG8Zrc4-5gf7Sw47PzzDg2XBhsudB04,724
|
|
23
23
|
dfm_python/models/base.py,sha256=WIx1d38bsaRkpfA4iYVG0_x7R1N8zVfi922LAlXqsEg,29261
|
|
24
|
-
dfm_python/models/ddfm.py,sha256=
|
|
25
|
-
dfm_python/models/dfm.py,sha256=
|
|
26
|
-
dfm_python/models/mcmc.py,sha256=
|
|
24
|
+
dfm_python/models/ddfm.py,sha256=oMHZTUMu7Dy1_8O1IK33prx_B15s0ex83Hq0lWP-91w,66657
|
|
25
|
+
dfm_python/models/dfm.py,sha256=AK4gdwi5a_oEX8FqovyE19bCWN2GpL6SWiEx6-HsGqw,43318
|
|
26
|
+
dfm_python/models/mcmc.py,sha256=IyQYyfNV5ou48Q3SfStPt3k07N74lSQE_u8W8QX93ro,24070
|
|
27
27
|
dfm_python/models/utils.py,sha256=LAtvBqUYxnnl4dIaWl6MC-NHSuoyeNkHZppfWB1lCxQ,20193
|
|
28
28
|
dfm_python/ssm/__init__.py,sha256=dFpt8_3jb_foISDNhma_JUAzch0VrKZcaeWP9zwpwoM,1161
|
|
29
29
|
dfm_python/ssm/em.py,sha256=sUiwuBVLTQBNHlBJ9YwemVY0wJYMTUqkuhyjPohENNw,63429
|
|
@@ -38,7 +38,7 @@ dfm_python/utils/diagnostics.py,sha256=UwrJfnn8VACATfQEkTcWw9eEJlTik-VCgTZ1dJABZ
|
|
|
38
38
|
dfm_python/utils/helpers.py,sha256=iOVwkP5pti-q-HKOAkp6bUPAp7BPV7BBgh8jL76g9go,13049
|
|
39
39
|
dfm_python/utils/statespace.py,sha256=P-GQiCP9HzI2d9PgYst4nmRuocEXr9gG1yzGYsYWkMg,44159
|
|
40
40
|
dfm_python/utils/time.py,sha256=o6NRPuk4Ynk8kPaUnzR6xq3pPAO6aLUTR9N_3hn_N88,35651
|
|
41
|
-
dfm_python-0.5.
|
|
42
|
-
dfm_python-0.5.
|
|
43
|
-
dfm_python-0.5.
|
|
44
|
-
dfm_python-0.5.
|
|
41
|
+
dfm_python-0.5.3.dist-info/METADATA,sha256=6yDzvYrImsw5VC1FF2XyRGy7HLxlAs4c-jKU5cKMsw0,12007
|
|
42
|
+
dfm_python-0.5.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
43
|
+
dfm_python-0.5.3.dist-info/licenses/LICENSE,sha256=6e87br6LCDHjy2hdIR1-sdBQy351qdmSEbOg6jQ7ZXY,1063
|
|
44
|
+
dfm_python-0.5.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|