l0-python 0.4.2__tar.gz → 0.4.3__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.
- {l0_python-0.4.2/l0_python.egg-info → l0_python-0.4.3}/PKG-INFO +54 -101
- l0_python-0.4.3/README.md +143 -0
- {l0_python-0.4.2 → l0_python-0.4.3/l0_python.egg-info}/PKG-INFO +54 -101
- {l0_python-0.4.2 → l0_python-0.4.3}/pyproject.toml +1 -1
- l0_python-0.4.2/README.md +0 -190
- {l0_python-0.4.2 → l0_python-0.4.3}/LICENSE +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/__init__.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/calibration.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/distributions.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/gates.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/layers.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/penalties.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0/sparse.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0_python.egg-info/SOURCES.txt +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0_python.egg-info/dependency_links.txt +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0_python.egg-info/requires.txt +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/l0_python.egg-info/top_level.txt +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/setup.cfg +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_calibration.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_distributions.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_gates.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_layers.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_penalties.py +0 -0
- {l0_python-0.4.2 → l0_python-0.4.3}/tests/test_sparse.py +0 -0
|
@@ -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
|
|
@@ -68,21 +68,7 @@ Dynamic: license-file
|
|
|
68
68
|
[](https://pypi.org/project/l0-python/)
|
|
69
69
|
[](https://github.com/PolicyEngine/L0/actions)
|
|
70
70
|
|
|
71
|
-
A PyTorch implementation of L0 regularization based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312),
|
|
72
|
-
|
|
73
|
-
## Why This Package?
|
|
74
|
-
|
|
75
|
-
The original L0 implementation from AMLab-Amsterdam contains a bug in test-time gate computation where the temperature parameter is incorrectly omitted. This prevents proper sparsity and degrades performance. Our implementation corrects this error:
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
# Original (buggy): gates never fully close
|
|
79
|
-
pi = sigmoid(log_alpha)
|
|
80
|
-
|
|
81
|
-
# Corrected: temperature division required for proper sparsity
|
|
82
|
-
pi = sigmoid(log_alpha / temperature)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
This fix enables gates to achieve true 0/1 values, producing exact sparsity as intended by the L0 formulation.
|
|
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.
|
|
86
72
|
|
|
87
73
|
## Installation
|
|
88
74
|
|
|
@@ -97,9 +83,24 @@ cd L0
|
|
|
97
83
|
pip install -e .[dev]
|
|
98
84
|
```
|
|
99
85
|
|
|
86
|
+
## Our Approach to Test-Time Gates
|
|
87
|
+
|
|
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.
|
|
89
|
+
|
|
90
|
+
We include temperature at test time:
|
|
91
|
+
```python
|
|
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
|
|
97
|
+
```
|
|
98
|
+
|
|
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
|
+
|
|
100
101
|
## Primary Use Case: Survey Calibration
|
|
101
102
|
|
|
102
|
-
This package was developed for PolicyEngine's survey calibration
|
|
103
|
+
This package was developed for PolicyEngine's survey calibration, where we select a sparse subset of survey households while matching population targets.
|
|
103
104
|
|
|
104
105
|
```python
|
|
105
106
|
import numpy as np
|
|
@@ -117,119 +118,73 @@ model = SparseCalibrationWeights(
|
|
|
117
118
|
beta=0.35,
|
|
118
119
|
gamma=-0.1,
|
|
119
120
|
zeta=1.1,
|
|
120
|
-
init_keep_prob=0.5,
|
|
121
|
-
init_weights=1.0,
|
|
121
|
+
init_keep_prob=0.5,
|
|
122
|
+
init_weights=1.0,
|
|
122
123
|
log_weight_jitter_sd=0.05,
|
|
123
|
-
device="cuda",
|
|
124
|
+
device="cuda",
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
# Train with L0+L2 regularization
|
|
127
128
|
model.fit(
|
|
128
129
|
M=M,
|
|
129
130
|
y=y,
|
|
130
|
-
lambda_l0=1e-6,
|
|
131
|
-
lambda_l2=1e-8,
|
|
131
|
+
lambda_l0=1e-6,
|
|
132
|
+
lambda_l2=1e-8,
|
|
132
133
|
lr=0.15,
|
|
133
134
|
epochs=2000,
|
|
134
|
-
loss_type="relative",
|
|
135
|
+
loss_type="relative",
|
|
135
136
|
verbose=True,
|
|
136
|
-
verbose_freq=100,
|
|
137
137
|
)
|
|
138
138
|
|
|
139
139
|
# Get results
|
|
140
140
|
active = model.get_active_weights()
|
|
141
|
-
print(f"Selected {active['count']} of {N} households
|
|
141
|
+
print(f"Selected {active['count']} of {N} households")
|
|
142
142
|
print(f"Sparsity: {model.get_sparsity():.1%}")
|
|
143
|
-
|
|
144
|
-
# Predict calibrated totals
|
|
145
|
-
y_pred = model.predict(M)
|
|
146
143
|
```
|
|
147
144
|
|
|
148
|
-
### Key Features
|
|
145
|
+
### Key Features
|
|
149
146
|
|
|
150
|
-
- **Non-negative weights**:
|
|
151
|
-
- **
|
|
152
|
-
- **Relative loss**: Scale-invariant
|
|
153
|
-
- **Group-wise averaging**: Balance loss
|
|
154
|
-
- **GPU support**: CUDA acceleration for large
|
|
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
|
|
155
152
|
|
|
156
|
-
##
|
|
153
|
+
## Sparse Regression
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
For sparse linear regression with scipy sparse matrices:
|
|
159
156
|
|
|
160
157
|
```python
|
|
161
|
-
import
|
|
162
|
-
from l0 import
|
|
163
|
-
|
|
164
|
-
class SparseModel(torch.nn.Module):
|
|
165
|
-
def __init__(self):
|
|
166
|
-
super().__init__()
|
|
167
|
-
self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
|
|
168
|
-
self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
|
|
169
|
-
|
|
170
|
-
def forward(self, x):
|
|
171
|
-
x = torch.relu(self.fc1(x))
|
|
172
|
-
return self.fc2(x)
|
|
173
|
-
|
|
174
|
-
model = SparseModel()
|
|
175
|
-
optimizer = torch.optim.Adam(model.parameters())
|
|
176
|
-
scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
|
|
177
|
-
|
|
178
|
-
for epoch in range(100):
|
|
179
|
-
temp = scheduler.get_temperature(epoch)
|
|
180
|
-
update_temperatures(model, temp)
|
|
181
|
-
|
|
182
|
-
output = model(input_data)
|
|
183
|
-
ce_loss = criterion(output, target)
|
|
184
|
-
penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
|
|
185
|
-
loss = ce_loss + penalty
|
|
186
|
-
|
|
187
|
-
optimizer.zero_grad()
|
|
188
|
-
loss.backward()
|
|
189
|
-
optimizer.step()
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Available Layers
|
|
193
|
-
|
|
194
|
-
- `L0Linear`: Fully connected layer with L0 gates on weights
|
|
195
|
-
- `L0Conv2d`: 2D convolution with channel-wise L0 gates
|
|
196
|
-
- `L0DepthwiseConv2d`: Depthwise convolution with L0 gates
|
|
197
|
-
- `SparseMLP`: Multi-layer perceptron with built-in L0 regularization
|
|
158
|
+
from scipy import sparse as sp
|
|
159
|
+
from l0.sparse import SparseL0Linear
|
|
198
160
|
|
|
199
|
-
|
|
161
|
+
# Sparse design matrix
|
|
162
|
+
X = sp.random(1000, 500, density=0.1, format="csr")
|
|
163
|
+
y = np.random.randn(1000)
|
|
200
164
|
|
|
201
|
-
|
|
165
|
+
model = SparseL0Linear(n_features=500)
|
|
166
|
+
model.fit(X, y, lambda_l0=0.001, epochs=1000)
|
|
202
167
|
|
|
203
|
-
|
|
204
|
-
|
|
168
|
+
# Get sparse coefficients
|
|
169
|
+
coef = model.get_coefficients(threshold=0.01)
|
|
170
|
+
```
|
|
205
171
|
|
|
206
|
-
|
|
207
|
-
gate = SampleGate(n_samples=10000, target_samples=1000)
|
|
208
|
-
selected_data, indices = gate.select_samples(data)
|
|
172
|
+
## Example: Variable Selection
|
|
209
173
|
|
|
210
|
-
|
|
211
|
-
gate = FeatureGate(n_features=1000, max_features=50)
|
|
212
|
-
selected_data, indices = gate.select_features(data)
|
|
174
|
+
The `examples/sparse_regression_demo.py` script demonstrates L0 regularization on a simple problem where the true coefficients are `[1, 0, -2, 0]`:
|
|
213
175
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
n_items=10000,
|
|
217
|
-
l0_fraction=0.25, # 25% via learned L0 gates
|
|
218
|
-
random_fraction=0.75 # 75% random for coverage
|
|
219
|
-
)
|
|
220
|
-
selected, indices, types = hybrid.select(data)
|
|
176
|
+
```bash
|
|
177
|
+
python examples/sparse_regression_demo.py
|
|
221
178
|
```
|
|
222
179
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
1.
|
|
228
|
-
|
|
229
|
-
3. **Expected L0 penalty**: Minimizes the expected number of active gates
|
|
230
|
-
4. **Temperature annealing**: Gradually sharpens gates from soft to hard decisions
|
|
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
|
+
```
|
|
231
186
|
|
|
232
|
-
The
|
|
187
|
+
The model correctly identifies that only variables 1 and 3 contribute to the outcome.
|
|
233
188
|
|
|
234
189
|
## Testing
|
|
235
190
|
|
|
@@ -239,8 +194,6 @@ pytest tests/ -v --cov=l0
|
|
|
239
194
|
|
|
240
195
|
## Citation
|
|
241
196
|
|
|
242
|
-
If you use this package, please cite the original paper:
|
|
243
|
-
|
|
244
197
|
```bibtex
|
|
245
198
|
@article{louizos2017learning,
|
|
246
199
|
title={Learning Sparse Neural Networks through L0 Regularization},
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# L0 Regularization
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/l0-python/)
|
|
4
|
+
[](https://github.com/PolicyEngine/L0/actions)
|
|
5
|
+
|
|
6
|
+
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.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install l0-python
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For development:
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/PolicyEngine/L0.git
|
|
17
|
+
cd L0
|
|
18
|
+
pip install -e .[dev]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Our Approach to Test-Time Gates
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
We include temperature at test time:
|
|
26
|
+
```python
|
|
27
|
+
# Our approach: include temperature
|
|
28
|
+
z = sigmoid(log_alpha / beta) * (zeta - gamma) + gamma
|
|
29
|
+
|
|
30
|
+
# Alternative: omit temperature
|
|
31
|
+
z = sigmoid(log_alpha) * (zeta - gamma) + gamma
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
## Primary Use Case: Survey Calibration
|
|
37
|
+
|
|
38
|
+
This package was developed for PolicyEngine's survey calibration, where we select a sparse subset of survey households while matching population targets.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import numpy as np
|
|
42
|
+
from scipy import sparse as sp
|
|
43
|
+
from l0.calibration import SparseCalibrationWeights
|
|
44
|
+
|
|
45
|
+
# Setup: Q targets, N households
|
|
46
|
+
Q, N = 200, 10000
|
|
47
|
+
M = sp.random(Q, N, density=0.3, format="csr") # Household characteristics
|
|
48
|
+
y = np.random.uniform(1e6, 1e8, size=Q) # Population targets
|
|
49
|
+
|
|
50
|
+
# Initialize model
|
|
51
|
+
model = SparseCalibrationWeights(
|
|
52
|
+
n_features=N,
|
|
53
|
+
beta=0.35,
|
|
54
|
+
gamma=-0.1,
|
|
55
|
+
zeta=1.1,
|
|
56
|
+
init_keep_prob=0.5,
|
|
57
|
+
init_weights=1.0,
|
|
58
|
+
log_weight_jitter_sd=0.05,
|
|
59
|
+
device="cuda",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Train with L0+L2 regularization
|
|
63
|
+
model.fit(
|
|
64
|
+
M=M,
|
|
65
|
+
y=y,
|
|
66
|
+
lambda_l0=1e-6,
|
|
67
|
+
lambda_l2=1e-8,
|
|
68
|
+
lr=0.15,
|
|
69
|
+
epochs=2000,
|
|
70
|
+
loss_type="relative",
|
|
71
|
+
verbose=True,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Get results
|
|
75
|
+
active = model.get_active_weights()
|
|
76
|
+
print(f"Selected {active['count']} of {N} households")
|
|
77
|
+
print(f"Sparsity: {model.get_sparsity():.1%}")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Key Features
|
|
81
|
+
|
|
82
|
+
- **Non-negative weights**: Constrained via log-space parameterization
|
|
83
|
+
- **L0 sparsity**: Directly minimizes the count of active weights
|
|
84
|
+
- **Relative loss**: Scale-invariant for targets spanning orders of magnitude
|
|
85
|
+
- **Group-wise averaging**: Balance loss across target groups with different sizes
|
|
86
|
+
- **GPU support**: CUDA acceleration for large problems
|
|
87
|
+
|
|
88
|
+
## Sparse Regression
|
|
89
|
+
|
|
90
|
+
For sparse linear regression with scipy sparse matrices:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from scipy import sparse as sp
|
|
94
|
+
from l0.sparse import SparseL0Linear
|
|
95
|
+
|
|
96
|
+
# Sparse design matrix
|
|
97
|
+
X = sp.random(1000, 500, density=0.1, format="csr")
|
|
98
|
+
y = np.random.randn(1000)
|
|
99
|
+
|
|
100
|
+
model = SparseL0Linear(n_features=500)
|
|
101
|
+
model.fit(X, y, lambda_l0=0.001, epochs=1000)
|
|
102
|
+
|
|
103
|
+
# Get sparse coefficients
|
|
104
|
+
coef = model.get_coefficients(threshold=0.01)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Example: Variable Selection
|
|
108
|
+
|
|
109
|
+
The `examples/sparse_regression_demo.py` script demonstrates L0 regularization on a simple problem where the true coefficients are `[1, 0, -2, 0]`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python examples/sparse_regression_demo.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Output:
|
|
116
|
+
```
|
|
117
|
+
True coefficients: [ 1. 0. -2. 0.]
|
|
118
|
+
Recovered coefficients: [ 1.039 0. -2.069 -0. ]
|
|
119
|
+
Gates: [1. 0. 1. 0.]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The model correctly identifies that only variables 1 and 3 contribute to the outcome.
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pytest tests/ -v --cov=l0
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Citation
|
|
131
|
+
|
|
132
|
+
```bibtex
|
|
133
|
+
@article{louizos2017learning,
|
|
134
|
+
title={Learning Sparse Neural Networks through L0 Regularization},
|
|
135
|
+
author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
|
|
136
|
+
journal={arXiv preprint arXiv:1712.01312},
|
|
137
|
+
year={2017}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -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
|
|
@@ -68,21 +68,7 @@ Dynamic: license-file
|
|
|
68
68
|
[](https://pypi.org/project/l0-python/)
|
|
69
69
|
[](https://github.com/PolicyEngine/L0/actions)
|
|
70
70
|
|
|
71
|
-
A PyTorch implementation of L0 regularization based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312),
|
|
72
|
-
|
|
73
|
-
## Why This Package?
|
|
74
|
-
|
|
75
|
-
The original L0 implementation from AMLab-Amsterdam contains a bug in test-time gate computation where the temperature parameter is incorrectly omitted. This prevents proper sparsity and degrades performance. Our implementation corrects this error:
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
# Original (buggy): gates never fully close
|
|
79
|
-
pi = sigmoid(log_alpha)
|
|
80
|
-
|
|
81
|
-
# Corrected: temperature division required for proper sparsity
|
|
82
|
-
pi = sigmoid(log_alpha / temperature)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
This fix enables gates to achieve true 0/1 values, producing exact sparsity as intended by the L0 formulation.
|
|
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.
|
|
86
72
|
|
|
87
73
|
## Installation
|
|
88
74
|
|
|
@@ -97,9 +83,24 @@ cd L0
|
|
|
97
83
|
pip install -e .[dev]
|
|
98
84
|
```
|
|
99
85
|
|
|
86
|
+
## Our Approach to Test-Time Gates
|
|
87
|
+
|
|
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.
|
|
89
|
+
|
|
90
|
+
We include temperature at test time:
|
|
91
|
+
```python
|
|
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
|
|
97
|
+
```
|
|
98
|
+
|
|
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
|
+
|
|
100
101
|
## Primary Use Case: Survey Calibration
|
|
101
102
|
|
|
102
|
-
This package was developed for PolicyEngine's survey calibration
|
|
103
|
+
This package was developed for PolicyEngine's survey calibration, where we select a sparse subset of survey households while matching population targets.
|
|
103
104
|
|
|
104
105
|
```python
|
|
105
106
|
import numpy as np
|
|
@@ -117,119 +118,73 @@ model = SparseCalibrationWeights(
|
|
|
117
118
|
beta=0.35,
|
|
118
119
|
gamma=-0.1,
|
|
119
120
|
zeta=1.1,
|
|
120
|
-
init_keep_prob=0.5,
|
|
121
|
-
init_weights=1.0,
|
|
121
|
+
init_keep_prob=0.5,
|
|
122
|
+
init_weights=1.0,
|
|
122
123
|
log_weight_jitter_sd=0.05,
|
|
123
|
-
device="cuda",
|
|
124
|
+
device="cuda",
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
# Train with L0+L2 regularization
|
|
127
128
|
model.fit(
|
|
128
129
|
M=M,
|
|
129
130
|
y=y,
|
|
130
|
-
lambda_l0=1e-6,
|
|
131
|
-
lambda_l2=1e-8,
|
|
131
|
+
lambda_l0=1e-6,
|
|
132
|
+
lambda_l2=1e-8,
|
|
132
133
|
lr=0.15,
|
|
133
134
|
epochs=2000,
|
|
134
|
-
loss_type="relative",
|
|
135
|
+
loss_type="relative",
|
|
135
136
|
verbose=True,
|
|
136
|
-
verbose_freq=100,
|
|
137
137
|
)
|
|
138
138
|
|
|
139
139
|
# Get results
|
|
140
140
|
active = model.get_active_weights()
|
|
141
|
-
print(f"Selected {active['count']} of {N} households
|
|
141
|
+
print(f"Selected {active['count']} of {N} households")
|
|
142
142
|
print(f"Sparsity: {model.get_sparsity():.1%}")
|
|
143
|
-
|
|
144
|
-
# Predict calibrated totals
|
|
145
|
-
y_pred = model.predict(M)
|
|
146
143
|
```
|
|
147
144
|
|
|
148
|
-
### Key Features
|
|
145
|
+
### Key Features
|
|
149
146
|
|
|
150
|
-
- **Non-negative weights**:
|
|
151
|
-
- **
|
|
152
|
-
- **Relative loss**: Scale-invariant
|
|
153
|
-
- **Group-wise averaging**: Balance loss
|
|
154
|
-
- **GPU support**: CUDA acceleration for large
|
|
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
|
|
155
152
|
|
|
156
|
-
##
|
|
153
|
+
## Sparse Regression
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
For sparse linear regression with scipy sparse matrices:
|
|
159
156
|
|
|
160
157
|
```python
|
|
161
|
-
import
|
|
162
|
-
from l0 import
|
|
163
|
-
|
|
164
|
-
class SparseModel(torch.nn.Module):
|
|
165
|
-
def __init__(self):
|
|
166
|
-
super().__init__()
|
|
167
|
-
self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
|
|
168
|
-
self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
|
|
169
|
-
|
|
170
|
-
def forward(self, x):
|
|
171
|
-
x = torch.relu(self.fc1(x))
|
|
172
|
-
return self.fc2(x)
|
|
173
|
-
|
|
174
|
-
model = SparseModel()
|
|
175
|
-
optimizer = torch.optim.Adam(model.parameters())
|
|
176
|
-
scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
|
|
177
|
-
|
|
178
|
-
for epoch in range(100):
|
|
179
|
-
temp = scheduler.get_temperature(epoch)
|
|
180
|
-
update_temperatures(model, temp)
|
|
181
|
-
|
|
182
|
-
output = model(input_data)
|
|
183
|
-
ce_loss = criterion(output, target)
|
|
184
|
-
penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
|
|
185
|
-
loss = ce_loss + penalty
|
|
186
|
-
|
|
187
|
-
optimizer.zero_grad()
|
|
188
|
-
loss.backward()
|
|
189
|
-
optimizer.step()
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Available Layers
|
|
193
|
-
|
|
194
|
-
- `L0Linear`: Fully connected layer with L0 gates on weights
|
|
195
|
-
- `L0Conv2d`: 2D convolution with channel-wise L0 gates
|
|
196
|
-
- `L0DepthwiseConv2d`: Depthwise convolution with L0 gates
|
|
197
|
-
- `SparseMLP`: Multi-layer perceptron with built-in L0 regularization
|
|
158
|
+
from scipy import sparse as sp
|
|
159
|
+
from l0.sparse import SparseL0Linear
|
|
198
160
|
|
|
199
|
-
|
|
161
|
+
# Sparse design matrix
|
|
162
|
+
X = sp.random(1000, 500, density=0.1, format="csr")
|
|
163
|
+
y = np.random.randn(1000)
|
|
200
164
|
|
|
201
|
-
|
|
165
|
+
model = SparseL0Linear(n_features=500)
|
|
166
|
+
model.fit(X, y, lambda_l0=0.001, epochs=1000)
|
|
202
167
|
|
|
203
|
-
|
|
204
|
-
|
|
168
|
+
# Get sparse coefficients
|
|
169
|
+
coef = model.get_coefficients(threshold=0.01)
|
|
170
|
+
```
|
|
205
171
|
|
|
206
|
-
|
|
207
|
-
gate = SampleGate(n_samples=10000, target_samples=1000)
|
|
208
|
-
selected_data, indices = gate.select_samples(data)
|
|
172
|
+
## Example: Variable Selection
|
|
209
173
|
|
|
210
|
-
|
|
211
|
-
gate = FeatureGate(n_features=1000, max_features=50)
|
|
212
|
-
selected_data, indices = gate.select_features(data)
|
|
174
|
+
The `examples/sparse_regression_demo.py` script demonstrates L0 regularization on a simple problem where the true coefficients are `[1, 0, -2, 0]`:
|
|
213
175
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
n_items=10000,
|
|
217
|
-
l0_fraction=0.25, # 25% via learned L0 gates
|
|
218
|
-
random_fraction=0.75 # 75% random for coverage
|
|
219
|
-
)
|
|
220
|
-
selected, indices, types = hybrid.select(data)
|
|
176
|
+
```bash
|
|
177
|
+
python examples/sparse_regression_demo.py
|
|
221
178
|
```
|
|
222
179
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
1.
|
|
228
|
-
|
|
229
|
-
3. **Expected L0 penalty**: Minimizes the expected number of active gates
|
|
230
|
-
4. **Temperature annealing**: Gradually sharpens gates from soft to hard decisions
|
|
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
|
+
```
|
|
231
186
|
|
|
232
|
-
The
|
|
187
|
+
The model correctly identifies that only variables 1 and 3 contribute to the outcome.
|
|
233
188
|
|
|
234
189
|
## Testing
|
|
235
190
|
|
|
@@ -239,8 +194,6 @@ pytest tests/ -v --cov=l0
|
|
|
239
194
|
|
|
240
195
|
## Citation
|
|
241
196
|
|
|
242
|
-
If you use this package, please cite the original paper:
|
|
243
|
-
|
|
244
197
|
```bibtex
|
|
245
198
|
@article{louizos2017learning,
|
|
246
199
|
title={Learning Sparse Neural Networks through L0 Regularization},
|
l0_python-0.4.2/README.md
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# L0 Regularization
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/l0-python/)
|
|
4
|
-
[](https://github.com/PolicyEngine/L0/actions)
|
|
5
|
-
|
|
6
|
-
A PyTorch implementation of L0 regularization based on [Louizos, Welling, & Kingma (2017)](https://arxiv.org/abs/1712.01312), with a **critical bug fix** to the original authors' implementation.
|
|
7
|
-
|
|
8
|
-
## Why This Package?
|
|
9
|
-
|
|
10
|
-
The original L0 implementation from AMLab-Amsterdam contains a bug in test-time gate computation where the temperature parameter is incorrectly omitted. This prevents proper sparsity and degrades performance. Our implementation corrects this error:
|
|
11
|
-
|
|
12
|
-
```python
|
|
13
|
-
# Original (buggy): gates never fully close
|
|
14
|
-
pi = sigmoid(log_alpha)
|
|
15
|
-
|
|
16
|
-
# Corrected: temperature division required for proper sparsity
|
|
17
|
-
pi = sigmoid(log_alpha / temperature)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
This fix enables gates to achieve true 0/1 values, producing exact sparsity as intended by the L0 formulation.
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
pip install l0-python
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
For development:
|
|
29
|
-
```bash
|
|
30
|
-
git clone https://github.com/PolicyEngine/L0.git
|
|
31
|
-
cd L0
|
|
32
|
-
pip install -e .[dev]
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Primary Use Case: Survey Calibration
|
|
36
|
-
|
|
37
|
-
This package was developed for PolicyEngine's survey calibration system, where we select a sparse subset of survey households while matching population targets.
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
import numpy as np
|
|
41
|
-
from scipy import sparse as sp
|
|
42
|
-
from l0.calibration import SparseCalibrationWeights
|
|
43
|
-
|
|
44
|
-
# Setup: Q targets, N households
|
|
45
|
-
Q, N = 200, 10000
|
|
46
|
-
M = sp.random(Q, N, density=0.3, format="csr") # Household characteristics
|
|
47
|
-
y = np.random.uniform(1e6, 1e8, size=Q) # Population targets
|
|
48
|
-
|
|
49
|
-
# Initialize model
|
|
50
|
-
model = SparseCalibrationWeights(
|
|
51
|
-
n_features=N,
|
|
52
|
-
beta=0.35,
|
|
53
|
-
gamma=-0.1,
|
|
54
|
-
zeta=1.1,
|
|
55
|
-
init_keep_prob=0.5, # Start with 50% active probability
|
|
56
|
-
init_weights=1.0, # Or pass array of initial weights
|
|
57
|
-
log_weight_jitter_sd=0.05,
|
|
58
|
-
device="cuda", # GPU acceleration
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Train with L0+L2 regularization
|
|
62
|
-
model.fit(
|
|
63
|
-
M=M,
|
|
64
|
-
y=y,
|
|
65
|
-
lambda_l0=1e-6, # Controls sparsity level
|
|
66
|
-
lambda_l2=1e-8, # Prevents weight explosion
|
|
67
|
-
lr=0.15,
|
|
68
|
-
epochs=2000,
|
|
69
|
-
loss_type="relative", # Scale-invariant loss
|
|
70
|
-
verbose=True,
|
|
71
|
-
verbose_freq=100,
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# Get results
|
|
75
|
-
active = model.get_active_weights()
|
|
76
|
-
print(f"Selected {active['count']} of {N} households ({100*active['count']/N:.1f}%)")
|
|
77
|
-
print(f"Sparsity: {model.get_sparsity():.1%}")
|
|
78
|
-
|
|
79
|
-
# Predict calibrated totals
|
|
80
|
-
y_pred = model.predict(M)
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Key Features for Calibration
|
|
84
|
-
|
|
85
|
-
- **Non-negative weights**: All weights constrained to be positive via log-space parameterization
|
|
86
|
-
- **Sparse solutions**: L0 penalty directly minimizes the count of active weights
|
|
87
|
-
- **Relative loss**: Scale-invariant loss for targets spanning orders of magnitude
|
|
88
|
-
- **Group-wise averaging**: Balance loss contributions across target groups with different cardinalities
|
|
89
|
-
- **GPU support**: CUDA acceleration for large-scale problems
|
|
90
|
-
|
|
91
|
-
## Neural Network Sparsification
|
|
92
|
-
|
|
93
|
-
The package also supports traditional neural network pruning:
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
import torch
|
|
97
|
-
from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
|
|
98
|
-
|
|
99
|
-
class SparseModel(torch.nn.Module):
|
|
100
|
-
def __init__(self):
|
|
101
|
-
super().__init__()
|
|
102
|
-
self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
|
|
103
|
-
self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
|
|
104
|
-
|
|
105
|
-
def forward(self, x):
|
|
106
|
-
x = torch.relu(self.fc1(x))
|
|
107
|
-
return self.fc2(x)
|
|
108
|
-
|
|
109
|
-
model = SparseModel()
|
|
110
|
-
optimizer = torch.optim.Adam(model.parameters())
|
|
111
|
-
scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
|
|
112
|
-
|
|
113
|
-
for epoch in range(100):
|
|
114
|
-
temp = scheduler.get_temperature(epoch)
|
|
115
|
-
update_temperatures(model, temp)
|
|
116
|
-
|
|
117
|
-
output = model(input_data)
|
|
118
|
-
ce_loss = criterion(output, target)
|
|
119
|
-
penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
|
|
120
|
-
loss = ce_loss + penalty
|
|
121
|
-
|
|
122
|
-
optimizer.zero_grad()
|
|
123
|
-
loss.backward()
|
|
124
|
-
optimizer.step()
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Available Layers
|
|
128
|
-
|
|
129
|
-
- `L0Linear`: Fully connected layer with L0 gates on weights
|
|
130
|
-
- `L0Conv2d`: 2D convolution with channel-wise L0 gates
|
|
131
|
-
- `L0DepthwiseConv2d`: Depthwise convolution with L0 gates
|
|
132
|
-
- `SparseMLP`: Multi-layer perceptron with built-in L0 regularization
|
|
133
|
-
|
|
134
|
-
## Intelligent Sampling Gates
|
|
135
|
-
|
|
136
|
-
Standalone gates for sample/feature selection:
|
|
137
|
-
|
|
138
|
-
```python
|
|
139
|
-
from l0 import SampleGate, FeatureGate, HybridGate
|
|
140
|
-
|
|
141
|
-
# Select samples via learned gates
|
|
142
|
-
gate = SampleGate(n_samples=10000, target_samples=1000)
|
|
143
|
-
selected_data, indices = gate.select_samples(data)
|
|
144
|
-
|
|
145
|
-
# Select features
|
|
146
|
-
gate = FeatureGate(n_features=1000, max_features=50)
|
|
147
|
-
selected_data, indices = gate.select_features(data)
|
|
148
|
-
|
|
149
|
-
# Hybrid: combine L0 selection with random sampling
|
|
150
|
-
hybrid = HybridGate(
|
|
151
|
-
n_items=10000,
|
|
152
|
-
l0_fraction=0.25, # 25% via learned L0 gates
|
|
153
|
-
random_fraction=0.75 # 75% random for coverage
|
|
154
|
-
)
|
|
155
|
-
selected, indices, types = hybrid.select(data)
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## How L0 Regularization Works
|
|
159
|
-
|
|
160
|
-
Unlike post-hoc pruning (setting small weights to zero), L0 regularization integrates sparsity into the training objective:
|
|
161
|
-
|
|
162
|
-
1. **Stochastic gates**: Each weight has a learned gate parameter controlling activation probability
|
|
163
|
-
2. **Hard Concrete distribution**: Enables differentiable 0/1 gate values during training
|
|
164
|
-
3. **Expected L0 penalty**: Minimizes the expected number of active gates
|
|
165
|
-
4. **Temperature annealing**: Gradually sharpens gates from soft to hard decisions
|
|
166
|
-
|
|
167
|
-
The result: the network learns *which* weights should be zero as part of optimization, not as a post-processing step.
|
|
168
|
-
|
|
169
|
-
## Testing
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
pytest tests/ -v --cov=l0
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Citation
|
|
176
|
-
|
|
177
|
-
If you use this package, please cite the original paper:
|
|
178
|
-
|
|
179
|
-
```bibtex
|
|
180
|
-
@article{louizos2017learning,
|
|
181
|
-
title={Learning Sparse Neural Networks through L0 Regularization},
|
|
182
|
-
author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
|
|
183
|
-
journal={arXiv preprint arXiv:1712.01312},
|
|
184
|
-
year={2017}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## License
|
|
189
|
-
|
|
190
|
-
MIT License - see [LICENSE](LICENSE) for details.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|