humancompatible-train 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {humancompatible_train-0.3.2/src/humancompatible_train.egg-info → humancompatible_train-0.4.0}/PKG-INFO +24 -5
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/README.md +2 -2
- humancompatible_train-0.4.0/pyproject.toml +56 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible/train/dual_optim/__init__.py +3 -0
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/alm.py +333 -0
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/base.py +502 -0
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/ialm.py +248 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible/train/dual_optim/moreau.py +11 -3
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/nupi.py +231 -0
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/pbm.py +505 -0
- humancompatible_train-0.4.0/src/humancompatible/train/dual_optim/ssg.py +171 -0
- humancompatible_train-0.4.0/src/humancompatible/train/fairness/utils/balanced_batch_sampler.py +142 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/__init__.py +1 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/__init__.py +28 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/ghost_sqp.py +103 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/mlmc.py +74 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/oracle.py +75 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/sampler.py +54 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/ghost/subproblems.py +75 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/__init__.py +12 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/direction.py +421 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/inverse_hessian.py +276 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/line_search.py +156 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/optimizer.py +442 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/point_set.py +79 -0
- humancompatible_train-0.4.0/src/humancompatible/train/sqp/nonopt/qp.py +111 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0/src/humancompatible_train.egg-info}/PKG-INFO +24 -5
- humancompatible_train-0.4.0/src/humancompatible_train.egg-info/SOURCES.txt +49 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible_train.egg-info/requires.txt +9 -1
- humancompatible_train-0.4.0/tests/test_alm.py +454 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/tests/test_balanced_batch_sampler.py +108 -0
- humancompatible_train-0.4.0/tests/test_cooper_parity.py +159 -0
- humancompatible_train-0.4.0/tests/test_distributed.py +64 -0
- humancompatible_train-0.4.0/tests/test_dual_optimizer_base.py +331 -0
- humancompatible_train-0.4.0/tests/test_fairness_problems.py +212 -0
- humancompatible_train-0.4.0/tests/test_ghost.py +86 -0
- humancompatible_train-0.4.0/tests/test_golden.py +90 -0
- humancompatible_train-0.4.0/tests/test_ialm.py +95 -0
- humancompatible_train-0.4.0/tests/test_llm_gates.py +461 -0
- humancompatible_train-0.4.0/tests/test_nonopt.py +265 -0
- humancompatible_train-0.4.0/tests/test_nupi.py +146 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/tests/test_pbm.py +47 -47
- humancompatible_train-0.4.0/tests/test_ssg.py +143 -0
- humancompatible_train-0.3.2/pyproject.toml +0 -29
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/Algorithm.py +0 -25
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/__init__.py +0 -8
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/ghost.py +0 -254
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/optim_wrapper.py +0 -184
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/sgd.py +0 -114
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/ssl_alm.py +0 -317
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/switching_subgradient.py +0 -199
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/algorithms/utils.py +0 -61
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/constraints/__init__.py +0 -15
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/constraints/constraint.py +0 -114
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/constraints/constraint_fns.py +0 -251
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/constraints/torch/__init__.py +0 -1
- humancompatible_train-0.3.2/src/humancompatible/train/benchmark/constraints/torch/constraints.py +0 -48
- humancompatible_train-0.3.2/src/humancompatible/train/dual_optim/alm.py +0 -369
- humancompatible_train-0.3.2/src/humancompatible/train/dual_optim/ialm.py +0 -330
- humancompatible_train-0.3.2/src/humancompatible/train/dual_optim/nupi.py +0 -357
- humancompatible_train-0.3.2/src/humancompatible/train/dual_optim/pbm.py +0 -503
- humancompatible_train-0.3.2/src/humancompatible/train/fairness/__init__.py +0 -0
- humancompatible_train-0.3.2/src/humancompatible/train/fairness/utils/balanced_batch_sampler.py +0 -126
- humancompatible_train-0.3.2/src/humancompatible/train/optim/PBM.py +0 -571
- humancompatible_train-0.3.2/src/humancompatible/train/optim/__init__.py +0 -5
- humancompatible_train-0.3.2/src/humancompatible/train/optim/barrier.py +0 -158
- humancompatible_train-0.3.2/src/humancompatible/train/optim/ssl_alm.py +0 -236
- humancompatible_train-0.3.2/src/humancompatible/train/optim/ssl_alm_adam.py +0 -347
- humancompatible_train-0.3.2/src/humancompatible/train/optim/ssl_alm_adam_moment.py +0 -315
- humancompatible_train-0.3.2/src/humancompatible/train/optim/ssw.py +0 -156
- humancompatible_train-0.3.2/src/humancompatible/train/optim/ssw_barrier.py +0 -236
- humancompatible_train-0.3.2/src/humancompatible_train.egg-info/SOURCES.txt +0 -44
- humancompatible_train-0.3.2/tests/test_alm.py +0 -90
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/LICENCE.txt +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/setup.cfg +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible/train/__init__.py +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible/train/dual_optim/barrier.py +0 -0
- {humancompatible_train-0.3.2/src/humancompatible/train/benchmark → humancompatible_train-0.4.0/src/humancompatible/train/fairness}/__init__.py +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible/train/fairness/utils/__init__.py +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible_train.egg-info/dependency_links.txt +0 -0
- {humancompatible_train-0.3.2 → humancompatible_train-0.4.0}/src/humancompatible_train.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: humancompatible-train
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: PyTorch-based package for constrained training of neural networks
|
|
5
|
-
Author:
|
|
5
|
+
Author: Adam Bosak, Gilles Bareilles, Jana Lepsova, Jakub Marecek
|
|
6
6
|
Author-email: Andrii Kliachkin <kliacand@fel.cvut.cz>
|
|
7
7
|
Maintainer-email: Andrii Kliachkin <kliacand@fel.cvut.cz>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Project-URL: Homepage, https://github.com/humancompatible/train
|
|
10
|
+
Project-URL: Repository, https://github.com/humancompatible/train
|
|
11
|
+
Project-URL: Issues, https://github.com/humancompatible/train/issues
|
|
12
|
+
Project-URL: Documentation, https://humancompatible-train.readthedocs.io
|
|
13
|
+
Keywords: constrained-optimization,pytorch,fairness,lagrangian,machine-learning
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
8
21
|
Requires-Python: >=3.11
|
|
9
22
|
Description-Content-Type: text/markdown
|
|
10
23
|
License-File: LICENCE.txt
|
|
@@ -23,16 +36,22 @@ Requires-Dist: matplotlib; extra == "benchmark"
|
|
|
23
36
|
Requires-Dist: pandas; extra == "benchmark"
|
|
24
37
|
Requires-Dist: folktables; extra == "benchmark"
|
|
25
38
|
Requires-Dist: pot; extra == "benchmark"
|
|
26
|
-
Requires-Dist: hydra; extra == "benchmark"
|
|
39
|
+
Requires-Dist: hydra-core; extra == "benchmark"
|
|
27
40
|
Requires-Dist: omegaconf; extra == "benchmark"
|
|
41
|
+
Provides-Extra: paper
|
|
42
|
+
Requires-Dist: hydra-core; extra == "paper"
|
|
43
|
+
Requires-Dist: omegaconf; extra == "paper"
|
|
44
|
+
Requires-Dist: hydra-submitit-launcher; extra == "paper"
|
|
28
45
|
Provides-Extra: ghost
|
|
29
46
|
Requires-Dist: qpsolvers; extra == "ghost"
|
|
30
47
|
Requires-Dist: scipy; extra == "ghost"
|
|
48
|
+
Provides-Extra: compare
|
|
49
|
+
Requires-Dist: cooper-optim>=1.0.1; extra == "compare"
|
|
31
50
|
Dynamic: license-file
|
|
32
51
|
|
|
33
52
|
# humancompatible-train: a package for constrained machine learning
|
|
34
53
|
|
|
35
|
-
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml)
|
|
54
|
+
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml) [](https://humancompatible-train.readthedocs.io/en/latest/?badge=latest)
|
|
36
55
|
|
|
37
56
|
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
38
57
|
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
@@ -61,7 +80,7 @@ The only dependencies of this package are `numpy` and `torch`.
|
|
|
61
80
|
|
|
62
81
|
## Using the toolkit
|
|
63
82
|
|
|
64
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
83
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch. For the documentation, please visit [our Read the Docs page!](https://humancompatible-train.readthedocs.io?version=latest)
|
|
65
84
|
|
|
66
85
|
The algorithms are intended for use in tandem with classic PyTorch optimizers, calculating the Lagrangian and keeping track of the dual variables.
|
|
67
86
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# humancompatible-train: a package for constrained machine learning
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml)
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/humancompatible/train/actions/workflows/setup.yml) [](https://humancompatible-train.readthedocs.io/en/latest/?badge=latest)
|
|
4
4
|
|
|
5
5
|
The toolkit implements algorithms for constrained training of neural networks based on PyTorch, and inspired by PyTorch's API.
|
|
6
6
|
<!-- , as well as a tool to compare stochastic-constrained stochastic optimization algorithms on a _fair learning_ task in the `experiments` folder. -->
|
|
@@ -29,7 +29,7 @@ The only dependencies of this package are `numpy` and `torch`.
|
|
|
29
29
|
|
|
30
30
|
## Using the toolkit
|
|
31
31
|
|
|
32
|
-
The toolkit implements algorithms for constrained training of neural networks based on PyTorch.
|
|
32
|
+
The toolkit implements algorithms for constrained training of neural networks based on PyTorch. For the documentation, please visit [our Read the Docs page!](https://humancompatible-train.readthedocs.io?version=latest)
|
|
33
33
|
|
|
34
34
|
The algorithms are intended for use in tandem with classic PyTorch optimizers, calculating the Lagrangian and keeping track of the dual variables.
|
|
35
35
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "humancompatible-train"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"torch",
|
|
10
|
+
"numpy",
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">= 3.11"
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Andrii Kliachkin", email = "kliacand@fel.cvut.cz"},
|
|
15
|
+
{name = "Adam Bosak"},
|
|
16
|
+
{name = "Gilles Bareilles"},
|
|
17
|
+
{name = "Jana Lepsova"},
|
|
18
|
+
{name = "Jakub Marecek"},
|
|
19
|
+
]
|
|
20
|
+
maintainers = [
|
|
21
|
+
{name = "Andrii Kliachkin", email = "kliacand@fel.cvut.cz"}
|
|
22
|
+
]
|
|
23
|
+
description = "PyTorch-based package for constrained training of neural networks"
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
license = "Apache-2.0"
|
|
26
|
+
keywords = ["constrained-optimization", "pytorch", "fairness", "lagrangian", "machine-learning"]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 3 - Alpha",
|
|
29
|
+
"Intended Audience :: Science/Research",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/humancompatible/train"
|
|
39
|
+
Repository = "https://github.com/humancompatible/train"
|
|
40
|
+
Issues = "https://github.com/humancompatible/train/issues"
|
|
41
|
+
Documentation = "https://humancompatible-train.readthedocs.io"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
examples = ["ipykernel", "ipympl", "fairret", "folktables", "scikit-learn", "matplotlib"]
|
|
45
|
+
# `hydra-core` is the distribution this repo imports. `hydra` on PyPI is an unrelated 2016
|
|
46
|
+
# project, which is what the lockfile had been resolving to (uv.lock pinned Hydra 2.5).
|
|
47
|
+
benchmark = ["fairret", "matplotlib", "pandas", "folktables", "pot", "hydra-core", "omegaconf"]
|
|
48
|
+
# The hyperparameter sweeps in paper/tune.py. The submitit launcher is what `-m
|
|
49
|
+
# hydra/launcher=slurm` needs; the basic sweeper covers the local case with no plugin.
|
|
50
|
+
# hydra-optuna-sweeper is deliberately absent: its 1.2.0 release requires optuna<3.0.0.
|
|
51
|
+
paper = ["hydra-core", "omegaconf", "hydra-submitit-launcher"]
|
|
52
|
+
ghost = ["qpsolvers", "scipy"]
|
|
53
|
+
# Cross-validation against another library (paper/e0/e_cooper.py,
|
|
54
|
+
# tests/test_cooper_parity.py). Pinned to a floor because E0e's claims are about
|
|
55
|
+
# specific update orderings in Cooper's optimizer classes.
|
|
56
|
+
compare = ["cooper-optim>=1.0.1"]
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.distributed as dist
|
|
3
|
+
from typing import Any, Optional, Tuple
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
|
|
6
|
+
from .base import DualOptimizer
|
|
7
|
+
|
|
8
|
+
# cite: Stochastic Smoothed Primal-Dual Algorithms for Nonconvex Optimization with Linear Inequality Constraints
|
|
9
|
+
# https://arxiv.org/pdf/2504.07607
|
|
10
|
+
|
|
11
|
+
AUGMENTATIONS = ("quadratic", "hpr")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ALM(DualOptimizer):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
m: int = None,
|
|
18
|
+
lr: float = 0.01,
|
|
19
|
+
init_duals: float | Tensor = None,
|
|
20
|
+
penalty: float = 1.0,
|
|
21
|
+
*,
|
|
22
|
+
dual_range: Tuple[float, float] = (-100.0, 100.0),
|
|
23
|
+
momentum: float = 0.0,
|
|
24
|
+
dampening: Optional[float] = None,
|
|
25
|
+
is_ineq: bool = False,
|
|
26
|
+
restart: bool = False,
|
|
27
|
+
augmentation: str = "hpr",
|
|
28
|
+
device=None,
|
|
29
|
+
process_group: Optional[dist.ProcessGroup] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
|
|
32
|
+
if augmentation not in AUGMENTATIONS:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"Unknown augmentation: {augmentation!r}; expected one of "
|
|
35
|
+
f"{AUGMENTATIONS}"
|
|
36
|
+
)
|
|
37
|
+
if augmentation == "hpr" and penalty <= 0:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"The 'hpr' augmentation divides by the penalty, so it requires "
|
|
40
|
+
f"penalty > 0; got {penalty}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.penalty = penalty
|
|
44
|
+
self.augmentation = augmentation
|
|
45
|
+
params, settings = self._make_group(
|
|
46
|
+
m, lr, momentum, dampening, init_duals, dual_range, is_ineq, restart, device
|
|
47
|
+
)
|
|
48
|
+
super().__init__(
|
|
49
|
+
[{"params": params, **settings}],
|
|
50
|
+
self._scalar_defaults(settings),
|
|
51
|
+
process_group=process_group,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def _make_group(
|
|
56
|
+
cls,
|
|
57
|
+
m: int = None,
|
|
58
|
+
lr: float = None,
|
|
59
|
+
momentum: float = None,
|
|
60
|
+
dampening: float = None,
|
|
61
|
+
init_duals: float | Tensor = None,
|
|
62
|
+
dual_range: Tuple[float, float] = None,
|
|
63
|
+
is_ineq: bool = None,
|
|
64
|
+
restart: bool = None,
|
|
65
|
+
device=None,
|
|
66
|
+
):
|
|
67
|
+
if momentum is not None and (momentum < 0 or momentum > 1):
|
|
68
|
+
raise ValueError(f"momentum must be within [0,1]; got {momentum}")
|
|
69
|
+
|
|
70
|
+
# Default dampening to momentum (EMA) when unset and momentum > 0; else 0.
|
|
71
|
+
if dampening is None:
|
|
72
|
+
dampening = momentum if (momentum is not None and momentum > 0) else 0.0
|
|
73
|
+
|
|
74
|
+
if not isinstance(restart, bool):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Expected a Boolean value for restart, got {type(restart)}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
duals, settings = cls._base_group(
|
|
80
|
+
m, init_duals, dual_range, is_ineq, device
|
|
81
|
+
)
|
|
82
|
+
settings.update(
|
|
83
|
+
{
|
|
84
|
+
"lr": lr,
|
|
85
|
+
"momentum": momentum,
|
|
86
|
+
"dampening": dampening,
|
|
87
|
+
"momentum_buffer": torch.zeros_like(
|
|
88
|
+
duals.data, requires_grad=False, device=device
|
|
89
|
+
),
|
|
90
|
+
"restart": restart,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return [duals], cls._drop_none(settings)
|
|
94
|
+
|
|
95
|
+
def add_constraint_group(
|
|
96
|
+
self,
|
|
97
|
+
m: int,
|
|
98
|
+
lr: float = None,
|
|
99
|
+
momentum: float = None,
|
|
100
|
+
dampening: Optional[float] = None,
|
|
101
|
+
init_duals: Tensor = None,
|
|
102
|
+
dual_range: tuple[float, float] = None,
|
|
103
|
+
is_ineq: bool = False,
|
|
104
|
+
restart: bool = False,
|
|
105
|
+
device = None,
|
|
106
|
+
*,
|
|
107
|
+
name: str = None,
|
|
108
|
+
bound: float = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Allows to add a group of dual variables with separate initial values and learning rates.
|
|
112
|
+
|
|
113
|
+
:param m: Size of group (number of dual variables to add)
|
|
114
|
+
:type m: int
|
|
115
|
+
:param lr: Dual variable update rate.
|
|
116
|
+
:type lr: float
|
|
117
|
+
:param momentum: Momentum/Smoothing factor for dual variables. Equivalent to SGD momentum. Set to `0` to disable.
|
|
118
|
+
:type momentum: float
|
|
119
|
+
:param dampening: Dampening for momentum. Equivalent to SGD dampening. Set to `0` to disable.
|
|
120
|
+
:type dampening: float
|
|
121
|
+
:param init_duals: Initial values for the new dual variables. Defaults to the value set when creating the optimizer.
|
|
122
|
+
:type init_duals: Tensor
|
|
123
|
+
:param dual_range: After each dual update, the dual variables will be clamped to this range.
|
|
124
|
+
:type dual_range: Tuple[float, float]
|
|
125
|
+
:param is_ineq: Whether to treat the constraints as equality or inequality. If`True`, dual variables will be relaxed on strict satisfaction and lower-bounded by `max(dual_range[0], 0)`.
|
|
126
|
+
:type is_ineq: bool
|
|
127
|
+
:param restart: Whether to set the dual variables to zero immediately on strict satisfaction of corresponding constraints. Not recommended for stochastic constraints.
|
|
128
|
+
:type restart: bool
|
|
129
|
+
:param name: Name for this group, used when passing constraints as a mapping. Defaults to `group<k>`.
|
|
130
|
+
:type name: str
|
|
131
|
+
:param bound: Right-hand side of this group's constraints, if any. Only used by :meth:`violation`.
|
|
132
|
+
:type bound: float
|
|
133
|
+
|
|
134
|
+
.. note::
|
|
135
|
+
Parameters here will default to values set when initializing the dual optimizer.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
params, settings = self._make_group(
|
|
139
|
+
m, lr, momentum, dampening, init_duals, dual_range, is_ineq, restart, device
|
|
140
|
+
)
|
|
141
|
+
if bound is not None:
|
|
142
|
+
settings["bound"] = bound
|
|
143
|
+
if name is not None:
|
|
144
|
+
settings["name"] = name
|
|
145
|
+
self.add_param_group({"params": params, **settings})
|
|
146
|
+
|
|
147
|
+
# ------------------------------------------------------------------ #
|
|
148
|
+
# hooks
|
|
149
|
+
# ------------------------------------------------------------------ #
|
|
150
|
+
|
|
151
|
+
def _ascent_direction(self, group: dict[str, Any], c: Tensor) -> Tensor:
|
|
152
|
+
"""This group's dual ascent direction, i.e. the surrogate's gradient in the duals.
|
|
153
|
+
"""
|
|
154
|
+
if self.augmentation == "quadratic" or not group.get("is_ineq"):
|
|
155
|
+
return c
|
|
156
|
+
duals = group["params"][0]
|
|
157
|
+
return (torch.clamp(duals + self.penalty * c, min=0.0) - duals) / self.penalty
|
|
158
|
+
|
|
159
|
+
def _dual_update(self, group: dict[str, Any], c: Tensor) -> None:
|
|
160
|
+
momentum = group.get("momentum", 0.0)
|
|
161
|
+
buffer = group["momentum_buffer"]
|
|
162
|
+
|
|
163
|
+
d = self._ascent_direction(group, c)
|
|
164
|
+
|
|
165
|
+
if momentum > 0:
|
|
166
|
+
_update_c_buffers(d, momentum, group.get("dampening", 0.0), buffer)
|
|
167
|
+
|
|
168
|
+
_update_duals(
|
|
169
|
+
group["params"][0],
|
|
170
|
+
buffer if momentum > 0 else d,
|
|
171
|
+
group["lr"],
|
|
172
|
+
group.get("restart"),
|
|
173
|
+
raw_constraints=c,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def _add_constraint_contributions(
|
|
177
|
+
self, lagrangian: Tensor, group: dict[str, Any], snapshot: Any, c: Tensor
|
|
178
|
+
) -> None:
|
|
179
|
+
if self.augmentation == "quadratic":
|
|
180
|
+
lagrangian.add_(snapshot @ c)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
rho = self.penalty
|
|
184
|
+
if group.get("is_ineq"):
|
|
185
|
+
with torch.no_grad():
|
|
186
|
+
lam_star = torch.clamp(snapshot + rho * c, min=0.0)
|
|
187
|
+
step = lam_star - snapshot
|
|
188
|
+
lagrangian.add_(lam_star @ c)
|
|
189
|
+
lagrangian.add_(step.dot(step), alpha=-1.0 / (2 * rho))
|
|
190
|
+
else:
|
|
191
|
+
lagrangian.add_(snapshot @ c)
|
|
192
|
+
lagrangian.add_(0.5 * rho * torch.dot(c, c))
|
|
193
|
+
|
|
194
|
+
def _add_global_terms(self, lagrangian: Tensor, constraints: Tensor) -> None:
|
|
195
|
+
# Under HPR the quadratic already sits inside each group's term.
|
|
196
|
+
if self.augmentation == "hpr" or self.penalty == 0:
|
|
197
|
+
return
|
|
198
|
+
# Violations only for inequality groups; see _penalty_constraints.
|
|
199
|
+
c = self._penalty_constraints(constraints)
|
|
200
|
+
lagrangian.add_(0.5 * self.penalty * torch.dot(c, c))
|
|
201
|
+
|
|
202
|
+
def _extra_state(self) -> dict[str, Any]:
|
|
203
|
+
return {"penalty": self.penalty, "augmentation": self.augmentation}
|
|
204
|
+
|
|
205
|
+
def _load_extra_state(self, state: dict[str, Any]) -> None:
|
|
206
|
+
self.penalty = state["penalty"]
|
|
207
|
+
# Checkpoints written before the augmentation option lack the key.
|
|
208
|
+
self.augmentation = state.get("augmentation", "quadratic")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _update_c_buffers(
|
|
212
|
+
constraints: Tensor,
|
|
213
|
+
momentum: float,
|
|
214
|
+
dampening: float,
|
|
215
|
+
buffer: Tensor,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Update the constraint buffer with momentum."""
|
|
218
|
+
buffer.mul_(momentum).add_(constraints, alpha=1 - dampening)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _update_duals(
|
|
222
|
+
duals: Tensor,
|
|
223
|
+
buffer: Tensor,
|
|
224
|
+
lr: float,
|
|
225
|
+
restart: bool,
|
|
226
|
+
raw_constraints: Tensor = None,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Update duals using the buffered constraint gradients."""
|
|
229
|
+
duals.add_(buffer, alpha=lr)
|
|
230
|
+
if restart:
|
|
231
|
+
# Use raw constraints (not the EMA buffer) to check satisfaction.
|
|
232
|
+
check = raw_constraints if raw_constraints is not None else buffer
|
|
233
|
+
duals.masked_fill_(check < 0, 0.0)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
ALM.__doc__ = (
|
|
238
|
+
|
|
239
|
+
# \textbf{input}: \gamma \text{ (lr) }, \pmb{\lambda}_t \text{ (dual variables, created by method) }, \\
|
|
240
|
+
# \mathbf{c}(\theta) \text{ (constraints) }, f(\theta) \text{ (objective) }, \rho \text{ (penalty coefficient) } \\
|
|
241
|
+
r"""
|
|
242
|
+
A Dual Optimizer that works on the dual maximization tasks according to the (Augmented) Lagrangian rule. Creates and updates dual variables. Reference: https://doi.org/10.48550/arXiv.2504.07607
|
|
243
|
+
|
|
244
|
+
.. math::
|
|
245
|
+
|
|
246
|
+
\pmb{\lambda}_{t+1} & \leftarrow \pmb{\lambda}_t + \gamma \mathbf{c}_t(\theta_{t})
|
|
247
|
+
|
|
248
|
+
\mathcal{L}_{t+1} & \leftarrow f_t(\theta_{t}) + \pmb{\lambda}_{t+1}^T \mathbf{c}_t(\theta_{t}) + \frac{\rho}{2} \| \mathbf{c}_t(\theta_{t}) \|^2_2
|
|
249
|
+
|
|
250
|
+
For constraint groups registered with ``is_ineq=True`` the quadratic term acts
|
|
251
|
+
on the violation, :math:`\frac{\rho}{2} \| [\mathbf{c}_t(\theta_t)]_+ \|^2_2`,
|
|
252
|
+
since penalising the raw value of an inequality constraint would also penalise
|
|
253
|
+
being strictly feasible. The linear term and the dual update always use the raw
|
|
254
|
+
values.
|
|
255
|
+
|
|
256
|
+
Setting ``augmentation="hpr"`` switches to the Hestenes--Powell--Rockafellar
|
|
257
|
+
augmentation, in which the linear and quadratic terms are replaced by a single
|
|
258
|
+
expression per group,
|
|
259
|
+
|
|
260
|
+
.. math::
|
|
261
|
+
|
|
262
|
+
\mathcal{L}_{t+1} \leftarrow f_t(\theta_t) + \frac{1}{2\rho} \left[
|
|
263
|
+
\big\| \big[ \pmb{\lambda}_{t+1} + \rho \, \mathbf{c}_t(\theta_t) \big]_+ \big\|^2_2
|
|
264
|
+
- \| \pmb{\lambda}_{t+1} \|^2_2 \right]
|
|
265
|
+
|
|
266
|
+
\pmb{\lambda}_{t+1} \leftarrow \left( 1 - \tfrac{\gamma}{\rho} \right) \pmb{\lambda}_t
|
|
267
|
+
+ \tfrac{\gamma}{\rho} \big[ \pmb{\lambda}_t + \rho \, \mathbf{c}_t(\theta_t) \big]_+
|
|
268
|
+
|
|
269
|
+
for inequality groups, the dual update again being gradient ascent on the
|
|
270
|
+
surrogate. Writing :math:`\sigma = \pmb{\lambda} + \rho \mathbf{c}` for the trial
|
|
271
|
+
multiplier, the whole difference from the default is where the clamp sits: the
|
|
272
|
+
weight the surrogate puts on :math:`\partial \mathbf{c} / \partial \theta` is
|
|
273
|
+
:math:`\max(\pmb{\lambda}, \sigma)` for ``"quadratic"`` but
|
|
274
|
+
:math:`\max(0, \sigma)` for ``"hpr"``. The default therefore never lets a
|
|
275
|
+
multiplier's pull on the primal step fall below :math:`\pmb{\lambda}` however
|
|
276
|
+
feasible the constraint is, whereas HPR switches that pull off entirely once
|
|
277
|
+
:math:`\sigma \le 0`. Three practical consequences:
|
|
278
|
+
|
|
279
|
+
* HPR is a **no-op for equality groups**, where
|
|
280
|
+
:math:`\frac{1}{2\rho}(\|\pmb{\mu} + \rho \mathbf{h}\|^2 - \|\pmb{\mu}\|^2)
|
|
281
|
+
= \pmb{\mu}^T \mathbf{h} + \frac{\rho}{2}\|\mathbf{h}\|^2` identically and the
|
|
282
|
+
dual update is unchanged.
|
|
283
|
+
* At ``lr == penalty`` both modes perform the *same* dual update
|
|
284
|
+
:math:`\pmb{\lambda}_{t+1} = [\pmb{\lambda}_t + \rho \mathbf{c}_t]_+` -- the
|
|
285
|
+
default reaches it via the non-negativity clamp -- so at that setting the choice
|
|
286
|
+
is purely a primal-side one. ``restart=True`` is also less necessary under HPR,
|
|
287
|
+
since an inactive multiplier stops influencing the primal step while still
|
|
288
|
+
nonzero.
|
|
289
|
+
* The clamp inside the dual update makes it a nonlinear function of the constraint
|
|
290
|
+
estimate, so with *stochastic* constraints HPR biases the multipliers upward
|
|
291
|
+
(Jensen), i.e. toward feasibility. The default's dual update is linear in the
|
|
292
|
+
estimate and carries no such bias.
|
|
293
|
+
|
|
294
|
+
:param m: Number of constraints (determines the number of dual variables to create)
|
|
295
|
+
:type m: int
|
|
296
|
+
:param lr: Dual variable update rate.
|
|
297
|
+
:type lr: float
|
|
298
|
+
:param init_duals: Initial values for the new dual variables. Defaults to 0 for all.
|
|
299
|
+
:type init_duals: float | Tensor
|
|
300
|
+
:param penalty: Augmented Lagrangian penalty parameter. Defaults to`1.`
|
|
301
|
+
:type penalty: float
|
|
302
|
+
:param dual_range: Safeguarding range for dual variables; they will be`clamp`-ed to this range.
|
|
303
|
+
:type dual_range: Tuple[float, float]
|
|
304
|
+
:param momentum: Momentum/Smoothing factor for dual variables. Equivalent to SGD momentum. Set to `0` to disable.
|
|
305
|
+
:type momentum: float
|
|
306
|
+
:param dampening: Dampening for momentum. Equivalent to SGD dampening. Set to `0` to disable.
|
|
307
|
+
:type dampening: float
|
|
308
|
+
:param is_ineq: Whether to treat the constraints as equality or inequality. If`True`, dual variables will be decreased on strict satisfaction and lower-bounded by `max(dual_range[0], 0)`.
|
|
309
|
+
:type is_ineq: bool
|
|
310
|
+
:param restart: Whether to set the dual variables to zero immediately on strict satisfaction of corresponding constraints. Not recommended for stochastic constraints.
|
|
311
|
+
:type restart: bool
|
|
312
|
+
:param ctol: Reserved for a constraint tolerance allowing tiny violations to account for noise. Accepted for API stability but **currently unused** by the dual update.
|
|
313
|
+
:type ctol: float
|
|
314
|
+
:param augmentation: Which augmentation to form, ``"quadratic"`` (default) or ``"hpr"``. The latter requires `penalty > 0` and, on inequality groups, replaces the linear-plus-quadratic terms by the Hestenes-Powell-Rockafellar expression above.
|
|
315
|
+
:type augmentation: str
|
|
316
|
+
:param process_group: Distributed process group for DDP. When set, constraint values are averaged across all workers via ``dist.all_reduce`` before each dual update, keeping dual variables consistent across replicas. Defaults to ``None`` (no synchronization).
|
|
317
|
+
:type process_group: dist.ProcessGroup, optional
|
|
318
|
+
|
|
319
|
+
.. note::
|
|
320
|
+
Constraint values may be passed to :meth:`forward` / :meth:`update` /
|
|
321
|
+
:meth:`forward_update` as a flat tensor, as one tensor per constraint
|
|
322
|
+
group, or as a mapping from group name to tensor. See
|
|
323
|
+
:meth:`~humancompatible.train.dual_optim.base.DualOptimizer._gather_constraints`.
|
|
324
|
+
|
|
325
|
+
.. note::
|
|
326
|
+
The HPR term also exists in this module as
|
|
327
|
+
:func:`~humancompatible.train.dual_optim.barrier.augmented_lagrangian`, which
|
|
328
|
+
:class:`~.pbm.PBM` reproduces with ``pbf="augmented_lagrangian"`` and
|
|
329
|
+
per-coordinate penalties :math:`p = \lambda / \rho`. That parametrisation is not
|
|
330
|
+
reused here because it divides by :math:`\lambda` and so breaks at
|
|
331
|
+
:math:`\lambda = 0`.
|
|
332
|
+
"""
|
|
333
|
+
)
|