humancompatible-train 0.1.2__tar.gz → 0.1.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.
- {humancompatible_train-0.1.2/humancompatible_train.egg-info → humancompatible_train-0.1.3}/PKG-INFO +46 -20
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/README.md +39 -20
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/__init__.py +1 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/ssl_alm.py +0 -1
- humancompatible_train-0.1.3/humancompatible/train/algorithms/ssl_alm_adam.py +330 -0
- humancompatible_train-0.1.3/humancompatible/train/algorithms/test/test_ssl_alm_adam.py +146 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3/humancompatible_train.egg-info}/PKG-INFO +46 -20
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible_train.egg-info/SOURCES.txt +2 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible_train.egg-info/requires.txt +8 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/pyproject.toml +2 -1
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/LICENCE.txt +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/experiments/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/experiments/calculate_iteration_values.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/experiments/run_folktables.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/experiments/run_folktables_torchalgs.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/ssw.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/test/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/test/test_ssl_alm.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/test/test_ssw.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/constraints/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/constraints/constraint.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/constraints/constraint_fns.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/constraints/torch/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/constraints/torch/constraints.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/utils/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/utils/balanced_batch_sampler.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/utils/tests/__init__.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/fairness/utils/tests/test_balanced_batch_sampler.py +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible_train.egg-info/dependency_links.txt +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible_train.egg-info/top_level.txt +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/setup.cfg +0 -0
- {humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/setup.py +0 -0
{humancompatible_train-0.1.2/humancompatible_train.egg-info → humancompatible_train-0.1.3}/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.3
|
|
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,6 +10,13 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENCE.txt
|
|
11
11
|
Requires-Dist: torch
|
|
12
12
|
Requires-Dist: numpy
|
|
13
|
+
Provides-Extra: examples
|
|
14
|
+
Requires-Dist: ipykernel; extra == "examples"
|
|
15
|
+
Requires-Dist: ipympl; extra == "examples"
|
|
16
|
+
Requires-Dist: fairret; extra == "examples"
|
|
17
|
+
Requires-Dist: folktables; extra == "examples"
|
|
18
|
+
Requires-Dist: scikit-learn; extra == "examples"
|
|
19
|
+
Requires-Dist: matplotlib; extra == "examples"
|
|
13
20
|
Provides-Extra: benchmark
|
|
14
21
|
Requires-Dist: fairret; extra == "benchmark"
|
|
15
22
|
Requires-Dist: matplotlib; extra == "benchmark"
|
|
@@ -31,22 +38,23 @@ The toolkit implements algorithms for constrained training of neural networks ba
|
|
|
31
38
|
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
32
39
|
|
|
33
40
|
## Table of Contents
|
|
41
|
+
|
|
34
42
|
1. [Basic installation instructions](#basic-installation-instructions)
|
|
35
43
|
2. [Using the toolkit](#using-the-toolkit)
|
|
36
|
-
3. [Extending the toolkit](#extending-the-toolkit)
|
|
44
|
+
3. [Extending the toolkit](#extending-the-toolkit)
|
|
37
45
|
4. [Reproducing the Benchmark](#reproducing-the-benchmark)
|
|
38
46
|
5. [License and terms of use](#license-and-terms-of-use)
|
|
39
47
|
6. [References](#references)
|
|
40
48
|
|
|
41
49
|
humancompatible-train is still under active development! If you find bugs or have feature
|
|
42
50
|
requests, please file a
|
|
43
|
-
[Github issue](https://github.com/humancompatible/train/issues).
|
|
51
|
+
[Github issue](https://github.com/humancompatible/train/issues).
|
|
44
52
|
|
|
45
53
|
## Installation
|
|
46
54
|
|
|
47
55
|
Use
|
|
48
56
|
|
|
49
|
-
```
|
|
57
|
+
```bash
|
|
50
58
|
pip install humancompatible-train
|
|
51
59
|
```
|
|
52
60
|
|
|
@@ -56,11 +64,11 @@ The only dependencies of this package are `numpy` and `torch`.
|
|
|
56
64
|
|
|
57
65
|
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
58
66
|
|
|
59
|
-
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `
|
|
67
|
+
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `dual_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
68
|
|
|
61
69
|
In general, your code using `humancompatible-train` would look something like this:
|
|
62
70
|
|
|
63
|
-
```
|
|
71
|
+
```python
|
|
64
72
|
for inputs, labels in dataloader:
|
|
65
73
|
# inference
|
|
66
74
|
outputs = model(inputs)
|
|
@@ -79,6 +87,7 @@ for inputs, labels in dataloader:
|
|
|
79
87
|
```
|
|
80
88
|
|
|
81
89
|
Our idea is to
|
|
90
|
+
|
|
82
91
|
1. Deviate minimally from the usual PyTorch workflow
|
|
83
92
|
2. Make different stochastic-constrained stochastic optimization algorithms nearly interchangable in the code.
|
|
84
93
|
|
|
@@ -86,6 +95,12 @@ Our idea is to
|
|
|
86
95
|
|
|
87
96
|
You are invited to check out our new API presented in notebooks in the `examples` folder.
|
|
88
97
|
|
|
98
|
+
The example notebooks have additional dependencies, such as `fairret`. To install those, run
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
pip install humancompatible-train[examples]
|
|
102
|
+
```
|
|
103
|
+
|
|
89
104
|
*The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.*
|
|
90
105
|
|
|
91
106
|
## Extending the toolkit
|
|
@@ -99,21 +114,27 @@ You are invited to check out our new API presented in notebooks in the `examples
|
|
|
99
114
|
The code used in [our benchmark paper](https://arxiv.org/abs/2507.04033) is not migrated to the new API yet (WIP).
|
|
100
115
|
|
|
101
116
|
### Basic installation instructions
|
|
117
|
+
|
|
102
118
|
The code requires Python version ```3.11```.
|
|
103
119
|
|
|
104
120
|
1. Create a virtual environment
|
|
105
121
|
|
|
106
122
|
**bash** (Linux)
|
|
123
|
+
|
|
107
124
|
```
|
|
108
125
|
python3.11 -m venv fairbenchenv
|
|
109
126
|
source fairbenchenv/bin/activate
|
|
110
127
|
```
|
|
128
|
+
|
|
111
129
|
**cmd** (Windows)
|
|
130
|
+
|
|
112
131
|
```
|
|
113
132
|
python -m venv fairbenchenv
|
|
114
133
|
fairbenchenv\Scripts\activate.bat
|
|
115
134
|
```
|
|
135
|
+
|
|
116
136
|
2. Install from source.
|
|
137
|
+
|
|
117
138
|
```
|
|
118
139
|
git clone https://github.com/humancompatible/train.git
|
|
119
140
|
cd train
|
|
@@ -122,11 +143,12 @@ pip install .
|
|
|
122
143
|
```
|
|
123
144
|
|
|
124
145
|
If you wish to edit the code of the algorithms, install as an editable package:
|
|
146
|
+
|
|
125
147
|
```
|
|
126
148
|
pip install -e .
|
|
127
149
|
```
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
**Warning**: it is recommended to use Stochastic Ghost with the mkl-accelerated version of the scipy package with Stochastic Ghost; to install it, run
|
|
130
152
|
|
|
131
153
|
```pip install --force-reinstall -i https://software.repos.intel.com/python/pypi scipy```
|
|
132
154
|
|
|
@@ -140,12 +162,14 @@ after installing requirements.txt; otherwise, the algorithm will run slower. How
|
|
|
140
162
|
### Running the algorithms
|
|
141
163
|
|
|
142
164
|
The benchmark comprises the following algorithms:
|
|
165
|
+
|
|
143
166
|
- Stochastic Ghost [[2]](#2),
|
|
144
167
|
- SSL-ALM [[3]](#3),
|
|
145
168
|
- Stochastic Switching Subgradient [[4]](#4).
|
|
146
169
|
|
|
147
170
|
To reproduce the experiments of the paper, run the following:
|
|
148
|
-
|
|
171
|
+
|
|
172
|
+
```
|
|
149
173
|
cd experiments
|
|
150
174
|
python run_folktables.py data=folktables alg=sslalm
|
|
151
175
|
python run_folktables.py data=folktables alg=alm
|
|
@@ -154,23 +178,25 @@ python run_folktables.py data=folktables alg=ssg
|
|
|
154
178
|
python run_folktables.py data=folktables alg=sgd # baseline, no fairness
|
|
155
179
|
python run_folktables.py data=folktables alg=fairret # baseline, fairness with regularizer
|
|
156
180
|
```
|
|
181
|
+
|
|
157
182
|
Each command will start 10 runs of the `alg`, 30 seconds each.
|
|
158
183
|
The results will be saved to `experiments/utils/saved_models` and `experiments/utils/exp_results`.
|
|
159
184
|
<!-- In the repository, we include the configuration needed to reproduce the experiments in the paper. To do so, go to `experiments` and run `python run_folktables.py data=folktables alg=sslalm`. -->
|
|
160
185
|
<!-- Repeat for the other algorithms by changing the `alg` parameter. -->
|
|
161
186
|
|
|
162
|
-
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
188
|
+
|
|
189
|
+
- To change the parameters of the experiment, such as the number of runs for each algorithm, run time, the dataset used (*note: for now supports only Folktables*) - use `experiment.yaml`.
|
|
190
|
+
- To change the dataset settings - such as file location - or do dataset-specific adjustments - such as the configuration of the protected attributes - use `data/{dataset_name}.yaml`
|
|
191
|
+
- To change algorithm hyperparameters, use `alg/{algorithm_name}.yaml`.
|
|
192
|
+
- To change constraint hyperparameters, use `constraint/{constraint_name}.yaml`
|
|
167
193
|
|
|
168
194
|
<!-- ; it is installed as one of the dependencies. -->
|
|
169
195
|
<!-- To learn more about using Hydra, please check out the [official tutorial](https://hydra.cc/docs/tutorials/basic/your_first_app). -->
|
|
170
196
|
|
|
171
197
|
### Producing plots
|
|
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.
|
|
173
198
|
|
|
199
|
+
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.
|
|
174
200
|
|
|
175
201
|
## License and terms of use
|
|
176
202
|
|
|
@@ -180,7 +206,7 @@ The benchmark part of the package relies on the Folktables package, provided und
|
|
|
180
206
|
It provides code to download data from the American Community Survey
|
|
181
207
|
(ACS) Public Use Microdata Sample (PUMS) files managed by the US Census Bureau.
|
|
182
208
|
The data itself is governed by the terms of use provided by the Census Bureau.
|
|
183
|
-
For more information, see https://www.census.gov/data/developers/about/terms-of-service.html
|
|
209
|
+
For more information, see <https://www.census.gov/data/developers/about/terms-of-service.html>
|
|
184
210
|
|
|
185
211
|
<!-- ## Cite this work -->
|
|
186
212
|
|
|
@@ -206,7 +232,7 @@ For more information, see https://www.census.gov/data/developers/about/terms-of-
|
|
|
206
232
|
|
|
207
233
|
If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs/2507.04033),
|
|
208
234
|
|
|
209
|
-
```
|
|
235
|
+
```bibtex
|
|
210
236
|
@misc{kliachkin2025benchmarkingstochasticapproximationalgorithms,
|
|
211
237
|
title={Benchmarking Stochastic Approximation Algorithms for Fairness-Constrained Training of Deep Neural Networks},
|
|
212
238
|
author={Andrii Kliachkin and Jana Lepšová and Gilles Bareilles and Jakub Mareček},
|
|
@@ -218,14 +244,14 @@ If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs
|
|
|
218
244
|
}
|
|
219
245
|
```
|
|
220
246
|
|
|
221
|
-
<a id="1">[1]</a>
|
|
247
|
+
<a id="1">[1]</a>
|
|
222
248
|
Ding, Hardt & Miller et al. (2021) Retiring Adult: New Datasets for Fair Machine Learning, Curran Associates, Inc..
|
|
223
249
|
|
|
224
|
-
<a id="2">[2]</a>
|
|
250
|
+
<a id="2">[2]</a>
|
|
225
251
|
Facchinei & Kungurtsev (2023) Stochastic Approximation for Expectation Objective and Expectation Inequality-Constrained Nonconvex Optimization, arXiv.
|
|
226
252
|
|
|
227
|
-
<a id="3">[3]</a>
|
|
253
|
+
<a id="3">[3]</a>
|
|
228
254
|
Huang, Zhang & Alacaoglu (2025) Stochastic Smoothed Primal-Dual Algorithms for Nonconvex Optimization with Linear Inequality Constraints, arXiv.
|
|
229
255
|
|
|
230
|
-
<a id="4">[4]</a>
|
|
256
|
+
<a id="4">[4]</a>
|
|
231
257
|
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
@@ -6,22 +6,23 @@ The toolkit implements algorithms for constrained training of neural networks ba
|
|
|
6
6
|
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
7
7
|
|
|
8
8
|
## Table of Contents
|
|
9
|
+
|
|
9
10
|
1. [Basic installation instructions](#basic-installation-instructions)
|
|
10
11
|
2. [Using the toolkit](#using-the-toolkit)
|
|
11
|
-
3. [Extending the toolkit](#extending-the-toolkit)
|
|
12
|
+
3. [Extending the toolkit](#extending-the-toolkit)
|
|
12
13
|
4. [Reproducing the Benchmark](#reproducing-the-benchmark)
|
|
13
14
|
5. [License and terms of use](#license-and-terms-of-use)
|
|
14
15
|
6. [References](#references)
|
|
15
16
|
|
|
16
17
|
humancompatible-train is still under active development! If you find bugs or have feature
|
|
17
18
|
requests, please file a
|
|
18
|
-
[Github issue](https://github.com/humancompatible/train/issues).
|
|
19
|
+
[Github issue](https://github.com/humancompatible/train/issues).
|
|
19
20
|
|
|
20
21
|
## Installation
|
|
21
22
|
|
|
22
23
|
Use
|
|
23
24
|
|
|
24
|
-
```
|
|
25
|
+
```bash
|
|
25
26
|
pip install humancompatible-train
|
|
26
27
|
```
|
|
27
28
|
|
|
@@ -31,11 +32,11 @@ The only dependencies of this package are `numpy` and `torch`.
|
|
|
31
32
|
|
|
32
33
|
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
33
34
|
|
|
34
|
-
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `
|
|
35
|
+
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `dual_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
|
|
|
36
37
|
In general, your code using `humancompatible-train` would look something like this:
|
|
37
38
|
|
|
38
|
-
```
|
|
39
|
+
```python
|
|
39
40
|
for inputs, labels in dataloader:
|
|
40
41
|
# inference
|
|
41
42
|
outputs = model(inputs)
|
|
@@ -54,6 +55,7 @@ for inputs, labels in dataloader:
|
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
Our idea is to
|
|
58
|
+
|
|
57
59
|
1. Deviate minimally from the usual PyTorch workflow
|
|
58
60
|
2. Make different stochastic-constrained stochastic optimization algorithms nearly interchangable in the code.
|
|
59
61
|
|
|
@@ -61,6 +63,12 @@ Our idea is to
|
|
|
61
63
|
|
|
62
64
|
You are invited to check out our new API presented in notebooks in the `examples` folder.
|
|
63
65
|
|
|
66
|
+
The example notebooks have additional dependencies, such as `fairret`. To install those, run
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
pip install humancompatible-train[examples]
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
*The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.*
|
|
65
73
|
|
|
66
74
|
## Extending the toolkit
|
|
@@ -74,21 +82,27 @@ You are invited to check out our new API presented in notebooks in the `examples
|
|
|
74
82
|
The code used in [our benchmark paper](https://arxiv.org/abs/2507.04033) is not migrated to the new API yet (WIP).
|
|
75
83
|
|
|
76
84
|
### Basic installation instructions
|
|
85
|
+
|
|
77
86
|
The code requires Python version ```3.11```.
|
|
78
87
|
|
|
79
88
|
1. Create a virtual environment
|
|
80
89
|
|
|
81
90
|
**bash** (Linux)
|
|
91
|
+
|
|
82
92
|
```
|
|
83
93
|
python3.11 -m venv fairbenchenv
|
|
84
94
|
source fairbenchenv/bin/activate
|
|
85
95
|
```
|
|
96
|
+
|
|
86
97
|
**cmd** (Windows)
|
|
98
|
+
|
|
87
99
|
```
|
|
88
100
|
python -m venv fairbenchenv
|
|
89
101
|
fairbenchenv\Scripts\activate.bat
|
|
90
102
|
```
|
|
103
|
+
|
|
91
104
|
2. Install from source.
|
|
105
|
+
|
|
92
106
|
```
|
|
93
107
|
git clone https://github.com/humancompatible/train.git
|
|
94
108
|
cd train
|
|
@@ -97,11 +111,12 @@ pip install .
|
|
|
97
111
|
```
|
|
98
112
|
|
|
99
113
|
If you wish to edit the code of the algorithms, install as an editable package:
|
|
114
|
+
|
|
100
115
|
```
|
|
101
116
|
pip install -e .
|
|
102
117
|
```
|
|
103
118
|
|
|
104
|
-
|
|
119
|
+
**Warning**: it is recommended to use Stochastic Ghost with the mkl-accelerated version of the scipy package with Stochastic Ghost; to install it, run
|
|
105
120
|
|
|
106
121
|
```pip install --force-reinstall -i https://software.repos.intel.com/python/pypi scipy```
|
|
107
122
|
|
|
@@ -115,12 +130,14 @@ after installing requirements.txt; otherwise, the algorithm will run slower. How
|
|
|
115
130
|
### Running the algorithms
|
|
116
131
|
|
|
117
132
|
The benchmark comprises the following algorithms:
|
|
133
|
+
|
|
118
134
|
- Stochastic Ghost [[2]](#2),
|
|
119
135
|
- SSL-ALM [[3]](#3),
|
|
120
136
|
- Stochastic Switching Subgradient [[4]](#4).
|
|
121
137
|
|
|
122
138
|
To reproduce the experiments of the paper, run the following:
|
|
123
|
-
|
|
139
|
+
|
|
140
|
+
```
|
|
124
141
|
cd experiments
|
|
125
142
|
python run_folktables.py data=folktables alg=sslalm
|
|
126
143
|
python run_folktables.py data=folktables alg=alm
|
|
@@ -129,23 +146,25 @@ python run_folktables.py data=folktables alg=ssg
|
|
|
129
146
|
python run_folktables.py data=folktables alg=sgd # baseline, no fairness
|
|
130
147
|
python run_folktables.py data=folktables alg=fairret # baseline, fairness with regularizer
|
|
131
148
|
```
|
|
149
|
+
|
|
132
150
|
Each command will start 10 runs of the `alg`, 30 seconds each.
|
|
133
151
|
The results will be saved to `experiments/utils/saved_models` and `experiments/utils/exp_results`.
|
|
134
152
|
<!-- In the repository, we include the configuration needed to reproduce the experiments in the paper. To do so, go to `experiments` and run `python run_folktables.py data=folktables alg=sslalm`. -->
|
|
135
153
|
<!-- Repeat for the other algorithms by changing the `alg` parameter. -->
|
|
136
154
|
|
|
137
|
-
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
156
|
+
|
|
157
|
+
- To change the parameters of the experiment, such as the number of runs for each algorithm, run time, the dataset used (*note: for now supports only Folktables*) - use `experiment.yaml`.
|
|
158
|
+
- To change the dataset settings - such as file location - or do dataset-specific adjustments - such as the configuration of the protected attributes - use `data/{dataset_name}.yaml`
|
|
159
|
+
- To change algorithm hyperparameters, use `alg/{algorithm_name}.yaml`.
|
|
160
|
+
- To change constraint hyperparameters, use `constraint/{constraint_name}.yaml`
|
|
142
161
|
|
|
143
162
|
<!-- ; it is installed as one of the dependencies. -->
|
|
144
163
|
<!-- To learn more about using Hydra, please check out the [official tutorial](https://hydra.cc/docs/tutorials/basic/your_first_app). -->
|
|
145
164
|
|
|
146
165
|
### Producing plots
|
|
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.
|
|
148
166
|
|
|
167
|
+
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.
|
|
149
168
|
|
|
150
169
|
## License and terms of use
|
|
151
170
|
|
|
@@ -155,7 +174,7 @@ The benchmark part of the package relies on the Folktables package, provided und
|
|
|
155
174
|
It provides code to download data from the American Community Survey
|
|
156
175
|
(ACS) Public Use Microdata Sample (PUMS) files managed by the US Census Bureau.
|
|
157
176
|
The data itself is governed by the terms of use provided by the Census Bureau.
|
|
158
|
-
For more information, see https://www.census.gov/data/developers/about/terms-of-service.html
|
|
177
|
+
For more information, see <https://www.census.gov/data/developers/about/terms-of-service.html>
|
|
159
178
|
|
|
160
179
|
<!-- ## Cite this work -->
|
|
161
180
|
|
|
@@ -181,7 +200,7 @@ For more information, see https://www.census.gov/data/developers/about/terms-of-
|
|
|
181
200
|
|
|
182
201
|
If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs/2507.04033),
|
|
183
202
|
|
|
184
|
-
```
|
|
203
|
+
```bibtex
|
|
185
204
|
@misc{kliachkin2025benchmarkingstochasticapproximationalgorithms,
|
|
186
205
|
title={Benchmarking Stochastic Approximation Algorithms for Fairness-Constrained Training of Deep Neural Networks},
|
|
187
206
|
author={Andrii Kliachkin and Jana Lepšová and Gilles Bareilles and Jakub Mareček},
|
|
@@ -193,14 +212,14 @@ If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs
|
|
|
193
212
|
}
|
|
194
213
|
```
|
|
195
214
|
|
|
196
|
-
<a id="1">[1]</a>
|
|
215
|
+
<a id="1">[1]</a>
|
|
197
216
|
Ding, Hardt & Miller et al. (2021) Retiring Adult: New Datasets for Fair Machine Learning, Curran Associates, Inc..
|
|
198
217
|
|
|
199
|
-
<a id="2">[2]</a>
|
|
218
|
+
<a id="2">[2]</a>
|
|
200
219
|
Facchinei & Kungurtsev (2023) Stochastic Approximation for Expectation Objective and Expectation Inequality-Constrained Nonconvex Optimization, arXiv.
|
|
201
220
|
|
|
202
|
-
<a id="3">[3]</a>
|
|
221
|
+
<a id="3">[3]</a>
|
|
203
222
|
Huang, Zhang & Alacaoglu (2025) Stochastic Smoothed Primal-Dual Algorithms for Nonconvex Optimization with Linear Inequality Constraints, arXiv.
|
|
204
223
|
|
|
205
|
-
<a id="4">[4]</a>
|
|
206
|
-
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
224
|
+
<a id="4">[4]</a>
|
|
225
|
+
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from typing import Iterable, Optional, Union
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
from torch.optim.optimizer import Optimizer, _use_grad_for_differentiable
|
|
6
|
+
|
|
7
|
+
class SSLALM_Adam(Optimizer):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
params,
|
|
11
|
+
m: int,
|
|
12
|
+
# tau in paper
|
|
13
|
+
lr: Union[float, Tensor] = 5e-2,
|
|
14
|
+
# eta in paper
|
|
15
|
+
dual_lr: Union[
|
|
16
|
+
float, Tensor
|
|
17
|
+
] = 5e-2, # keep as tensor for different learning rates for different constraints in the future? idk
|
|
18
|
+
dual_bound : Union[
|
|
19
|
+
float, Tensor
|
|
20
|
+
] = 100,
|
|
21
|
+
# penalty term multiplier
|
|
22
|
+
rho: float = 1.0,
|
|
23
|
+
# smoothing term multiplier
|
|
24
|
+
mu: float = 2.0,
|
|
25
|
+
# smoothing term update multiplier
|
|
26
|
+
beta: float = 0.5,
|
|
27
|
+
beta1: float = 0.9,
|
|
28
|
+
beta2: float = 0.999,
|
|
29
|
+
eps: float = 1e-8,
|
|
30
|
+
amsgrad: bool = False,
|
|
31
|
+
*,
|
|
32
|
+
init_dual_vars: Optional[Tensor] = None,
|
|
33
|
+
# whether some of the dual variables should not be updated
|
|
34
|
+
fix_dual_vars: Optional[Tensor] = None,
|
|
35
|
+
differentiable: bool = False,
|
|
36
|
+
# custom_project_fn: Optional[Callable] = project_fn
|
|
37
|
+
):
|
|
38
|
+
if isinstance(lr, torch.Tensor) and lr.numel() != 1:
|
|
39
|
+
raise ValueError("Tensor lr must be 1-element")
|
|
40
|
+
if isinstance(dual_lr, torch.Tensor) and lr.numel() != 1:
|
|
41
|
+
raise ValueError("Tensor dual_lr must be 1-element")
|
|
42
|
+
if lr < 0.0:
|
|
43
|
+
raise ValueError(f"Invalid learning rate: {lr}")
|
|
44
|
+
if dual_lr < 0.0:
|
|
45
|
+
raise ValueError(f"Invalid dual learning rate: {dual_lr}")
|
|
46
|
+
if init_dual_vars is not None and len(init_dual_vars) != m:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"init_dual_vars should be of length m: expected {m}, got {len(init_dual_vars)}"
|
|
49
|
+
)
|
|
50
|
+
if fix_dual_vars is not None:
|
|
51
|
+
raise NotImplementedError()
|
|
52
|
+
if init_dual_vars is None and fix_dual_vars is not None:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"if fix_dual_vars is not None, init_dual_vars should not be None."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if differentiable:
|
|
58
|
+
raise NotImplementedError("TorchSSLALM does not support differentiable")
|
|
59
|
+
|
|
60
|
+
defaults = dict(
|
|
61
|
+
lr=lr,
|
|
62
|
+
dual_lr=dual_lr,
|
|
63
|
+
rho=rho,
|
|
64
|
+
mu=mu,
|
|
65
|
+
beta=beta,
|
|
66
|
+
amsgrad=amsgrad,
|
|
67
|
+
differentiable=differentiable,
|
|
68
|
+
# custom_project_fn=custom_project_fn
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
super().__init__(params, defaults)
|
|
72
|
+
|
|
73
|
+
# self.param_groups.append()
|
|
74
|
+
|
|
75
|
+
self.m = m
|
|
76
|
+
self.dual_lr = dual_lr
|
|
77
|
+
self.dual_bound = dual_bound
|
|
78
|
+
self.rho = rho
|
|
79
|
+
self.beta = beta
|
|
80
|
+
self.beta1 = beta1
|
|
81
|
+
self.beta2 = beta2
|
|
82
|
+
self.mu = mu
|
|
83
|
+
self.c_vals: list[Union[float, Tensor]] = []
|
|
84
|
+
self._c_val_average = [None]
|
|
85
|
+
self.beta1 = beta1
|
|
86
|
+
self.beta2 = beta2
|
|
87
|
+
self.eps = eps
|
|
88
|
+
# essentially, move everything here to self.state[param_group]
|
|
89
|
+
# self.state[param_group]['smoothing_avg'] <= z for that param_group;
|
|
90
|
+
# ...['grad'] <= grad w.r.t. that param_group
|
|
91
|
+
# ...['G'] <= G w.r.t. that param_group // idk if necessary
|
|
92
|
+
# ...['c_grad'][c_i] <= grad of ith constraint w.r.t. that group<w
|
|
93
|
+
if init_dual_vars is not None:
|
|
94
|
+
self._dual_vars = init_dual_vars
|
|
95
|
+
else:
|
|
96
|
+
self._dual_vars = torch.zeros(m, requires_grad=False)
|
|
97
|
+
|
|
98
|
+
def _init_group(
|
|
99
|
+
self,
|
|
100
|
+
group,
|
|
101
|
+
params,
|
|
102
|
+
grads,
|
|
103
|
+
c_grads,
|
|
104
|
+
exp_avgs,
|
|
105
|
+
exp_avg_sqs,
|
|
106
|
+
max_exp_avg_sqs,
|
|
107
|
+
state_steps,
|
|
108
|
+
smoothing
|
|
109
|
+
):
|
|
110
|
+
# SHOULDN'T calculate values, only set them from the state of the respective param_group
|
|
111
|
+
# calculations only happen in step() (or rather in the func version of step)
|
|
112
|
+
has_sparse_grad = False
|
|
113
|
+
|
|
114
|
+
for p in group["params"]:
|
|
115
|
+
state = self.state[p]
|
|
116
|
+
|
|
117
|
+
params.append(p)
|
|
118
|
+
|
|
119
|
+
# load z (smoothing term)
|
|
120
|
+
# Lazy state initialization
|
|
121
|
+
if len(state) == 0:
|
|
122
|
+
state["smoothing"] = p.detach().clone()
|
|
123
|
+
state["c_grad"] = []
|
|
124
|
+
|
|
125
|
+
state["step"] = (
|
|
126
|
+
torch.tensor(0.0)
|
|
127
|
+
)
|
|
128
|
+
# Exponential moving average of gradient values
|
|
129
|
+
state["exp_avg"] = torch.zeros_like(
|
|
130
|
+
p, memory_format=torch.preserve_format
|
|
131
|
+
)
|
|
132
|
+
# Exponential moving average of squared gradient values
|
|
133
|
+
state["exp_avg_sq"] = torch.zeros_like(
|
|
134
|
+
p, memory_format=torch.preserve_format
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if group["amsgrad"]:
|
|
138
|
+
# raise NotImplementedError()
|
|
139
|
+
# Maintains max of all exp. moving avg. of sq. grad. values
|
|
140
|
+
state["max_exp_avg_sq"] = torch.zeros_like(
|
|
141
|
+
p, memory_format=torch.preserve_format
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
exp_avgs.append(state["exp_avg"])
|
|
145
|
+
exp_avg_sqs.append(state["exp_avg_sq"])
|
|
146
|
+
|
|
147
|
+
if group["amsgrad"]:
|
|
148
|
+
max_exp_avg_sqs.append(state["max_exp_avg_sq"])
|
|
149
|
+
if group["differentiable"] and state["step"].requires_grad:
|
|
150
|
+
raise RuntimeError(
|
|
151
|
+
"`requires_grad` is not supported for `step` in differentiable mode"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
smoothing.append(state.get("smoothing"))
|
|
155
|
+
|
|
156
|
+
state_steps.append(state["step"])
|
|
157
|
+
|
|
158
|
+
grads.append(p.grad)
|
|
159
|
+
c_grads.append(state.get("c_grad"))
|
|
160
|
+
return has_sparse_grad
|
|
161
|
+
|
|
162
|
+
def __setstate__(self, state):
|
|
163
|
+
super().__setstate__(state)
|
|
164
|
+
for group in self.param_groups:
|
|
165
|
+
group.setdefault("amsgrad", False)
|
|
166
|
+
group.setdefault("maximize", False)
|
|
167
|
+
group.setdefault("foreach", None)
|
|
168
|
+
group.setdefault("capturable", False)
|
|
169
|
+
group.setdefault("differentiable", False)
|
|
170
|
+
group.setdefault("decoupled_weight_decay", False)
|
|
171
|
+
|
|
172
|
+
def dual_step(self, i: int, c_val: Tensor):
|
|
173
|
+
r"""Perform an update of the dual parameters.
|
|
174
|
+
Also saves constraint gradient for weight update. To be called BEFORE :func:`step` in an iteration!
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
i (int): index of the constraint
|
|
178
|
+
c_val (Tensor): an estimate of the value of the constraint at which the gradient was computed; used for dual parameter update
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
# c_vals is cleaned in step()
|
|
182
|
+
self.c_vals.append(c_val.detach())
|
|
183
|
+
|
|
184
|
+
# update dual multipliers
|
|
185
|
+
dual_update_tensor = torch.zeros_like(self._dual_vars)
|
|
186
|
+
dual_update_tensor[i] = self.dual_lr * c_val
|
|
187
|
+
self._dual_vars.add_(dual_update_tensor)
|
|
188
|
+
for i in range(len(self._dual_vars)):
|
|
189
|
+
if self._dual_vars[i] >= self.dual_bound or self._dual_vars[i] < 0:
|
|
190
|
+
self._dual_vars[i].zero_()
|
|
191
|
+
|
|
192
|
+
# save constraint grad
|
|
193
|
+
for group in self.param_groups:
|
|
194
|
+
params: list[Tensor] = []
|
|
195
|
+
grads: list[Tensor] = []
|
|
196
|
+
c_grads: list[Tensor] = []
|
|
197
|
+
smoothing: list[Tensor] = []
|
|
198
|
+
exp_avgs: list[Tensor] = []
|
|
199
|
+
exp_avg_sqs: list[Tensor] = []
|
|
200
|
+
max_exp_avg_sqs: list[Tensor] = []
|
|
201
|
+
state_steps: list[Tensor] = []
|
|
202
|
+
_ = self._init_group(
|
|
203
|
+
group,
|
|
204
|
+
params,
|
|
205
|
+
grads,
|
|
206
|
+
c_grads,
|
|
207
|
+
exp_avgs,
|
|
208
|
+
exp_avg_sqs,
|
|
209
|
+
max_exp_avg_sqs,
|
|
210
|
+
state_steps,
|
|
211
|
+
smoothing
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
for p in group["params"]:
|
|
215
|
+
state = self.state[p]
|
|
216
|
+
# state['c_grad'] is cleaned in step()
|
|
217
|
+
# so it is always empty on dual_step()
|
|
218
|
+
state["c_grad"].append(p.grad)
|
|
219
|
+
|
|
220
|
+
@_use_grad_for_differentiable
|
|
221
|
+
def step(self, c_val: Union[Iterable | Tensor] = None):
|
|
222
|
+
r"""Perform an update of the primal parameters (network weights & slack variables). To be called AFTER :func:`dual_step` in an iteration!
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
c_val (Tensor): an Iterable of estimates of values of **ALL** constraints; used for primal parameter update.
|
|
226
|
+
Ideally, must be evaluated on an independent sample from the one used in :func:`dual_step`
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
if c_val is None:
|
|
230
|
+
c_val = self.c_vals
|
|
231
|
+
if isinstance(c_val, Iterable) and not isinstance(c_val, torch.Tensor):
|
|
232
|
+
# if len(c_val) == 1 and isinstance(c_val[0], torch.Tensor):
|
|
233
|
+
# c_val = c_val[0]
|
|
234
|
+
# else:
|
|
235
|
+
c_val = torch.stack(c_val)
|
|
236
|
+
if c_val.ndim > 1:
|
|
237
|
+
c_val = c_val.squeeze(-1)
|
|
238
|
+
|
|
239
|
+
if c_val.numel() != self.m:
|
|
240
|
+
raise ValueError(f"Number of elements in c_val must be equal to m={self.m}, got {c_val.numel()}")
|
|
241
|
+
G = []
|
|
242
|
+
|
|
243
|
+
for group in self.param_groups:
|
|
244
|
+
params: list[Tensor] = []
|
|
245
|
+
grads: list[Tensor] = []
|
|
246
|
+
c_grads: list[Tensor] = []
|
|
247
|
+
smoothing: list[Tensor] = []
|
|
248
|
+
exp_avgs: list[Tensor] = []
|
|
249
|
+
exp_avg_sqs: list[Tensor] = []
|
|
250
|
+
max_exp_avg_sqs: list[Tensor] = []
|
|
251
|
+
state_steps: list[Tensor] = []
|
|
252
|
+
lr = group["lr"]
|
|
253
|
+
amsgrad = group["amsgrad"]
|
|
254
|
+
|
|
255
|
+
_ = self._init_group(
|
|
256
|
+
group,
|
|
257
|
+
params,
|
|
258
|
+
grads,
|
|
259
|
+
c_grads,
|
|
260
|
+
exp_avgs,
|
|
261
|
+
exp_avg_sqs,
|
|
262
|
+
max_exp_avg_sqs,
|
|
263
|
+
state_steps,
|
|
264
|
+
smoothing
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
for i, param in enumerate(params):
|
|
268
|
+
### calculate Lagrange f-n gradient (G) ###
|
|
269
|
+
|
|
270
|
+
# stack list of grads w.r.t. constraints to get
|
|
271
|
+
# tensor of shape (*param.shape, m)
|
|
272
|
+
l_term_grad = 0
|
|
273
|
+
aug_term_grad = 0
|
|
274
|
+
# if c_grads[i] is not None:
|
|
275
|
+
for j, c_grad in enumerate(c_grads[i]):
|
|
276
|
+
if c_grad is None:
|
|
277
|
+
continue
|
|
278
|
+
l_term_grad += c_grad * self._dual_vars[j]
|
|
279
|
+
aug_term_grad += c_grad * c_val[j]
|
|
280
|
+
|
|
281
|
+
G_i = (
|
|
282
|
+
grads[i]
|
|
283
|
+
+ l_term_grad
|
|
284
|
+
+ self.rho * aug_term_grad
|
|
285
|
+
+ self.mu * (param - smoothing[i])
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
G.append(G_i)
|
|
289
|
+
|
|
290
|
+
exp_avg = exp_avgs[i]
|
|
291
|
+
exp_avg_sq = exp_avg_sqs[i]
|
|
292
|
+
step_t = state_steps[i]
|
|
293
|
+
step_t += 1
|
|
294
|
+
beta1 = self.beta1
|
|
295
|
+
beta2 = self.beta2
|
|
296
|
+
eps = self.eps
|
|
297
|
+
|
|
298
|
+
exp_avg.lerp_(G_i, 1 - beta1)
|
|
299
|
+
exp_avg_sq.mul_(beta2).addcmul_(G_i, G_i, value=1 - beta2)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
smoothing[i].add_(param - smoothing[i], alpha=self.beta)
|
|
303
|
+
|
|
304
|
+
# param.add_(G_i, alpha=-lr)
|
|
305
|
+
|
|
306
|
+
bias_correction1 = 1 - beta1**step_t
|
|
307
|
+
bias_correction2 = 1 - beta2**step_t
|
|
308
|
+
|
|
309
|
+
step_size = lr / bias_correction1
|
|
310
|
+
|
|
311
|
+
bias_correction2_sqrt = bias_correction2**0.5
|
|
312
|
+
|
|
313
|
+
if amsgrad:
|
|
314
|
+
# Maintains the maximum of all 2nd moment running avg. till now
|
|
315
|
+
torch.maximum(max_exp_avg_sqs[i], exp_avg_sq, out=max_exp_avg_sqs[i])
|
|
316
|
+
|
|
317
|
+
# Use the max. for normalizing running avg. of gradient
|
|
318
|
+
denom = (max_exp_avg_sqs[i].sqrt() / bias_correction2_sqrt).add_(eps)
|
|
319
|
+
else:
|
|
320
|
+
denom = (exp_avg_sq.sqrt() / bias_correction2_sqrt).add_(eps)
|
|
321
|
+
|
|
322
|
+
param.addcdiv_(exp_avg, denom, value=-step_size)
|
|
323
|
+
|
|
324
|
+
## PROJECT (keep in mind we do layer by layer)
|
|
325
|
+
## add slack variables to params in constructor?
|
|
326
|
+
|
|
327
|
+
c_grads[i].clear()
|
|
328
|
+
|
|
329
|
+
self.c_vals.clear()
|
|
330
|
+
return G
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
from torch import Tensor
|
|
4
|
+
from humancompatible.train.algorithms import SSLALM_Adam
|
|
5
|
+
|
|
6
|
+
class TestSSLALMAdam(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_Adam(
|
|
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
|
+
beta1=0.9,
|
|
22
|
+
beta2=0.999
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def test_initialization(self):
|
|
26
|
+
# Test if the optimizer is initialized correctly
|
|
27
|
+
self.assertEqual(len(self.optimizer.param_groups), 1)
|
|
28
|
+
self.assertEqual(self.optimizer.m, self.m)
|
|
29
|
+
self.assertEqual(self.optimizer.dual_lr, 0.01)
|
|
30
|
+
self.assertEqual(self.optimizer.dual_bound, 100.0)
|
|
31
|
+
self.assertEqual(self.optimizer.rho, 1.0)
|
|
32
|
+
self.assertEqual(self.optimizer.mu, 2.0)
|
|
33
|
+
self.assertEqual(self.optimizer.beta, 0.5)
|
|
34
|
+
self.assertTrue(isinstance(self.optimizer._dual_vars, Tensor))
|
|
35
|
+
self.assertEqual(self.optimizer._dual_vars.shape, (self.m,))
|
|
36
|
+
|
|
37
|
+
def test_dual_step(self):
|
|
38
|
+
# Test dual variable update
|
|
39
|
+
c_val = torch.tensor([0.5, 0.1])
|
|
40
|
+
self.optimizer.dual_step(0, c_val[0])
|
|
41
|
+
self.assertEqual(self.optimizer._dual_vars[0], 0.005) # 0 + 0.01 * 0.5
|
|
42
|
+
self.optimizer.dual_step(1, c_val[1])
|
|
43
|
+
self.assertEqual(self.optimizer._dual_vars[1], 0.001) # 0 + 0.01 * 0.1
|
|
44
|
+
|
|
45
|
+
def test_dual_bound(self):
|
|
46
|
+
# Test dual variable bounding
|
|
47
|
+
self.optimizer._dual_vars = torch.tensor([101.0, -1.0])
|
|
48
|
+
c_val = torch.tensor([1.0, -1.0])
|
|
49
|
+
self.optimizer.dual_step(0, c_val[0])
|
|
50
|
+
self.optimizer.dual_step(1, c_val[1])
|
|
51
|
+
self.assertEqual(self.optimizer._dual_vars[0], 0.0) # Should be zeroed out
|
|
52
|
+
self.assertEqual(self.optimizer._dual_vars[1], 0.0) # Should be zeroed out
|
|
53
|
+
|
|
54
|
+
# ADD TEST DEALING WITH CONSTRAINTS THAT DONT USE SOME OF THE PARAMS
|
|
55
|
+
|
|
56
|
+
def test_step(self):
|
|
57
|
+
# Test primal parameter update
|
|
58
|
+
# Mock gradients and constraint gradients
|
|
59
|
+
p_pre_step = {}
|
|
60
|
+
for p in self.params:
|
|
61
|
+
p.grad = torch.ones_like(p)
|
|
62
|
+
p_pre_step[p] = p.detach().clone()
|
|
63
|
+
|
|
64
|
+
c_val = torch.tensor([0.1, -0.1])
|
|
65
|
+
c_grads = {p: [torch.ones_like(p) for _ in c_val] for p in self.params}
|
|
66
|
+
self.optimizer._dual_vars = torch.ones(2)
|
|
67
|
+
|
|
68
|
+
for p in self.params:
|
|
69
|
+
self.optimizer.state[p]["c_grad"] = [g.clone() for g in c_grads[p]]
|
|
70
|
+
self.optimizer.state[p]["smoothing"] = p.detach().clone()
|
|
71
|
+
|
|
72
|
+
self.optimizer.state[p]["step"] = 0
|
|
73
|
+
self.optimizer.state[p]["exp_avg"] = torch.ones_like(p)
|
|
74
|
+
self.optimizer.state[p]["exp_avg_sq"] = torch.ones_like(p)
|
|
75
|
+
G = self.optimizer.step(c_val)
|
|
76
|
+
# Check if G is computed and parameters are updated
|
|
77
|
+
self.assertEqual(len(G), len(self.params))
|
|
78
|
+
for i, p in enumerate(self.params):
|
|
79
|
+
## assert correct Lagrange f-n gradient
|
|
80
|
+
self.assertTrue(
|
|
81
|
+
torch.equal(
|
|
82
|
+
G[i],
|
|
83
|
+
(
|
|
84
|
+
p.grad +
|
|
85
|
+
sum(_lambda * c_grads[p][j] for j, _lambda in enumerate(self.optimizer._dual_vars)) +
|
|
86
|
+
sum([c_grads[p][j] * cv for j, cv in enumerate(c_val)])
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
# assert correct update of params
|
|
91
|
+
beta1 = 0.9
|
|
92
|
+
beta2 = 0.999
|
|
93
|
+
fm = beta1*torch.ones_like(p) + (1-beta1)*G[i]
|
|
94
|
+
sm = beta2*torch.ones_like(p) + (1-beta2)*(torch.pow(G[i],2))
|
|
95
|
+
fm_bc = fm/(1-beta1)
|
|
96
|
+
sm_bc = sm/(1-beta2)
|
|
97
|
+
|
|
98
|
+
self.assertTrue(
|
|
99
|
+
torch.equal(
|
|
100
|
+
p,
|
|
101
|
+
p_pre_step[p] - 0.01*fm_bc/(sm_bc.sqrt() + 1e-8)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
# assert correct update of exp_avg and exp_avg_sq
|
|
105
|
+
self.assertTrue(
|
|
106
|
+
torch.equal(
|
|
107
|
+
self.optimizer.state[p]['exp_avg'],
|
|
108
|
+
fm
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
self.assertTrue(
|
|
112
|
+
torch.equal(
|
|
113
|
+
self.optimizer.state[p]['exp_avg_sq'],
|
|
114
|
+
sm
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def test_step_with_invalid_c_val(self):
|
|
119
|
+
# Test step with invalid c_val (wrong shape)
|
|
120
|
+
with self.assertRaises(ValueError):
|
|
121
|
+
self.optimizer.step(torch.tensor([0.1]))
|
|
122
|
+
|
|
123
|
+
def test_smoothing_update(self):
|
|
124
|
+
# Test smoothing term update
|
|
125
|
+
p_pre_step = {}
|
|
126
|
+
for p in self.params:
|
|
127
|
+
p.grad = torch.ones_like(p)
|
|
128
|
+
p_pre_step[p] = p.detach().clone()
|
|
129
|
+
c_val = torch.tensor([0.1, -0.1])
|
|
130
|
+
self.optimizer.step(c_val)
|
|
131
|
+
for p in self.params:
|
|
132
|
+
state = self.optimizer.state[p]
|
|
133
|
+
self.assertTrue("smoothing" in state)
|
|
134
|
+
self.assertTrue(torch.all(state["smoothing"] == p_pre_step[p]))
|
|
135
|
+
|
|
136
|
+
def test_error_handling(self):
|
|
137
|
+
# Test error handling for invalid inputs
|
|
138
|
+
with self.assertRaises(ValueError):
|
|
139
|
+
SSLALM_Adam(self.params, m=self.m, lr=-0.01)
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
SSLALM_Adam(self.params, m=self.m, dual_lr=-0.01)
|
|
142
|
+
with self.assertRaises(ValueError):
|
|
143
|
+
SSLALM_Adam(self.params, m=self.m, init_dual_vars=torch.tensor([1.0]))
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
unittest.main()
|
{humancompatible_train-0.1.2 → humancompatible_train-0.1.3/humancompatible_train.egg-info}/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.3
|
|
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,6 +10,13 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENCE.txt
|
|
11
11
|
Requires-Dist: torch
|
|
12
12
|
Requires-Dist: numpy
|
|
13
|
+
Provides-Extra: examples
|
|
14
|
+
Requires-Dist: ipykernel; extra == "examples"
|
|
15
|
+
Requires-Dist: ipympl; extra == "examples"
|
|
16
|
+
Requires-Dist: fairret; extra == "examples"
|
|
17
|
+
Requires-Dist: folktables; extra == "examples"
|
|
18
|
+
Requires-Dist: scikit-learn; extra == "examples"
|
|
19
|
+
Requires-Dist: matplotlib; extra == "examples"
|
|
13
20
|
Provides-Extra: benchmark
|
|
14
21
|
Requires-Dist: fairret; extra == "benchmark"
|
|
15
22
|
Requires-Dist: matplotlib; extra == "benchmark"
|
|
@@ -31,22 +38,23 @@ The toolkit implements algorithms for constrained training of neural networks ba
|
|
|
31
38
|
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
32
39
|
|
|
33
40
|
## Table of Contents
|
|
41
|
+
|
|
34
42
|
1. [Basic installation instructions](#basic-installation-instructions)
|
|
35
43
|
2. [Using the toolkit](#using-the-toolkit)
|
|
36
|
-
3. [Extending the toolkit](#extending-the-toolkit)
|
|
44
|
+
3. [Extending the toolkit](#extending-the-toolkit)
|
|
37
45
|
4. [Reproducing the Benchmark](#reproducing-the-benchmark)
|
|
38
46
|
5. [License and terms of use](#license-and-terms-of-use)
|
|
39
47
|
6. [References](#references)
|
|
40
48
|
|
|
41
49
|
humancompatible-train is still under active development! If you find bugs or have feature
|
|
42
50
|
requests, please file a
|
|
43
|
-
[Github issue](https://github.com/humancompatible/train/issues).
|
|
51
|
+
[Github issue](https://github.com/humancompatible/train/issues).
|
|
44
52
|
|
|
45
53
|
## Installation
|
|
46
54
|
|
|
47
55
|
Use
|
|
48
56
|
|
|
49
|
-
```
|
|
57
|
+
```bash
|
|
50
58
|
pip install humancompatible-train
|
|
51
59
|
```
|
|
52
60
|
|
|
@@ -56,11 +64,11 @@ The only dependencies of this package are `numpy` and `torch`.
|
|
|
56
64
|
|
|
57
65
|
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
58
66
|
|
|
59
|
-
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `
|
|
67
|
+
The algorithms follow the `dual_step()` - `step()` framework: taking inspiration from PyTorch, the `dual_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
68
|
|
|
61
69
|
In general, your code using `humancompatible-train` would look something like this:
|
|
62
70
|
|
|
63
|
-
```
|
|
71
|
+
```python
|
|
64
72
|
for inputs, labels in dataloader:
|
|
65
73
|
# inference
|
|
66
74
|
outputs = model(inputs)
|
|
@@ -79,6 +87,7 @@ for inputs, labels in dataloader:
|
|
|
79
87
|
```
|
|
80
88
|
|
|
81
89
|
Our idea is to
|
|
90
|
+
|
|
82
91
|
1. Deviate minimally from the usual PyTorch workflow
|
|
83
92
|
2. Make different stochastic-constrained stochastic optimization algorithms nearly interchangable in the code.
|
|
84
93
|
|
|
@@ -86,6 +95,12 @@ Our idea is to
|
|
|
86
95
|
|
|
87
96
|
You are invited to check out our new API presented in notebooks in the `examples` folder.
|
|
88
97
|
|
|
98
|
+
The example notebooks have additional dependencies, such as `fairret`. To install those, run
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
pip install humancompatible-train[examples]
|
|
102
|
+
```
|
|
103
|
+
|
|
89
104
|
*The legacy API used for the benchmark is presented in `examples/_old_/algorithm_demo.ipynb` and `examples/_old_/constraint_demo.ipynb`.*
|
|
90
105
|
|
|
91
106
|
## Extending the toolkit
|
|
@@ -99,21 +114,27 @@ You are invited to check out our new API presented in notebooks in the `examples
|
|
|
99
114
|
The code used in [our benchmark paper](https://arxiv.org/abs/2507.04033) is not migrated to the new API yet (WIP).
|
|
100
115
|
|
|
101
116
|
### Basic installation instructions
|
|
117
|
+
|
|
102
118
|
The code requires Python version ```3.11```.
|
|
103
119
|
|
|
104
120
|
1. Create a virtual environment
|
|
105
121
|
|
|
106
122
|
**bash** (Linux)
|
|
123
|
+
|
|
107
124
|
```
|
|
108
125
|
python3.11 -m venv fairbenchenv
|
|
109
126
|
source fairbenchenv/bin/activate
|
|
110
127
|
```
|
|
128
|
+
|
|
111
129
|
**cmd** (Windows)
|
|
130
|
+
|
|
112
131
|
```
|
|
113
132
|
python -m venv fairbenchenv
|
|
114
133
|
fairbenchenv\Scripts\activate.bat
|
|
115
134
|
```
|
|
135
|
+
|
|
116
136
|
2. Install from source.
|
|
137
|
+
|
|
117
138
|
```
|
|
118
139
|
git clone https://github.com/humancompatible/train.git
|
|
119
140
|
cd train
|
|
@@ -122,11 +143,12 @@ pip install .
|
|
|
122
143
|
```
|
|
123
144
|
|
|
124
145
|
If you wish to edit the code of the algorithms, install as an editable package:
|
|
146
|
+
|
|
125
147
|
```
|
|
126
148
|
pip install -e .
|
|
127
149
|
```
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
**Warning**: it is recommended to use Stochastic Ghost with the mkl-accelerated version of the scipy package with Stochastic Ghost; to install it, run
|
|
130
152
|
|
|
131
153
|
```pip install --force-reinstall -i https://software.repos.intel.com/python/pypi scipy```
|
|
132
154
|
|
|
@@ -140,12 +162,14 @@ after installing requirements.txt; otherwise, the algorithm will run slower. How
|
|
|
140
162
|
### Running the algorithms
|
|
141
163
|
|
|
142
164
|
The benchmark comprises the following algorithms:
|
|
165
|
+
|
|
143
166
|
- Stochastic Ghost [[2]](#2),
|
|
144
167
|
- SSL-ALM [[3]](#3),
|
|
145
168
|
- Stochastic Switching Subgradient [[4]](#4).
|
|
146
169
|
|
|
147
170
|
To reproduce the experiments of the paper, run the following:
|
|
148
|
-
|
|
171
|
+
|
|
172
|
+
```
|
|
149
173
|
cd experiments
|
|
150
174
|
python run_folktables.py data=folktables alg=sslalm
|
|
151
175
|
python run_folktables.py data=folktables alg=alm
|
|
@@ -154,23 +178,25 @@ python run_folktables.py data=folktables alg=ssg
|
|
|
154
178
|
python run_folktables.py data=folktables alg=sgd # baseline, no fairness
|
|
155
179
|
python run_folktables.py data=folktables alg=fairret # baseline, fairness with regularizer
|
|
156
180
|
```
|
|
181
|
+
|
|
157
182
|
Each command will start 10 runs of the `alg`, 30 seconds each.
|
|
158
183
|
The results will be saved to `experiments/utils/saved_models` and `experiments/utils/exp_results`.
|
|
159
184
|
<!-- In the repository, we include the configuration needed to reproduce the experiments in the paper. To do so, go to `experiments` and run `python run_folktables.py data=folktables alg=sslalm`. -->
|
|
160
185
|
<!-- Repeat for the other algorithms by changing the `alg` parameter. -->
|
|
161
186
|
|
|
162
|
-
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
This repository uses [Hydra](https://hydra.cc/) to manage parameters; see `experiments/conf` for configuration files.
|
|
188
|
+
|
|
189
|
+
- To change the parameters of the experiment, such as the number of runs for each algorithm, run time, the dataset used (*note: for now supports only Folktables*) - use `experiment.yaml`.
|
|
190
|
+
- To change the dataset settings - such as file location - or do dataset-specific adjustments - such as the configuration of the protected attributes - use `data/{dataset_name}.yaml`
|
|
191
|
+
- To change algorithm hyperparameters, use `alg/{algorithm_name}.yaml`.
|
|
192
|
+
- To change constraint hyperparameters, use `constraint/{constraint_name}.yaml`
|
|
167
193
|
|
|
168
194
|
<!-- ; it is installed as one of the dependencies. -->
|
|
169
195
|
<!-- To learn more about using Hydra, please check out the [official tutorial](https://hydra.cc/docs/tutorials/basic/your_first_app). -->
|
|
170
196
|
|
|
171
197
|
### Producing plots
|
|
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.
|
|
173
198
|
|
|
199
|
+
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.
|
|
174
200
|
|
|
175
201
|
## License and terms of use
|
|
176
202
|
|
|
@@ -180,7 +206,7 @@ The benchmark part of the package relies on the Folktables package, provided und
|
|
|
180
206
|
It provides code to download data from the American Community Survey
|
|
181
207
|
(ACS) Public Use Microdata Sample (PUMS) files managed by the US Census Bureau.
|
|
182
208
|
The data itself is governed by the terms of use provided by the Census Bureau.
|
|
183
|
-
For more information, see https://www.census.gov/data/developers/about/terms-of-service.html
|
|
209
|
+
For more information, see <https://www.census.gov/data/developers/about/terms-of-service.html>
|
|
184
210
|
|
|
185
211
|
<!-- ## Cite this work -->
|
|
186
212
|
|
|
@@ -206,7 +232,7 @@ For more information, see https://www.census.gov/data/developers/about/terms-of-
|
|
|
206
232
|
|
|
207
233
|
If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs/2507.04033),
|
|
208
234
|
|
|
209
|
-
```
|
|
235
|
+
```bibtex
|
|
210
236
|
@misc{kliachkin2025benchmarkingstochasticapproximationalgorithms,
|
|
211
237
|
title={Benchmarking Stochastic Approximation Algorithms for Fairness-Constrained Training of Deep Neural Networks},
|
|
212
238
|
author={Andrii Kliachkin and Jana Lepšová and Gilles Bareilles and Jakub Mareček},
|
|
@@ -218,14 +244,14 @@ If you use this work, we encourage you to cite [our paper](https://arxiv.org/abs
|
|
|
218
244
|
}
|
|
219
245
|
```
|
|
220
246
|
|
|
221
|
-
<a id="1">[1]</a>
|
|
247
|
+
<a id="1">[1]</a>
|
|
222
248
|
Ding, Hardt & Miller et al. (2021) Retiring Adult: New Datasets for Fair Machine Learning, Curran Associates, Inc..
|
|
223
249
|
|
|
224
|
-
<a id="2">[2]</a>
|
|
250
|
+
<a id="2">[2]</a>
|
|
225
251
|
Facchinei & Kungurtsev (2023) Stochastic Approximation for Expectation Objective and Expectation Inequality-Constrained Nonconvex Optimization, arXiv.
|
|
226
252
|
|
|
227
|
-
<a id="3">[3]</a>
|
|
253
|
+
<a id="3">[3]</a>
|
|
228
254
|
Huang, Zhang & Alacaoglu (2025) Stochastic Smoothed Primal-Dual Algorithms for Nonconvex Optimization with Linear Inequality Constraints, arXiv.
|
|
229
255
|
|
|
230
|
-
<a id="4">[4]</a>
|
|
256
|
+
<a id="4">[4]</a>
|
|
231
257
|
Huang & Lin (2023) Oracle Complexity of Single-Loop Switching Subgradient Methods for Non-Smooth Weakly Convex Functional Constrained Optimization, Curran Associates Inc..
|
|
@@ -10,9 +10,11 @@ humancompatible/__init__.py
|
|
|
10
10
|
humancompatible/train/__init__.py
|
|
11
11
|
humancompatible/train/algorithms/__init__.py
|
|
12
12
|
humancompatible/train/algorithms/ssl_alm.py
|
|
13
|
+
humancompatible/train/algorithms/ssl_alm_adam.py
|
|
13
14
|
humancompatible/train/algorithms/ssw.py
|
|
14
15
|
humancompatible/train/algorithms/test/__init__.py
|
|
15
16
|
humancompatible/train/algorithms/test/test_ssl_alm.py
|
|
17
|
+
humancompatible/train/algorithms/test/test_ssl_alm_adam.py
|
|
16
18
|
humancompatible/train/algorithms/test/test_ssw.py
|
|
17
19
|
humancompatible/train/fairness/__init__.py
|
|
18
20
|
humancompatible/train/fairness/constraints/__init__.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "humancompatible-train"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"torch",
|
|
10
10
|
"numpy",
|
|
@@ -24,5 +24,6 @@ readme = "README.md"
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
|
+
examples = ["ipykernel", "ipympl", "fairret", "folktables", "scikit-learn", "matplotlib"]
|
|
27
28
|
benchmark = ["fairret", "matplotlib", "pandas", "folktables", "pot", "hydra", "omegaconf"]
|
|
28
29
|
ghost = ["qpsolvers", "scipy"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/experiments/run_folktables_torchalgs.py
RENAMED
|
File without changes
|
|
File without changes
|
{humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/__init__.py
RENAMED
|
File without changes
|
{humancompatible_train-0.1.2 → humancompatible_train-0.1.3}/humancompatible/train/algorithms/ssw.py
RENAMED
|
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
|