ldif-model 0.1.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.
@@ -0,0 +1,3 @@
1
+ MIT
2
+
3
+ Copyright (c) 2026 [Muhammad Muhaimin, Muhammad Waseem Ashraf, Shahzadi Tayyaba] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, do its origin, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldif-model
3
+ Version: 0.1.0
4
+ Summary: Latent Dual Interaction Flow neural architecture with response-conditioned gating and adaptive spectrum pruning.
5
+ Author: Muhammad Muhaimin, Muhammad Waseem Ashraf, Shahzadi Tayyaba
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/engrdrwaseem
8
+ Project-URL: Repository, https://github.com/yourusername/ldif
9
+ Project-URL: Issues, https://github.com/yourusername/ldif/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: License
20
+ Requires-Dist: torch>=1.9.0
21
+ Requires-Dist: numpy>=1.19.0
22
+ Dynamic: license-file
23
+
24
+ # LDIF: Latent Dual Interaction Flow
25
+
26
+ **LDIF** is a PyTorch implementation of the Latent Dual Interaction Flow. It is a continuous time neural architecture with response conditioned gating and adaptive spectrum pruning.
27
+
28
+ ## Installation
29
+
30
+ pip install ldif-model
31
+
32
+ ## Quick Start
33
+
34
+ ### Static (tabular) data
35
+
36
+ LDIF can be implemented in python for static data using this example code.
37
+
38
+ import torch
39
+ from ldif import LDIFStatic
40
+ model = LDIFStatic(
41
+ input_dim=128,
42
+ output_dim=1,
43
+ task_type='binary_classification',
44
+ num_layers=2,
45
+ k_max=16,
46
+ r=8,
47
+ gamma=0.01
48
+ )
49
+
50
+ x = torch.randn(32, 128)
51
+ logits = model(x) # shape (32,)
52
+
53
+ For regression, use task_type='regression'; for multiclass, task_type='multiclass' and set output_dim accordingly.
54
+
55
+ ### Sequential (time‑series) data
56
+
57
+ LDIF can be implemented in python for sequential (temporal) data using this example code.
58
+
59
+ from ldif import LDIFSequential
60
+ model = LDIFSequential(
61
+ input_dim=8,
62
+ output_dim=1,
63
+ task_type='regression',
64
+ num_layers=2,
65
+ k_max=16,
66
+ r=8,
67
+ gamma=0.2
68
+ )
69
+
70
+ # x: (batch, sequence_length, features)
71
+ x = torch.randn(16, 50, 8)
72
+ out = model(x) # shape (16,)
73
+
74
+ ## Default Hyperparameters
75
+
76
+ The default hyperparameters for both `LDIFStatic` and `LDIFSequential` are explained here.
77
+
78
+ ### `LDIFStatic`
79
+
80
+ LDIFStatic is tuned for static tabular datasets (e.g., regression or classification on feature vectors). The defaults are chosen to balance capacity and stability for moderate sized data (up to ~10k samples):
81
+
82
+ num_layers=2: Two stacked LDIF blocks provide sufficient depth to capture non‑linear interactions without overfitting.
83
+ k_max=16: The adaptive spectrum has a maximum rank of 16, which is ample for most tabular problems; the L1 penalty will prune unnecessary components.
84
+ r=8: The gate projections use a rank of 8, offering a good trade‑off between routing expressiveness and parameter efficiency.
85
+ gamma=0.01: A mild decay coefficient prevents unbounded growth of the hidden state while allowing long‑term information retention.
86
+ input_scale=2.0: A relatively strong input injection boosts the signal from the raw features, helping the model quickly adapt to the data.
87
+ z_init_mean=1.0: The spectrum logits start with a mean of 1.0, corresponding to sigmoid values around 0.73 – this encourages most components to be initially active, giving the model full capacity from the start.
88
+ z_init_std=0.01: Very tight initialization ensures that the spectrum does not vary wildly early in training, promoting stable gradient flow.
89
+ MU is a hyper parameter it is L1 penalty and explained in the paper. The mu is dependent on the dataset samples. It is preferred to define your N_train like:
90
+ N_train = len(train_loader.dataset) # if using DataLoader
91
+ # or
92
+ N_train = len(train_dataset) # if using TensorDataset directly
93
+ The number of samples calculate the value of mu, for proper implementation it is preferred to make the mu factor trained using optuna, some data may even require mu factor as low as 0.07 or lower so optuna is prefered to be used to find best mu factor or if optuna is not available or computationally expensive simply using grid search to find best mu factor with validation metrics being monitored is recommended. The default mu_factor in the code is 1.0 (the default argument in compute_mu(N_train, mu_factor=1.0)).
94
+ This value corresponds to the base scaling recommended by the paper – i.e., mu = 1.82 * N_train^(-0.55). Users can easily override it by passing a different mu_factor (e.g., from Optuna tuning) when calling compute_mu().
95
+
96
+ Important: mu itself is not hardcoded anywhere in the model; it must be computed during training and applied to the loss. The helper function simply provides the recommended formula. Users should call it before training, e.g.:
97
+
98
+ mu = compute_mu(N_train, mu_factor=1.0) # or any tuned value
99
+
100
+ mu_factor=1.0 is a sensible starting point for most datasets.
101
+
102
+ ### LDIFSequential
103
+
104
+ LDIFSequential is designed for time‑series data (e.g., sensor readings, financial sequences). Its defaults reflect the need for stable recurrent dynamics and awareness of temporal order:
105
+
106
+ num_layers=2: Same as static – two layers are sufficient for most sequence lengths up to ~100 time steps.
107
+ k_max=16: The spectrum capacity is kept at 16, which is enough for typical sequential patterns; the L1 penalty will still prune irrelevant components.
108
+ r=8: Gate projection rank remains 8, offering sufficient flexibility for mixing symmetric and skew‑symmetric responses.
109
+ gamma=0.2: A stronger decay coefficient is used to dissipate energy more quickly, preventing the hidden state from becoming unstable over long sequences.
110
+ input_scale=0.05: Input forcing is much weaker compared to the static variant – this ensures that new observations gently influence the state without overwhelming the recurrent dynamics.
111
+ z_init_mean=-2.0: The spectrum logits start with a mean of -2.0, corresponding to sigmoid values around 0.12 – this highly conservative initialization avoids sudden large updates early in training, which is crucial for stable recurrent learning.
112
+ z_init_std=0.01: As with the static variant, the tight standard deviation keeps initial spectrum values close to each other, reducing variance in early gradients.
113
+ use_positional_encoding=True: Positional encoding is enabled to inject temporal order information, helping the model distinguish between different time steps and better capture sequential patterns.
114
+ mu is as above.
115
+
116
+ ## Hyperparameter Tuning
117
+
118
+ The defaults work well for moderate‑sized datasets (up to ~10k samples for static, sequences up to length ~100). For larger or more complex data, we recommend tuning the following with Optuna or similar:
119
+
120
+ k_max: 12–64 (larger for high‑capacity tasks)
121
+ r: 6–24 (larger for richer gate interactions)
122
+ num_layers: 1–4 (more layers for long‑range dependencies)
123
+ gamma: 0.001–0.1 (static) or 0.05–0.5 (sequential)
124
+ lr (optimiser): typically 1e‑4 to 1e‑3
125
+ weight_decay: 1e‑5 to 1e‑2
126
+ mu (spectrum penalty coefficient): use mu = 1.82 * (N_train ** (-0.55)) * mu_factor, where mu_factor is tuned (e.g., 0.01–10).
127
+
128
+ A good starting point for the L1 penalty is `mu = 0.01` for datasets with >10k samples, scaling down for smaller datasets.
129
+
130
+ The initial draft of paper explaining the model is titled: 'LDIF: Latent dual interaction flow' currently published on arxiv. For more detail check the paper.
131
+
132
+ ## Difference between LDIFStatic and LDIFSequential
133
+ The model architecture is same only difference between the import is that LDIFStatic is designed for static/tabular data. It uses a stronger input scaling (2.0) and lower decay (gamma=0.01) to capture feature interactions, with positional encoding disabled.
134
+ LDIFSequential targets time-series data. It uses weaker input scaling (0.05), stronger decay (gamma=0.2) for stable recurrent dynamics, and enables positional encoding to preserve temporal order.
135
+ For z_init_mean value or the initialization of the spectrum values the static varient has a value of 1.0 so the spectrum starts near 0.73, for sequential to promote stability and prevent any type of exploding gradients the value is set -2.0 so spectrum value starts near 0.12.
136
+ In our experiments these configuration for the initialization and input scaling were found to be properly working for respective data type.
@@ -0,0 +1,113 @@
1
+ # LDIF: Latent Dual Interaction Flow
2
+
3
+ **LDIF** is a PyTorch implementation of the Latent Dual Interaction Flow. It is a continuous time neural architecture with response conditioned gating and adaptive spectrum pruning.
4
+
5
+ ## Installation
6
+
7
+ pip install ldif-model
8
+
9
+ ## Quick Start
10
+
11
+ ### Static (tabular) data
12
+
13
+ LDIF can be implemented in python for static data using this example code.
14
+
15
+ import torch
16
+ from ldif import LDIFStatic
17
+ model = LDIFStatic(
18
+ input_dim=128,
19
+ output_dim=1,
20
+ task_type='binary_classification',
21
+ num_layers=2,
22
+ k_max=16,
23
+ r=8,
24
+ gamma=0.01
25
+ )
26
+
27
+ x = torch.randn(32, 128)
28
+ logits = model(x) # shape (32,)
29
+
30
+ For regression, use task_type='regression'; for multiclass, task_type='multiclass' and set output_dim accordingly.
31
+
32
+ ### Sequential (time‑series) data
33
+
34
+ LDIF can be implemented in python for sequential (temporal) data using this example code.
35
+
36
+ from ldif import LDIFSequential
37
+ model = LDIFSequential(
38
+ input_dim=8,
39
+ output_dim=1,
40
+ task_type='regression',
41
+ num_layers=2,
42
+ k_max=16,
43
+ r=8,
44
+ gamma=0.2
45
+ )
46
+
47
+ # x: (batch, sequence_length, features)
48
+ x = torch.randn(16, 50, 8)
49
+ out = model(x) # shape (16,)
50
+
51
+ ## Default Hyperparameters
52
+
53
+ The default hyperparameters for both `LDIFStatic` and `LDIFSequential` are explained here.
54
+
55
+ ### `LDIFStatic`
56
+
57
+ LDIFStatic is tuned for static tabular datasets (e.g., regression or classification on feature vectors). The defaults are chosen to balance capacity and stability for moderate sized data (up to ~10k samples):
58
+
59
+ num_layers=2: Two stacked LDIF blocks provide sufficient depth to capture non‑linear interactions without overfitting.
60
+ k_max=16: The adaptive spectrum has a maximum rank of 16, which is ample for most tabular problems; the L1 penalty will prune unnecessary components.
61
+ r=8: The gate projections use a rank of 8, offering a good trade‑off between routing expressiveness and parameter efficiency.
62
+ gamma=0.01: A mild decay coefficient prevents unbounded growth of the hidden state while allowing long‑term information retention.
63
+ input_scale=2.0: A relatively strong input injection boosts the signal from the raw features, helping the model quickly adapt to the data.
64
+ z_init_mean=1.0: The spectrum logits start with a mean of 1.0, corresponding to sigmoid values around 0.73 – this encourages most components to be initially active, giving the model full capacity from the start.
65
+ z_init_std=0.01: Very tight initialization ensures that the spectrum does not vary wildly early in training, promoting stable gradient flow.
66
+ MU is a hyper parameter it is L1 penalty and explained in the paper. The mu is dependent on the dataset samples. It is preferred to define your N_train like:
67
+ N_train = len(train_loader.dataset) # if using DataLoader
68
+ # or
69
+ N_train = len(train_dataset) # if using TensorDataset directly
70
+ The number of samples calculate the value of mu, for proper implementation it is preferred to make the mu factor trained using optuna, some data may even require mu factor as low as 0.07 or lower so optuna is prefered to be used to find best mu factor or if optuna is not available or computationally expensive simply using grid search to find best mu factor with validation metrics being monitored is recommended. The default mu_factor in the code is 1.0 (the default argument in compute_mu(N_train, mu_factor=1.0)).
71
+ This value corresponds to the base scaling recommended by the paper – i.e., mu = 1.82 * N_train^(-0.55). Users can easily override it by passing a different mu_factor (e.g., from Optuna tuning) when calling compute_mu().
72
+
73
+ Important: mu itself is not hardcoded anywhere in the model; it must be computed during training and applied to the loss. The helper function simply provides the recommended formula. Users should call it before training, e.g.:
74
+
75
+ mu = compute_mu(N_train, mu_factor=1.0) # or any tuned value
76
+
77
+ mu_factor=1.0 is a sensible starting point for most datasets.
78
+
79
+ ### LDIFSequential
80
+
81
+ LDIFSequential is designed for time‑series data (e.g., sensor readings, financial sequences). Its defaults reflect the need for stable recurrent dynamics and awareness of temporal order:
82
+
83
+ num_layers=2: Same as static – two layers are sufficient for most sequence lengths up to ~100 time steps.
84
+ k_max=16: The spectrum capacity is kept at 16, which is enough for typical sequential patterns; the L1 penalty will still prune irrelevant components.
85
+ r=8: Gate projection rank remains 8, offering sufficient flexibility for mixing symmetric and skew‑symmetric responses.
86
+ gamma=0.2: A stronger decay coefficient is used to dissipate energy more quickly, preventing the hidden state from becoming unstable over long sequences.
87
+ input_scale=0.05: Input forcing is much weaker compared to the static variant – this ensures that new observations gently influence the state without overwhelming the recurrent dynamics.
88
+ z_init_mean=-2.0: The spectrum logits start with a mean of -2.0, corresponding to sigmoid values around 0.12 – this highly conservative initialization avoids sudden large updates early in training, which is crucial for stable recurrent learning.
89
+ z_init_std=0.01: As with the static variant, the tight standard deviation keeps initial spectrum values close to each other, reducing variance in early gradients.
90
+ use_positional_encoding=True: Positional encoding is enabled to inject temporal order information, helping the model distinguish between different time steps and better capture sequential patterns.
91
+ mu is as above.
92
+
93
+ ## Hyperparameter Tuning
94
+
95
+ The defaults work well for moderate‑sized datasets (up to ~10k samples for static, sequences up to length ~100). For larger or more complex data, we recommend tuning the following with Optuna or similar:
96
+
97
+ k_max: 12–64 (larger for high‑capacity tasks)
98
+ r: 6–24 (larger for richer gate interactions)
99
+ num_layers: 1–4 (more layers for long‑range dependencies)
100
+ gamma: 0.001–0.1 (static) or 0.05–0.5 (sequential)
101
+ lr (optimiser): typically 1e‑4 to 1e‑3
102
+ weight_decay: 1e‑5 to 1e‑2
103
+ mu (spectrum penalty coefficient): use mu = 1.82 * (N_train ** (-0.55)) * mu_factor, where mu_factor is tuned (e.g., 0.01–10).
104
+
105
+ A good starting point for the L1 penalty is `mu = 0.01` for datasets with >10k samples, scaling down for smaller datasets.
106
+
107
+ The initial draft of paper explaining the model is titled: 'LDIF: Latent dual interaction flow' currently published on arxiv. For more detail check the paper.
108
+
109
+ ## Difference between LDIFStatic and LDIFSequential
110
+ The model architecture is same only difference between the import is that LDIFStatic is designed for static/tabular data. It uses a stronger input scaling (2.0) and lower decay (gamma=0.01) to capture feature interactions, with positional encoding disabled.
111
+ LDIFSequential targets time-series data. It uses weaker input scaling (0.05), stronger decay (gamma=0.2) for stable recurrent dynamics, and enables positional encoding to preserve temporal order.
112
+ For z_init_mean value or the initialization of the spectrum values the static varient has a value of 1.0 so the spectrum starts near 0.73, for sequential to promote stability and prevent any type of exploding gradients the value is set -2.0 so spectrum value starts near 0.12.
113
+ In our experiments these configuration for the initialization and input scaling were found to be properly working for respective data type.
@@ -0,0 +1 @@
1
+ from .model import LDIFStatic, LDIFSequential, LDIFModel, LDIFBlock, compute_mu, compile_model
@@ -0,0 +1,321 @@
1
+ """
2
+ LDIF: Latent Dual Interaction Flow – PyTorch implementation.
3
+
4
+ Provides LDIFBlock, LDIFModel, LDIFStatic, and LDIFSequential.
5
+ """
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+ import math
11
+ import os
12
+
13
+ # Performance tweaks
14
+ if torch.cuda.is_available():
15
+ torch.set_float32_matmul_precision('high')
16
+ torch.backends.cudnn.benchmark = True
17
+ else:
18
+ try:
19
+ n_threads = max(1, os.cpu_count() - 1)
20
+ torch.set_num_threads(n_threads)
21
+ except:
22
+ pass
23
+
24
+
25
+ class LDIFBlock(nn.Module):
26
+ """
27
+ Single LDIF layer with full gate.
28
+ """
29
+ def __init__(self, d, k_max, r, gamma=0.01, z_init_mean=1.0, z_init_std=0.01):
30
+ super().__init__()
31
+ self.d = d
32
+ self.k_max = k_max
33
+ self.r = r
34
+ self.gamma = gamma
35
+
36
+ # Spectrum: s = sigma(z)
37
+ self.z = nn.Parameter(torch.randn(k_max) * z_init_std + z_init_mean)
38
+
39
+ # Symmetric branch
40
+ U_S_raw = torch.empty(k_max, d)
41
+ V_S_raw = torch.empty(k_max, d)
42
+ nn.init.kaiming_uniform_(U_S_raw, a=math.sqrt(5))
43
+ nn.init.kaiming_uniform_(V_S_raw, a=math.sqrt(5))
44
+ self.U_S = nn.Parameter(U_S_raw.T)
45
+ self.V_S = nn.Parameter(V_S_raw.T)
46
+
47
+ # Skew-symmetric branch
48
+ U_A_raw = torch.empty(k_max, d)
49
+ V_A_raw = torch.empty(k_max, d)
50
+ nn.init.kaiming_uniform_(U_A_raw, a=math.sqrt(5))
51
+ nn.init.kaiming_uniform_(V_A_raw, a=math.sqrt(5))
52
+ self.U_A = nn.Parameter(U_A_raw.T)
53
+ self.V_A = nn.Parameter(V_A_raw.T)
54
+
55
+ # Gate projections
56
+ V_h_raw = torch.empty(r, d)
57
+ V_Sr_raw = torch.empty(r, d)
58
+ V_Ar_raw = torch.empty(r, d)
59
+ nn.init.kaiming_uniform_(V_h_raw, a=math.sqrt(5))
60
+ nn.init.kaiming_uniform_(V_Sr_raw, a=math.sqrt(5))
61
+ nn.init.kaiming_uniform_(V_Ar_raw, a=math.sqrt(5))
62
+ self.V_h = nn.Parameter(V_h_raw.T)
63
+ self.V_Sr = nn.Parameter(V_Sr_raw.T)
64
+ self.V_Ar = nn.Parameter(V_Ar_raw.T)
65
+
66
+ U_h_raw = torch.empty(d, r)
67
+ U_Sr_raw = torch.empty(d, r)
68
+ U_Ar_raw = torch.empty(d, r)
69
+ std_h = 1.0 / math.sqrt(r)
70
+ bound_h = math.sqrt(3.0) * std_h
71
+ U_h_raw.uniform_(-bound_h, bound_h)
72
+ U_Sr_raw.uniform_(-bound_h, bound_h)
73
+ U_Ar_raw.uniform_(-bound_h, bound_h)
74
+ self.U_h = nn.Parameter(U_h_raw)
75
+ self.U_Sr = nn.Parameter(U_Sr_raw)
76
+ self.U_Ar = nn.Parameter(U_Ar_raw)
77
+ self.b_g = nn.Parameter(torch.zeros(d))
78
+
79
+ # Bounded step-size parameters
80
+ self.alpha_raw = nn.Parameter(torch.ones(d))
81
+ self.lambda_raw = nn.Parameter(torch.tensor(0.0))
82
+
83
+ @property
84
+ def s(self):
85
+ return torch.sigmoid(self.z)
86
+
87
+ @property
88
+ def S2(self):
89
+ return self.s ** 2
90
+
91
+ @property
92
+ def alpha(self):
93
+ return torch.clamp(F.softplus(self.alpha_raw), min=0.5, max=2.0)
94
+
95
+ @property
96
+ def lambda_(self):
97
+ return 0.05 + 0.45 * torch.sigmoid(self.lambda_raw)
98
+
99
+ def _ensure_batch(self, h):
100
+ if h.dim() == 1:
101
+ return h.unsqueeze(0), True
102
+ return h, False
103
+
104
+ def _restore_batch(self, h, was_1d):
105
+ if was_1d:
106
+ return h.squeeze(0)
107
+ return h
108
+
109
+ def forward(self, h):
110
+ h, was_1d = self._ensure_batch(h)
111
+ s2 = self.S2
112
+
113
+ # Symmetric response
114
+ U_S_h = torch.matmul(h, self.U_S)
115
+ V_S_h = torch.matmul(h, self.V_S)
116
+ u_scaled = U_S_h * s2.unsqueeze(0)
117
+ v_scaled = V_S_h * s2.unsqueeze(0)
118
+ r_S = torch.matmul(u_scaled, self.V_S.T) + torch.matmul(v_scaled, self.U_S.T)
119
+
120
+ # Skew-symmetric response
121
+ U_A_h = torch.matmul(h, self.U_A)
122
+ V_A_h = torch.matmul(h, self.V_A)
123
+ uA_scaled = U_A_h * s2.unsqueeze(0)
124
+ vA_scaled = V_A_h * s2.unsqueeze(0)
125
+ r_A = torch.matmul(uA_scaled, self.V_A.T) - torch.matmul(vA_scaled, self.U_A.T)
126
+
127
+ # Gating
128
+ gate_in = torch.matmul(h, self.V_h) @ self.U_h.T
129
+ gate_in += torch.matmul(r_S, self.V_Sr) @ self.U_Sr.T
130
+ gate_in += torch.matmul(r_A, self.V_Ar) @ self.U_Ar.T
131
+ gate_in += self.b_g
132
+ g = torch.sigmoid(gate_in)
133
+ R = g * r_S + (1 - g) * r_A
134
+
135
+ # Master update
136
+ lambda_ = self.lambda_
137
+ alpha = self.alpha
138
+ h_next = h + lambda_ * alpha * torch.tanh(R) - lambda_ * self.gamma * h
139
+
140
+ intermediates = {
141
+ 'r_S': r_S, 'r_A': r_A, 'gate': g, 'R': R,
142
+ 's': self.s, 'alpha': alpha, 'lambda': lambda_,
143
+ 'z': self.z, 'h_next': h_next,
144
+ }
145
+ return self._restore_batch(h_next, was_1d), intermediates
146
+
147
+ def spectrum_penalty(self):
148
+ return torch.sum(self.s)
149
+
150
+ def mean_spectrum(self):
151
+ return self.s.mean().item()
152
+
153
+ def effective_rank(self, s_threshold=0.1):
154
+ return (self.s > s_threshold).sum().item()
155
+
156
+
157
+ class LDIFModel(nn.Module):
158
+ """
159
+ Base LDIF model supporting both static and sequential data.
160
+ """
161
+ def __init__(self, input_dim, output_dim, task_type,
162
+ num_layers=2, k_max=16, r=8, gamma=0.01,
163
+ input_scale=2.0, z_init_mean=1.0, z_init_std=0.01,
164
+ use_positional_encoding=True, use_random_init=True):
165
+ super().__init__()
166
+ self.input_dim = input_dim
167
+ self.output_dim = output_dim
168
+ self.task_type = task_type
169
+ self.num_layers = num_layers
170
+ self.use_positional_encoding = use_positional_encoding
171
+ self.use_random_init = use_random_init
172
+
173
+ self.input_proj = nn.Linear(input_dim, input_dim)
174
+ self.input_scale = nn.Parameter(torch.ones(1) * input_scale)
175
+
176
+ if use_positional_encoding:
177
+ self.pos_embed = nn.Linear(1, input_dim)
178
+ nn.init.normal_(self.pos_embed.weight, std=0.02)
179
+ nn.init.zeros_(self.pos_embed.bias)
180
+ else:
181
+ self.pos_embed = None
182
+
183
+ self.blocks = nn.ModuleList([
184
+ LDIFBlock(input_dim, k_max, r,
185
+ gamma=gamma,
186
+ z_init_mean=z_init_mean,
187
+ z_init_std=z_init_std)
188
+ for _ in range(num_layers)
189
+ ])
190
+
191
+ if task_type == 'regression':
192
+ self.head = nn.Linear(input_dim, 1)
193
+ elif task_type in ['binary_classification', 'imbalanced_binary']:
194
+ self.head = nn.Linear(input_dim, 1)
195
+ elif task_type == 'multiclass':
196
+ self.head = nn.Linear(input_dim, output_dim)
197
+ else:
198
+ self.head = nn.Linear(input_dim, output_dim)
199
+
200
+ nn.init.xavier_uniform_(self.head.weight)
201
+ if self.head.bias is not None:
202
+ nn.init.zeros_(self.head.bias)
203
+
204
+ def forward(self, x, return_intermediates=False):
205
+ is_sequential = x.dim() == 3
206
+ all_intermediates = {}
207
+
208
+ if not is_sequential:
209
+ h = x
210
+ for idx, block in enumerate(self.blocks):
211
+ h, inter = block(h)
212
+ if return_intermediates:
213
+ all_intermediates[f'layer_{idx}'] = {k: v.detach() if isinstance(v, torch.Tensor) else v for k, v in inter.items()}
214
+ out = self.head(h)
215
+ if self.task_type in ['regression', 'binary_classification', 'imbalanced_binary']:
216
+ out = out.view(-1)
217
+ return (out, all_intermediates) if return_intermediates else out
218
+
219
+ # Sequential path
220
+ B, T, D = x.shape
221
+ if self.use_random_init:
222
+ h = torch.randn(B, D, device=x.device) * 0.01
223
+ else:
224
+ h = torch.zeros(B, D, device=x.device)
225
+
226
+ if self.use_positional_encoding and self.pos_embed is not None:
227
+ time_indices = torch.arange(1, T+1, device=x.device).float().view(1, T, 1)
228
+ pos_enc = self.pos_embed(time_indices)
229
+ pos_enc_exp = pos_enc.expand(B, -1, -1)
230
+ else:
231
+ pos_enc_exp = None
232
+
233
+ for t in range(T):
234
+ x_t = x[:, t, :]
235
+ if pos_enc_exp is not None:
236
+ x_t = x_t + pos_enc_exp[:, t, :]
237
+ x_t = torch.tanh(self.input_proj(x_t))
238
+ h = h + self.input_scale * x_t
239
+
240
+ for idx, block in enumerate(self.blocks):
241
+ h, inter = block(h)
242
+ if return_intermediates and (t == 0 or t == T-1):
243
+ time_tag = 't0' if t == 0 else 't_end'
244
+ all_intermediates[f'{time_tag}_layer_{idx}'] = {
245
+ k: v.detach() if isinstance(v, torch.Tensor) else v for k, v in inter.items()
246
+ }
247
+ out = self.head(h)
248
+ if self.task_type in ['regression', 'binary_classification', 'imbalanced_binary']:
249
+ out = out.view(-1)
250
+ return (out, all_intermediates) if return_intermediates else out
251
+
252
+ def spectrum_penalty(self):
253
+ return torch.stack([block.spectrum_penalty() for block in self.blocks]).sum()
254
+
255
+ def get_mean_spectrum(self):
256
+ return [block.mean_spectrum() for block in self.blocks]
257
+
258
+
259
+ class LDIFStatic(LDIFModel):
260
+ """
261
+ LDIF for static (tabular) data with recommended defaults.
262
+ """
263
+ def __init__(self, input_dim, output_dim, task_type,
264
+ num_layers=2, k_max=16, r=8, gamma=0.01):
265
+ super().__init__(
266
+ input_dim=input_dim,
267
+ output_dim=output_dim,
268
+ task_type=task_type,
269
+ num_layers=num_layers,
270
+ k_max=k_max,
271
+ r=r,
272
+ gamma=gamma,
273
+ input_scale=2.0,
274
+ z_init_mean=1.0,
275
+ z_init_std=0.01,
276
+ use_positional_encoding=False,
277
+ use_random_init=True
278
+ )
279
+
280
+
281
+ class LDIFSequential(LDIFModel):
282
+ """
283
+ LDIF for sequential (time-series) data with recommended defaults.
284
+ """
285
+ def __init__(self, input_dim, output_dim, task_type,
286
+ num_layers=2, k_max=16, r=8, gamma=0.2):
287
+ super().__init__(
288
+ input_dim=input_dim,
289
+ output_dim=output_dim,
290
+ task_type=task_type,
291
+ num_layers=num_layers,
292
+ k_max=k_max,
293
+ r=r,
294
+ gamma=gamma,
295
+ input_scale=0.05,
296
+ z_init_mean=-2.0,
297
+ z_init_std=0.01,
298
+ use_positional_encoding=True,
299
+ use_random_init=False
300
+ )
301
+
302
+
303
+ def compute_mu(N_train, mu_factor=1.0):
304
+ """
305
+ Compute L1 penalty coefficient using: mu = 1.82 * N_train^(-0.55) * mu_factor
306
+ """
307
+ return 1.82 * (N_train ** (-0.55)) * mu_factor
308
+
309
+
310
+ def compile_model(model, mode='default'):
311
+ """
312
+ Compile the model with torch.compile for faster execution.
313
+ """
314
+ if hasattr(torch, 'compile'):
315
+ try:
316
+ return torch.compile(model, mode=mode, fullgraph=False)
317
+ except Exception as e:
318
+ print(f"torch.compile failed: {e}. Using uncompiled model.")
319
+ return model
320
+ else:
321
+ return model
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldif-model
3
+ Version: 0.1.0
4
+ Summary: Latent Dual Interaction Flow neural architecture with response-conditioned gating and adaptive spectrum pruning.
5
+ Author: Muhammad Muhaimin, Muhammad Waseem Ashraf, Shahzadi Tayyaba
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/engrdrwaseem
8
+ Project-URL: Repository, https://github.com/yourusername/ldif
9
+ Project-URL: Issues, https://github.com/yourusername/ldif/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: License
20
+ Requires-Dist: torch>=1.9.0
21
+ Requires-Dist: numpy>=1.19.0
22
+ Dynamic: license-file
23
+
24
+ # LDIF: Latent Dual Interaction Flow
25
+
26
+ **LDIF** is a PyTorch implementation of the Latent Dual Interaction Flow. It is a continuous time neural architecture with response conditioned gating and adaptive spectrum pruning.
27
+
28
+ ## Installation
29
+
30
+ pip install ldif-model
31
+
32
+ ## Quick Start
33
+
34
+ ### Static (tabular) data
35
+
36
+ LDIF can be implemented in python for static data using this example code.
37
+
38
+ import torch
39
+ from ldif import LDIFStatic
40
+ model = LDIFStatic(
41
+ input_dim=128,
42
+ output_dim=1,
43
+ task_type='binary_classification',
44
+ num_layers=2,
45
+ k_max=16,
46
+ r=8,
47
+ gamma=0.01
48
+ )
49
+
50
+ x = torch.randn(32, 128)
51
+ logits = model(x) # shape (32,)
52
+
53
+ For regression, use task_type='regression'; for multiclass, task_type='multiclass' and set output_dim accordingly.
54
+
55
+ ### Sequential (time‑series) data
56
+
57
+ LDIF can be implemented in python for sequential (temporal) data using this example code.
58
+
59
+ from ldif import LDIFSequential
60
+ model = LDIFSequential(
61
+ input_dim=8,
62
+ output_dim=1,
63
+ task_type='regression',
64
+ num_layers=2,
65
+ k_max=16,
66
+ r=8,
67
+ gamma=0.2
68
+ )
69
+
70
+ # x: (batch, sequence_length, features)
71
+ x = torch.randn(16, 50, 8)
72
+ out = model(x) # shape (16,)
73
+
74
+ ## Default Hyperparameters
75
+
76
+ The default hyperparameters for both `LDIFStatic` and `LDIFSequential` are explained here.
77
+
78
+ ### `LDIFStatic`
79
+
80
+ LDIFStatic is tuned for static tabular datasets (e.g., regression or classification on feature vectors). The defaults are chosen to balance capacity and stability for moderate sized data (up to ~10k samples):
81
+
82
+ num_layers=2: Two stacked LDIF blocks provide sufficient depth to capture non‑linear interactions without overfitting.
83
+ k_max=16: The adaptive spectrum has a maximum rank of 16, which is ample for most tabular problems; the L1 penalty will prune unnecessary components.
84
+ r=8: The gate projections use a rank of 8, offering a good trade‑off between routing expressiveness and parameter efficiency.
85
+ gamma=0.01: A mild decay coefficient prevents unbounded growth of the hidden state while allowing long‑term information retention.
86
+ input_scale=2.0: A relatively strong input injection boosts the signal from the raw features, helping the model quickly adapt to the data.
87
+ z_init_mean=1.0: The spectrum logits start with a mean of 1.0, corresponding to sigmoid values around 0.73 – this encourages most components to be initially active, giving the model full capacity from the start.
88
+ z_init_std=0.01: Very tight initialization ensures that the spectrum does not vary wildly early in training, promoting stable gradient flow.
89
+ MU is a hyper parameter it is L1 penalty and explained in the paper. The mu is dependent on the dataset samples. It is preferred to define your N_train like:
90
+ N_train = len(train_loader.dataset) # if using DataLoader
91
+ # or
92
+ N_train = len(train_dataset) # if using TensorDataset directly
93
+ The number of samples calculate the value of mu, for proper implementation it is preferred to make the mu factor trained using optuna, some data may even require mu factor as low as 0.07 or lower so optuna is prefered to be used to find best mu factor or if optuna is not available or computationally expensive simply using grid search to find best mu factor with validation metrics being monitored is recommended. The default mu_factor in the code is 1.0 (the default argument in compute_mu(N_train, mu_factor=1.0)).
94
+ This value corresponds to the base scaling recommended by the paper – i.e., mu = 1.82 * N_train^(-0.55). Users can easily override it by passing a different mu_factor (e.g., from Optuna tuning) when calling compute_mu().
95
+
96
+ Important: mu itself is not hardcoded anywhere in the model; it must be computed during training and applied to the loss. The helper function simply provides the recommended formula. Users should call it before training, e.g.:
97
+
98
+ mu = compute_mu(N_train, mu_factor=1.0) # or any tuned value
99
+
100
+ mu_factor=1.0 is a sensible starting point for most datasets.
101
+
102
+ ### LDIFSequential
103
+
104
+ LDIFSequential is designed for time‑series data (e.g., sensor readings, financial sequences). Its defaults reflect the need for stable recurrent dynamics and awareness of temporal order:
105
+
106
+ num_layers=2: Same as static – two layers are sufficient for most sequence lengths up to ~100 time steps.
107
+ k_max=16: The spectrum capacity is kept at 16, which is enough for typical sequential patterns; the L1 penalty will still prune irrelevant components.
108
+ r=8: Gate projection rank remains 8, offering sufficient flexibility for mixing symmetric and skew‑symmetric responses.
109
+ gamma=0.2: A stronger decay coefficient is used to dissipate energy more quickly, preventing the hidden state from becoming unstable over long sequences.
110
+ input_scale=0.05: Input forcing is much weaker compared to the static variant – this ensures that new observations gently influence the state without overwhelming the recurrent dynamics.
111
+ z_init_mean=-2.0: The spectrum logits start with a mean of -2.0, corresponding to sigmoid values around 0.12 – this highly conservative initialization avoids sudden large updates early in training, which is crucial for stable recurrent learning.
112
+ z_init_std=0.01: As with the static variant, the tight standard deviation keeps initial spectrum values close to each other, reducing variance in early gradients.
113
+ use_positional_encoding=True: Positional encoding is enabled to inject temporal order information, helping the model distinguish between different time steps and better capture sequential patterns.
114
+ mu is as above.
115
+
116
+ ## Hyperparameter Tuning
117
+
118
+ The defaults work well for moderate‑sized datasets (up to ~10k samples for static, sequences up to length ~100). For larger or more complex data, we recommend tuning the following with Optuna or similar:
119
+
120
+ k_max: 12–64 (larger for high‑capacity tasks)
121
+ r: 6–24 (larger for richer gate interactions)
122
+ num_layers: 1–4 (more layers for long‑range dependencies)
123
+ gamma: 0.001–0.1 (static) or 0.05–0.5 (sequential)
124
+ lr (optimiser): typically 1e‑4 to 1e‑3
125
+ weight_decay: 1e‑5 to 1e‑2
126
+ mu (spectrum penalty coefficient): use mu = 1.82 * (N_train ** (-0.55)) * mu_factor, where mu_factor is tuned (e.g., 0.01–10).
127
+
128
+ A good starting point for the L1 penalty is `mu = 0.01` for datasets with >10k samples, scaling down for smaller datasets.
129
+
130
+ The initial draft of paper explaining the model is titled: 'LDIF: Latent dual interaction flow' currently published on arxiv. For more detail check the paper.
131
+
132
+ ## Difference between LDIFStatic and LDIFSequential
133
+ The model architecture is same only difference between the import is that LDIFStatic is designed for static/tabular data. It uses a stronger input scaling (2.0) and lower decay (gamma=0.01) to capture feature interactions, with positional encoding disabled.
134
+ LDIFSequential targets time-series data. It uses weaker input scaling (0.05), stronger decay (gamma=0.2) for stable recurrent dynamics, and enables positional encoding to preserve temporal order.
135
+ For z_init_mean value or the initialization of the spectrum values the static varient has a value of 1.0 so the spectrum starts near 0.73, for sequential to promote stability and prevent any type of exploding gradients the value is set -2.0 so spectrum value starts near 0.12.
136
+ In our experiments these configuration for the initialization and input scaling were found to be properly working for respective data type.
@@ -0,0 +1,10 @@
1
+ License
2
+ README.md
3
+ pyproject.toml
4
+ ldif_model/__init__.py
5
+ ldif_model/model.py
6
+ ldif_model.egg-info/PKG-INFO
7
+ ldif_model.egg-info/SOURCES.txt
8
+ ldif_model.egg-info/dependency_links.txt
9
+ ldif_model.egg-info/requires.txt
10
+ ldif_model.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ torch>=1.9.0
2
+ numpy>=1.19.0
@@ -0,0 +1 @@
1
+ ldif_model
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ldif-model"
7
+ version = "0.1.0"
8
+ description = "Latent Dual Interaction Flow neural architecture with response-conditioned gating and adaptive spectrum pruning."
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "Muhammad Muhaimin"},
12
+ {name = "Muhammad Waseem Ashraf"},
13
+ {name = "Shahzadi Tayyaba"}
14
+ ]
15
+ license = "MIT"
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Topic :: Scientific/Engineering :: Artificial Intelligence"
24
+ ]
25
+ requires-python = ">=3.8"
26
+ dependencies = [
27
+ "torch>=1.9.0",
28
+ "numpy>=1.19.0"
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/engrdrwaseem"
33
+ Repository = "https://github.com/yourusername/ldif"
34
+ Issues = "https://github.com/yourusername/ldif/issues"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["."]
38
+ include = ["ldif*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+