humancompatible-train 0.3.0__tar.gz → 0.3.0.1__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.3.0/src/humancompatible_train.egg-info → humancompatible_train-0.3.0.1}/PKG-INFO +1 -3
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/README.md +0 -2
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/pyproject.toml +1 -1
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/dual_optim/pbm.py +126 -88
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/PBM.py +1 -1
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1/src/humancompatible_train.egg-info}/PKG-INFO +1 -3
- humancompatible_train-0.3.0.1/tests/test_pbm.py +532 -0
- humancompatible_train-0.3.0/tests/test_pbm.py +0 -94
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/LICENCE.txt +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/setup.cfg +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/Algorithm.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/ghost.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/optim_wrapper.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/sgd.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/ssl_alm.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/switching_subgradient.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/algorithms/utils.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/constraints/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/constraints/constraint.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/constraints/constraint_fns.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/constraints/torch/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/benchmark/constraints/torch/constraints.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/dual_optim/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/dual_optim/alm.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/dual_optim/barrier.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/dual_optim/moreau.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/fairness/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/fairness/utils/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/fairness/utils/balanced_batch_sampler.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/__init__.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/barrier.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssl_alm.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssl_alm_adam.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssl_alm_adam_moment.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssw.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssw_barrier.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible_train.egg-info/SOURCES.txt +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible_train.egg-info/dependency_links.txt +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible_train.egg-info/requires.txt +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible_train.egg-info/top_level.txt +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/tests/test_alm.py +0 -0
- {humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/tests/test_balanced_batch_sampler.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humancompatible-train
|
|
3
|
-
Version: 0.3.0
|
|
3
|
+
Version: 0.3.0.1
|
|
4
4
|
Summary: PyTorch-based package for constrained training of neural networks
|
|
5
5
|
Author: Gilles Bareilles, Adam Bosak, Jana Lepsova, Jakub Marecek
|
|
6
6
|
Author-email: Andrii Kliachkin <kliacand@fel.cvut.cz>
|
|
@@ -156,8 +156,6 @@ The constraint options are:
|
|
|
156
156
|
- `equalized_odds_vec`: constraint on the Positive Rate of each group as defined by `fairret.NormLoss`;
|
|
157
157
|
- `weight_norm`: constraint on the Frobenius norm of the weights and biases of each layer of the neural network.
|
|
158
158
|
|
|
159
|
-
The above command will run the benchmark and save the logs and models in the `benchmark/results/[dataset]_[task]` directory; it will also plot the trajectories into a .png file saved in the same location.
|
|
160
|
-
|
|
161
159
|
The benchmarking code (all of which is contained in the `benchmark` directory) is easy to parse and extend with other datasets and constraints.
|
|
162
160
|
|
|
163
161
|
|
|
@@ -124,8 +124,6 @@ The constraint options are:
|
|
|
124
124
|
- `equalized_odds_vec`: constraint on the Positive Rate of each group as defined by `fairret.NormLoss`;
|
|
125
125
|
- `weight_norm`: constraint on the Frobenius norm of the weights and biases of each layer of the neural network.
|
|
126
126
|
|
|
127
|
-
The above command will run the benchmark and save the logs and models in the `benchmark/results/[dataset]_[task]` directory; it will also plot the trajectories into a .png file saved in the same location.
|
|
128
|
-
|
|
129
127
|
The benchmarking code (all of which is contained in the `benchmark` directory) is easy to parse and extend with other datasets and constraints.
|
|
130
128
|
|
|
131
129
|
|
|
@@ -11,34 +11,35 @@ from .barrier import (
|
|
|
11
11
|
quad_recipr_der
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
14
|
class PBM(Optimizer):
|
|
18
15
|
def __init__(
|
|
19
16
|
self,
|
|
20
17
|
m: int = None,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
init_duals: float | Tensor = 0.01,
|
|
26
|
-
init_penalties: float | Tensor = 10,
|
|
18
|
+
penalty_mult: float = 0.1,
|
|
19
|
+
gamma: float = 0.9,
|
|
20
|
+
delta: float = 0.9,
|
|
21
|
+
penalty_update: str = 'dimin_adapt',
|
|
27
22
|
*,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
pbf: str = 'quadratic_logarithmic',
|
|
24
|
+
init_duals: float | Tensor = None,
|
|
25
|
+
init_penalties: float | Tensor = None,
|
|
26
|
+
dual_range: Tuple[float, float] = (0.0001, 100.),
|
|
27
|
+
penalty_range: Tuple[float, float] = (0.1, 100.),
|
|
28
|
+
device = None,
|
|
29
|
+
primal_update_process_length=1, # length of the primal update process - if =1, is the original algorithm
|
|
33
30
|
) -> None:
|
|
34
31
|
"""
|
|
35
32
|
A wrapper over a PyTorch`Optimizer` that works on the dual maximization tasks according to the Penalty-Barrier Method rule. Creates and updates dual variables.
|
|
36
33
|
|
|
37
34
|
:param m: Number of constraints (determines the number of dual variables to create)
|
|
38
35
|
:type m: int
|
|
39
|
-
:param
|
|
40
|
-
:type
|
|
41
|
-
:param
|
|
36
|
+
:param penalty_mult: Multiplier for penalty update (K1 or K2). For K2 (adaptive penalty update), values close to 1 correspond to a high "momentum".
|
|
37
|
+
:type penalty_mult: float
|
|
38
|
+
:param gamma: Multiplier for dual parameter update. Values close to 1 correspond to a high "momentum".
|
|
39
|
+
:type gamma: float
|
|
40
|
+
:param delta: Violation/satisfaction parameter for penalty update; values > 1 make the penalties decrease faster on violated constraints and vice versa.
|
|
41
|
+
:type delta: float
|
|
42
|
+
:param penalty_update: Penalty update strategy; must be one of `dimin`,`dimin_dual`,`dimin_adapt`,`const`. Defaults to`dimin_adapt`.
|
|
42
43
|
:type penalty_update: str
|
|
43
44
|
:param pbf: Penalty-Barrier Function to use. Must be one of `quadratic_logarithmic`,`quadratic_reciprocal`
|
|
44
45
|
:type pbf: str
|
|
@@ -48,40 +49,28 @@ class PBM(Optimizer):
|
|
|
48
49
|
:type init_penalties: float | Tensor
|
|
49
50
|
:param dual_range: Safeguarding range for dual variables; they will be`clamp`-ed to this range.
|
|
50
51
|
:type dual_range: Tuple[float, float]
|
|
51
|
-
:param momentum: Momentum/Smoothing factor for dual variables. Set to `0` to disable.
|
|
52
|
-
:type momentum: float
|
|
53
|
-
:param dampening: Dampening for momentum. Defaults to 1 - momentum if momentum is not 0 and 0 otherwise. Set to `0` to disable.
|
|
54
|
-
:type dampening: float
|
|
55
52
|
"""
|
|
56
53
|
|
|
57
|
-
# ## checks ##
|
|
58
|
-
|
|
59
54
|
self.dual_range = dual_range
|
|
60
55
|
self.penalty_range = penalty_range
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if momentum > 0 and dampening == 0:
|
|
66
|
-
dampening = momentum
|
|
67
|
-
|
|
68
|
-
params, defaults = self._init_constraint_group(m, mu, lr, penalty_update, pbf, init_duals, init_penalties, momentum, dampening, dual_range, penalty_range, device)
|
|
69
|
-
|
|
57
|
+
params, defaults = self._init_constraint_group(m, penalty_mult, penalty_update, delta, pbf, init_duals, init_penalties, gamma, dual_range, penalty_range, primal_update_process_length, device)
|
|
58
|
+
self.iter = 0
|
|
70
59
|
super().__init__(params, defaults)
|
|
71
60
|
|
|
72
61
|
@staticmethod
|
|
73
62
|
def _init_constraint_group(
|
|
74
63
|
m: int,
|
|
75
|
-
|
|
76
|
-
lr: float = None,
|
|
64
|
+
p_mult: float = None,
|
|
77
65
|
penalty_update: str = None,
|
|
66
|
+
delta: float = None,
|
|
78
67
|
pbf: str = None,
|
|
79
68
|
init_duals: float | Tensor = None,
|
|
80
69
|
init_penalties: float | Tensor = None,
|
|
81
|
-
|
|
82
|
-
dampening: float = None,
|
|
70
|
+
dual_momentum: float = None,
|
|
83
71
|
dual_range: Tuple[float, float] = None,
|
|
84
72
|
penalty_range: Tuple[float, float] = None,
|
|
73
|
+
primal_update_process_length: int = 1,
|
|
85
74
|
device = None
|
|
86
75
|
):
|
|
87
76
|
if init_duals is None and m is None:
|
|
@@ -95,23 +84,29 @@ class PBM(Optimizer):
|
|
|
95
84
|
duals = Parameter(init_duals, requires_grad=False)
|
|
96
85
|
penalties = Parameter(init_penalties, requires_grad=False)
|
|
97
86
|
|
|
87
|
+
primal_update_process_length = primal_update_process_length
|
|
88
|
+
|
|
98
89
|
if penalty_update == 'dimin':
|
|
99
90
|
penalty_update_f = _update_penalties_dimin
|
|
100
91
|
elif penalty_update == 'dimin_dual':
|
|
101
92
|
penalty_update_f = _update_penalties_dimin_dual
|
|
93
|
+
elif penalty_update == 'dimin_adapt':
|
|
94
|
+
penalty_update_f = _update_penalties_adapt
|
|
102
95
|
elif penalty_update == 'const':
|
|
103
96
|
penalty_update_f = _update_penalties_const
|
|
104
|
-
|
|
97
|
+
elif penalty_update is None:
|
|
105
98
|
penalty_update_f = None
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError(f'Unknown penalty update function: {penalty_update}!')
|
|
106
101
|
|
|
107
102
|
settings_dict = {
|
|
108
|
-
"
|
|
109
|
-
"lr": lr,
|
|
103
|
+
"p_mult": p_mult,
|
|
110
104
|
"penalty_update": penalty_update_f,
|
|
105
|
+
"delta": delta,
|
|
111
106
|
"pbf": pbf,
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
107
|
+
"dual_momentum": dual_momentum,
|
|
108
|
+
"primal_update_process_length": primal_update_process_length,
|
|
109
|
+
"dual_momentum_buffer": torch.zeros_like(init_duals, requires_grad = False, device=device),
|
|
115
110
|
}
|
|
116
111
|
settings_dict = {k:v for k,v in settings_dict.items() if v is not None}
|
|
117
112
|
|
|
@@ -122,6 +117,8 @@ class PBM(Optimizer):
|
|
|
122
117
|
@property
|
|
123
118
|
def duals(self) -> Tensor:
|
|
124
119
|
"""
|
|
120
|
+
Returns all dual variables concatenated from all constraint groups.
|
|
121
|
+
|
|
125
122
|
:return: Dual variables, concatenated into a single tensor.
|
|
126
123
|
:rtype: Tensor
|
|
127
124
|
"""
|
|
@@ -130,6 +127,8 @@ class PBM(Optimizer):
|
|
|
130
127
|
@property
|
|
131
128
|
def penalties(self) -> Tensor:
|
|
132
129
|
"""
|
|
130
|
+
Returns all penalty variables concatenated from all constraint groups.
|
|
131
|
+
|
|
133
132
|
:return: Penalties, concatenated into a single tensor.
|
|
134
133
|
:rtype: Tensor
|
|
135
134
|
"""
|
|
@@ -138,35 +137,41 @@ class PBM(Optimizer):
|
|
|
138
137
|
def add_constraint_group(
|
|
139
138
|
self,
|
|
140
139
|
m: int,
|
|
141
|
-
|
|
142
|
-
lr: float = None,
|
|
140
|
+
penalty_mult: float = None,
|
|
143
141
|
penalty_update: str = None,
|
|
142
|
+
delta: float = None,
|
|
144
143
|
pbf: str = None,
|
|
145
144
|
init_duals: float | Tensor = None,
|
|
146
145
|
init_penalties: float | Tensor = None,
|
|
147
146
|
*,
|
|
148
147
|
momentum: float = None,
|
|
149
|
-
|
|
148
|
+
primal_update_process_length: int = 1
|
|
150
149
|
) -> None:
|
|
151
150
|
"""
|
|
152
|
-
|
|
151
|
+
Adds an additional group of dual variables with separate hyperparameters and barrier functions.
|
|
153
152
|
|
|
154
|
-
:param m:
|
|
153
|
+
:param m: Number of constraints in this group (determines the number of dual variables to add)
|
|
155
154
|
:type m: int
|
|
156
|
-
:param
|
|
157
|
-
:type
|
|
158
|
-
:param penalty_update: Penalty update strategy; must be one of `dimin
|
|
155
|
+
:param penalty_mult: Multiplier for penalty update (K1 or K2). If None, inherits from parent. For adaptive penalty update, values close to 1 correspond to high "momentum".
|
|
156
|
+
:type penalty_mult: float
|
|
157
|
+
:param penalty_update: Penalty update strategy; must be one of `dimin`, `dimin_dual`, `dimin_adapt`, `const`. If None, defaults to `dimin`.
|
|
159
158
|
:type penalty_update: str
|
|
160
|
-
:param
|
|
159
|
+
:param delta: Violation/satisfaction parameter for penalty update. If None, inherits from parent.
|
|
160
|
+
:type delta: float
|
|
161
|
+
:param pbf: Penalty-Barrier Function to use. Must be one of `quadratic_logarithmic`, `quadratic_reciprocal`.
|
|
161
162
|
:type pbf: str
|
|
162
|
-
:param init_duals: Initial values for the dual variables. Defaults to
|
|
163
|
+
:param init_duals: Initial values for the dual variables in this group. Defaults to dual lower bound for all.
|
|
163
164
|
:type init_duals: float | Tensor
|
|
164
|
-
:param init_penalties: Initial values for the penalty variables. Defaults to
|
|
165
|
+
:param init_penalties: Initial values for the penalty variables in this group. Defaults to penalty upper bound for all.
|
|
165
166
|
:type init_penalties: float | Tensor
|
|
167
|
+
:param momentum: Multiplier for dual parameter update in this group. Values close to 1 correspond to high "momentum". If None, inherits from parent.
|
|
168
|
+
:type momentum: float
|
|
169
|
+
:param primal_update_process_length: Length of the primal update process for this group. If 1 (default), uses original algorithm variant.
|
|
170
|
+
:type primal_update_process_length: int
|
|
166
171
|
"""
|
|
167
172
|
|
|
168
173
|
|
|
169
|
-
params, settings_dict = self._init_constraint_group(m,
|
|
174
|
+
params, settings_dict = self._init_constraint_group(m, penalty_mult, penalty_update, delta, pbf, init_duals, init_penalties, momentum, self.dual_range, self.penalty_range, primal_update_process_length)
|
|
170
175
|
param_group_dict = {"params": params, **settings_dict}
|
|
171
176
|
self.add_param_group(param_group_dict)
|
|
172
177
|
|
|
@@ -181,14 +186,14 @@ class PBM(Optimizer):
|
|
|
181
186
|
"""
|
|
182
187
|
|
|
183
188
|
for i, group in enumerate(self.param_groups):
|
|
184
|
-
duals, penalties,
|
|
189
|
+
duals, penalties, p_mult, _update_penalties, delta, pbf, momentum, primal_update_process_length = group["params"][0], group["params"][1], group["p_mult"], group["penalty_update"], group["delta"], group["pbf"], group['dual_momentum'], group["primal_update_process_length"]
|
|
185
190
|
group_constraints = constraints[i * len(duals) : (i + 1) * len(duals)]
|
|
186
191
|
cdivp = group_constraints.div(penalties)
|
|
187
192
|
with torch.no_grad():
|
|
188
|
-
_update_duals(duals, cdivp, penalty_barrier_funcs[pbf]['d'],
|
|
193
|
+
_update_duals(duals, cdivp, penalty_barrier_funcs[pbf]['d'], momentum)
|
|
189
194
|
clamp_(duals, min=self.dual_range[0], max=self.dual_range[1])
|
|
190
|
-
|
|
191
|
-
|
|
195
|
+
_update_penalties(penalties, p_mult, duals, penalty_barrier_funcs[pbf]['d'](group_constraints), delta)
|
|
196
|
+
clamp_(penalties, min=self.penalty_range[0], max=self.penalty_range[1])
|
|
192
197
|
|
|
193
198
|
def forward(self, loss: Tensor, constraints: Tensor) -> Tensor:
|
|
194
199
|
"""
|
|
@@ -221,7 +226,7 @@ class PBM(Optimizer):
|
|
|
221
226
|
Evaluates the Penalty-Barrier Lagrangian and updates the dual variables and penalties.
|
|
222
227
|
|
|
223
228
|
Combines the computation of the Lagrangian and the update of dual variables and penalties
|
|
224
|
-
in a single step
|
|
229
|
+
in a single step.
|
|
225
230
|
|
|
226
231
|
:param loss: Loss (objective function) value.
|
|
227
232
|
:type loss: torch.Tensor
|
|
@@ -233,45 +238,71 @@ class PBM(Optimizer):
|
|
|
233
238
|
|
|
234
239
|
lagrangian = torch.zeros_like(loss)
|
|
235
240
|
lagrangian.add_(loss)
|
|
241
|
+
_last_c_group_index = 0
|
|
236
242
|
for i, group in enumerate(self.param_groups):
|
|
237
|
-
duals, penalties,
|
|
238
|
-
group_constraints = constraints[
|
|
243
|
+
duals, penalties, p_mult, _update_penalties, delta, pbf, momentum, primal_update_process_length = group["params"][0], group["params"][1], group["p_mult"], group["penalty_update"], group["delta"], group["pbf"], group['dual_momentum'], group["primal_update_process_length"]
|
|
244
|
+
group_constraints = constraints[_last_c_group_index : _last_c_group_index + len(duals)]
|
|
245
|
+
_last_c_group_index = _last_c_group_index + len(duals)
|
|
239
246
|
# calculate lagrangian
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
if self.iter + 1 == primal_update_process_length: # this enables a second variant of the algorithm
|
|
248
|
+
# update duals and penalties
|
|
249
|
+
cdivp = group_constraints.div(penalties)
|
|
250
|
+
with torch.no_grad():
|
|
251
|
+
_update_duals(duals, cdivp, penalty_barrier_funcs[pbf]['d'], momentum)
|
|
252
|
+
clamp_(duals, min=self.dual_range[0], max=self.dual_range[1])
|
|
253
|
+
_update_penalties(penalties, p_mult, duals, penalty_barrier_funcs[pbf]['d'](group_constraints), delta)
|
|
254
|
+
clamp_(penalties, min=self.penalty_range[0], max=self.penalty_range[1])
|
|
247
255
|
|
|
248
256
|
cdivp = group_constraints.div(penalties)
|
|
249
257
|
pbf_val = penalty_barrier_funcs[pbf]['f'](cdivp)
|
|
250
258
|
lagrangian.add_(duals.mul(penalties) @ pbf_val)
|
|
251
259
|
|
|
260
|
+
# update the iter
|
|
261
|
+
self.iter = (self.iter + 1) % primal_update_process_length
|
|
262
|
+
|
|
252
263
|
return lagrangian
|
|
253
264
|
|
|
254
|
-
|
|
255
|
-
def update_penalties(self):
|
|
265
|
+
def update_penalties(self, constraints: Tensor) -> None:
|
|
256
266
|
"""
|
|
257
|
-
|
|
267
|
+
Updates penalties according to the specified penalty update strategy for each constraint group.
|
|
268
|
+
|
|
269
|
+
:param constraints: Tensor of constraint violations.
|
|
270
|
+
:type constraints: torch.Tensor
|
|
271
|
+
:return: None
|
|
272
|
+
:rtype: None
|
|
258
273
|
"""
|
|
259
|
-
for group in self.param_groups:
|
|
260
|
-
duals, penalties,
|
|
261
|
-
|
|
274
|
+
for i, group in enumerate(self.param_groups):
|
|
275
|
+
duals, penalties, p_mult, _update_penalties, pbf = group["params"][0], group["params"][1], group["p_mult"], group["penalty_update"], group['pbf']
|
|
276
|
+
group_constraints = constraints[i * len(duals) : (i + 1) * len(duals)]
|
|
277
|
+
_update_penalties(penalties, p_mult, duals, penalty_barrier_funcs[pbf]['d'](group_constraints))
|
|
262
278
|
clamp_(penalties, min=self.penalty_range[0], max=self.penalty_range[1])
|
|
263
279
|
|
|
280
|
+
|
|
264
281
|
def state_dict(self) -> dict[str, Any]:
|
|
282
|
+
"""
|
|
283
|
+
Returns the state of the optimizer as a dictionary, including dual and penalty ranges and all constraint groups.
|
|
284
|
+
|
|
285
|
+
:return: Dictionary containing optimizer state with param groups and configuration.
|
|
286
|
+
:rtype: dict[str, Any]
|
|
287
|
+
"""
|
|
265
288
|
|
|
266
289
|
state_dict = super().state_dict()
|
|
267
290
|
state_dict["state"]["penalty_range"] = self.penalty_range
|
|
268
291
|
state_dict["state"]["dual_range"] = self.dual_range
|
|
269
292
|
# save params themselves in state_dict instead of param ID in default PyTorch
|
|
270
293
|
for id_pg, pg in enumerate(state_dict['param_groups']):
|
|
271
|
-
pg['params'] =
|
|
294
|
+
pg['params'] = self.param_groups[id_pg]['params']
|
|
272
295
|
return state_dict
|
|
273
296
|
|
|
274
297
|
def load_state_dict(self, state_dict: dict[str, Any]) -> None:
|
|
298
|
+
"""
|
|
299
|
+
Loads the optimizer state from a dictionary, including ranges and all constraint groups.
|
|
300
|
+
|
|
301
|
+
:param state_dict: Dictionary containing optimizer state (as returned by state_dict).
|
|
302
|
+
:type state_dict: dict[str, Any]
|
|
303
|
+
:return: None
|
|
304
|
+
:rtype: None
|
|
305
|
+
"""
|
|
275
306
|
self.penalty_range = state_dict["state"]["penalty_range"]
|
|
276
307
|
self.dual_range = state_dict["state"]["dual_range"]
|
|
277
308
|
params = state_dict["param_groups"]
|
|
@@ -286,22 +317,29 @@ penalty_barrier_funcs = {
|
|
|
286
317
|
'quadratic_reciprocal': {'f': quad_recipr, 'd': quad_recipr_der}
|
|
287
318
|
}
|
|
288
319
|
|
|
289
|
-
def _update_duals(duals: Tensor, cdivp: Tensor, pbf_der: Callable,
|
|
320
|
+
def _update_duals(duals: Tensor, cdivp: Tensor, pbf_der: Callable, gamma: float) -> None:
|
|
290
321
|
pbf_der_val = pbf_der(cdivp)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
else:
|
|
294
|
-
buffer.mul_(momentum).add_(pbf_der_val, alpha = 1 - dampening)
|
|
322
|
+
upd = pbf_der_val.mul(duals)
|
|
323
|
+
duals.mul_(gamma).add_(upd, alpha=1-gamma)
|
|
295
324
|
|
|
296
|
-
|
|
297
|
-
duals.mul_(buffer)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _update_penalties_const(penalties: Tensor, mu: Tensor, duals: Tensor):
|
|
325
|
+
def _update_penalties_const(penalties: Tensor, p_mult: Tensor = None, duals: Tensor = None, phi_der: Tensor = None, delta: float = None):
|
|
301
326
|
pass
|
|
302
327
|
|
|
303
|
-
def _update_penalties_dimin(penalties: Tensor,
|
|
304
|
-
penalties.mul_(
|
|
328
|
+
def _update_penalties_dimin(penalties: Tensor, p_mult: Tensor, duals: Tensor = None, phi_der: Tensor = None, delta: float = None):
|
|
329
|
+
penalties.mul_(p_mult)
|
|
330
|
+
|
|
331
|
+
def _update_penalties_adapt(penalties: Tensor, p_mult: Tensor, duals: Tensor, phi_der: Tensor, delta: float):
|
|
332
|
+
d_phd = torch.where(phi_der < 1., phi_der, torch.clamp(delta * phi_der, min=1.0))
|
|
333
|
+
b = (1-p_mult)*penalties/(d_phd + 1e-8)
|
|
334
|
+
penalties.mul_(p_mult).add_(b)
|
|
335
|
+
|
|
336
|
+
def _update_penalties_dimin_dual(penalties: Tensor, p_mult: Tensor, duals: Tensor, phi_der: Tensor = None, delta: float = None):
|
|
337
|
+
penalties.mul_(p_mult).mul_(duals)
|
|
338
|
+
|
|
305
339
|
|
|
306
|
-
|
|
307
|
-
|
|
340
|
+
penalty_update_funcs = {
|
|
341
|
+
'const': _update_penalties_const,
|
|
342
|
+
'dimin': _update_penalties_dimin,
|
|
343
|
+
'adapt': _update_penalties_adapt,
|
|
344
|
+
'dimin_dual': _update_penalties_dimin_dual
|
|
345
|
+
}
|
{humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/PBM.py
RENAMED
|
@@ -556,7 +556,7 @@ class PBM(Optimizer):
|
|
|
556
556
|
# define the augmented F and backpropagate
|
|
557
557
|
F_loss = loss + self._dual_vars @ self.constraints
|
|
558
558
|
|
|
559
|
-
# TODO: implement smoothing
|
|
559
|
+
# TODO: implement smoothing herew
|
|
560
560
|
|
|
561
561
|
# clean the gradients
|
|
562
562
|
# self.constraints = torch.zeros(self.m, device=self.device)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humancompatible-train
|
|
3
|
-
Version: 0.3.0
|
|
3
|
+
Version: 0.3.0.1
|
|
4
4
|
Summary: PyTorch-based package for constrained training of neural networks
|
|
5
5
|
Author: Gilles Bareilles, Adam Bosak, Jana Lepsova, Jakub Marecek
|
|
6
6
|
Author-email: Andrii Kliachkin <kliacand@fel.cvut.cz>
|
|
@@ -156,8 +156,6 @@ The constraint options are:
|
|
|
156
156
|
- `equalized_odds_vec`: constraint on the Positive Rate of each group as defined by `fairret.NormLoss`;
|
|
157
157
|
- `weight_norm`: constraint on the Frobenius norm of the weights and biases of each layer of the neural network.
|
|
158
158
|
|
|
159
|
-
The above command will run the benchmark and save the logs and models in the `benchmark/results/[dataset]_[task]` directory; it will also plot the trajectories into a .png file saved in the same location.
|
|
160
|
-
|
|
161
159
|
The benchmarking code (all of which is contained in the `benchmark` directory) is easy to parse and extend with other datasets and constraints.
|
|
162
160
|
|
|
163
161
|
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
from humancompatible.train.dual_optim import PBM
|
|
4
|
+
from humancompatible.train.dual_optim.barrier import quad_log, quad_log_der, quad_recipr, quad_recipr_der
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestPBMInitialization(unittest.TestCase):
|
|
8
|
+
"""Test PBM initialization and error handling."""
|
|
9
|
+
|
|
10
|
+
def test_init_with_m(self):
|
|
11
|
+
"""Test initialization with m parameter."""
|
|
12
|
+
pbm = PBM(m=5, penalty_mult=0.1)
|
|
13
|
+
self.assertEqual(len(pbm.duals), 5)
|
|
14
|
+
self.assertEqual(len(pbm.penalties), 5)
|
|
15
|
+
|
|
16
|
+
def test_init_with_scalar_duals(self):
|
|
17
|
+
"""Test initialization with scalar init_duals."""
|
|
18
|
+
pbm = PBM(m=3, init_duals=0.5, init_penalties=10.0)
|
|
19
|
+
self.assertTrue(torch.allclose(pbm.duals, torch.tensor([0.5, 0.5, 0.5])))
|
|
20
|
+
self.assertTrue(torch.allclose(pbm.penalties, torch.tensor([10.0, 10.0, 10.0])))
|
|
21
|
+
|
|
22
|
+
def test_init_with_tensor_duals(self):
|
|
23
|
+
"""Test initialization with tensor init_duals."""
|
|
24
|
+
init_duals = torch.tensor([0.1, 0.2, 0.3])
|
|
25
|
+
pbm = PBM(m=3, init_duals=init_duals, init_penalties=2.0)
|
|
26
|
+
self.assertTrue(torch.allclose(pbm.duals, init_duals))
|
|
27
|
+
|
|
28
|
+
def test_init_defaults_to_dual_range(self):
|
|
29
|
+
"""Test that None init_duals defaults to dual_range lower bound."""
|
|
30
|
+
pbm = PBM(m=3, dual_range=(0.01, 100.0))
|
|
31
|
+
self.assertTrue(torch.allclose(pbm.duals, torch.tensor([0.01, 0.01, 0.01])))
|
|
32
|
+
|
|
33
|
+
def test_init_defaults_to_penalty_range(self):
|
|
34
|
+
"""Test that None init_penalties defaults to penalty_range upper bound."""
|
|
35
|
+
pbm = PBM(m=3, penalty_range=(0.1, 50.0))
|
|
36
|
+
self.assertTrue(torch.allclose(pbm.penalties, torch.tensor([50.0, 50.0, 50.0])))
|
|
37
|
+
|
|
38
|
+
def test_init_missing_m_and_duals_raises_error(self):
|
|
39
|
+
"""Test that ValueError is raised when both m and init_duals are None."""
|
|
40
|
+
with self.assertRaises(ValueError):
|
|
41
|
+
PBM(m=None, init_duals=None)
|
|
42
|
+
|
|
43
|
+
def test_init_with_device(self):
|
|
44
|
+
"""Test initialization on specific device."""
|
|
45
|
+
device = 'cpu'
|
|
46
|
+
pbm = PBM(m=3, device=device)
|
|
47
|
+
self.assertEqual(pbm.duals.device.type, device)
|
|
48
|
+
|
|
49
|
+
def test_init_different_pbf(self):
|
|
50
|
+
"""Test initialization with different penalty-barrier functions."""
|
|
51
|
+
pbm_log = PBM(m=3, pbf='quadratic_logarithmic')
|
|
52
|
+
pbm_recipr = PBM(m=3, pbf='quadratic_reciprocal')
|
|
53
|
+
# Just verify they don't raise errors
|
|
54
|
+
self.assertEqual(len(pbm_log.duals), 3)
|
|
55
|
+
self.assertEqual(len(pbm_recipr.duals), 3)
|
|
56
|
+
|
|
57
|
+
def test_init_invalid_pbf_raises_error(self):
|
|
58
|
+
"""Test that invalid pbf raises KeyError when used."""
|
|
59
|
+
pbm = PBM(m=3, pbf='invalid_function')
|
|
60
|
+
# PBM creation doesn't validate pbf, but using it will fail
|
|
61
|
+
loss = torch.tensor(1.0)
|
|
62
|
+
constraints = torch.tensor([0.1, 0.1, 0.1])
|
|
63
|
+
with self.assertRaises(KeyError):
|
|
64
|
+
pbm.forward(loss, constraints)
|
|
65
|
+
|
|
66
|
+
def test_init_invalid_penalty_update_raises_error(self):
|
|
67
|
+
"""Test that invalid penalty_update raises ValueError."""
|
|
68
|
+
with self.assertRaises(ValueError):
|
|
69
|
+
PBM(m=3, penalty_update='invalid_strategy')
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestDualAndPenaltyBounds(unittest.TestCase):
|
|
73
|
+
"""Test that duals and penalties stay within specified bounds."""
|
|
74
|
+
|
|
75
|
+
def test_duals_clamped_to_range(self):
|
|
76
|
+
"""Test that duals are clamped to dual_range."""
|
|
77
|
+
pbm = PBM(m=2, dual_range=(0.01, 10.0), init_duals=5.0)
|
|
78
|
+
# Large constraint violations through forward_update
|
|
79
|
+
loss = torch.tensor(1.0)
|
|
80
|
+
constraints = torch.tensor([1000.0, 1000.0])
|
|
81
|
+
for _ in range(5):
|
|
82
|
+
pbm.forward_update(loss, constraints)
|
|
83
|
+
# Duals should not exceed upper bound
|
|
84
|
+
self.assertTrue(torch.all(pbm.duals <= 10.0))
|
|
85
|
+
# Duals should not fall below lower bound
|
|
86
|
+
self.assertTrue(torch.all(pbm.duals >= 0.01))
|
|
87
|
+
|
|
88
|
+
def test_penalties_clamped_to_range(self):
|
|
89
|
+
"""Test that penalties are clamped to penalty_range."""
|
|
90
|
+
pbm = PBM(m=2, penalty_mult=0.05, penalty_range=(0.1, 100.0))
|
|
91
|
+
loss = torch.tensor(1.0)
|
|
92
|
+
# Run multiple updates
|
|
93
|
+
for _ in range(10):
|
|
94
|
+
constraints = torch.tensor([0.5, 0.5])
|
|
95
|
+
pbm.forward_update(loss, constraints)
|
|
96
|
+
# Penalties should not exceed upper bound
|
|
97
|
+
self.assertTrue(torch.all(pbm.penalties <= 100.0))
|
|
98
|
+
# Penalties should not fall below lower bound
|
|
99
|
+
self.assertTrue(torch.all(pbm.penalties >= 0.1))
|
|
100
|
+
|
|
101
|
+
def test_duals_stay_positive_with_small_range(self):
|
|
102
|
+
"""Test duals with very small range."""
|
|
103
|
+
pbm = PBM(m=3, dual_range=(1e-6, 1e-3), init_duals=5e-4)
|
|
104
|
+
loss = torch.tensor(1.0)
|
|
105
|
+
constraints = torch.tensor([0.1, 0.2, 0.3])
|
|
106
|
+
pbm.forward_update(loss, constraints)
|
|
107
|
+
self.assertTrue(torch.all(pbm.duals >= 1e-6))
|
|
108
|
+
self.assertTrue(torch.all(pbm.duals <= 1e-3))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestPenaltyUpdateStrategies(unittest.TestCase):
|
|
112
|
+
"""Test different penalty update strategies."""
|
|
113
|
+
|
|
114
|
+
def test_constant_penalty(self):
|
|
115
|
+
"""Test that penalty_update='const' keeps penalties constant during forward_update."""
|
|
116
|
+
init_penalty = 5.0
|
|
117
|
+
pbm = PBM(m=3, penalty_update='const', init_penalties=init_penalty)
|
|
118
|
+
penalties_before = pbm.penalties.clone()
|
|
119
|
+
loss = torch.tensor(1.0)
|
|
120
|
+
|
|
121
|
+
for _ in range(5):
|
|
122
|
+
constraints = torch.tensor([0.1, 0.2, 0.3])
|
|
123
|
+
pbm.forward_update(loss, constraints)
|
|
124
|
+
|
|
125
|
+
self.assertTrue(torch.allclose(pbm.penalties, penalties_before))
|
|
126
|
+
|
|
127
|
+
def test_diminishing_penalty_monotonic(self):
|
|
128
|
+
"""Test that penalty_update='dimin' decreases penalties monotonically."""
|
|
129
|
+
penalty_mult = 0.8
|
|
130
|
+
pbm = PBM(m=2, penalty_update='dimin', penalty_mult=penalty_mult, init_penalties=100.0)
|
|
131
|
+
|
|
132
|
+
penalties_history = [pbm.penalties.clone()]
|
|
133
|
+
loss = torch.tensor(1.0)
|
|
134
|
+
constraints = torch.tensor([0.5, 0.5])
|
|
135
|
+
|
|
136
|
+
for _ in range(5):
|
|
137
|
+
pbm.forward_update(loss, constraints)
|
|
138
|
+
penalties_history.append(pbm.penalties.clone())
|
|
139
|
+
|
|
140
|
+
# Verify monotonic decrease (from 1 onwards since 0 is initial)
|
|
141
|
+
for i in range(len(penalties_history) - 1):
|
|
142
|
+
self.assertTrue(torch.all(penalties_history[i + 1] <= penalties_history[i]))
|
|
143
|
+
|
|
144
|
+
def test_adaptive_penalty_responds_to_violations(self):
|
|
145
|
+
"""Test that adaptive penalty responds to constraint violations."""
|
|
146
|
+
pbm_adapt = PBM(m=1, penalty_update='dimin_adapt', penalty_mult=0.8, delta=0.9, init_penalties=10.0)
|
|
147
|
+
pbm_const = PBM(m=1, penalty_update='const', init_penalties=10.0)
|
|
148
|
+
loss = torch.tensor(1.0)
|
|
149
|
+
|
|
150
|
+
# Use larger constraint to see penalty difference
|
|
151
|
+
constraint = torch.tensor([1.0])
|
|
152
|
+
pbm_adapt.forward_update(loss, constraint)
|
|
153
|
+
pbm_const.forward_update(loss, constraint)
|
|
154
|
+
|
|
155
|
+
# Adaptive should differ from constant after update
|
|
156
|
+
# (or at least both updates should complete without error)
|
|
157
|
+
self.assertEqual(len(pbm_adapt.penalties), 1)
|
|
158
|
+
|
|
159
|
+
# def test_dimin_dual_penalty_changes(self):
|
|
160
|
+
# """Test that dimin_dual strategy multiplies by duals."""
|
|
161
|
+
# pbm = PBM(m=2, penalty_update='dimin_dual', penalty_mult=0.8, init_duals=2.0, init_penalties=10.0)
|
|
162
|
+
|
|
163
|
+
# initial_penalty = pbm.penalties.clone()
|
|
164
|
+
# loss = torch.tensor(1.0)
|
|
165
|
+
# # Use larger constraints to trigger meaningful updates
|
|
166
|
+
# constraints = torch.tensor([1.0, 1.0])
|
|
167
|
+
# pbm.forward_update(loss, constraints)
|
|
168
|
+
|
|
169
|
+
# # With dimin_dual, penalties should decrease due to low initial duals
|
|
170
|
+
# breakpoint()
|
|
171
|
+
# self.assertTrue(torch.all(pbm.penalties <= initial_penalty))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestBarrierFunctions(unittest.TestCase):
|
|
175
|
+
"""Test barrier function properties."""
|
|
176
|
+
|
|
177
|
+
def test_quad_log_positive_derivative_at_violation(self):
|
|
178
|
+
"""Test that quad_log derivative is positive at constraint violations."""
|
|
179
|
+
t = torch.tensor([-0.2, 0.0, 0.5])
|
|
180
|
+
deriv = quad_log_der(t)
|
|
181
|
+
self.assertTrue(torch.all(deriv > 0.0))
|
|
182
|
+
|
|
183
|
+
def test_quad_recipr_positive_derivative_at_violation(self):
|
|
184
|
+
"""Test that quad_recipr derivative is positive at constraint violations."""
|
|
185
|
+
t = torch.tensor([-0.2, 0.0, 0.3])
|
|
186
|
+
deriv = quad_recipr_der(t)
|
|
187
|
+
self.assertTrue(torch.all(deriv > 0.0))
|
|
188
|
+
|
|
189
|
+
def test_quad_log_derivative_matches_finite_diff_smooth_region(self):
|
|
190
|
+
"""Test quad_log derivative against finite differences in smooth region."""
|
|
191
|
+
# Use values well within smooth region (t >= -0.5)
|
|
192
|
+
t = torch.tensor([0.5, 1.0, 2.0])
|
|
193
|
+
eps = 1e-3
|
|
194
|
+
|
|
195
|
+
# Finite difference
|
|
196
|
+
deriv_fd = (quad_log(t + eps) - quad_log(t - eps)) / (2 * eps)
|
|
197
|
+
deriv_analytical = quad_log_der(t)
|
|
198
|
+
|
|
199
|
+
self.assertTrue(torch.allclose(deriv_fd, deriv_analytical, atol=1e-3))
|
|
200
|
+
|
|
201
|
+
def test_barrier_functions_increase_with_violation(self):
|
|
202
|
+
"""Test that barrier functions penalize larger violations more."""
|
|
203
|
+
t1 = torch.tensor([0.0])
|
|
204
|
+
t2 = torch.tensor([0.5])
|
|
205
|
+
|
|
206
|
+
log_v1 = quad_log(t1)
|
|
207
|
+
log_v2 = quad_log(t2)
|
|
208
|
+
self.assertTrue(log_v2 > log_v1)
|
|
209
|
+
|
|
210
|
+
recipr_v1 = quad_recipr(t1)
|
|
211
|
+
recipr_v2 = quad_recipr(t2)
|
|
212
|
+
self.assertTrue(recipr_v2 > recipr_v1)
|
|
213
|
+
|
|
214
|
+
def test_barrier_functions_nonzero_at_zero(self):
|
|
215
|
+
"""Test that barrier function values at zero constraint are reasonable."""
|
|
216
|
+
t = torch.tensor([0.0])
|
|
217
|
+
log_val = quad_log(t)
|
|
218
|
+
recipr_val = quad_recipr(t)
|
|
219
|
+
|
|
220
|
+
# Both should be exactly 0 at t=0
|
|
221
|
+
self.assertTrue(torch.allclose(log_val, torch.tensor([0.0]), atol=1e-6))
|
|
222
|
+
self.assertTrue(torch.allclose(recipr_val, torch.tensor([0.0]), atol=1e-6))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TestDualVariableUpdates(unittest.TestCase):
|
|
226
|
+
"""Test dual variable update mechanisms."""
|
|
227
|
+
|
|
228
|
+
def test_dual_update_with_zero_momentum(self):
|
|
229
|
+
"""Test dual update with gamma=0 (complete replacement)."""
|
|
230
|
+
pbm = PBM(m=2, gamma=0.0, init_duals=1.0, init_penalties=10.0)
|
|
231
|
+
loss = torch.tensor(1.0)
|
|
232
|
+
constraints = torch.tensor([0.5, 0.5])
|
|
233
|
+
|
|
234
|
+
# With gamma=0, new duals should be completely replaced
|
|
235
|
+
pbm.forward_update(loss, constraints)
|
|
236
|
+
# Just verify update happened without error
|
|
237
|
+
self.assertEqual(len(pbm.duals), 2)
|
|
238
|
+
|
|
239
|
+
def test_dual_update_with_high_momentum(self):
|
|
240
|
+
"""Test dual update with gamma close to 1 (damped update)."""
|
|
241
|
+
pbm_high = PBM(m=2, gamma=0.99, init_duals=1.0)
|
|
242
|
+
pbm_low = PBM(m=2, gamma=0.1, init_duals=1.0)
|
|
243
|
+
loss = torch.tensor(1.0)
|
|
244
|
+
|
|
245
|
+
constraints = torch.tensor([0.5, 0.5])
|
|
246
|
+
duals_high_before = pbm_high.duals.clone()
|
|
247
|
+
duals_low_before = pbm_low.duals.clone()
|
|
248
|
+
|
|
249
|
+
pbm_high.forward_update(loss, constraints)
|
|
250
|
+
pbm_low.forward_update(loss, constraints)
|
|
251
|
+
|
|
252
|
+
# High momentum should change duals less
|
|
253
|
+
high_change = torch.abs(pbm_high.duals - duals_high_before).sum()
|
|
254
|
+
low_change = torch.abs(pbm_low.duals - duals_low_before).sum()
|
|
255
|
+
self.assertTrue(high_change < low_change)
|
|
256
|
+
|
|
257
|
+
def test_dual_momentum_buffer_tracked(self):
|
|
258
|
+
"""Test that momentum buffer is tracked across updates."""
|
|
259
|
+
pbm = PBM(m=2, gamma=0.8)
|
|
260
|
+
loss = torch.tensor(1.0)
|
|
261
|
+
constraints = torch.tensor([0.5, 0.5])
|
|
262
|
+
|
|
263
|
+
# Run multiple updates to accumulate momentum
|
|
264
|
+
for _ in range(3):
|
|
265
|
+
pbm.forward_update(loss, constraints)
|
|
266
|
+
# Just verify no errors occur
|
|
267
|
+
self.assertEqual(len(pbm.duals), 2)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class TestMultipleConstraintGroups(unittest.TestCase):
|
|
271
|
+
"""Test adding and managing multiple constraint groups."""
|
|
272
|
+
|
|
273
|
+
def test_add_constraint_group(self):
|
|
274
|
+
"""Test adding a second constraint group."""
|
|
275
|
+
pbm = PBM(m=2, penalty_mult=0.1)
|
|
276
|
+
self.assertEqual(len(pbm.duals), 2)
|
|
277
|
+
|
|
278
|
+
pbm.add_constraint_group(m=3, penalty_mult=0.15)
|
|
279
|
+
self.assertEqual(len(pbm.duals), 5) # 2 + 3
|
|
280
|
+
self.assertEqual(len(pbm.penalties), 5)
|
|
281
|
+
|
|
282
|
+
def test_constraint_groups_independent_updates(self):
|
|
283
|
+
"""Test that constraint groups update independently."""
|
|
284
|
+
pbm = PBM(m=2, penalty_mult=0.8, penalty_update='dimin')
|
|
285
|
+
pbm.add_constraint_group(m=2, penalty_mult=0.5, penalty_update='dimin', delta=0.9, pbf='quadratic_logarithmic')
|
|
286
|
+
loss = torch.tensor(1.0)
|
|
287
|
+
constraints = torch.tensor([0.5, 0.5, 0.1, 0.1])
|
|
288
|
+
pbm.forward_update(loss, constraints)
|
|
289
|
+
|
|
290
|
+
# Just verify both groups were updated
|
|
291
|
+
self.assertEqual(len(pbm.penalties), 4)
|
|
292
|
+
|
|
293
|
+
def test_multiple_groups_different_pbf(self):
|
|
294
|
+
"""Test adding groups with different barrier functions."""
|
|
295
|
+
pbm = PBM(m=2, pbf='quadratic_logarithmic', penalty_mult=0.1)
|
|
296
|
+
pbm.add_constraint_group(m=2, pbf='quadratic_reciprocal', penalty_mult=0.1, penalty_update='dimin', delta=0.9)
|
|
297
|
+
loss = torch.tensor(1.0)
|
|
298
|
+
constraints = torch.tensor([0.5, 0.5, 0.5, 0.5])
|
|
299
|
+
pbm.forward_update(loss, constraints)
|
|
300
|
+
|
|
301
|
+
# Just verify both groups update without error
|
|
302
|
+
self.assertEqual(len(pbm.duals), 4)
|
|
303
|
+
|
|
304
|
+
def test_duals_property_concatenates_groups(self):
|
|
305
|
+
"""Test that duals property correctly concatenates groups."""
|
|
306
|
+
pbm = PBM(m=3, init_duals=1.0, penalty_mult=0.1)
|
|
307
|
+
pbm.add_constraint_group(m=2, init_duals=2.0, penalty_mult=0.1, penalty_update='dimin', delta=0.9, pbf='quadratic_logarithmic')
|
|
308
|
+
|
|
309
|
+
duals_concat = pbm.duals
|
|
310
|
+
self.assertEqual(len(duals_concat), 5)
|
|
311
|
+
self.assertTrue(torch.allclose(duals_concat[:3], torch.tensor([1.0, 1.0, 1.0])))
|
|
312
|
+
self.assertTrue(torch.allclose(duals_concat[3:], torch.tensor([2.0, 2.0])))
|
|
313
|
+
|
|
314
|
+
def test_penalties_property_concatenates_groups(self):
|
|
315
|
+
"""Test that penalties property correctly concatenates groups."""
|
|
316
|
+
pbm = PBM(m=2, init_penalties=5.0, penalty_mult=0.1)
|
|
317
|
+
pbm.add_constraint_group(m=3, init_penalties=10.0, penalty_mult=0.1, penalty_update='dimin', delta=0.9, pbf='quadratic_logarithmic')
|
|
318
|
+
|
|
319
|
+
penalties_concat = pbm.penalties
|
|
320
|
+
self.assertEqual(len(penalties_concat), 5)
|
|
321
|
+
self.assertTrue(torch.allclose(penalties_concat[:2], torch.tensor([5.0, 5.0])))
|
|
322
|
+
self.assertTrue(torch.allclose(penalties_concat[2:], torch.tensor([10.0, 10.0, 10.0])))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class TestLagrangianComputation(unittest.TestCase):
|
|
326
|
+
"""Test Lagrangian computation accuracy."""
|
|
327
|
+
|
|
328
|
+
def test_lagrangian_includes_loss(self):
|
|
329
|
+
"""Test that Lagrangian includes the loss term."""
|
|
330
|
+
pbm = PBM(m=2)
|
|
331
|
+
loss = torch.tensor(5.0)
|
|
332
|
+
constraints = torch.tensor([0.1, 0.2])
|
|
333
|
+
|
|
334
|
+
lagrangian = pbm.forward(loss, constraints)
|
|
335
|
+
# Lagrangian should be at least the loss
|
|
336
|
+
self.assertTrue(lagrangian >= loss)
|
|
337
|
+
|
|
338
|
+
def test_lagrangian_zero_constraint_equals_loss(self):
|
|
339
|
+
"""Test that Lagrangian equals loss when constraints are zero."""
|
|
340
|
+
pbm = PBM(m=2, init_duals=1.0, init_penalties=1.0)
|
|
341
|
+
loss = torch.tensor(3.0)
|
|
342
|
+
constraints = torch.tensor([0.0, 0.0])
|
|
343
|
+
|
|
344
|
+
lagrangian = pbm.forward(loss, constraints)
|
|
345
|
+
self.assertTrue(torch.allclose(lagrangian, loss))
|
|
346
|
+
|
|
347
|
+
def test_lagrangian_increases_with_violation(self):
|
|
348
|
+
"""Test that Lagrangian increases with constraint violation."""
|
|
349
|
+
pbm = PBM(m=1, init_duals=1.0, init_penalties=1.0)
|
|
350
|
+
loss = torch.tensor(1.0)
|
|
351
|
+
|
|
352
|
+
lag_small = pbm.forward(loss, torch.tensor([0.1]))
|
|
353
|
+
lag_large = pbm.forward(loss, torch.tensor([0.5]))
|
|
354
|
+
|
|
355
|
+
self.assertTrue(lag_large > lag_small)
|
|
356
|
+
|
|
357
|
+
def test_forward_update_produces_same_lagrangian(self):
|
|
358
|
+
"""Test that forward_update produces valid Lagrangian."""
|
|
359
|
+
pbm = PBM(m=2, gamma=0.8, init_duals=0.5, init_penalties=5.0)
|
|
360
|
+
|
|
361
|
+
loss = torch.tensor(2.0)
|
|
362
|
+
constraints = torch.tensor([0.3, 0.4])
|
|
363
|
+
|
|
364
|
+
# forward_update should produce a Lagrangian value
|
|
365
|
+
lag = pbm.forward_update(loss, constraints)
|
|
366
|
+
|
|
367
|
+
# Lagrangian should be a scalar
|
|
368
|
+
self.assertTrue(lag.dim() == 0 or lag.shape == torch.Size([]))
|
|
369
|
+
|
|
370
|
+
def test_manual_lagrangian_computation(self):
|
|
371
|
+
"""Test Lagrangian against manual calculation with simple values."""
|
|
372
|
+
pbm = PBM(m=1, init_duals=1.0, init_penalties=1.0, pbf='quadratic_logarithmic')
|
|
373
|
+
loss = torch.tensor(1.0)
|
|
374
|
+
constraints = torch.tensor([0.0])
|
|
375
|
+
|
|
376
|
+
lagrangian = pbm.forward(loss, constraints)
|
|
377
|
+
|
|
378
|
+
# When constraint is 0, barrier function contribution should be 0
|
|
379
|
+
# So lagrangian should equal loss
|
|
380
|
+
self.assertTrue(torch.allclose(lagrangian, loss, atol=1e-5))
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class TestSerialization(unittest.TestCase):
|
|
384
|
+
"""Test state dict save and load."""
|
|
385
|
+
|
|
386
|
+
def test_state_dict_contains_ranges(self):
|
|
387
|
+
"""Test that state_dict includes dual and penalty ranges."""
|
|
388
|
+
pbm = PBM(m=3, dual_range=(0.01, 50.0), penalty_range=(0.5, 200.0))
|
|
389
|
+
state = pbm.state_dict()
|
|
390
|
+
|
|
391
|
+
self.assertEqual(state["state"]["dual_range"], (0.01, 50.0))
|
|
392
|
+
self.assertEqual(state["state"]["penalty_range"], (0.5, 200.0))
|
|
393
|
+
|
|
394
|
+
def test_state_dict_roundtrip_preserves_duals_and_penalties(self):
|
|
395
|
+
"""Test that save/load preserves duals and penalties."""
|
|
396
|
+
pbm1 = PBM(m=3, init_duals=1.5, init_penalties=8.0, penalty_update='dimin')
|
|
397
|
+
loss = torch.tensor(1.0)
|
|
398
|
+
|
|
399
|
+
# Modify state
|
|
400
|
+
constraints = torch.tensor([0.1, 0.2, 0.3])
|
|
401
|
+
pbm1.forward_update(loss, constraints)
|
|
402
|
+
|
|
403
|
+
duals_before = pbm1.duals.clone()
|
|
404
|
+
penalties_before = pbm1.penalties.clone()
|
|
405
|
+
|
|
406
|
+
# Save and load
|
|
407
|
+
state = pbm1.state_dict()
|
|
408
|
+
pbm2 = PBM(m=3, penalty_update='dimin')
|
|
409
|
+
pbm2.load_state_dict(state)
|
|
410
|
+
|
|
411
|
+
self.assertTrue(torch.allclose(pbm2.duals, duals_before))
|
|
412
|
+
self.assertTrue(torch.allclose(pbm2.penalties, penalties_before))
|
|
413
|
+
|
|
414
|
+
def test_state_dict_roundtrip_with_multiple_groups(self):
|
|
415
|
+
"""Test state dict roundtrip with multiple constraint groups."""
|
|
416
|
+
pbm1 = PBM(m=2, init_duals=1.0, init_penalties=5.0, penalty_mult=0.1, penalty_update='dimin')
|
|
417
|
+
pbm1.add_constraint_group(m=3, init_duals=0.5, init_penalties=10.0, penalty_mult=0.1, penalty_update='dimin', delta=0.9, pbf='quadratic_logarithmic')
|
|
418
|
+
loss = torch.tensor(1.0)
|
|
419
|
+
|
|
420
|
+
# Update some
|
|
421
|
+
constraints = torch.tensor([0.1, 0.1, 0.2, 0.2, 0.2])
|
|
422
|
+
pbm1.forward_update(loss, constraints)
|
|
423
|
+
|
|
424
|
+
state = pbm1.state_dict()
|
|
425
|
+
pbm2 = PBM(m=2, penalty_mult=0.1, penalty_update='dimin')
|
|
426
|
+
pbm2.add_constraint_group(m=3, penalty_mult=0.1, penalty_update='dimin', delta=0.9, pbf='quadratic_logarithmic')
|
|
427
|
+
pbm2.load_state_dict(state)
|
|
428
|
+
|
|
429
|
+
self.assertTrue(torch.allclose(pbm2.duals, pbm1.duals))
|
|
430
|
+
self.assertTrue(torch.allclose(pbm2.penalties, pbm1.penalties))
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TestParameterInteractions(unittest.TestCase):
|
|
434
|
+
"""Test interactions between different parameters."""
|
|
435
|
+
|
|
436
|
+
def test_gamma_affects_dual_convergence_rate(self):
|
|
437
|
+
"""Test that higher gamma slows dual changes."""
|
|
438
|
+
pbm_fast = PBM(m=1, gamma=0.1, init_duals=1.0, init_penalties=10.0)
|
|
439
|
+
pbm_slow = PBM(m=1, gamma=0.9, init_duals=1.0, init_penalties=10.0)
|
|
440
|
+
loss = torch.tensor(1.0)
|
|
441
|
+
constraints = torch.tensor([1.0])
|
|
442
|
+
|
|
443
|
+
duals_fast_before = pbm_fast.duals.clone()
|
|
444
|
+
duals_slow_before = pbm_slow.duals.clone()
|
|
445
|
+
|
|
446
|
+
pbm_fast.forward_update(loss, constraints)
|
|
447
|
+
pbm_slow.forward_update(loss, constraints)
|
|
448
|
+
|
|
449
|
+
change_fast = torch.abs(pbm_fast.duals - duals_fast_before)
|
|
450
|
+
change_slow = torch.abs(pbm_slow.duals - duals_slow_before)
|
|
451
|
+
|
|
452
|
+
self.assertTrue(change_fast > change_slow)
|
|
453
|
+
|
|
454
|
+
def test_delta_affects_adaptive_penalty_behavior(self):
|
|
455
|
+
"""Test that delta parameter affects adaptive penalty update."""
|
|
456
|
+
pbm_low_delta = PBM(m=1, penalty_update='dimin_adapt', delta=0.5, penalty_mult=0.8, init_penalties=10.0)
|
|
457
|
+
pbm_high_delta = PBM(m=1, penalty_update='dimin_adapt', delta=2.0, penalty_mult=0.8, init_penalties=10.0)
|
|
458
|
+
loss = torch.tensor(1.0)
|
|
459
|
+
|
|
460
|
+
# Constraint with significant violation
|
|
461
|
+
constraints = torch.tensor([2.0])
|
|
462
|
+
|
|
463
|
+
pbm_low_delta.forward_update(loss, constraints)
|
|
464
|
+
pbm_high_delta.forward_update(loss, constraints)
|
|
465
|
+
|
|
466
|
+
# Different delta should produce different penalties
|
|
467
|
+
self.assertFalse(torch.allclose(pbm_low_delta.penalties, pbm_high_delta.penalties))
|
|
468
|
+
|
|
469
|
+
def test_penalty_mult_scale_different_strategies(self):
|
|
470
|
+
"""Test that penalty_mult works across different strategies."""
|
|
471
|
+
loss = torch.tensor(1.0)
|
|
472
|
+
constraints = torch.tensor([0.5])
|
|
473
|
+
|
|
474
|
+
pbm_dimin = PBM(m=1, penalty_update='dimin', penalty_mult=0.5, init_penalties=100.0)
|
|
475
|
+
pbm_dimin.forward_update(loss, constraints)
|
|
476
|
+
dimin_penalty = pbm_dimin.penalties.clone()
|
|
477
|
+
|
|
478
|
+
pbm_adapt = PBM(m=1, penalty_update='dimin_adapt', penalty_mult=0.5, init_penalties=100.0)
|
|
479
|
+
pbm_adapt.forward_update(loss, constraints)
|
|
480
|
+
|
|
481
|
+
# Both should be affected by penalty_mult, though differently
|
|
482
|
+
self.assertTrue(torch.all(dimin_penalty < 100.0))
|
|
483
|
+
self.assertTrue(torch.all(pbm_adapt.penalties < 100.0))
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class TestConstraintSatisfactionProgress(unittest.TestCase):
|
|
487
|
+
"""Test constraint satisfaction and convergence behavior."""
|
|
488
|
+
|
|
489
|
+
def test_constraints_drive_duals_up(self):
|
|
490
|
+
"""Test that constraints drive dual variables upward."""
|
|
491
|
+
pbm = PBM(m=1, gamma=0.5, init_duals=0.1, init_penalties=10.0)
|
|
492
|
+
duals_before = pbm.duals.clone()
|
|
493
|
+
loss = torch.tensor(1.0)
|
|
494
|
+
|
|
495
|
+
# Positive constraint violation
|
|
496
|
+
constraints = torch.tensor([1.0])
|
|
497
|
+
pbm.forward_update(loss, constraints)
|
|
498
|
+
|
|
499
|
+
duals_after = pbm.duals.clone()
|
|
500
|
+
self.assertTrue(duals_after > duals_before)
|
|
501
|
+
|
|
502
|
+
def test_lagrangian_monotonic_on_consistent_constraints(self):
|
|
503
|
+
"""Test that Lagrangian progression with consistent constraints."""
|
|
504
|
+
pbm = PBM(m=1, gamma=0.7, init_duals=0.5, init_penalties=5.0)
|
|
505
|
+
loss = torch.tensor(1.0)
|
|
506
|
+
constraints = torch.tensor([0.5])
|
|
507
|
+
|
|
508
|
+
lagrangians = []
|
|
509
|
+
for _ in range(3):
|
|
510
|
+
lag = pbm.forward_update(loss, constraints)
|
|
511
|
+
lagrangians.append(lag.item())
|
|
512
|
+
|
|
513
|
+
# Just verify sequence is computed without error
|
|
514
|
+
self.assertEqual(len(lagrangians), 3)
|
|
515
|
+
|
|
516
|
+
def test_penalties_decrease_with_dimin(self):
|
|
517
|
+
"""Test that penalties monotonically decrease with dimin strategy."""
|
|
518
|
+
pbm = PBM(m=2, penalty_update='dimin', penalty_mult=0.7, init_penalties=100.0)
|
|
519
|
+
loss = torch.tensor(1.0)
|
|
520
|
+
constraints = torch.tensor([0.1, 0.1])
|
|
521
|
+
|
|
522
|
+
penalties_list = [pbm.penalties.clone()]
|
|
523
|
+
for _ in range(5):
|
|
524
|
+
pbm.forward_update(loss, constraints)
|
|
525
|
+
penalties_list.append(pbm.penalties.clone())
|
|
526
|
+
|
|
527
|
+
for i in range(len(penalties_list) - 1):
|
|
528
|
+
self.assertTrue(torch.all(penalties_list[i + 1] <= penalties_list[i]))
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
if __name__ == '__main__':
|
|
532
|
+
unittest.main()
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import torch
|
|
3
|
-
from torch.optim import Optimizer
|
|
4
|
-
from humancompatible.train.dual_optim.pbm import PBM
|
|
5
|
-
from humancompatible.train.dual_optim.barrier import quad_log, quad_log_der, quad_recipr, quad_recipr_der
|
|
6
|
-
|
|
7
|
-
class TestPBM(unittest.TestCase):
|
|
8
|
-
def setUp(self):
|
|
9
|
-
# Initialize PBM instances for reuse in tests
|
|
10
|
-
self.pbm_default = PBM(m=3, mu=0.3, lr=0.1, penalty_update='dimin', pbf='quadratic_logarithmic')
|
|
11
|
-
self.pbm_reciprocal = PBM(m=3, mu=0.3, lr=0.1, penalty_update='dimin', pbf='quadratic_reciprocal')
|
|
12
|
-
self.pbm_const_penalty = PBM(m=3, mu=0.3, lr=0.1, penalty_update='const', pbf='quadratic_logarithmic')
|
|
13
|
-
self.pbm_dimin_dual = PBM(m=3, mu=0.3, lr=0.1, penalty_update='dimin_dual', pbf='quadratic_logarithmic', init_duals=2.0)
|
|
14
|
-
|
|
15
|
-
# Common test data
|
|
16
|
-
self.loss = torch.tensor(5.0)
|
|
17
|
-
self.constraints = torch.tensor([1.0, 2.0, 3.0])
|
|
18
|
-
self.large_constraints = torch.tensor([10.0, 20.0, 30.0])
|
|
19
|
-
|
|
20
|
-
def test_pbm_initialization(self):
|
|
21
|
-
# Test initialization with m
|
|
22
|
-
self.assertEqual(len(self.pbm_default.duals), 3)
|
|
23
|
-
self.assertEqual(len(self.pbm_default.penalties), 3)
|
|
24
|
-
self.assertEqual(self.pbm_default.defaults['mu'], 0.3)
|
|
25
|
-
self.assertEqual(self.pbm_default.defaults['pbf'], 'quadratic_logarithmic')
|
|
26
|
-
|
|
27
|
-
# Test initialization with init_duals and init_penalties
|
|
28
|
-
init_duals = torch.tensor([1.0, 2.0, 3.0])
|
|
29
|
-
init_penalties = torch.tensor([10.0, 20.0, 30.0])
|
|
30
|
-
pbm = PBM(init_duals=init_duals, init_penalties=init_penalties, mu=0.3, lr=0.1, penalty_update='dimin', pbf='quadratic_logarithmic')
|
|
31
|
-
self.assertTrue(torch.all(pbm.duals == init_duals))
|
|
32
|
-
self.assertTrue(torch.all(pbm.penalties == init_penalties))
|
|
33
|
-
|
|
34
|
-
# Test invalid initialization
|
|
35
|
-
with self.assertRaises(ValueError):
|
|
36
|
-
PBM(m=None, init_duals=None)
|
|
37
|
-
|
|
38
|
-
def test_pbm_forward(self):
|
|
39
|
-
# Test Lagrangian computation
|
|
40
|
-
lagrangian = self.pbm_default.forward(self.loss, self.constraints)
|
|
41
|
-
cdivp = self.constraints / self.pbm_default.penalties
|
|
42
|
-
pbf_val = quad_log(cdivp)
|
|
43
|
-
expected_lagrangian = self.loss + torch.dot(self.pbm_default.duals * self.pbm_default.penalties, pbf_val)
|
|
44
|
-
self.assertTrue(torch.allclose(lagrangian, expected_lagrangian))
|
|
45
|
-
|
|
46
|
-
def test_pbm_update_duals(self):
|
|
47
|
-
# Test dual variable update
|
|
48
|
-
cdivp = self.constraints / self.pbm_default.penalties
|
|
49
|
-
pbf_der_val = quad_log_der(cdivp)
|
|
50
|
-
expected_duals = self.pbm_default.duals * torch.clamp(pbf_der_val, 0.3, 1/0.3)
|
|
51
|
-
|
|
52
|
-
self.pbm_default.update(self.constraints)
|
|
53
|
-
self.assertTrue(torch.allclose(self.pbm_default.duals, expected_duals))
|
|
54
|
-
|
|
55
|
-
def test_pbm_update_penalties_dimin(self):
|
|
56
|
-
# Test penalty update with 'dimin' strategy
|
|
57
|
-
initial_penalties = self.pbm_default.penalties.clone()
|
|
58
|
-
self.pbm_default.update_penalties()
|
|
59
|
-
expected_penalties = initial_penalties * 0.1
|
|
60
|
-
self.assertTrue(torch.allclose(self.pbm_default.penalties, expected_penalties))
|
|
61
|
-
|
|
62
|
-
def test_pbm_update_penalties_dimin_dual(self):
|
|
63
|
-
# Test penalty update with 'dimin_dual' strategy
|
|
64
|
-
initial_penalties = self.pbm_dimin_dual.penalties.clone()
|
|
65
|
-
expected_penalties = initial_penalties * 0.1 * self.pbm_dimin_dual.duals
|
|
66
|
-
|
|
67
|
-
self.pbm_dimin_dual.update_penalties()
|
|
68
|
-
self.assertTrue(torch.allclose(self.pbm_dimin_dual.penalties, expected_penalties))
|
|
69
|
-
|
|
70
|
-
def test_pbm_update_penalties_const(self):
|
|
71
|
-
# Test penalty update with 'const' strategy
|
|
72
|
-
initial_penalties = self.pbm_const_penalty.penalties.clone()
|
|
73
|
-
self.pbm_const_penalty.update_penalties()
|
|
74
|
-
self.assertTrue(torch.allclose(self.pbm_const_penalty.penalties, initial_penalties))
|
|
75
|
-
|
|
76
|
-
def test_pbm_forward_update(self):
|
|
77
|
-
# Test combined forward and update
|
|
78
|
-
lagrangian = self.pbm_default.forward_update(self.loss, self.constraints)
|
|
79
|
-
cdivp = self.constraints / self.pbm_default.penalties
|
|
80
|
-
pbf_val = quad_log(cdivp)
|
|
81
|
-
expected_lagrangian = self.loss + torch.dot(self.pbm_default.duals * self.pbm_default.penalties, pbf_val)
|
|
82
|
-
self.assertTrue(torch.allclose(lagrangian, expected_lagrangian))
|
|
83
|
-
|
|
84
|
-
def test_pbm_add_constraint_group(self):
|
|
85
|
-
# Test adding a new constraint group
|
|
86
|
-
self.pbm_default.add_constraint_group(m=2, mu=0.5, penalty_update='dimin', pbf='quadratic_reciprocal')
|
|
87
|
-
self.assertEqual(len(self.pbm_default.duals), 5)
|
|
88
|
-
self.assertEqual(len(self.pbm_default.penalties), 5)
|
|
89
|
-
self.assertEqual(self.pbm_default.param_groups[1]['mu'], 0.5)
|
|
90
|
-
self.assertEqual(self.pbm_default.param_groups[1]['pbf'], 'quadratic_reciprocal')
|
|
91
|
-
self.assertEqual(self.pbm_default.param_groups[1]['lr'], 0.1)
|
|
92
|
-
|
|
93
|
-
if __name__ == '__main__':
|
|
94
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
{humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/__init__.py
RENAMED
|
File without changes
|
{humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/__init__.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
|
|
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
|
{humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/src/humancompatible/train/optim/ssw.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{humancompatible_train-0.3.0 → humancompatible_train-0.3.0.1}/tests/test_balanced_batch_sampler.py
RENAMED
|
File without changes
|