l0-python 0.1.0__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.
- l0/__init__.py +53 -0
- l0/distributions.py +205 -0
- l0/gates.py +407 -0
- l0/layers.py +455 -0
- l0/penalties.py +381 -0
- l0_python-0.1.0.dist-info/METADATA +215 -0
- l0_python-0.1.0.dist-info/RECORD +10 -0
- l0_python-0.1.0.dist-info/WHEEL +5 -0
- l0_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- l0_python-0.1.0.dist-info/top_level.txt +1 -0
l0/penalties.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Penalty computation and management utilities for L0 regularization.
|
|
3
|
+
|
|
4
|
+
Provides functions for computing penalties, tracking sparsity,
|
|
5
|
+
and managing temperature schedules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import torch
|
|
13
|
+
import torch.nn as nn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compute_l0_penalty(model: nn.Module) -> torch.Tensor:
|
|
17
|
+
"""
|
|
18
|
+
Compute total L0 penalty across all L0 layers in a model.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
model : nn.Module
|
|
23
|
+
Model containing L0 layers
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
torch.Tensor
|
|
28
|
+
Total L0 penalty (expected number of active parameters)
|
|
29
|
+
"""
|
|
30
|
+
penalty = torch.tensor(0.0)
|
|
31
|
+
|
|
32
|
+
for module in model.modules():
|
|
33
|
+
if hasattr(module, "get_l0_penalty"):
|
|
34
|
+
penalty = penalty + module.get_l0_penalty() # type: ignore[operator]
|
|
35
|
+
|
|
36
|
+
return penalty
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def compute_l2_penalty(model: nn.Module) -> torch.Tensor:
|
|
40
|
+
"""
|
|
41
|
+
Compute L2 penalty (weight decay) across all layers.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
model : nn.Module
|
|
46
|
+
Model to compute L2 penalty for
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
torch.Tensor
|
|
51
|
+
Total L2 penalty (sum of squared weights)
|
|
52
|
+
"""
|
|
53
|
+
penalty = torch.tensor(0.0)
|
|
54
|
+
|
|
55
|
+
for name, param in model.named_parameters():
|
|
56
|
+
if "weight" in name and param.requires_grad:
|
|
57
|
+
penalty = penalty + (param**2).sum()
|
|
58
|
+
|
|
59
|
+
return penalty
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def compute_l0l2_penalty(
|
|
63
|
+
model: nn.Module,
|
|
64
|
+
l0_lambda: float = 1e-3,
|
|
65
|
+
l2_lambda: float = 1e-4,
|
|
66
|
+
) -> torch.Tensor:
|
|
67
|
+
"""
|
|
68
|
+
Compute combined L0L2 penalty.
|
|
69
|
+
|
|
70
|
+
This combination is recommended in practice to prevent overfitting
|
|
71
|
+
while maintaining sparsity.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
model : nn.Module
|
|
76
|
+
Model to compute penalties for
|
|
77
|
+
l0_lambda : float
|
|
78
|
+
Weight for L0 penalty
|
|
79
|
+
l2_lambda : float
|
|
80
|
+
Weight for L2 penalty
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
torch.Tensor
|
|
85
|
+
Combined penalty value
|
|
86
|
+
"""
|
|
87
|
+
l0_penalty = compute_l0_penalty(model)
|
|
88
|
+
l2_penalty = compute_l2_penalty(model)
|
|
89
|
+
|
|
90
|
+
return l0_lambda * l0_penalty + l2_lambda * l2_penalty
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_sparsity_stats(model: nn.Module) -> dict[str, dict[str, Any]]:
|
|
94
|
+
"""
|
|
95
|
+
Get sparsity statistics for all L0 layers in a model.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
model : nn.Module
|
|
100
|
+
Model containing L0 layers
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
Dict[str, Dict[str, Any]]
|
|
105
|
+
Dictionary mapping layer names to their statistics
|
|
106
|
+
"""
|
|
107
|
+
stats = {}
|
|
108
|
+
|
|
109
|
+
for name, module in model.named_modules():
|
|
110
|
+
if hasattr(module, "get_sparsity") and hasattr(
|
|
111
|
+
module, "get_l0_penalty"
|
|
112
|
+
):
|
|
113
|
+
active_params = module.get_l0_penalty().item()
|
|
114
|
+
|
|
115
|
+
# Calculate total parameters
|
|
116
|
+
if hasattr(module, "weight"):
|
|
117
|
+
total_params = module.weight.numel()
|
|
118
|
+
elif hasattr(module, "gates"):
|
|
119
|
+
total_params = module.gates.gate_size
|
|
120
|
+
if isinstance(total_params, tuple):
|
|
121
|
+
total_params = np.prod(total_params)
|
|
122
|
+
else:
|
|
123
|
+
total_params = active_params / (1 - module.get_sparsity())
|
|
124
|
+
|
|
125
|
+
stats[name] = {
|
|
126
|
+
"sparsity": module.get_sparsity(),
|
|
127
|
+
"active_params": active_params,
|
|
128
|
+
"total_params": total_params,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return stats
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_active_parameter_count(model: nn.Module) -> int:
|
|
135
|
+
"""
|
|
136
|
+
Get the total number of active parameters in the model.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
model : nn.Module
|
|
141
|
+
Model containing L0 layers
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
int
|
|
146
|
+
Total number of active parameters
|
|
147
|
+
"""
|
|
148
|
+
total = 0
|
|
149
|
+
|
|
150
|
+
for module in model.modules():
|
|
151
|
+
if hasattr(module, "get_l0_penalty"):
|
|
152
|
+
total += int(module.get_l0_penalty().item()) # type: ignore[operator]
|
|
153
|
+
|
|
154
|
+
return total
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TemperatureScheduler:
|
|
158
|
+
"""
|
|
159
|
+
Temperature scheduler for annealing during training.
|
|
160
|
+
|
|
161
|
+
Gradually decreases temperature to make gates more discrete.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
initial_temp : float
|
|
166
|
+
Starting temperature
|
|
167
|
+
final_temp : float
|
|
168
|
+
Final temperature
|
|
169
|
+
anneal_epochs : int
|
|
170
|
+
Number of epochs over which to anneal
|
|
171
|
+
schedule : str
|
|
172
|
+
Type of schedule ('linear', 'exponential', 'cosine')
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
initial_temp: float = 2.0,
|
|
178
|
+
final_temp: float = 0.1,
|
|
179
|
+
anneal_epochs: int = 100,
|
|
180
|
+
schedule: str = "exponential",
|
|
181
|
+
):
|
|
182
|
+
self.initial_temp = initial_temp
|
|
183
|
+
self.final_temp = final_temp
|
|
184
|
+
self.anneal_epochs = anneal_epochs
|
|
185
|
+
self.schedule = schedule
|
|
186
|
+
|
|
187
|
+
def get_temperature(self, epoch: int) -> float:
|
|
188
|
+
"""
|
|
189
|
+
Get temperature for a given epoch.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
epoch : int
|
|
194
|
+
Current epoch number
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
float
|
|
199
|
+
Temperature value
|
|
200
|
+
"""
|
|
201
|
+
if epoch >= self.anneal_epochs:
|
|
202
|
+
return self.final_temp
|
|
203
|
+
|
|
204
|
+
progress = epoch / self.anneal_epochs
|
|
205
|
+
|
|
206
|
+
if self.schedule == "linear":
|
|
207
|
+
temp = (
|
|
208
|
+
self.initial_temp
|
|
209
|
+
- (self.initial_temp - self.final_temp) * progress
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
elif self.schedule == "exponential":
|
|
213
|
+
log_temp = (
|
|
214
|
+
np.log(self.initial_temp)
|
|
215
|
+
- (np.log(self.initial_temp) - np.log(self.final_temp))
|
|
216
|
+
* progress
|
|
217
|
+
)
|
|
218
|
+
temp = np.exp(log_temp)
|
|
219
|
+
|
|
220
|
+
elif self.schedule == "cosine":
|
|
221
|
+
temp = (
|
|
222
|
+
self.final_temp
|
|
223
|
+
+ (self.initial_temp - self.final_temp)
|
|
224
|
+
* (1 + np.cos(np.pi * progress))
|
|
225
|
+
/ 2
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError(f"Unknown schedule type: {self.schedule}")
|
|
230
|
+
|
|
231
|
+
return float(temp)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def update_temperatures(model: nn.Module, temperature: float) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Update temperature for all HardConcrete distributions in model.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
model : nn.Module
|
|
241
|
+
Model containing HardConcrete gates
|
|
242
|
+
temperature : float
|
|
243
|
+
New temperature value
|
|
244
|
+
"""
|
|
245
|
+
for module in model.modules():
|
|
246
|
+
# Check for HardConcrete gates in layers
|
|
247
|
+
if hasattr(module, "weight_gates"):
|
|
248
|
+
module.weight_gates.temperature = temperature # type: ignore[union-attr]
|
|
249
|
+
if hasattr(module, "channel_gates"):
|
|
250
|
+
module.channel_gates.temperature = temperature # type: ignore[union-attr]
|
|
251
|
+
if hasattr(module, "gates"):
|
|
252
|
+
if hasattr(module.gates, "temperature"):
|
|
253
|
+
module.gates.temperature = temperature
|
|
254
|
+
|
|
255
|
+
# Direct HardConcrete modules
|
|
256
|
+
from .distributions import HardConcrete
|
|
257
|
+
|
|
258
|
+
if isinstance(module, HardConcrete):
|
|
259
|
+
module.temperature = temperature
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class PenaltyTracker:
|
|
263
|
+
"""
|
|
264
|
+
Track penalties and sparsity metrics during training.
|
|
265
|
+
|
|
266
|
+
Useful for monitoring and visualization.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(self) -> None:
|
|
270
|
+
self.history: dict[str, list[float]] = defaultdict(list)
|
|
271
|
+
|
|
272
|
+
def log(self, name: str, value: float) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Log a metric value.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
name : str
|
|
279
|
+
Metric name
|
|
280
|
+
value : float
|
|
281
|
+
Metric value
|
|
282
|
+
"""
|
|
283
|
+
self.history[name].append(value)
|
|
284
|
+
|
|
285
|
+
def get_history(self, name: str) -> list[float]:
|
|
286
|
+
"""
|
|
287
|
+
Get history for a metric.
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
name : str
|
|
292
|
+
Metric name
|
|
293
|
+
|
|
294
|
+
Returns
|
|
295
|
+
-------
|
|
296
|
+
List[float]
|
|
297
|
+
History of values
|
|
298
|
+
"""
|
|
299
|
+
return self.history[name]
|
|
300
|
+
|
|
301
|
+
def get_stats(self, name: str) -> dict[str, float]:
|
|
302
|
+
"""
|
|
303
|
+
Get statistics for a metric.
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
name : str
|
|
308
|
+
Metric name
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
Dict[str, float]
|
|
313
|
+
Statistics (mean, min, max, last)
|
|
314
|
+
"""
|
|
315
|
+
values = self.history[name]
|
|
316
|
+
if not values:
|
|
317
|
+
return {}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"mean": float(np.mean(values)),
|
|
321
|
+
"std": float(np.std(values)),
|
|
322
|
+
"min": float(np.min(values)),
|
|
323
|
+
"max": float(np.max(values)),
|
|
324
|
+
"last": values[-1],
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
def clear(self) -> None:
|
|
328
|
+
"""Clear all history."""
|
|
329
|
+
self.history.clear()
|
|
330
|
+
|
|
331
|
+
def save_to_csv(self, filepath: str) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Save history to CSV file.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
filepath : str
|
|
338
|
+
Path to save CSV
|
|
339
|
+
"""
|
|
340
|
+
import pandas as pd
|
|
341
|
+
|
|
342
|
+
df = pd.DataFrame(self.history)
|
|
343
|
+
df.to_csv(filepath, index=False)
|
|
344
|
+
|
|
345
|
+
def plot_history(
|
|
346
|
+
self,
|
|
347
|
+
metrics: list[str] | None = None,
|
|
348
|
+
save_path: str | None = None,
|
|
349
|
+
) -> None:
|
|
350
|
+
"""
|
|
351
|
+
Plot metric history.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
metrics : Optional[List[str]]
|
|
356
|
+
Metrics to plot (all if None)
|
|
357
|
+
save_path : Optional[str]
|
|
358
|
+
Path to save figure
|
|
359
|
+
"""
|
|
360
|
+
import matplotlib.pyplot as plt
|
|
361
|
+
|
|
362
|
+
metrics = metrics or list(self.history.keys())
|
|
363
|
+
|
|
364
|
+
fig, axes = plt.subplots(
|
|
365
|
+
len(metrics), 1, figsize=(10, 4 * len(metrics)), squeeze=False
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
for i, metric in enumerate(metrics):
|
|
369
|
+
values = self.history[metric]
|
|
370
|
+
axes[i, 0].plot(values)
|
|
371
|
+
axes[i, 0].set_title(metric)
|
|
372
|
+
axes[i, 0].set_xlabel("Step")
|
|
373
|
+
axes[i, 0].set_ylabel("Value")
|
|
374
|
+
axes[i, 0].grid(True)
|
|
375
|
+
|
|
376
|
+
plt.tight_layout()
|
|
377
|
+
|
|
378
|
+
if save_path:
|
|
379
|
+
plt.savefig(save_path)
|
|
380
|
+
else:
|
|
381
|
+
plt.show()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: l0-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: L0 regularization for sparse neural networks and intelligent sampling
|
|
5
|
+
Author-email: PolicyEngine <hello@policyengine.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 PolicyEngine
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/PolicyEngine/L0
|
|
29
|
+
Project-URL: Repository, https://github.com/PolicyEngine/L0
|
|
30
|
+
Project-URL: Documentation, https://github.com/PolicyEngine/L0#readme
|
|
31
|
+
Project-URL: Issues, https://github.com/PolicyEngine/L0/issues
|
|
32
|
+
Keywords: l0,regularization,sparsity,neural networks,calibration
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
|
+
Requires-Python: >=3.13
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: torch>=2.0.0
|
|
43
|
+
Requires-Dist: numpy>=1.20.0
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
46
|
+
Requires-Dist: pytest-cov>=3.0; extra == "dev"
|
|
47
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
48
|
+
Requires-Dist: mypy>=0.900; extra == "dev"
|
|
49
|
+
Requires-Dist: ruff>=0.0.280; extra == "dev"
|
|
50
|
+
Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
51
|
+
Requires-Dist: build>=1.3; extra == "dev"
|
|
52
|
+
Provides-Extra: docs
|
|
53
|
+
Requires-Dist: mystmd>=1.3; extra == "docs"
|
|
54
|
+
Requires-Dist: jupyter>=1.0; extra == "docs"
|
|
55
|
+
Requires-Dist: notebook>=7.0; extra == "docs"
|
|
56
|
+
Requires-Dist: ipykernel>=6.0; extra == "docs"
|
|
57
|
+
Provides-Extra: examples
|
|
58
|
+
Requires-Dist: matplotlib>=3.3; extra == "examples"
|
|
59
|
+
Requires-Dist: tqdm>=4.60; extra == "examples"
|
|
60
|
+
Requires-Dist: pandas>=1.3; extra == "examples"
|
|
61
|
+
Requires-Dist: scikit-learn>=1.0; extra == "examples"
|
|
62
|
+
Requires-Dist: torchvision>=0.15; extra == "examples"
|
|
63
|
+
Dynamic: license-file
|
|
64
|
+
|
|
65
|
+
# L0 Regularization
|
|
66
|
+
|
|
67
|
+
A PyTorch implementation of L0 regularization for neural network sparsification and intelligent sampling, based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312).
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **Hard Concrete Distribution**: Differentiable approximation of L0 norm
|
|
72
|
+
- **Sparse Neural Network Layers**: L0Linear, L0Conv2d with automatic pruning
|
|
73
|
+
- **Intelligent Sampling**: Sample/feature selection gates for calibration
|
|
74
|
+
- **L0L2 Combined Penalty**: Recommended approach to prevent overfitting
|
|
75
|
+
- **Temperature Scheduling**: Annealing for improved convergence
|
|
76
|
+
- **TDD Development**: Comprehensive test coverage
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install l0
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For development:
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/PolicyEngine/L0.git
|
|
87
|
+
cd L0
|
|
88
|
+
pip install -e .[dev]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
### Neural Network Sparsification
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import torch
|
|
97
|
+
from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
|
|
98
|
+
|
|
99
|
+
# Create a sparse model
|
|
100
|
+
class SparseModel(torch.nn.Module):
|
|
101
|
+
def __init__(self):
|
|
102
|
+
super().__init__()
|
|
103
|
+
self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
|
|
104
|
+
self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
|
|
105
|
+
|
|
106
|
+
def forward(self, x):
|
|
107
|
+
x = torch.relu(self.fc1(x))
|
|
108
|
+
return self.fc2(x)
|
|
109
|
+
|
|
110
|
+
model = SparseModel()
|
|
111
|
+
optimizer = torch.optim.Adam(model.parameters())
|
|
112
|
+
scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
|
|
113
|
+
|
|
114
|
+
# Training loop
|
|
115
|
+
for epoch in range(100):
|
|
116
|
+
# Update temperature
|
|
117
|
+
temp = scheduler.get_temperature(epoch)
|
|
118
|
+
update_temperatures(model, temp)
|
|
119
|
+
|
|
120
|
+
# Forward pass
|
|
121
|
+
output = model(input_data)
|
|
122
|
+
ce_loss = criterion(output, target)
|
|
123
|
+
|
|
124
|
+
# Add L0L2 penalty
|
|
125
|
+
penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
|
|
126
|
+
loss = ce_loss + penalty
|
|
127
|
+
|
|
128
|
+
# Backward pass
|
|
129
|
+
optimizer.zero_grad()
|
|
130
|
+
loss.backward()
|
|
131
|
+
optimizer.step()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Intelligent Sample Selection
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from l0 import SampleGate, HybridGate
|
|
138
|
+
|
|
139
|
+
# Pure L0 selection
|
|
140
|
+
gate = SampleGate(n_samples=10000, target_samples=1000)
|
|
141
|
+
selected_data, indices = gate.select_samples(data)
|
|
142
|
+
|
|
143
|
+
# Hybrid selection (25% L0, 75% random)
|
|
144
|
+
hybrid = HybridGate(
|
|
145
|
+
n_items=10000,
|
|
146
|
+
l0_fraction=0.25,
|
|
147
|
+
random_fraction=0.75,
|
|
148
|
+
target_items=1000
|
|
149
|
+
)
|
|
150
|
+
selected, indices, types = hybrid.select(data)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Feature Selection
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from l0 import FeatureGate
|
|
157
|
+
|
|
158
|
+
# Select top features
|
|
159
|
+
gate = FeatureGate(n_features=1000, max_features=50)
|
|
160
|
+
selected_data, feature_indices = gate.select_features(data)
|
|
161
|
+
|
|
162
|
+
# Get feature importance
|
|
163
|
+
importance = gate.get_feature_importance()
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Integration with PolicyEngine
|
|
167
|
+
|
|
168
|
+
This package is designed to work with PolicyEngine's calibration system:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# In policyengine-us-data or similar
|
|
172
|
+
from l0 import HardConcrete
|
|
173
|
+
|
|
174
|
+
# Use for household selection in CPS calibration
|
|
175
|
+
gates = HardConcrete(
|
|
176
|
+
len(household_weights),
|
|
177
|
+
temperature=0.25,
|
|
178
|
+
init_mean=0.999 # Start with most households
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Apply gates during reweighting
|
|
182
|
+
masked_weights = weights * gates()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
Full documentation available at: https://policyengine.github.io/L0/
|
|
188
|
+
|
|
189
|
+
## Testing
|
|
190
|
+
|
|
191
|
+
Run tests with:
|
|
192
|
+
```bash
|
|
193
|
+
pytest tests/ -v --cov=l0
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Acknowledgments
|
|
197
|
+
|
|
198
|
+
This implementation is inspired by and builds upon the [original L0 regularization code](https://github.com/AMLab-Amsterdam/L0_regularization) by AMLab Amsterdam, which accompanied the paper by Louizos et al. (2018).
|
|
199
|
+
|
|
200
|
+
## Citation
|
|
201
|
+
|
|
202
|
+
If you use this package, please cite:
|
|
203
|
+
|
|
204
|
+
```bibtex
|
|
205
|
+
@article{louizos2017learning,
|
|
206
|
+
title={Learning Sparse Neural Networks through L0 Regularization},
|
|
207
|
+
author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
|
|
208
|
+
journal={arXiv preprint arXiv:1712.01312},
|
|
209
|
+
year={2017}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
l0/__init__.py,sha256=meL6jJ1CxO6pO373nY-nwGXnpzWZGC1Ts6q2TLyAeLM,1122
|
|
2
|
+
l0/distributions.py,sha256=GvVoDasdxoDlPPdzt33lFzkuJgQsfkEn9e-abxru3nM,6325
|
|
3
|
+
l0/gates.py,sha256=54OORMg9qSsdbIyzYIL9t9CCHWsKbumSKl_d4-YFuNU,11443
|
|
4
|
+
l0/layers.py,sha256=j_DPEFdfUc37ojtoK2ufGzVYzZ_DwusqTcJXCVxZS68,13785
|
|
5
|
+
l0/penalties.py,sha256=okqXgX2eGUmg5prcDclicTQALPPPF9FA5eQP09V4GTE,9569
|
|
6
|
+
l0_python-0.1.0.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
|
|
7
|
+
l0_python-0.1.0.dist-info/METADATA,sha256=hrAugZ-F7oKmleyJ8WjVTykRixBmuTiQ2M4oae1Ceu4,6794
|
|
8
|
+
l0_python-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
l0_python-0.1.0.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
|
|
10
|
+
l0_python-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 PolicyEngine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
l0
|