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 +4 -4
- {l0_python-0.4.1.dist-info → l0_python-0.4.3.dist-info}/METADATA +87 -95
- {l0_python-0.4.1.dist-info → l0_python-0.4.3.dist-info}/RECORD +6 -6
- {l0_python-0.4.1.dist-info → l0_python-0.4.3.dist-info}/WHEEL +0 -0
- {l0_python-0.4.1.dist-info → l0_python-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {l0_python-0.4.1.dist-info → l0_python-0.4.3.dist-info}/top_level.txt +0 -0
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
|
-
|
|
447
|
+
mean_group_mare = np.mean(group_losses)
|
|
448
448
|
else:
|
|
449
|
-
|
|
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"
|
|
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:.
|
|
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.
|
|
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
|
-
|
|
68
|
+
[](https://pypi.org/project/l0-python/)
|
|
69
|
+
[](https://github.com/PolicyEngine/L0/actions)
|
|
69
70
|
|
|
70
|
-
|
|
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
|
-
##
|
|
86
|
+
## Our Approach to Test-Time Gates
|
|
93
87
|
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
#
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
158
|
+
from scipy import sparse as sp
|
|
159
|
+
from l0.sparse import SparseL0Linear
|
|
158
160
|
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
# Sparse design matrix
|
|
162
|
+
X = sp.random(1000, 500, density=0.1, format="csr")
|
|
163
|
+
y = np.random.randn(1000)
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
##
|
|
172
|
+
## Example: Variable Selection
|
|
168
173
|
|
|
169
|
-
|
|
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
|
-
```
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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=
|
|
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.
|
|
9
|
-
l0_python-0.4.
|
|
10
|
-
l0_python-0.4.
|
|
11
|
-
l0_python-0.4.
|
|
12
|
-
l0_python-0.4.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|