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.
Files changed (41) hide show
  1. {humancompatible_train-0.1.0/humancompatible_train.egg-info → humancompatible_train-0.1.2}/PKG-INFO +74 -31
  2. humancompatible_train-0.1.0/PKG-INFO → humancompatible_train-0.1.2/README.md +66 -48
  3. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/run_folktables.py +2 -2
  4. humancompatible_train-0.1.2/humancompatible/train/algorithms/test/test_ssl_alm.py +144 -0
  5. humancompatible_train-0.1.2/humancompatible/train/algorithms/test/test_ssw.py +64 -0
  6. humancompatible_train-0.1.2/humancompatible/train/fairness/__init__.py +0 -0
  7. humancompatible_train-0.1.2/humancompatible/train/fairness/utils/tests/__init__.py +0 -0
  8. humancompatible_train-0.1.2/humancompatible/train/fairness/utils/tests/test_balanced_batch_sampler.py +78 -0
  9. humancompatible_train-0.1.0/README.md → humancompatible_train-0.1.2/humancompatible_train.egg-info/PKG-INFO +90 -30
  10. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/SOURCES.txt +6 -8
  11. humancompatible_train-0.1.2/humancompatible_train.egg-info/requires.txt +15 -0
  12. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/pyproject.toml +2 -1
  13. humancompatible_train-0.1.0/humancompatible/train/algorithms/Algorithm.py +0 -25
  14. humancompatible_train-0.1.0/humancompatible/train/algorithms/__init__.py +0 -8
  15. humancompatible_train-0.1.0/humancompatible/train/algorithms/ghost.py +0 -250
  16. humancompatible_train-0.1.0/humancompatible/train/algorithms/sgd.py +0 -107
  17. humancompatible_train-0.1.0/humancompatible/train/algorithms/ssl_alm.py +0 -311
  18. humancompatible_train-0.1.0/humancompatible/train/algorithms/switching_subgradient.py +0 -192
  19. humancompatible_train-0.1.0/humancompatible/train/algorithms/utils.py +0 -61
  20. humancompatible_train-0.1.0/humancompatible_train.egg-info/requires.txt +0 -6
  21. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/LICENCE.txt +0 -0
  22. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/__init__.py +0 -0
  23. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/calculate_iteration_values.py +0 -0
  24. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/experiments/run_folktables_torchalgs.py +0 -0
  25. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/__init__.py +0 -0
  26. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/__init__.py +0 -0
  27. {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/__init__.py +0 -0
  28. {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/ssl_alm.py +0 -0
  29. {humancompatible_train-0.1.0/humancompatible/train/algorithms/torch → humancompatible_train-0.1.2/humancompatible/train/algorithms}/ssw.py +0 -0
  30. {humancompatible_train-0.1.0/humancompatible/train/fairness → humancompatible_train-0.1.2/humancompatible/train/algorithms/test}/__init__.py +0 -0
  31. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/__init__.py +0 -0
  32. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/constraint.py +0 -0
  33. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/constraint_fns.py +0 -0
  34. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/torch/__init__.py +0 -0
  35. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/constraints/torch/constraints.py +0 -0
  36. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/utils/__init__.py +0 -0
  37. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible/train/fairness/utils/balanced_batch_sampler.py +0 -0
  38. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/dependency_links.txt +0 -0
  39. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/humancompatible_train.egg-info/top_level.txt +0 -0
  40. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/setup.cfg +0 -0
  41. {humancompatible_train-0.1.0 → humancompatible_train-0.1.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: humancompatible-train
3
- Version: 0.1.0
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
- # Benchmarking Stochastic Approximation Algorithms for Fairness-Constrained Training of Deep Neural Networks
26
+ # humancompatible-train: a package for constrained machine learning
19
27
 
20
28
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Setup](https://github.com/humancompatible/train/actions/workflows/setup.yml/badge.svg)](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, as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder.
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. [Reproducing the Benchmark](#reproducing-the-benchmark)
28
- 4. [Extending the toolkit](#extending-the-toolkit) <!-- 6. [Citing humancompatible/train](#Citing-humancompatible/train) -->
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
- Humancompatible/train is still under active development! If you find bugs or have feature
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
- ## Basic installation instructions
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/train is provided under the Apache 2.0 Licence.
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 with PyTorch-like API
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
- Metadata-Version: 2.4
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
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Setup](https://github.com/humancompatible/train/actions/workflows/setup.yml/badge.svg)](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, as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder.
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. [Reproducing the Benchmark](#reproducing-the-benchmark)
28
- 4. [Extending the toolkit](#extending-the-toolkit) <!-- 6. [Citing humancompatible/train](#Citing-humancompatible/train) -->
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
- Humancompatible/train is still under active development! If you find bugs or have feature
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
- ## Basic installation instructions
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/train is provided under the Apache 2.0 Licence.
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 with PyTorch-like API
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
+ )
@@ -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()