l0-python 0.4.1__py3-none-any.whl → 0.4.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.
l0/calibration.py CHANGED
@@ -444,9 +444,9 @@ class SparseCalibrationWeights(nn.Module):
444
444
  rel_errors[group_mask].mean().item()
445
445
  )
446
446
  group_losses.append(group_mean_err)
447
- mean_group_loss = np.mean(group_losses)
447
+ mean_group_mare = np.mean(group_losses)
448
448
  else:
449
- mean_group_loss = mean_rel_err
449
+ mean_group_mare = mean_rel_err
450
450
 
451
451
  # Calculate sparsity percentage
452
452
  sparsity_pct = 100 * (
@@ -461,7 +461,7 @@ class SparseCalibrationWeights(nn.Module):
461
461
  if target_groups is not None:
462
462
  print(
463
463
  f"Epoch {epoch+1:4d}: "
464
- f"mean_group_loss={mean_group_loss:.1%}, "
464
+ f"mean_group_mare={mean_group_mare:.4%}, "
465
465
  f"max_error={max_rel_err:.1%}, "
466
466
  f"total_loss={actual_total_loss:.3f}, "
467
467
  f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)"
@@ -469,7 +469,7 @@ class SparseCalibrationWeights(nn.Module):
469
469
  else:
470
470
  print(
471
471
  f"Epoch {epoch+1:4d}: "
472
- f"mean_error={mean_rel_err:.1%}, "
472
+ f"mean_error={mean_rel_err:.4%}, "
473
473
  f"max_error={max_rel_err:.1%}, "
474
474
  f"total_loss={actual_total_loss:.3f}, "
475
475
  f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: l0-python
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: L0 regularization for sparse neural networks and intelligent sampling
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: MIT License
@@ -65,21 +65,15 @@ Dynamic: license-file
65
65
 
66
66
  # L0 Regularization
67
67
 
68
- 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
+ [![PyPI version](https://badge.fury.io/py/l0-python.svg)](https://pypi.org/project/l0-python/)
69
+ [![CI](https://github.com/PolicyEngine/L0/actions/workflows/push.yml/badge.svg)](https://github.com/PolicyEngine/L0/actions)
69
70
 
70
- ## Features
71
-
72
- - **Hard Concrete Distribution**: Differentiable approximation of L0 norm
73
- - **Sparse Neural Network Layers**: L0Linear, L0Conv2d with automatic pruning
74
- - **Intelligent Sampling**: Sample/feature selection gates for calibration
75
- - **L0L2 Combined Penalty**: Recommended approach to prevent overfitting
76
- - **Temperature Scheduling**: Annealing for improved convergence
77
- - **TDD Development**: Comprehensive test coverage
71
+ A PyTorch implementation of L0 regularization based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312), designed for survey calibration and sparse regression.
78
72
 
79
73
  ## Installation
80
74
 
81
75
  ```bash
82
- pip install l0
76
+ pip install l0-python
83
77
  ```
84
78
 
85
79
  For development:
@@ -89,119 +83,117 @@ cd L0
89
83
  pip install -e .[dev]
90
84
  ```
91
85
 
92
- ## Quick Start
86
+ ## Our Approach to Test-Time Gates
93
87
 
94
- ### Neural Network Sparsification
88
+ The original Hard Concrete formulation uses temperature (β) during training to control the sharpness of stochastic gates. At test time, there's a design choice: whether to include temperature in the deterministic gate computation.
95
89
 
90
+ We include temperature at test time:
96
91
  ```python
97
- import torch
98
- from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
99
-
100
- # Create a sparse model
101
- class SparseModel(torch.nn.Module):
102
- def __init__(self):
103
- super().__init__()
104
- self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
105
- self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
106
-
107
- def forward(self, x):
108
- x = torch.relu(self.fc1(x))
109
- return self.fc2(x)
110
-
111
- model = SparseModel()
112
- optimizer = torch.optim.Adam(model.parameters())
113
- scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
114
-
115
- # Training loop
116
- for epoch in range(100):
117
- # Update temperature
118
- temp = scheduler.get_temperature(epoch)
119
- update_temperatures(model, temp)
120
-
121
- # Forward pass
122
- output = model(input_data)
123
- ce_loss = criterion(output, target)
124
-
125
- # Add L0L2 penalty
126
- penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
127
- loss = ce_loss + penalty
128
-
129
- # Backward pass
130
- optimizer.zero_grad()
131
- loss.backward()
132
- optimizer.step()
92
+ # Our approach: include temperature
93
+ z = sigmoid(log_alpha / beta) * (zeta - gamma) + gamma
94
+
95
+ # Alternative: omit temperature
96
+ z = sigmoid(log_alpha) * (zeta - gamma) + gamma
133
97
  ```
134
98
 
135
- ### Intelligent Sample Selection
99
+ Including temperature produces sharper 0/1 decisions, which we find beneficial for achieving clean sparsity in our applications. See `examples/sparse_regression_demo.py` for a demonstration on a 4-variable regression problem.
100
+
101
+ ## Primary Use Case: Survey Calibration
102
+
103
+ This package was developed for PolicyEngine's survey calibration, where we select a sparse subset of survey households while matching population targets.
136
104
 
137
105
  ```python
138
- from l0 import SampleGate, HybridGate
139
-
140
- # Pure L0 selection
141
- gate = SampleGate(n_samples=10000, target_samples=1000)
142
- selected_data, indices = gate.select_samples(data)
143
-
144
- # Hybrid selection (25% L0, 75% random)
145
- hybrid = HybridGate(
146
- n_items=10000,
147
- l0_fraction=0.25,
148
- random_fraction=0.75,
149
- target_items=1000
106
+ import numpy as np
107
+ from scipy import sparse as sp
108
+ from l0.calibration import SparseCalibrationWeights
109
+
110
+ # Setup: Q targets, N households
111
+ Q, N = 200, 10000
112
+ M = sp.random(Q, N, density=0.3, format="csr") # Household characteristics
113
+ y = np.random.uniform(1e6, 1e8, size=Q) # Population targets
114
+
115
+ # Initialize model
116
+ model = SparseCalibrationWeights(
117
+ n_features=N,
118
+ beta=0.35,
119
+ gamma=-0.1,
120
+ zeta=1.1,
121
+ init_keep_prob=0.5,
122
+ init_weights=1.0,
123
+ log_weight_jitter_sd=0.05,
124
+ device="cuda",
125
+ )
126
+
127
+ # Train with L0+L2 regularization
128
+ model.fit(
129
+ M=M,
130
+ y=y,
131
+ lambda_l0=1e-6,
132
+ lambda_l2=1e-8,
133
+ lr=0.15,
134
+ epochs=2000,
135
+ loss_type="relative",
136
+ verbose=True,
150
137
  )
151
- selected, indices, types = hybrid.select(data)
138
+
139
+ # Get results
140
+ active = model.get_active_weights()
141
+ print(f"Selected {active['count']} of {N} households")
142
+ print(f"Sparsity: {model.get_sparsity():.1%}")
152
143
  ```
153
144
 
154
- ### Feature Selection
145
+ ### Key Features
146
+
147
+ - **Non-negative weights**: Constrained via log-space parameterization
148
+ - **L0 sparsity**: Directly minimizes the count of active weights
149
+ - **Relative loss**: Scale-invariant for targets spanning orders of magnitude
150
+ - **Group-wise averaging**: Balance loss across target groups with different sizes
151
+ - **GPU support**: CUDA acceleration for large problems
152
+
153
+ ## Sparse Regression
154
+
155
+ For sparse linear regression with scipy sparse matrices:
155
156
 
156
157
  ```python
157
- from l0 import FeatureGate
158
+ from scipy import sparse as sp
159
+ from l0.sparse import SparseL0Linear
158
160
 
159
- # Select top features
160
- gate = FeatureGate(n_features=1000, max_features=50)
161
- selected_data, feature_indices = gate.select_features(data)
161
+ # Sparse design matrix
162
+ X = sp.random(1000, 500, density=0.1, format="csr")
163
+ y = np.random.randn(1000)
162
164
 
163
- # Get feature importance
164
- importance = gate.get_feature_importance()
165
+ model = SparseL0Linear(n_features=500)
166
+ model.fit(X, y, lambda_l0=0.001, epochs=1000)
167
+
168
+ # Get sparse coefficients
169
+ coef = model.get_coefficients(threshold=0.01)
165
170
  ```
166
171
 
167
- ## Integration with PolicyEngine
172
+ ## Example: Variable Selection
168
173
 
169
- This package is designed to work with PolicyEngine's calibration system:
174
+ The `examples/sparse_regression_demo.py` script demonstrates L0 regularization on a simple problem where the true coefficients are `[1, 0, -2, 0]`:
170
175
 
171
- ```python
172
- # In policyengine-us-data or similar
173
- from l0 import HardConcrete
174
-
175
- # Use for household selection in CPS calibration
176
- gates = HardConcrete(
177
- len(household_weights),
178
- temperature=0.25,
179
- init_mean=0.999 # Start with most households
180
- )
181
-
182
- # Apply gates during reweighting
183
- masked_weights = weights * gates()
176
+ ```bash
177
+ python examples/sparse_regression_demo.py
184
178
  ```
185
179
 
186
- ## Documentation
180
+ Output:
181
+ ```
182
+ True coefficients: [ 1. 0. -2. 0.]
183
+ Recovered coefficients: [ 1.039 0. -2.069 -0. ]
184
+ Gates: [1. 0. 1. 0.]
185
+ ```
187
186
 
188
- Full documentation available at: https://policyengine.github.io/L0/
187
+ The model correctly identifies that only variables 1 and 3 contribute to the outcome.
189
188
 
190
189
  ## Testing
191
190
 
192
- Run tests with:
193
191
  ```bash
194
192
  pytest tests/ -v --cov=l0
195
193
  ```
196
194
 
197
- ## Acknowledgments
198
-
199
- 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).
200
-
201
195
  ## Citation
202
196
 
203
- If you use this package, please cite:
204
-
205
197
  ```bibtex
206
198
  @article{louizos2017learning,
207
199
  title={Learning Sparse Neural Networks through L0 Regularization},
@@ -213,4 +205,4 @@ If you use this package, please cite:
213
205
 
214
206
  ## License
215
207
 
216
- MIT License - see [LICENSE](LICENSE) file for details.
208
+ MIT License - see [LICENSE](LICENSE) for details.
@@ -1,12 +1,12 @@
1
1
  l0/__init__.py,sha256=ezGVLGbdU_jAElAJSdLKjSWCnZRILAZi8qMBPjcHwQc,1192
2
- l0/calibration.py,sha256=EnIhQPkC6yvRp0-o1qG3SCVUp6pIqwkqkx3DX3uWdG0,17838
2
+ l0/calibration.py,sha256=3VM-28M1yp-DM6AlHX7sqZCaE7OJevpY9Ey-syjSPik,17838
3
3
  l0/distributions.py,sha256=GvVoDasdxoDlPPdzt33lFzkuJgQsfkEn9e-abxru3nM,6325
4
4
  l0/gates.py,sha256=54OORMg9qSsdbIyzYIL9t9CCHWsKbumSKl_d4-YFuNU,11443
5
5
  l0/layers.py,sha256=j_DPEFdfUc37ojtoK2ufGzVYzZ_DwusqTcJXCVxZS68,13785
6
6
  l0/penalties.py,sha256=okqXgX2eGUmg5prcDclicTQALPPPF9FA5eQP09V4GTE,9569
7
7
  l0/sparse.py,sha256=UVpkrrJBnQ9iSNDoWK5F_sr43tQareWXzYHyMq_cKss,9726
8
- l0_python-0.4.1.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
9
- l0_python-0.4.1.dist-info/METADATA,sha256=K89M7X45E5jCgA_2-YBGY238rfLlxQOUFWePoFPGiLk,6839
10
- l0_python-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- l0_python-0.4.1.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
12
- l0_python-0.4.1.dist-info/RECORD,,
8
+ l0_python-0.4.3.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
9
+ l0_python-0.4.3.dist-info/METADATA,sha256=gN2ufd7zkVDielkTS2iGbUeKkf9bIMTiGZtvCkpc960,7095
10
+ l0_python-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ l0_python-0.4.3.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
12
+ l0_python-0.4.3.dist-info/RECORD,,