humancompatible-train 0.1.0__tar.gz → 0.1.2__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.
- {humancompatible_train-0.1.0/humancompatible_train.egg-info → humancompatible_train-0.1.2}/PKG-INFO +74 -31
- humancompatible_train-0.1.0/PKG-INFO → humancompatible_train-0.1.2/README.md +66 -48
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/run_folktables.py +2 -2
- humancompatible_train-0.1.2/humancompatible/train/algorithms/test/test_ssl_alm.py +144 -0
- humancompatible_train-0.1.2/humancompatible/train/algorithms/test/test_ssw.py +64 -0
- humancompatible_train-0.1.2/humancompatible/train/fairness/__init__.py +0 -0
- humancompatible_train-0.1.2/humancompatible/train/fairness/utils/tests/__init__.py +0 -0
- humancompatible_train-0.1.2/humancompatible/train/fairness/utils/tests/test_balanced_batch_sampler.py +78 -0
- humancompatible_train-0.1.0/README.md → humancompatible_train-0.1.2/humancompatible_train.egg-info/PKG-INFO +90 -30
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/SOURCES.txt +6 -8
- humancompatible_train-0.1.2/humancompatible_train.egg-info/requires.txt +15 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/pyproject.toml +2 -1
- humancompatible_train-0.1.0/humancompatible/train/algorithms/Algorithm.py +0 -25
- humancompatible_train-0.1.0/humancompatible/train/algorithms/__init__.py +0 -8
- humancompatible_train-0.1.0/humancompatible/train/algorithms/ghost.py +0 -250
- humancompatible_train-0.1.0/humancompatible/train/algorithms/sgd.py +0 -107
- humancompatible_train-0.1.0/humancompatible/train/algorithms/ssl_alm.py +0 -311
- humancompatible_train-0.1.0/humancompatible/train/algorithms/switching_subgradient.py +0 -192
- humancompatible_train-0.1.0/humancompatible/train/algorithms/utils.py +0 -61
- humancompatible_train-0.1.0/humancompatible_train.egg-info/requires.txt +0 -6
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/LICENCE.txt +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/calculate_iteration_values.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/run_folktables_torchalgs.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/__init__.py +0 -0
- {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/__init__.py +0 -0
- {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/ssl_alm.py +0 -0
- {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/ssw.py +0 -0
- {humancompatible_train-0.1.0/humancompatible/train/fairness → humancompatible_train-0.1.2/humancompatible/train/algorithms/test}/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/constraint.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/constraint_fns.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/torch/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/torch/constraints.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/utils/__init__.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/utils/balanced_batch_sampler.py +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/dependency_links.txt +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/top_level.txt +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/setup.cfg +0 -0
- {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/setup.py +0 -0
{humancompatible_train-0.1.0/humancompatible_train.egg-info → humancompatible_train-0.1.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humancompatible-train
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: PyTorch-based package for constrained training of neural networks
|
|
5
5
|
Author: Gilles Bareilles, Jana Lepsova, Jakub Marecek
|
|
6
6
|
Author-email: Andrii Kliachkin <kliachkin.andrii@gmail.com>
|
|
@@ -10,30 +10,95 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENCE.txt
|
|
11
11
|
Requires-Dist: torch
|
|
12
12
|
Requires-Dist: numpy
|
|
13
|
+
Provides-Extra: benchmark
|
|
14
|
+
Requires-Dist: fairret; extra == "benchmark"
|
|
15
|
+
Requires-Dist: matplotlib; extra == "benchmark"
|
|
16
|
+
Requires-Dist: pandas; extra == "benchmark"
|
|
17
|
+
Requires-Dist: folktables; extra == "benchmark"
|
|
18
|
+
Requires-Dist: pot; extra == "benchmark"
|
|
19
|
+
Requires-Dist: hydra; extra == "benchmark"
|
|
20
|
+
Requires-Dist: omegaconf; extra == "benchmark"
|
|
13
21
|
Provides-Extra: ghost
|
|
14
22
|
Requires-Dist: qpsolvers; extra == "ghost"
|
|
15
23
|
Requires-Dist: scipy; extra == "ghost"
|
|
16
24
|
Dynamic: license-file
|
|
17
25
|
|
|
18
|
-
#
|
|
26
|
+
# humancompatible-train: a package for constrained machine learning
|
|
19
27
|
|
|
20
28
|
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml)
|
|
21
29
|
|
|
22
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API
|
|
30
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
31
|
+
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
23
32
|
|
|
24
33
|
## Table of Contents
|
|
25
34
|
1. [Basic installation instructions](#basic-installation-instructions)
|
|
26
35
|
2. [Using the toolkit](#using-the-toolkit)
|
|
27
|
-
3. [
|
|
28
|
-
4. [
|
|
36
|
+
3. [Extending the toolkit](#extending-the-toolkit)
|
|
37
|
+
4. [Reproducing the Benchmark](#reproducing-the-benchmark)
|
|
29
38
|
5. [License and terms of use](#license-and-terms-of-use)
|
|
30
39
|
6. [References](#references)
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
humancompatible-train is still under active development! If you find bugs or have feature
|
|
33
42
|
requests, please file a
|
|
34
43
|
[Github issue](https://github.com/humancompatible/train/issues).
|
|
35
44
|
|
|
36
|
-
##
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
Use
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pip install humancompatible-train
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The only dependencies of this package are `numpy` and `torch`.
|
|
54
|
+
|
|
55
|
+
## Using the toolkit
|
|
56
|
+
|
|
57
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
58
|
+
|
|
59
|
+
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `double_step` does updates related to the dual parameters and prepares for the primal update (by, e.g., saving constraint gradients), and `step()` updates the primal parameters.
|
|
60
|
+
|
|
61
|
+
In general, your code using `humancompatible-train` would look something like this:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
for inputs, labels in dataloader:
|
|
65
|
+
# inference
|
|
66
|
+
outputs = model(inputs)
|
|
67
|
+
# calculate constraints and grads
|
|
68
|
+
for constraint in constraints:
|
|
69
|
+
c_eval = constraint(outputs, labels)
|
|
70
|
+
c_eval.backwards(retain_grad=True)
|
|
71
|
+
# depending on optimizer, update dual parameters / save constraint gradient / both
|
|
72
|
+
optimizer.dual_step(c_eval)
|
|
73
|
+
optimizer.zero_grad()
|
|
74
|
+
# calculate objective
|
|
75
|
+
loss = criterion(outputs,labels)
|
|
76
|
+
loss.backwards()
|
|
77
|
+
optimizer.step()
|
|
78
|
+
optimizer.zero_grad()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Our idea is to
|
|
82
|
+
1. Deviate minimally from the usual PyTorch workflow
|
|
83
|
+
2. Make different stochastic-constrained stochastic optimization algorithms nearly interchangable in the code.
|
|
84
|
+
|
|
85
|
+
### Code examples
|
|
86
|
+
|
|
87
|
+
You are invited to check out our new API presented in notebooks in the `examples` folder.
|
|
88
|
+
|
|
89
|
+
*The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.*
|
|
90
|
+
|
|
91
|
+
## Extending the toolkit
|
|
92
|
+
|
|
93
|
+
### Adding new code
|
|
94
|
+
|
|
95
|
+
**To add a new algorithm**, you can subclass the PyTorch ```Optimizer``` class and proceed following the API guideline presented above.
|
|
96
|
+
|
|
97
|
+
## Reproducing the Benchmark
|
|
98
|
+
|
|
99
|
+
The code used in [our benchmark paper](https://arxiv.org/abs/2507.04033) is not migrated to the new API yet (WIP).
|
|
100
|
+
|
|
101
|
+
### Basic installation instructions
|
|
37
102
|
The code requires Python version ```3.11```.
|
|
38
103
|
|
|
39
104
|
1. Create a virtual environment
|
|
@@ -72,22 +137,6 @@ after installing requirements.txt; otherwise, the algorithm will run slower. How
|
|
|
72
137
|
<!-- pip install folktables -->
|
|
73
138
|
<!-- ``` -->
|
|
74
139
|
|
|
75
|
-
## Using the toolkit
|
|
76
|
-
|
|
77
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
78
|
-
|
|
79
|
-
### Code examples
|
|
80
|
-
|
|
81
|
-
You are invited to check out the new API presented in notebooks in the `examples` folder.
|
|
82
|
-
|
|
83
|
-
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `double_step` does updates related to the dual parameters and prepares for the primal update (by, e.g., saving constraint gradients), and `step()` updates the primal parameters.
|
|
84
|
-
|
|
85
|
-
The idea is to make different algorithms nearly interchangable in the code.
|
|
86
|
-
|
|
87
|
-
The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.
|
|
88
|
-
|
|
89
|
-
## Reproducing the Benchmark
|
|
90
|
-
|
|
91
140
|
### Running the algorithms
|
|
92
141
|
|
|
93
142
|
The benchmark comprises the following algorithms:
|
|
@@ -122,15 +171,10 @@ This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `exper
|
|
|
122
171
|
### Producing plots
|
|
123
172
|
The plots and tables like the ones in the paper can be produced using the two notebooks. `experiments/algo_plots.ipynb` houses the convergence plots, and `experiments/model_plots.ipynb` - all the others.
|
|
124
173
|
|
|
125
|
-
## Extending the toolkit
|
|
126
|
-
|
|
127
|
-
### Adding new code
|
|
128
|
-
|
|
129
|
-
**To add a new algorithm**, you can subclass the PyTorch ```Optimizer``` class and proceed following the API guideline presented above.
|
|
130
174
|
|
|
131
175
|
## License and terms of use
|
|
132
176
|
|
|
133
|
-
humancompatible
|
|
177
|
+
humancompatible-train is provided under the Apache 2.0 Licence.
|
|
134
178
|
|
|
135
179
|
The benchmark part of the package relies on the Folktables package, provided under MIT Licence.
|
|
136
180
|
It provides code to download data from the American Community Survey
|
|
@@ -154,7 +198,7 @@ For more information, see https://www.census.gov/data/developers/about/terms-of-
|
|
|
154
198
|
|
|
155
199
|
## Future work
|
|
156
200
|
|
|
157
|
-
- Add more algorithms
|
|
201
|
+
- Add more algorithms
|
|
158
202
|
- Add more examples from different fields where constrained training of DNNs is employed
|
|
159
203
|
- Migrate the benchmark to the new API
|
|
160
204
|
|
|
@@ -185,4 +229,3 @@ Huang, Zhang & Alacaoglu (2025) Stochastic Smoothed Primal-Dual Algorithms for N
|
|
|
185
229
|
|
|
186
230
|
<a id="4">[4]</a>
|
|
187
231
|
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
188
|
-
|
|
@@ -1,39 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
Name: humancompatible-train
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: PyTorch-based package for constrained training of neural networks
|
|
5
|
-
Author: Gilles Bareilles, Jana Lepsova, Jakub Marecek
|
|
6
|
-
Author-email: Andrii Kliachkin <kliachkin.andrii@gmail.com>
|
|
7
|
-
Maintainer-email: Andrii Kliachkin <kliachkin.andrii@gmail.com>
|
|
8
|
-
Requires-Python: >=3.11
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
License-File: LICENCE.txt
|
|
11
|
-
Requires-Dist: torch
|
|
12
|
-
Requires-Dist: numpy
|
|
13
|
-
Provides-Extra: ghost
|
|
14
|
-
Requires-Dist: qpsolvers; extra == "ghost"
|
|
15
|
-
Requires-Dist: scipy; extra == "ghost"
|
|
16
|
-
Dynamic: license-file
|
|
17
|
-
|
|
18
|
-
# Benchmarking Stochastic Approximation Algorithms for Fairness-Constrained Training of Deep Neural Networks
|
|
1
|
+
# humancompatible-train: a package for constrained machine learning
|
|
19
2
|
|
|
20
3
|
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml)
|
|
21
4
|
|
|
22
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API
|
|
5
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
6
|
+
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
23
7
|
|
|
24
8
|
## Table of Contents
|
|
25
9
|
1. [Basic installation instructions](#basic-installation-instructions)
|
|
26
10
|
2. [Using the toolkit](#using-the-toolkit)
|
|
27
|
-
3. [
|
|
28
|
-
4. [
|
|
11
|
+
3. [Extending the toolkit](#extending-the-toolkit)
|
|
12
|
+
4. [Reproducing the Benchmark](#reproducing-the-benchmark)
|
|
29
13
|
5. [License and terms of use](#license-and-terms-of-use)
|
|
30
14
|
6. [References](#references)
|
|
31
15
|
|
|
32
|
-
|
|
16
|
+
humancompatible-train is still under active development! If you find bugs or have feature
|
|
33
17
|
requests, please file a
|
|
34
18
|
[Github issue](https://github.com/humancompatible/train/issues).
|
|
35
19
|
|
|
36
|
-
##
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Use
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pip install humancompatible-train
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The only dependencies of this package are `numpy` and `torch`.
|
|
29
|
+
|
|
30
|
+
## Using the toolkit
|
|
31
|
+
|
|
32
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
33
|
+
|
|
34
|
+
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `double_step` does updates related to the dual parameters and prepares for the primal update (by, e.g., saving constraint gradients), and `step()` updates the primal parameters.
|
|
35
|
+
|
|
36
|
+
In general, your code using `humancompatible-train` would look something like this:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
for inputs, labels in dataloader:
|
|
40
|
+
# inference
|
|
41
|
+
outputs = model(inputs)
|
|
42
|
+
# calculate constraints and grads
|
|
43
|
+
for constraint in constraints:
|
|
44
|
+
c_eval = constraint(outputs, labels)
|
|
45
|
+
c_eval.backwards(retain_grad=True)
|
|
46
|
+
# depending on optimizer, update dual parameters / save constraint gradient / both
|
|
47
|
+
optimizer.dual_step(c_eval)
|
|
48
|
+
optimizer.zero_grad()
|
|
49
|
+
# calculate objective
|
|
50
|
+
loss = criterion(outputs,labels)
|
|
51
|
+
loss.backwards()
|
|
52
|
+
optimizer.step()
|
|
53
|
+
optimizer.zero_grad()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Our idea is to
|
|
57
|
+
1. Deviate minimally from the usual PyTorch workflow
|
|
58
|
+
2. Make different stochastic-constrained stochastic optimization algorithms nearly interchangable in the code.
|
|
59
|
+
|
|
60
|
+
### Code examples
|
|
61
|
+
|
|
62
|
+
You are invited to check out our new API presented in notebooks in the `examples` folder.
|
|
63
|
+
|
|
64
|
+
*The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.*
|
|
65
|
+
|
|
66
|
+
## Extending the toolkit
|
|
67
|
+
|
|
68
|
+
### Adding new code
|
|
69
|
+
|
|
70
|
+
**To add a new algorithm**, you can subclass the PyTorch ```Optimizer``` class and proceed following the API guideline presented above.
|
|
71
|
+
|
|
72
|
+
## Reproducing the Benchmark
|
|
73
|
+
|
|
74
|
+
The code used in [our benchmark paper](https://arxiv.org/abs/2507.04033) is not migrated to the new API yet (WIP).
|
|
75
|
+
|
|
76
|
+
### Basic installation instructions
|
|
37
77
|
The code requires Python version ```3.11```.
|
|
38
78
|
|
|
39
79
|
1. Create a virtual environment
|
|
@@ -72,22 +112,6 @@ after installing requirements.txt; otherwise, the algorithm will run slower. How
|
|
|
72
112
|
<!-- pip install folktables -->
|
|
73
113
|
<!-- ``` -->
|
|
74
114
|
|
|
75
|
-
## Using the toolkit
|
|
76
|
-
|
|
77
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
78
|
-
|
|
79
|
-
### Code examples
|
|
80
|
-
|
|
81
|
-
You are invited to check out the new API presented in notebooks in the `examples` folder.
|
|
82
|
-
|
|
83
|
-
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `double_step` does updates related to the dual parameters and prepares for the primal update (by, e.g., saving constraint gradients), and `step()` updates the primal parameters.
|
|
84
|
-
|
|
85
|
-
The idea is to make different algorithms nearly interchangable in the code.
|
|
86
|
-
|
|
87
|
-
The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.
|
|
88
|
-
|
|
89
|
-
## Reproducing the Benchmark
|
|
90
|
-
|
|
91
115
|
### Running the algorithms
|
|
92
116
|
|
|
93
117
|
The benchmark comprises the following algorithms:
|
|
@@ -122,15 +146,10 @@ This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `exper
|
|
|
122
146
|
### Producing plots
|
|
123
147
|
The plots and tables like the ones in the paper can be produced using the two notebooks. `experiments/algo_plots.ipynb` houses the convergence plots, and `experiments/model_plots.ipynb` - all the others.
|
|
124
148
|
|
|
125
|
-
## Extending the toolkit
|
|
126
|
-
|
|
127
|
-
### Adding new code
|
|
128
|
-
|
|
129
|
-
**To add a new algorithm**, you can subclass the PyTorch ```Optimizer``` class and proceed following the API guideline presented above.
|
|
130
149
|
|
|
131
150
|
## License and terms of use
|
|
132
151
|
|
|
133
|
-
humancompatible
|
|
152
|
+
humancompatible-train is provided under the Apache 2.0 Licence.
|
|
134
153
|
|
|
135
154
|
The benchmark part of the package relies on the Folktables package, provided under MIT Licence.
|
|
136
155
|
It provides code to download data from the American Community Survey
|
|
@@ -154,7 +173,7 @@ For more information, see https://www.census.gov/data/developers/about/terms-of-
|
|
|
154
173
|
|
|
155
174
|
## Future work
|
|
156
175
|
|
|
157
|
-
- Add more algorithms
|
|
176
|
+
- Add more algorithms
|
|
158
177
|
- Add more examples from different fields where constrained training of DNNs is employed
|
|
159
178
|
- Migrate the benchmark to the new API
|
|
160
179
|
|
|
@@ -184,5 +203,4 @@ Facchinei & Kungurtsev (2023) Stochastic Approximation for Expectation Objective
|
|
|
184
203
|
Huang, Zhang & Alacaoglu (2025) Stochastic Smoothed Primal-Dual Algorithms for Nonconvex Optimization with Linear Inequality Constraints, arXiv.
|
|
185
204
|
|
|
186
205
|
<a id="4">[4]</a>
|
|
187
|
-
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
188
|
-
|
|
206
|
+
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
@@ -12,7 +12,7 @@ from omegaconf import DictConfig, OmegaConf
|
|
|
12
12
|
from torch import nn, tensor
|
|
13
13
|
from utils.load_folktables import prepare_folktables_multattr
|
|
14
14
|
from utils.network import SimpleNet
|
|
15
|
-
from humancompatible.train.algorithms.utils import net_grads_to_tensor
|
|
15
|
+
from humancompatible.train.benchmark.algorithms.utils import net_grads_to_tensor
|
|
16
16
|
from itertools import combinations
|
|
17
17
|
from humancompatible.train.fairness.constraints import FairnessConstraint
|
|
18
18
|
|
|
@@ -177,7 +177,7 @@ def run(cfg: DictConfig) -> None:
|
|
|
177
177
|
net = SimpleNet(in_shape=X_test.shape[1], out_shape=1, dtype=DTYPE).to(device)
|
|
178
178
|
|
|
179
179
|
optimizer_name = cfg.alg.import_name
|
|
180
|
-
module = importlib.import_module("humancompatible.train.algorithms")
|
|
180
|
+
module = importlib.import_module("humancompatible.train.benchmark.algorithms")
|
|
181
181
|
Optimizer = getattr(module, optimizer_name)
|
|
182
182
|
|
|
183
183
|
optimizer = Optimizer(net, train_ds, loss_fn, c)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
from torch import Tensor
|
|
4
|
+
from humancompatible.train.algorithms import SSLALM
|
|
5
|
+
|
|
6
|
+
class TestSSLALM(unittest.TestCase):
|
|
7
|
+
def setUp(self):
|
|
8
|
+
# Simple model for testing
|
|
9
|
+
self.model = torch.nn.Linear(2, 1)
|
|
10
|
+
self.params = list(self.model.parameters())
|
|
11
|
+
self.m = 2 # Number of constraints
|
|
12
|
+
self.optimizer = SSLALM(
|
|
13
|
+
self.params,
|
|
14
|
+
m=self.m,
|
|
15
|
+
lr=0.01,
|
|
16
|
+
dual_lr=0.01,
|
|
17
|
+
dual_bound=100.0,
|
|
18
|
+
rho=1.0,
|
|
19
|
+
mu=2.0,
|
|
20
|
+
beta=0.5,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def test_initialization(self):
|
|
24
|
+
# Test if the optimizer is initialized correctly
|
|
25
|
+
self.assertEqual(len(self.optimizer.param_groups), 1)
|
|
26
|
+
self.assertEqual(self.optimizer.m, self.m)
|
|
27
|
+
self.assertEqual(self.optimizer.dual_lr, 0.01)
|
|
28
|
+
self.assertEqual(self.optimizer.dual_bound, 100.0)
|
|
29
|
+
self.assertEqual(self.optimizer.rho, 1.0)
|
|
30
|
+
self.assertEqual(self.optimizer.mu, 2.0)
|
|
31
|
+
self.assertEqual(self.optimizer.beta, 0.5)
|
|
32
|
+
self.assertTrue(isinstance(self.optimizer._dual_vars, Tensor))
|
|
33
|
+
self.assertEqual(self.optimizer._dual_vars.shape, (self.m,))
|
|
34
|
+
|
|
35
|
+
def test_dual_step(self):
|
|
36
|
+
# Test dual variable update
|
|
37
|
+
c_val = torch.tensor([0.5, 0.1])
|
|
38
|
+
self.optimizer.dual_step(0, c_val[0])
|
|
39
|
+
self.assertEqual(self.optimizer._dual_vars[0], 0.005) # 0 + 0.01 * 0.5
|
|
40
|
+
self.optimizer.dual_step(1, c_val[1])
|
|
41
|
+
self.assertEqual(self.optimizer._dual_vars[1], 0.001) # 0 + 0.01 * 0.1
|
|
42
|
+
|
|
43
|
+
def test_dual_bound(self):
|
|
44
|
+
# Test dual variable bounding
|
|
45
|
+
self.optimizer._dual_vars = torch.tensor([101.0, -1.0])
|
|
46
|
+
c_val = torch.tensor([1.0, -1.0])
|
|
47
|
+
self.optimizer.dual_step(0, c_val[0])
|
|
48
|
+
self.optimizer.dual_step(1, c_val[1])
|
|
49
|
+
self.assertEqual(self.optimizer._dual_vars[0], 0.0) # Should be zeroed out
|
|
50
|
+
self.assertEqual(self.optimizer._dual_vars[1], 0.0) # Should be zeroed out
|
|
51
|
+
|
|
52
|
+
# ADD TEST DEALING WITH CONSTRAINTS THAT DONT USE SOME OF THE PARAMS
|
|
53
|
+
|
|
54
|
+
def test_step(self):
|
|
55
|
+
# Test primal parameter update
|
|
56
|
+
# Mock gradients and constraint gradients
|
|
57
|
+
p_pre_step = {}
|
|
58
|
+
for p in self.params:
|
|
59
|
+
p.grad = torch.ones_like(p)
|
|
60
|
+
p_pre_step[p] = p.detach().clone()
|
|
61
|
+
|
|
62
|
+
c_val = torch.tensor([0.1, -0.1])
|
|
63
|
+
c_grads = {p: [torch.ones_like(p) for _ in c_val] for p in self.params}
|
|
64
|
+
|
|
65
|
+
for p in self.params:
|
|
66
|
+
self.optimizer.state[p]["c_grad"] = [g.clone() for g in c_grads[p]]
|
|
67
|
+
self.optimizer.state[p]["smoothing"] = p.detach().clone()
|
|
68
|
+
|
|
69
|
+
self.optimizer._dual_vars = torch.ones(2)
|
|
70
|
+
G = self.optimizer.step(c_val)
|
|
71
|
+
# Check if G is computed and parameters are updated
|
|
72
|
+
self.assertEqual(len(G), len(self.params))
|
|
73
|
+
for i, p in enumerate(self.params):
|
|
74
|
+
self.assertTrue(
|
|
75
|
+
torch.equal(
|
|
76
|
+
G[i],
|
|
77
|
+
(
|
|
78
|
+
p.grad +
|
|
79
|
+
sum(_lambda * c_grads[p][j] for j, _lambda in enumerate(self.optimizer._dual_vars)) +
|
|
80
|
+
sum([c_grads[p][j] * cv for j, cv in enumerate(c_val)])
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_step_with_none_c_val(self):
|
|
86
|
+
# Test step with None c_val (should use self.c_vals)
|
|
87
|
+
c_val = torch.tensor([0.1, -0.1])
|
|
88
|
+
self.optimizer.dual_step(0, c_val[0])
|
|
89
|
+
self.optimizer.dual_step(1, c_val[1])
|
|
90
|
+
|
|
91
|
+
p_pre_step = {}
|
|
92
|
+
for p in self.params:
|
|
93
|
+
p.grad = torch.ones_like(p)
|
|
94
|
+
p_pre_step[p] = p.detach().clone()
|
|
95
|
+
|
|
96
|
+
c_grads = {p: [torch.ones_like(p) for _ in c_val] for p in self.params}
|
|
97
|
+
for p in self.params:
|
|
98
|
+
self.optimizer.state[p]["c_grad"] = [g.clone() for g in c_grads[p]]
|
|
99
|
+
self.optimizer.state[p]["smoothing"] = p.detach().clone()
|
|
100
|
+
|
|
101
|
+
G = self.optimizer.step()
|
|
102
|
+
# Check if G is computed and parameters are updated
|
|
103
|
+
self.assertEqual(len(G), len(self.params))
|
|
104
|
+
for i, p in enumerate(self.params):
|
|
105
|
+
self.assertTrue(
|
|
106
|
+
torch.equal(
|
|
107
|
+
G[i],
|
|
108
|
+
(
|
|
109
|
+
p.grad +
|
|
110
|
+
sum(_lambda * c_grads[p][j] for j, _lambda in enumerate(self.optimizer._dual_vars)) +
|
|
111
|
+
sum([c_grads[p][j] * cv for j, cv in enumerate(c_val)])
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def test_step_with_invalid_c_val(self):
|
|
117
|
+
# Test step with invalid c_val (wrong shape)
|
|
118
|
+
with self.assertRaises(ValueError):
|
|
119
|
+
self.optimizer.step(torch.tensor([0.1]))
|
|
120
|
+
|
|
121
|
+
def test_smoothing_update(self):
|
|
122
|
+
# Test smoothing term update
|
|
123
|
+
p_pre_step = {}
|
|
124
|
+
for p in self.params:
|
|
125
|
+
p.grad = torch.ones_like(p)
|
|
126
|
+
p_pre_step[p] = p.detach().clone()
|
|
127
|
+
c_val = torch.tensor([0.1, -0.1])
|
|
128
|
+
self.optimizer.step(c_val)
|
|
129
|
+
for p in self.params:
|
|
130
|
+
state = self.optimizer.state[p]
|
|
131
|
+
self.assertTrue("smoothing" in state)
|
|
132
|
+
self.assertTrue(torch.all(state["smoothing"] == p_pre_step[p]))
|
|
133
|
+
|
|
134
|
+
def test_error_handling(self):
|
|
135
|
+
# Test error handling for invalid inputs
|
|
136
|
+
with self.assertRaises(ValueError):
|
|
137
|
+
SSLALM(self.params, m=self.m, lr=-0.01)
|
|
138
|
+
with self.assertRaises(ValueError):
|
|
139
|
+
SSLALM(self.params, m=self.m, dual_lr=-0.01)
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
SSLALM(self.params, m=self.m, init_dual_vars=torch.tensor([1.0]))
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
unittest.main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
from humancompatible.train.algorithms import SSG
|
|
4
|
+
|
|
5
|
+
class TestSSG(unittest.TestCase):
|
|
6
|
+
def setUp(self):
|
|
7
|
+
# Simple model for testing
|
|
8
|
+
self.model = torch.nn.Linear(2, 1)
|
|
9
|
+
self.params = list(self.model.parameters())
|
|
10
|
+
self.m = 1 # Number of constraints
|
|
11
|
+
self.optimizer = SSG(
|
|
12
|
+
self.params,
|
|
13
|
+
m=self.m,
|
|
14
|
+
lr=0.01,
|
|
15
|
+
dual_lr=0.01
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def test_initialization(self):
|
|
19
|
+
# Test if the optimizer is initialized correctly
|
|
20
|
+
self.assertEqual(len(self.optimizer.param_groups), 1)
|
|
21
|
+
self.assertEqual(self.optimizer.m, self.m)
|
|
22
|
+
self.assertEqual(self.optimizer.dual_lr, 0.01)
|
|
23
|
+
|
|
24
|
+
def test_dual_step(self):
|
|
25
|
+
# Test dual step saving constraint gradients
|
|
26
|
+
for p in self.params:
|
|
27
|
+
p.grad = torch.ones_like(p)
|
|
28
|
+
self.optimizer.dual_step(0)
|
|
29
|
+
|
|
30
|
+
for p in self.params:
|
|
31
|
+
state = self.optimizer.state[p]
|
|
32
|
+
self.assertTrue(state["c_grad"] is not None)
|
|
33
|
+
|
|
34
|
+
def test_step_obj(self):
|
|
35
|
+
p_pre_step = {}
|
|
36
|
+
for p in self.params:
|
|
37
|
+
p.grad = torch.ones_like(p)
|
|
38
|
+
self.optimizer.state[p]['c_grad'] = [-1.*torch.ones_like(p)]
|
|
39
|
+
p_pre_step[p] = p.detach().clone()
|
|
40
|
+
|
|
41
|
+
self.optimizer.step(c_val=torch.tensor(-1.))
|
|
42
|
+
|
|
43
|
+
for p in self.params:
|
|
44
|
+
self.assertTrue(
|
|
45
|
+
torch.all(
|
|
46
|
+
p == p_pre_step[p]-0.01*torch.ones_like(p)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def test_step_c(self):
|
|
51
|
+
p_pre_step = {}
|
|
52
|
+
for p in self.params:
|
|
53
|
+
p.grad = torch.ones_like(p)
|
|
54
|
+
self.optimizer.state[p]['c_grad'] = [-1.*torch.ones_like(p)]
|
|
55
|
+
p_pre_step[p] = p.detach().clone()
|
|
56
|
+
|
|
57
|
+
self.optimizer.step(c_val=torch.tensor(1.))
|
|
58
|
+
|
|
59
|
+
for p in self.params:
|
|
60
|
+
self.assertTrue(
|
|
61
|
+
torch.all(
|
|
62
|
+
p == p_pre_step[p]+0.01*torch.ones_like(p)
|
|
63
|
+
)
|
|
64
|
+
)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
from torch.utils.data import TensorDataset, Subset, DataLoader
|
|
4
|
+
from humancompatible.train.fairness.utils import BalancedBatchSampler
|
|
5
|
+
|
|
6
|
+
class TestBalancedBatchSampler(unittest.TestCase):
|
|
7
|
+
def setUp(self):
|
|
8
|
+
self.data = torch.tensor([[i, i+1] for i in range(10)])
|
|
9
|
+
self.labels = torch.tensor([0, 0, 1, 1, 1, 2, 2, 2, 2, 2])
|
|
10
|
+
self.dataset = TensorDataset(self.data, self.labels)
|
|
11
|
+
self.subset_indices = [
|
|
12
|
+
[0, 1], # Class 0
|
|
13
|
+
[2, 3, 4], # Class 1
|
|
14
|
+
[5, 6, 7, 8, 9], # Class 2
|
|
15
|
+
]
|
|
16
|
+
self.subset_onehot = torch.tensor([
|
|
17
|
+
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
18
|
+
[0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
|
|
19
|
+
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
|
|
20
|
+
]).T
|
|
21
|
+
|
|
22
|
+
def test_batch_size_divisible(self):
|
|
23
|
+
with self.assertRaises(AssertionError):
|
|
24
|
+
BalancedBatchSampler(subgroup_indices=self.subset_indices, batch_size=4, drop_last=True)
|
|
25
|
+
|
|
26
|
+
def test_onehot_init(self):
|
|
27
|
+
sampler = BalancedBatchSampler(subgroup_onehot=self.subset_onehot, batch_size=3)
|
|
28
|
+
self.assertListEqual(
|
|
29
|
+
[i.tolist() for i in sampler.subset_indices],
|
|
30
|
+
self.subset_indices
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def test_iter(self):
|
|
34
|
+
sampler = BalancedBatchSampler(subgroup_indices=self.subset_indices, batch_size=6, drop_last=True)
|
|
35
|
+
batches = list(sampler)
|
|
36
|
+
self.assertEqual(len(batches), 1) # Only 1 full batch of size 6 (2+2+2)
|
|
37
|
+
self.assertEqual(len(batches[0]), 6)
|
|
38
|
+
|
|
39
|
+
def test_len_drop_last_true(self):
|
|
40
|
+
sampler = BalancedBatchSampler(subgroup_indices=self.subset_indices, batch_size=6, drop_last=True)
|
|
41
|
+
self.assertEqual(len(sampler), 1)
|
|
42
|
+
|
|
43
|
+
def test_balanced_batches(self):
|
|
44
|
+
sampler = BalancedBatchSampler(subgroup_indices=self.subset_indices, batch_size=6, drop_last=True)
|
|
45
|
+
batch = next(iter(sampler))
|
|
46
|
+
# Check that each subset contributes 2 samples
|
|
47
|
+
self.assertEqual(len([i for i in batch if i in self.subset_indices[0]]), 2)
|
|
48
|
+
self.assertEqual(len([i for i in batch if i in self.subset_indices[1]]), 2)
|
|
49
|
+
self.assertEqual(len([i for i in batch if i in self.subset_indices[2]]), 2)
|
|
50
|
+
|
|
51
|
+
class TestDataLoaderIntegration(unittest.TestCase):
|
|
52
|
+
def setUp(self):
|
|
53
|
+
self.data = torch.tensor([[i, i+1] for i in range(10)])
|
|
54
|
+
self.labels = torch.tensor([0, 0, 1, 1, 1, 2, 2, 2, 2, 2])
|
|
55
|
+
self.dataset = TensorDataset(self.data, self.labels)
|
|
56
|
+
self.subset_indices = [
|
|
57
|
+
[0, 1], # Class 0
|
|
58
|
+
[2, 3, 4], # Class 1
|
|
59
|
+
[5, 6, 7, 8, 9], # Class 2
|
|
60
|
+
]
|
|
61
|
+
self.subsets = [Subset(self.dataset, indices) for indices in self.subset_indices]
|
|
62
|
+
|
|
63
|
+
def test_dataloader(self):
|
|
64
|
+
sampler = BalancedBatchSampler(subgroup_indices=self.subset_indices, batch_size=6, drop_last=True)
|
|
65
|
+
dataloader = DataLoader(
|
|
66
|
+
self.dataset,
|
|
67
|
+
batch_sampler=sampler
|
|
68
|
+
)
|
|
69
|
+
batch_data, batch_labels = next(iter(dataloader))
|
|
70
|
+
self.assertEqual(batch_data.shape, (6, 2))
|
|
71
|
+
self.assertEqual(len(batch_labels), 6)
|
|
72
|
+
# Check balance: 2 samples from each class
|
|
73
|
+
self.assertEqual((batch_labels == 0).sum().item(), 2)
|
|
74
|
+
self.assertEqual((batch_labels == 1).sum().item(), 2)
|
|
75
|
+
self.assertEqual((batch_labels == 2).sum().item(), 2)
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
unittest.main()
|