l0-python 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.
- l0_python-0.1.0/LICENSE +21 -0
- l0_python-0.1.0/PKG-INFO +215 -0
- l0_python-0.1.0/README.md +151 -0
- l0_python-0.1.0/l0/__init__.py +53 -0
- l0_python-0.1.0/l0/distributions.py +205 -0
- l0_python-0.1.0/l0/gates.py +407 -0
- l0_python-0.1.0/l0/layers.py +455 -0
- l0_python-0.1.0/l0/penalties.py +381 -0
- l0_python-0.1.0/l0_python.egg-info/PKG-INFO +215 -0
- l0_python-0.1.0/l0_python.egg-info/SOURCES.txt +17 -0
- l0_python-0.1.0/l0_python.egg-info/dependency_links.txt +1 -0
- l0_python-0.1.0/l0_python.egg-info/requires.txt +24 -0
- l0_python-0.1.0/l0_python.egg-info/top_level.txt +1 -0
- l0_python-0.1.0/pyproject.toml +87 -0
- l0_python-0.1.0/setup.cfg +4 -0
- l0_python-0.1.0/tests/test_distributions.py +195 -0
- l0_python-0.1.0/tests/test_gates.py +249 -0
- l0_python-0.1.0/tests/test_layers.py +304 -0
- l0_python-0.1.0/tests/test_penalties.py +182 -0
l0_python-0.1.0/LICENSE
ADDED
|
@@ -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.
|
l0_python-0.1.0/PKG-INFO
ADDED
|
@@ -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,151 @@
|
|
|
1
|
+
# L0 Regularization
|
|
2
|
+
|
|
3
|
+
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).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Hard Concrete Distribution**: Differentiable approximation of L0 norm
|
|
8
|
+
- **Sparse Neural Network Layers**: L0Linear, L0Conv2d with automatic pruning
|
|
9
|
+
- **Intelligent Sampling**: Sample/feature selection gates for calibration
|
|
10
|
+
- **L0L2 Combined Penalty**: Recommended approach to prevent overfitting
|
|
11
|
+
- **Temperature Scheduling**: Annealing for improved convergence
|
|
12
|
+
- **TDD Development**: Comprehensive test coverage
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install l0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For development:
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/PolicyEngine/L0.git
|
|
23
|
+
cd L0
|
|
24
|
+
pip install -e .[dev]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Neural Network Sparsification
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import torch
|
|
33
|
+
from l0 import L0Linear, compute_l0l2_penalty, TemperatureScheduler, update_temperatures
|
|
34
|
+
|
|
35
|
+
# Create a sparse model
|
|
36
|
+
class SparseModel(torch.nn.Module):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.fc1 = L0Linear(784, 256, init_sparsity=0.5)
|
|
40
|
+
self.fc2 = L0Linear(256, 10, init_sparsity=0.7)
|
|
41
|
+
|
|
42
|
+
def forward(self, x):
|
|
43
|
+
x = torch.relu(self.fc1(x))
|
|
44
|
+
return self.fc2(x)
|
|
45
|
+
|
|
46
|
+
model = SparseModel()
|
|
47
|
+
optimizer = torch.optim.Adam(model.parameters())
|
|
48
|
+
scheduler = TemperatureScheduler(initial_temp=1.0, final_temp=0.1)
|
|
49
|
+
|
|
50
|
+
# Training loop
|
|
51
|
+
for epoch in range(100):
|
|
52
|
+
# Update temperature
|
|
53
|
+
temp = scheduler.get_temperature(epoch)
|
|
54
|
+
update_temperatures(model, temp)
|
|
55
|
+
|
|
56
|
+
# Forward pass
|
|
57
|
+
output = model(input_data)
|
|
58
|
+
ce_loss = criterion(output, target)
|
|
59
|
+
|
|
60
|
+
# Add L0L2 penalty
|
|
61
|
+
penalty = compute_l0l2_penalty(model, l0_lambda=1e-3, l2_lambda=1e-4)
|
|
62
|
+
loss = ce_loss + penalty
|
|
63
|
+
|
|
64
|
+
# Backward pass
|
|
65
|
+
optimizer.zero_grad()
|
|
66
|
+
loss.backward()
|
|
67
|
+
optimizer.step()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Intelligent Sample Selection
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from l0 import SampleGate, HybridGate
|
|
74
|
+
|
|
75
|
+
# Pure L0 selection
|
|
76
|
+
gate = SampleGate(n_samples=10000, target_samples=1000)
|
|
77
|
+
selected_data, indices = gate.select_samples(data)
|
|
78
|
+
|
|
79
|
+
# Hybrid selection (25% L0, 75% random)
|
|
80
|
+
hybrid = HybridGate(
|
|
81
|
+
n_items=10000,
|
|
82
|
+
l0_fraction=0.25,
|
|
83
|
+
random_fraction=0.75,
|
|
84
|
+
target_items=1000
|
|
85
|
+
)
|
|
86
|
+
selected, indices, types = hybrid.select(data)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Feature Selection
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from l0 import FeatureGate
|
|
93
|
+
|
|
94
|
+
# Select top features
|
|
95
|
+
gate = FeatureGate(n_features=1000, max_features=50)
|
|
96
|
+
selected_data, feature_indices = gate.select_features(data)
|
|
97
|
+
|
|
98
|
+
# Get feature importance
|
|
99
|
+
importance = gate.get_feature_importance()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Integration with PolicyEngine
|
|
103
|
+
|
|
104
|
+
This package is designed to work with PolicyEngine's calibration system:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# In policyengine-us-data or similar
|
|
108
|
+
from l0 import HardConcrete
|
|
109
|
+
|
|
110
|
+
# Use for household selection in CPS calibration
|
|
111
|
+
gates = HardConcrete(
|
|
112
|
+
len(household_weights),
|
|
113
|
+
temperature=0.25,
|
|
114
|
+
init_mean=0.999 # Start with most households
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Apply gates during reweighting
|
|
118
|
+
masked_weights = weights * gates()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
Full documentation available at: https://policyengine.github.io/L0/
|
|
124
|
+
|
|
125
|
+
## Testing
|
|
126
|
+
|
|
127
|
+
Run tests with:
|
|
128
|
+
```bash
|
|
129
|
+
pytest tests/ -v --cov=l0
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Acknowledgments
|
|
133
|
+
|
|
134
|
+
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).
|
|
135
|
+
|
|
136
|
+
## Citation
|
|
137
|
+
|
|
138
|
+
If you use this package, please cite:
|
|
139
|
+
|
|
140
|
+
```bibtex
|
|
141
|
+
@article{louizos2017learning,
|
|
142
|
+
title={Learning Sparse Neural Networks through L0 Regularization},
|
|
143
|
+
author={Louizos, Christos and Welling, Max and Kingma, Diederik P},
|
|
144
|
+
journal={arXiv preprint arXiv:1712.01312},
|
|
145
|
+
year={2017}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
L0 Regularization Package
|
|
3
|
+
|
|
4
|
+
A PyTorch implementation of L0 regularization for neural network sparsification
|
|
5
|
+
and intelligent sampling, based on Louizos, Welling, & Kingma (2017).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .distributions import HardConcrete
|
|
11
|
+
from .gates import FeatureGate, HybridGate, L0Gate, SampleGate
|
|
12
|
+
from .layers import (
|
|
13
|
+
L0Conv2d,
|
|
14
|
+
L0DepthwiseConv2d,
|
|
15
|
+
L0Linear,
|
|
16
|
+
SparseMLP,
|
|
17
|
+
prune_model,
|
|
18
|
+
)
|
|
19
|
+
from .penalties import (
|
|
20
|
+
PenaltyTracker,
|
|
21
|
+
TemperatureScheduler,
|
|
22
|
+
compute_l0_penalty,
|
|
23
|
+
compute_l0l2_penalty,
|
|
24
|
+
compute_l2_penalty,
|
|
25
|
+
get_active_parameter_count,
|
|
26
|
+
get_sparsity_stats,
|
|
27
|
+
update_temperatures,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Distributions
|
|
32
|
+
"HardConcrete",
|
|
33
|
+
# Layers
|
|
34
|
+
"L0Linear",
|
|
35
|
+
"L0Conv2d",
|
|
36
|
+
"L0DepthwiseConv2d",
|
|
37
|
+
"SparseMLP",
|
|
38
|
+
"prune_model",
|
|
39
|
+
# Gates
|
|
40
|
+
"L0Gate",
|
|
41
|
+
"SampleGate",
|
|
42
|
+
"FeatureGate",
|
|
43
|
+
"HybridGate",
|
|
44
|
+
# Penalties
|
|
45
|
+
"compute_l0_penalty",
|
|
46
|
+
"compute_l2_penalty",
|
|
47
|
+
"compute_l0l2_penalty",
|
|
48
|
+
"get_sparsity_stats",
|
|
49
|
+
"get_active_parameter_count",
|
|
50
|
+
"TemperatureScheduler",
|
|
51
|
+
"update_temperatures",
|
|
52
|
+
"PenaltyTracker",
|
|
53
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hard Concrete distribution for L0 regularization.
|
|
3
|
+
|
|
4
|
+
Based on Louizos, Welling, & Kingma (2017): "Learning Sparse Neural Networks
|
|
5
|
+
through L0 Regularization" (https://arxiv.org/abs/1712.01312)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
import torch.nn as nn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HardConcrete(nn.Module):
|
|
15
|
+
"""
|
|
16
|
+
Hard Concrete distribution for L0 regularization.
|
|
17
|
+
|
|
18
|
+
This distribution enables differentiable approximation of the L0 norm
|
|
19
|
+
by using stochastic gates that can be optimized via gradient descent.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
input_dim : int
|
|
24
|
+
Size of the first dimension (e.g., number of samples or features)
|
|
25
|
+
output_dim : Optional[int]
|
|
26
|
+
Size of the second dimension for 2D gates (e.g., for weight matrices)
|
|
27
|
+
temperature : float
|
|
28
|
+
Temperature parameter controlling the hardness of the distribution.
|
|
29
|
+
Lower values make the distribution more discrete.
|
|
30
|
+
stretch : float
|
|
31
|
+
Stretch parameter for the hard concrete transformation
|
|
32
|
+
init_mean : float
|
|
33
|
+
Initial mean activation probability for the gates (0 to 1)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
input_dim: int,
|
|
39
|
+
output_dim: int | None = None,
|
|
40
|
+
temperature: float = 0.5,
|
|
41
|
+
stretch: float = 0.1,
|
|
42
|
+
init_mean: float = 0.5,
|
|
43
|
+
):
|
|
44
|
+
super().__init__()
|
|
45
|
+
|
|
46
|
+
# Set gate dimensions
|
|
47
|
+
if output_dim is None:
|
|
48
|
+
self.gate_size: tuple[int, ...] = (input_dim,)
|
|
49
|
+
else:
|
|
50
|
+
self.gate_size = (input_dim, output_dim)
|
|
51
|
+
|
|
52
|
+
# Learnable parameters - logits for each gate
|
|
53
|
+
self.qz_logits = nn.Parameter(torch.zeros(self.gate_size))
|
|
54
|
+
|
|
55
|
+
# Distribution parameters
|
|
56
|
+
self.temperature = temperature
|
|
57
|
+
self.stretch = stretch
|
|
58
|
+
self.gamma = -stretch # Lower bound after stretching
|
|
59
|
+
self.zeta = 1.0 + stretch # Upper bound after stretching
|
|
60
|
+
self.init_mean = init_mean
|
|
61
|
+
|
|
62
|
+
# Initialize parameters
|
|
63
|
+
self.reset_parameters()
|
|
64
|
+
|
|
65
|
+
def reset_parameters(self) -> None:
|
|
66
|
+
"""Initialize gate logits based on desired initial mean."""
|
|
67
|
+
if self.init_mean is not None:
|
|
68
|
+
# Convert mean probability to logit space
|
|
69
|
+
init_val = math.log(self.init_mean / (1 - self.init_mean))
|
|
70
|
+
self.qz_logits.data.fill_(init_val)
|
|
71
|
+
|
|
72
|
+
def forward(
|
|
73
|
+
self, input_shape: tuple[int, ...] | None = None
|
|
74
|
+
) -> torch.Tensor:
|
|
75
|
+
"""
|
|
76
|
+
Sample or compute gates.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
input_shape : Optional[Tuple[int, ...]]
|
|
81
|
+
Shape of the input tensor for broadcasting gates if needed
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
torch.Tensor
|
|
86
|
+
Gate values in [0, 1], either sampled (training) or deterministic (eval)
|
|
87
|
+
"""
|
|
88
|
+
if self.training:
|
|
89
|
+
gates = self._sample_gates()
|
|
90
|
+
else:
|
|
91
|
+
gates = self._deterministic_gates()
|
|
92
|
+
|
|
93
|
+
# Broadcast gates if needed for convolution layers
|
|
94
|
+
if input_shape is not None and len(input_shape) > len(gates.shape):
|
|
95
|
+
# Add dimensions for spatial dims (e.g., height, width in Conv2d)
|
|
96
|
+
for _ in range(len(input_shape) - len(gates.shape)):
|
|
97
|
+
gates = gates.unsqueeze(-1)
|
|
98
|
+
|
|
99
|
+
return gates
|
|
100
|
+
|
|
101
|
+
def _sample_gates(self) -> torch.Tensor:
|
|
102
|
+
"""
|
|
103
|
+
Sample gates using the reparameterization trick.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
torch.Tensor
|
|
108
|
+
Sampled gate values in [0, 1]
|
|
109
|
+
"""
|
|
110
|
+
# Sample uniform noise (avoiding exact 0 and 1 for numerical stability)
|
|
111
|
+
u = torch.zeros_like(self.qz_logits).uniform_(1e-8, 1.0 - 1e-8)
|
|
112
|
+
|
|
113
|
+
# Apply the concrete distribution transformation
|
|
114
|
+
# s = sigmoid((log(u) - log(1-u) + logits) / temperature)
|
|
115
|
+
s = torch.log(u) - torch.log(1 - u) + self.qz_logits
|
|
116
|
+
s = torch.sigmoid(s / self.temperature)
|
|
117
|
+
|
|
118
|
+
# Stretch and clamp to create hard concrete
|
|
119
|
+
s = s * (self.zeta - self.gamma) + self.gamma
|
|
120
|
+
gates = torch.clamp(s, 0, 1)
|
|
121
|
+
|
|
122
|
+
return gates
|
|
123
|
+
|
|
124
|
+
def _deterministic_gates(self) -> torch.Tensor:
|
|
125
|
+
"""
|
|
126
|
+
Compute deterministic gates for evaluation.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
torch.Tensor
|
|
131
|
+
Deterministic gate values in [0, 1]
|
|
132
|
+
"""
|
|
133
|
+
# Use mean of the distribution
|
|
134
|
+
probs = torch.sigmoid(self.qz_logits)
|
|
135
|
+
|
|
136
|
+
# Apply stretching transformation
|
|
137
|
+
gates = probs * (self.zeta - self.gamma) + self.gamma
|
|
138
|
+
|
|
139
|
+
return torch.clamp(gates, 0, 1)
|
|
140
|
+
|
|
141
|
+
def get_penalty(self) -> torch.Tensor:
|
|
142
|
+
"""
|
|
143
|
+
Compute the expected L0 norm (number of active gates).
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
torch.Tensor
|
|
148
|
+
Expected number of non-zero gates
|
|
149
|
+
"""
|
|
150
|
+
# Shift logits to account for hard concrete bounds
|
|
151
|
+
logits_shifted = self.qz_logits - self.temperature * math.log(
|
|
152
|
+
-self.gamma / self.zeta
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Probability that gate is active (non-zero)
|
|
156
|
+
prob_active = torch.sigmoid(logits_shifted)
|
|
157
|
+
|
|
158
|
+
return prob_active.sum()
|
|
159
|
+
|
|
160
|
+
def get_active_prob(self) -> torch.Tensor:
|
|
161
|
+
"""
|
|
162
|
+
Get the probability that each gate is active.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
torch.Tensor
|
|
167
|
+
Probability of each gate being non-zero
|
|
168
|
+
"""
|
|
169
|
+
logits_shifted = self.qz_logits - self.temperature * math.log(
|
|
170
|
+
-self.gamma / self.zeta
|
|
171
|
+
)
|
|
172
|
+
return torch.sigmoid(logits_shifted)
|
|
173
|
+
|
|
174
|
+
def get_sparsity(self) -> float:
|
|
175
|
+
"""
|
|
176
|
+
Get the overall sparsity level (fraction of inactive gates).
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
float
|
|
181
|
+
Sparsity level in [0, 1] where 1 means all gates are inactive
|
|
182
|
+
"""
|
|
183
|
+
with torch.no_grad():
|
|
184
|
+
prob_active = self.get_active_prob()
|
|
185
|
+
return 1.0 - prob_active.mean().item()
|
|
186
|
+
|
|
187
|
+
@torch.no_grad()
|
|
188
|
+
def get_num_active(self) -> int:
|
|
189
|
+
"""
|
|
190
|
+
Get the number of active gates (for monitoring).
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
int
|
|
195
|
+
Number of gates that are likely active
|
|
196
|
+
"""
|
|
197
|
+
prob_active = self.get_active_prob()
|
|
198
|
+
return int((prob_active > 0.5).sum().item())
|
|
199
|
+
|
|
200
|
+
def extra_repr(self) -> str:
|
|
201
|
+
"""String representation for printing."""
|
|
202
|
+
return (
|
|
203
|
+
f"gate_size={self.gate_size}, temperature={self.temperature}, "
|
|
204
|
+
f"stretch={self.stretch}, init_mean={self.init_mean}"
|
|
205
|
+
)
|