optax 0.2.0__tar.gz → 0.2.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.
- {optax-0.2.0 → optax-0.2.2}/PKG-INFO +3 -2
- {optax-0.2.0 → optax-0.2.2}/optax/__init__.py +5 -1
- {optax-0.2.0 → optax-0.2.2}/optax/_src/alias.py +136 -11
- {optax-0.2.0 → optax-0.2.2}/optax/_src/alias_test.py +23 -13
- {optax-0.2.0 → optax-0.2.2}/optax/_src/combine.py +23 -8
- {optax-0.2.0 → optax-0.2.2}/optax/_src/linear_algebra.py +98 -42
- optax-0.2.2/optax/_src/linear_algebra_test.py +217 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/linesearch.py +109 -91
- {optax-0.2.0 → optax-0.2.2}/optax/_src/linesearch_test.py +1 -1
- {optax-0.2.0 → optax-0.2.2}/optax/_src/transform.py +53 -11
- {optax-0.2.0 → optax-0.2.2}/optax/_src/transform_test.py +30 -2
- {optax-0.2.0 → optax-0.2.2}/optax/_src/utils.py +7 -76
- {optax-0.2.0 → optax-0.2.2}/optax/_src/utils_test.py +3 -40
- optax-0.2.2/optax/contrib/__init__.py +35 -0
- optax-0.2.0/optax/contrib/complex_valued_test.py → optax-0.2.2/optax/contrib/_complex_valued_test.py +3 -3
- optax-0.2.0/optax/contrib/mechanic_test.py → optax-0.2.2/optax/contrib/_mechanic_test.py +3 -3
- optax-0.2.0/optax/contrib/privacy_test.py → optax-0.2.2/optax/contrib/_privacy_test.py +6 -6
- optax-0.2.0/optax/contrib/reduce_on_plateau.py → optax-0.2.2/optax/contrib/_reduce_on_plateau.py +68 -38
- optax-0.2.0/optax/contrib/reduce_on_plateau_test.py → optax-0.2.2/optax/contrib/_reduce_on_plateau_test.py +45 -39
- optax-0.2.0/optax/contrib/sam_test.py → optax-0.2.2/optax/contrib/_sam_test.py +5 -5
- {optax-0.2.0 → optax-0.2.2}/optax/losses/__init__.py +1 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_classification.py +69 -3
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_classification_test.py +43 -2
- optax-0.2.2/optax/losses/_fenchel_young.py +63 -0
- optax-0.2.2/optax/losses/_fenchel_young_test.py +63 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_regression.py +5 -1
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_inject.py +2 -2
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_schedule.py +15 -3
- {optax-0.2.0 → optax-0.2.2}/optax/tree_utils/__init__.py +6 -0
- optax-0.2.2/optax/tree_utils/_state_utils.py +786 -0
- optax-0.2.2/optax/tree_utils/_state_utils_test.py +599 -0
- {optax-0.2.0 → optax-0.2.2}/optax/tree_utils/_tree_math.py +5 -0
- {optax-0.2.0 → optax-0.2.2}/pyproject.toml +2 -1
- optax-0.2.0/optax/_src/linear_algebra_test.py +0 -62
- optax-0.2.0/optax/contrib/__init__.py +0 -35
- optax-0.2.0/optax/tree_utils/_state_utils.py +0 -119
- optax-0.2.0/optax/tree_utils/_state_utils_test.py +0 -273
- {optax-0.2.0 → optax-0.2.2}/LICENSE +0 -0
- {optax-0.2.0 → optax-0.2.2}/README.md +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/base.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/base_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/clipping.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/clipping_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/combine_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/constrain.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/constrain_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/factorized.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/factorized_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/float64_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/lookahead.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/lookahead_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/numerics.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/numerics_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/schedule.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/update.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/update_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/wrappers.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/_src/wrappers_test.py +0 -0
- /optax-0.2.0/optax/contrib/cocob.py → /optax-0.2.2/optax/contrib/_cocob.py +0 -0
- /optax-0.2.0/optax/contrib/common_test.py → /optax-0.2.2/optax/contrib/_common_test.py +0 -0
- /optax-0.2.0/optax/contrib/complex_valued.py → /optax-0.2.2/optax/contrib/_complex_valued.py +0 -0
- /optax-0.2.0/optax/contrib/dadapt_adamw.py → /optax-0.2.2/optax/contrib/_dadapt_adamw.py +0 -0
- /optax-0.2.0/optax/contrib/mechanic.py → /optax-0.2.2/optax/contrib/_mechanic.py +0 -0
- /optax-0.2.0/optax/contrib/privacy.py → /optax-0.2.2/optax/contrib/_privacy.py +0 -0
- /optax-0.2.0/optax/contrib/prodigy.py → /optax-0.2.2/optax/contrib/_prodigy.py +0 -0
- /optax-0.2.0/optax/contrib/sam.py → /optax-0.2.2/optax/contrib/_sam.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_ranking.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_ranking_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_regression_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_smoothing.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/losses/_smoothing_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/__init__.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/control_variates.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/control_variates_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/stochastic_gradient_estimators.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/stochastic_gradient_estimators_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/optax_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/projections/__init__.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/projections/_projections.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/projections/_projections_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/__init__.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_inject_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_join.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_join_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/_schedule_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/schedules/inject.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/second_order/__init__.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/second_order/_base.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/second_order/_fisher.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/second_order/_hessian.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/second_order/_hessian_test.py +0 -0
- {optax-0.2.0 → optax-0.2.2}/optax/tree_utils/_tree_math_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: optax
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A gradient processing and optimisation library in JAX.
|
|
5
5
|
Keywords: python,machine learning,reinforcement-learning
|
|
6
6
|
Author-email: Google DeepMind <optax-dev@google.com>
|
|
@@ -17,7 +17,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Dist: absl-py>=0.7.1
|
|
20
|
-
Requires-Dist: chex>=0.1.
|
|
20
|
+
Requires-Dist: chex>=0.1.86
|
|
21
21
|
Requires-Dist: jax>=0.1.55
|
|
22
22
|
Requires-Dist: jaxlib>=0.1.37
|
|
23
23
|
Requires-Dist: numpy>=1.18.0
|
|
@@ -33,6 +33,7 @@ Requires-Dist: sphinx-collections>=0.0.1 ; extra == "docs"
|
|
|
33
33
|
Requires-Dist: tensorflow>=2.4.0 ; extra == "docs"
|
|
34
34
|
Requires-Dist: tensorflow-datasets>=4.2.0 ; extra == "docs"
|
|
35
35
|
Requires-Dist: flax ; extra == "docs"
|
|
36
|
+
Requires-Dist: sphinx_contributors ; extra == "docs"
|
|
36
37
|
Requires-Dist: absl-py>=1.0.0 ; extra == "dp-accounting"
|
|
37
38
|
Requires-Dist: attrs>=21.4.0 ; extra == "dp-accounting"
|
|
38
39
|
Requires-Dist: mpmath>=1.2.1 ; extra == "dp-accounting"
|
|
@@ -40,6 +40,7 @@ from optax._src.alias import nadamw
|
|
|
40
40
|
from optax._src.alias import noisy_sgd
|
|
41
41
|
from optax._src.alias import novograd
|
|
42
42
|
from optax._src.alias import optimistic_gradient_descent
|
|
43
|
+
from optax._src.alias import polyak_sgd
|
|
43
44
|
from optax._src.alias import radam
|
|
44
45
|
from optax._src.alias import rmsprop
|
|
45
46
|
from optax._src.alias import rprop
|
|
@@ -115,6 +116,7 @@ from optax._src.transform import scale_by_novograd
|
|
|
115
116
|
from optax._src.transform import scale_by_optimistic_gradient
|
|
116
117
|
from optax._src.transform import scale_by_param_block_norm
|
|
117
118
|
from optax._src.transform import scale_by_param_block_rms
|
|
119
|
+
from optax._src.transform import scale_by_polyak
|
|
118
120
|
from optax._src.transform import scale_by_radam
|
|
119
121
|
from optax._src.transform import scale_by_rms
|
|
120
122
|
from optax._src.transform import scale_by_rprop
|
|
@@ -213,7 +215,7 @@ DifferentiallyPrivateAggregateState = (
|
|
|
213
215
|
)
|
|
214
216
|
dpsgd = contrib.dpsgd
|
|
215
217
|
|
|
216
|
-
__version__ = "0.2.
|
|
218
|
+
__version__ = "0.2.2"
|
|
217
219
|
|
|
218
220
|
__all__ = (
|
|
219
221
|
"adabelief",
|
|
@@ -308,6 +310,7 @@ __all__ = (
|
|
|
308
310
|
"piecewise_interpolate_schedule",
|
|
309
311
|
"polynomial_schedule",
|
|
310
312
|
"power_iteration",
|
|
313
|
+
"polyak_sgd",
|
|
311
314
|
"radam",
|
|
312
315
|
"rmsprop",
|
|
313
316
|
"rprop",
|
|
@@ -326,6 +329,7 @@ __all__ = (
|
|
|
326
329
|
"scale_by_novograd",
|
|
327
330
|
"scale_by_param_block_norm",
|
|
328
331
|
"scale_by_param_block_rms",
|
|
332
|
+
"scale_by_polyak",
|
|
329
333
|
"scale_by_radam",
|
|
330
334
|
"scale_by_rms",
|
|
331
335
|
"scale_by_rprop",
|
|
@@ -36,15 +36,43 @@ def adabelief(
|
|
|
36
36
|
b2: float = 0.999,
|
|
37
37
|
eps: float = 1e-16,
|
|
38
38
|
eps_root: float = 1e-16) -> base.GradientTransformation:
|
|
39
|
-
"""The AdaBelief optimizer.
|
|
39
|
+
r"""The AdaBelief optimizer.
|
|
40
40
|
|
|
41
41
|
AdaBelief is an adaptive learning rate optimizer that focuses on fast
|
|
42
42
|
convergence, generalization, and stability. It adapts the step size depending
|
|
43
43
|
on its "belief" in the gradient direction — the optimizer adaptively scales
|
|
44
44
|
the step size by the difference between the predicted and observed gradients.
|
|
45
|
-
AdaBelief is a modified version of
|
|
46
|
-
parameters.
|
|
47
|
-
|
|
45
|
+
AdaBelief is a modified version of :func:`optax.adam` and contains the same
|
|
46
|
+
number of parameters.
|
|
47
|
+
|
|
48
|
+
Let :math:`\alpha_t` represent the learning rate and :math:`\beta_1, \beta_2`,
|
|
49
|
+
:math:`\varepsilon`, :math:`\bar{\varepsilon}` represent the arguments
|
|
50
|
+
``b1``, ``b2``, ``eps`` and ``eps_root`` respectively. The learning rate is
|
|
51
|
+
indexed by :math:`t` since the learning rate may also be provided by a
|
|
52
|
+
schedule function.
|
|
53
|
+
|
|
54
|
+
The ``init`` function of this optimizer initializes an internal state
|
|
55
|
+
:math:`S_0 := (m_0, s_0) = (0, 0)`, representing initial estimates for the
|
|
56
|
+
first and second moments. In practice these values are stored as pytrees
|
|
57
|
+
containing all zeros, with the same shape as the model updates.
|
|
58
|
+
At step :math:`t`, the ``update`` function of this optimizer takes as
|
|
59
|
+
arguments the incoming gradients :math:`g_t` and optimizer state :math:`S_t`
|
|
60
|
+
and computes updates :math:`u_t` and new state :math:`S_{t+1}`. Thus, for
|
|
61
|
+
:math:`t > 0`, we have,
|
|
62
|
+
|
|
63
|
+
.. math::
|
|
64
|
+
|
|
65
|
+
\begin{align*}
|
|
66
|
+
m_t &\leftarrow \beta_1 \cdot m_{t-1} + (1-\beta_1) \cdot g_t \\
|
|
67
|
+
s_t &\leftarrow \beta_2 \cdot s_{t-1} + (1-\beta_2) \cdot (g_t - m_t)^2
|
|
68
|
+
+ \bar{\varepsilon} \\
|
|
69
|
+
\hat{m}_t &\leftarrow m_t / {(1-\beta_1^t)} \\
|
|
70
|
+
\hat{s}_t &\leftarrow s_t / {(1-\beta_2^t)} \\
|
|
71
|
+
u_t &\leftarrow -\alpha_t \cdot \hat{m}_t / \left(\sqrt{\hat{s}_t}
|
|
72
|
+
+ \varepsilon \right) \\
|
|
73
|
+
S_t &\leftarrow (m_t, s_t).
|
|
74
|
+
\end{align*}
|
|
75
|
+
|
|
48
76
|
Examples:
|
|
49
77
|
>>> import optax
|
|
50
78
|
>>> import jax
|
|
@@ -322,7 +350,7 @@ def adam(
|
|
|
322
350
|
|
|
323
351
|
Let :math:`\alpha_t` represent the learning rate and :math:`\beta_1, \beta_2`,
|
|
324
352
|
:math:`\varepsilon`, :math:`\bar{\varepsilon}` represent the arguments
|
|
325
|
-
``b1``, ``b2``, ``eps`` and ``eps_root``
|
|
353
|
+
``b1``, ``b2``, ``eps`` and ``eps_root`` respectively. The learning rate is
|
|
326
354
|
indexed by :math:`t` since the learning rate may also be provided by a
|
|
327
355
|
schedule function.
|
|
328
356
|
|
|
@@ -994,9 +1022,27 @@ def noisy_sgd(
|
|
|
994
1022
|
) -> base.GradientTransformation:
|
|
995
1023
|
r"""A variant of SGD with added noise.
|
|
996
1024
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1025
|
+
Noisy SGD is a variant of :func:`optax.sgd` that incorporates Gaussian noise
|
|
1026
|
+
into the updates. It has been found that adding noise to the gradients can
|
|
1027
|
+
improve both the training error and the generalization error in very deep
|
|
1028
|
+
networks.
|
|
1029
|
+
|
|
1030
|
+
The update :math:`u_t` is modified to include this noise as follows:
|
|
1031
|
+
|
|
1032
|
+
.. math::
|
|
1033
|
+
u_t \leftarrow -\alpha_t (g_t + N(0, \sigma_t^2)),
|
|
1034
|
+
|
|
1035
|
+
where :math:`N(0, \sigma_t^2)` represents Gaussian noise with zero mean and a
|
|
1036
|
+
variance of :math:`\sigma_t^2`.
|
|
1037
|
+
|
|
1038
|
+
The variance of this noise decays over time according to the formula
|
|
1039
|
+
|
|
1040
|
+
.. math::
|
|
1041
|
+
\sigma_t^2 = \frac{\eta}{(1+t)^\gamma},
|
|
1042
|
+
|
|
1043
|
+
where :math:`\gamma` is the decay rate parameter ``gamma`` and :math:`\eta`
|
|
1044
|
+
represents the initial variance ``eta``.
|
|
1045
|
+
|
|
1000
1046
|
Examples:
|
|
1001
1047
|
>>> import optax
|
|
1002
1048
|
>>> import jax
|
|
@@ -1025,8 +1071,8 @@ def noisy_sgd(
|
|
|
1025
1071
|
learning_rate: A global scaling factor, either fixed or evolving along
|
|
1026
1072
|
iterations with a scheduler, see :func:`optax.scale_by_learning_rate`.
|
|
1027
1073
|
eta: Initial variance for the Gaussian noise added to gradients.
|
|
1028
|
-
gamma: A parameter controlling the annealing of noise over time
|
|
1029
|
-
variance decays according to
|
|
1074
|
+
gamma: A parameter controlling the annealing of noise over time ``t``, the
|
|
1075
|
+
variance decays according to ``(1+t)**(-gamma)``.
|
|
1030
1076
|
seed: Seed for the pseudo-random generation process.
|
|
1031
1077
|
|
|
1032
1078
|
Returns:
|
|
@@ -1631,7 +1677,7 @@ def rprop(
|
|
|
1631
1677
|
gradient descent. It responds only to the sign of the gradient by increasing
|
|
1632
1678
|
or decreasing the step size selected per parameter exponentially to speed up
|
|
1633
1679
|
convergence and avoid oscillations.
|
|
1634
|
-
|
|
1680
|
+
|
|
1635
1681
|
Examples:
|
|
1636
1682
|
>>> import optax
|
|
1637
1683
|
>>> import jax
|
|
@@ -1684,3 +1730,82 @@ def rprop(
|
|
|
1684
1730
|
),
|
|
1685
1731
|
transform.scale(-1.0),
|
|
1686
1732
|
)
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
def polyak_sgd(
|
|
1736
|
+
max_learning_rate: float = 1.,
|
|
1737
|
+
scaling: base.ScalarOrSchedule = 1.,
|
|
1738
|
+
f_min: float = 0.0,
|
|
1739
|
+
eps: float = 0.0,
|
|
1740
|
+
) -> base.GradientTransformationExtraArgs:
|
|
1741
|
+
r"""SGD with Polyak step-size.
|
|
1742
|
+
|
|
1743
|
+
This solver implements the SGD with Polyak step size of (Loizou et al. 2021).
|
|
1744
|
+
It sets the step-size as
|
|
1745
|
+
|
|
1746
|
+
.. math::
|
|
1747
|
+
s \min\left\{\frac{f(x) - f^\star}{\|\nabla f(x)\|^2 + \epsilon},
|
|
1748
|
+
\gamma_{\max}\right\}\,,
|
|
1749
|
+
|
|
1750
|
+
where :math:`f` is the function from which a gradient is computed,
|
|
1751
|
+
:math:`\gamma_{\max}` is a maximal acceptable learning rate set by
|
|
1752
|
+
``max_learning_rate``, :math:`\epsilon` is a constant preventing division by
|
|
1753
|
+
zero set with ``eps``, :math:`s` scales the formula by ``scaling``, and
|
|
1754
|
+
:math:`f^\star` is a guess of the minimum value of the function set with
|
|
1755
|
+
``f_min``.
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
Examples:
|
|
1759
|
+
>>> import optax
|
|
1760
|
+
>>> import jax
|
|
1761
|
+
>>> import jax.numpy as jnp
|
|
1762
|
+
>>> def f(x): return jnp.sum(x ** 2) # simple quadratic function
|
|
1763
|
+
>>> solver = optax.polyak_sgd()
|
|
1764
|
+
>>> params = jnp.array([1., 2., 3.])
|
|
1765
|
+
>>> print('Objective function: ', f(params))
|
|
1766
|
+
Objective function: 14.0
|
|
1767
|
+
>>> opt_state = solver.init(params)
|
|
1768
|
+
>>> for _ in range(5):
|
|
1769
|
+
... value, grad = jax.value_and_grad(f)(params)
|
|
1770
|
+
... params, opt_state = solver.update(grad, opt_state, params, value=value)
|
|
1771
|
+
... print('Objective function: ', f(params))
|
|
1772
|
+
Objective function: 3.5
|
|
1773
|
+
Objective function: 0.875
|
|
1774
|
+
Objective function: 0.21875
|
|
1775
|
+
Objective function: 0.0546875
|
|
1776
|
+
Objective function: 0.013671875
|
|
1777
|
+
|
|
1778
|
+
.. warning::
|
|
1779
|
+
This method requires knowledge of an approximate value of the of the
|
|
1780
|
+
objective function minimum, passed through the ``f_min`` argument.
|
|
1781
|
+
For models that interpolate the data, this can be set to 0 (default
|
|
1782
|
+
value).
|
|
1783
|
+
Failing to set an appropriate value for ``f_min`` can lead to
|
|
1784
|
+
divergence or convergence to a suboptimal solution.
|
|
1785
|
+
|
|
1786
|
+
References:
|
|
1787
|
+
Loizou et al. `Stochastic polyak step-size for SGD: An adaptive learning
|
|
1788
|
+
rate for fast convergence <https://arxiv.org/abs/2002.10542>`_, 2021
|
|
1789
|
+
|
|
1790
|
+
Berrada et al., `Training neural networks for and by interpolation
|
|
1791
|
+
<https://arxiv.org/pdf/1906.05661.pdf>`_, 2020
|
|
1792
|
+
|
|
1793
|
+
Args:
|
|
1794
|
+
max_learning_rate: a maximum step size to use (defaults to 1).
|
|
1795
|
+
scaling: A global scaling factor, either fixed or evolving along
|
|
1796
|
+
iterations with a scheduler (defaults to 1).
|
|
1797
|
+
f_min: a lower bound on the objective function (defaults to 0). Corresponds
|
|
1798
|
+
to :math:`f^\star` in the formula above.
|
|
1799
|
+
eps: a value to add in the denominator of the update (defaults to 0).
|
|
1800
|
+
|
|
1801
|
+
Returns:
|
|
1802
|
+
A :class:`GradientTransformationExtraArgs`, where the ``update`` function
|
|
1803
|
+
takes an additional keyword argument ``value`` containing the current
|
|
1804
|
+
value of the objective function.
|
|
1805
|
+
"""
|
|
1806
|
+
return combine.chain(
|
|
1807
|
+
sgd(learning_rate=scaling),
|
|
1808
|
+
transform.scale_by_polyak(
|
|
1809
|
+
max_learning_rate=max_learning_rate, f_min=f_min, eps=eps
|
|
1810
|
+
),
|
|
1811
|
+
)
|
|
@@ -60,6 +60,7 @@ _OPTIMIZERS_UNDER_TEST = (
|
|
|
60
60
|
dict(opt_name='rprop', opt_kwargs=dict(learning_rate=1e-1)),
|
|
61
61
|
dict(opt_name='sm3', opt_kwargs=dict(learning_rate=1.0)),
|
|
62
62
|
dict(opt_name='yogi', opt_kwargs=dict(learning_rate=1e-1)),
|
|
63
|
+
dict(opt_name='polyak_sgd', opt_kwargs=dict(max_learning_rate=1.))
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
|
|
@@ -71,11 +72,10 @@ def _setup_parabola(dtype):
|
|
|
71
72
|
if jnp.iscomplexobj(dtype):
|
|
72
73
|
final_params *= 1 + 1j
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
def get_updates(params):
|
|
75
|
+
def objective(params):
|
|
76
76
|
return jnp.sum(numerics.abs_sq(params - final_params))
|
|
77
77
|
|
|
78
|
-
return initial_params, final_params,
|
|
78
|
+
return initial_params, final_params, objective
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def _setup_rosenbrock(dtype):
|
|
@@ -89,12 +89,11 @@ def _setup_rosenbrock(dtype):
|
|
|
89
89
|
initial_params = jnp.array([0.0, 0.0], dtype=dtype)
|
|
90
90
|
final_params = jnp.array([a, a**2], dtype=dtype)
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
def get_updates(params):
|
|
92
|
+
def objective(params):
|
|
94
93
|
return (numerics.abs_sq(a - params[0]) +
|
|
95
94
|
b * numerics.abs_sq(params[1] - params[0]**2))
|
|
96
95
|
|
|
97
|
-
return initial_params, final_params,
|
|
96
|
+
return initial_params, final_params, objective
|
|
98
97
|
|
|
99
98
|
|
|
100
99
|
class AliasTest(chex.TestCase):
|
|
@@ -113,21 +112,26 @@ class AliasTest(chex.TestCase):
|
|
|
113
112
|
'lion',
|
|
114
113
|
'rprop',
|
|
115
114
|
'adadelta',
|
|
115
|
+
'polyak_sgd',
|
|
116
116
|
) and jnp.iscomplexobj(dtype):
|
|
117
117
|
raise absltest.SkipTest(
|
|
118
118
|
f'{opt_name} does not support complex parameters.'
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
opt = getattr(alias, opt_name)(**opt_kwargs)
|
|
122
|
-
initial_params, final_params,
|
|
122
|
+
initial_params, final_params, objective = target(dtype)
|
|
123
123
|
|
|
124
124
|
@jax.jit
|
|
125
125
|
def step(params, state):
|
|
126
|
-
updates =
|
|
126
|
+
value, updates = jax.value_and_grad(objective)(params)
|
|
127
127
|
# Complex gradients need to be conjugated before being added to parameters
|
|
128
128
|
# https://gist.github.com/wdphy16/118aef6fb5f82c49790d7678cf87da29
|
|
129
129
|
updates = jax.tree_util.tree_map(lambda x: x.conj(), updates)
|
|
130
|
-
|
|
130
|
+
if opt_name == 'polyak_sgd':
|
|
131
|
+
update_kwargs = {'value': value}
|
|
132
|
+
else:
|
|
133
|
+
update_kwargs = {}
|
|
134
|
+
updates, state = opt.update(updates, state, params, **update_kwargs)
|
|
131
135
|
params = update.apply_updates(params, updates)
|
|
132
136
|
return params, state
|
|
133
137
|
|
|
@@ -146,13 +150,13 @@ class AliasTest(chex.TestCase):
|
|
|
146
150
|
def test_optimizers_can_be_wrapped_in_inject_hyperparams(
|
|
147
151
|
self, opt_name, opt_kwargs):
|
|
148
152
|
"""Checks that optimizers can be wrapped in inject_hyperparams."""
|
|
149
|
-
# See also https://github.com/deepmind/optax/issues/412.
|
|
153
|
+
# See also https://github.com/google-deepmind/optax/issues/412.
|
|
150
154
|
opt_factory = getattr(alias, opt_name)
|
|
151
155
|
opt = opt_factory(**opt_kwargs)
|
|
152
156
|
if opt_name == 'adafactor':
|
|
153
157
|
# Adafactor wrapped in inject_hyperparams currently needs a static
|
|
154
158
|
# argument to be specified in order to be jittable. See issue
|
|
155
|
-
# https://github.com/deepmind/optax/issues/412.
|
|
159
|
+
# https://github.com/google-deepmind/optax/issues/412.
|
|
156
160
|
opt_inject = _inject.inject_hyperparams(
|
|
157
161
|
opt_factory, static_args=('min_dim_size_to_factor',))(**opt_kwargs)
|
|
158
162
|
else:
|
|
@@ -162,11 +166,17 @@ class AliasTest(chex.TestCase):
|
|
|
162
166
|
grads = [jnp.ones((2, 3)), jnp.negative(jnp.ones((2, 5, 2)))]
|
|
163
167
|
|
|
164
168
|
state = self.variant(opt.init)(params)
|
|
165
|
-
|
|
169
|
+
if opt_name == 'polyak_sgd':
|
|
170
|
+
update_kwargs = {'value': jnp.array(0.)}
|
|
171
|
+
else:
|
|
172
|
+
update_kwargs = {}
|
|
173
|
+
updates, new_state = self.variant(opt.update)(
|
|
174
|
+
grads, state, params, **update_kwargs
|
|
175
|
+
)
|
|
166
176
|
|
|
167
177
|
state_inject = self.variant(opt_inject.init)(params)
|
|
168
178
|
updates_inject, new_state_inject = self.variant(opt_inject.update)(
|
|
169
|
-
grads, state_inject, params)
|
|
179
|
+
grads, state_inject, params, **update_kwargs)
|
|
170
180
|
|
|
171
181
|
with self.subTest('Equality of updates.'):
|
|
172
182
|
chex.assert_trees_all_close(updates_inject, updates, rtol=1e-4)
|
|
@@ -27,16 +27,30 @@ def chain(
|
|
|
27
27
|
) -> base.GradientTransformationExtraArgs:
|
|
28
28
|
"""Applies a list of chainable update transformations.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
This function creates a new :func:`optax.GradientTransformation` that applies
|
|
31
|
+
a sequence of gradient transformations in order. The ``init`` function of the
|
|
32
|
+
new transformation constructs the optimizer state by concatenating the states
|
|
33
|
+
of the individual transforms, while the ``update`` function applies the
|
|
34
|
+
updates in the given order.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
|
|
38
|
+
A transform that scales by -0.1 the adam update:
|
|
39
|
+
|
|
40
|
+
>>> import optax
|
|
41
|
+
>>> transform1 = optax.scale_by_adam()
|
|
42
|
+
>>> transform2 = optax.scale(-0.1)
|
|
43
|
+
>>> chained_transform = optax.chain(transform1, transform2)
|
|
44
|
+
>>> params = {'a': 1.0}
|
|
45
|
+
>>> state = chained_transform.init(params)
|
|
46
|
+
>>> updates = {'a': -0.5}
|
|
47
|
+
>>> updates, new_state = chained_transform.update(updates, state, params)
|
|
34
48
|
|
|
35
49
|
Args:
|
|
36
50
|
*args: a sequence of chainable (init_fn, update_fn) tuples.
|
|
37
51
|
|
|
38
52
|
Returns:
|
|
39
|
-
A
|
|
53
|
+
A :func:`GradientTransformationExtraArgs`, created by chaining the input
|
|
40
54
|
transformations. Note that independent of the argument types, the resulting
|
|
41
55
|
transformation always supports extra args. Any extra arguments passed to the
|
|
42
56
|
returned transformation will be passed only to those transformations in the
|
|
@@ -147,7 +161,7 @@ def multi_transform(
|
|
|
147
161
|
import jax.numpy as jnp
|
|
148
162
|
|
|
149
163
|
def map_nested_fn(fn):
|
|
150
|
-
'''Recursively apply `fn` to the key-value pairs of a nested dict'''
|
|
164
|
+
'''Recursively apply `fn` to the key-value pairs of a nested dict.'''
|
|
151
165
|
def map_fn(nested_dict):
|
|
152
166
|
return {k: (map_fn(v) if isinstance(v, dict) else fn(k, v))
|
|
153
167
|
for k, v in nested_dict.items()}
|
|
@@ -178,7 +192,7 @@ def multi_transform(
|
|
|
178
192
|
param_labels)
|
|
179
193
|
|
|
180
194
|
If you would like to not optimize some parameters, you may wrap
|
|
181
|
-
|
|
195
|
+
:func:`optax.multi_transform` with :func:`optax.masked`.
|
|
182
196
|
|
|
183
197
|
Args:
|
|
184
198
|
transforms: A mapping from labels to transformations. Each transformation
|
|
@@ -191,7 +205,8 @@ def multi_transform(
|
|
|
191
205
|
extra_arg fields with the same tree structure as params/updates.
|
|
192
206
|
|
|
193
207
|
Returns:
|
|
194
|
-
|
|
208
|
+
A :func:`optax.GradientTransformationExtraArgs` that implements an ``init``
|
|
209
|
+
and ``update`` function.
|
|
195
210
|
"""
|
|
196
211
|
|
|
197
212
|
transforms = {
|
|
@@ -14,70 +14,126 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Linear algebra utilities used in optimisation."""
|
|
16
16
|
|
|
17
|
+
import functools
|
|
18
|
+
from typing import Callable, Optional, Union
|
|
19
|
+
|
|
17
20
|
import chex
|
|
18
21
|
import jax
|
|
19
22
|
from jax import lax
|
|
20
23
|
import jax.numpy as jnp
|
|
21
|
-
import
|
|
22
|
-
|
|
24
|
+
from optax import tree_utils as otu
|
|
23
25
|
from optax._src import base
|
|
24
26
|
from optax._src import numerics
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def _normalize_tree(x):
|
|
30
|
+
# divide by the L2 norm of the tree weights.
|
|
31
|
+
return otu.tree_scalar_mul(1.0 / otu.tree_l2_norm(x), x)
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
def global_norm(updates: base.PyTree) -> chex.Array:
|
|
28
35
|
"""Compute the global norm across a nested structure of tensors."""
|
|
29
36
|
return jnp.sqrt(sum(
|
|
30
37
|
jnp.sum(numerics.abs_sq(x)) for x in jax.tree_util.tree_leaves(updates)))
|
|
31
38
|
|
|
32
39
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
def _power_iteration_cond_fun(error_tolerance, num_iters, loop_vars):
|
|
41
|
+
normalized_eigvec, unnormalized_eigvec, eig, iter_num = loop_vars
|
|
42
|
+
residual = otu.tree_sub(
|
|
43
|
+
unnormalized_eigvec, otu.tree_scalar_mul(eig, normalized_eigvec)
|
|
44
|
+
)
|
|
45
|
+
residual_norm = otu.tree_l2_norm(residual)
|
|
46
|
+
converged = jnp.abs(residual_norm / eig) < error_tolerance
|
|
47
|
+
return ~converged & (iter_num < num_iters)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def power_iteration(
|
|
51
|
+
matrix: Union[chex.Array, Callable[[chex.ArrayTree], chex.ArrayTree]],
|
|
52
|
+
*,
|
|
53
|
+
v0: Optional[chex.ArrayTree] = None,
|
|
54
|
+
num_iters: int = 100,
|
|
55
|
+
error_tolerance: float = 1e-6,
|
|
56
|
+
precision: lax.Precision = lax.Precision.HIGHEST,
|
|
57
|
+
key: Optional[chex.PRNGKey] = None,
|
|
58
|
+
) -> tuple[chex.Numeric, chex.ArrayTree]:
|
|
37
59
|
r"""Power iteration algorithm.
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
a
|
|
41
|
-
|
|
61
|
+
This algorithm computes the dominant eigenvalue and its associated eigenvector
|
|
62
|
+
of a diagonalizable matrix. This matrix can be given as an array or as a
|
|
63
|
+
callable implementing a matrix-vector product.
|
|
42
64
|
|
|
43
65
|
References:
|
|
44
|
-
|
|
66
|
+
Wikipedia contributors. `Power iteration
|
|
67
|
+
<https://en.wikipedia.org/w/index.php?tit0le=Power_iteration>`_.
|
|
45
68
|
|
|
46
69
|
Args:
|
|
47
|
-
matrix:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
a
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
matrix: a square matrix, either as an array or a callable implementing a
|
|
71
|
+
matrix-vector product.
|
|
72
|
+
v0: initial vector approximating the dominiant eigenvector. If ``matrix``
|
|
73
|
+
is an array of size (n, n), v0 must be a vector of size (n,). If instead
|
|
74
|
+
``matrix`` is a callable, then v0 must be a tree with the same structure
|
|
75
|
+
as the input of this callable. If this argument is None and ``matrix`` is
|
|
76
|
+
an array, then a random vector sampled from a uniform distribution in
|
|
77
|
+
[-1, 1] is used as initial vector.
|
|
78
|
+
num_iters: Number of power iterations.
|
|
79
|
+
error_tolerance: Iterative exit condition. The procedure stops when the
|
|
80
|
+
relative error of the estimate of the dominant eigenvalue is below this
|
|
81
|
+
threshold.
|
|
82
|
+
precision: precision XLA related flag, the available options are: a)
|
|
83
|
+
lax.Precision.DEFAULT (better step time, but not precise); b)
|
|
84
|
+
lax.Precision.HIGH (increased precision, slower); c) lax.Precision.HIGHEST
|
|
85
|
+
(best possible precision, slowest).
|
|
86
|
+
key: random key for the initialization of ``v0`` when not given
|
|
87
|
+
explicitly. When this argument is None, `jax.random.PRNGKey(0)` is used.
|
|
54
88
|
|
|
55
89
|
Returns:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
matrix_size = matrix.shape[-1]
|
|
59
|
-
def _iter_condition(state):
|
|
60
|
-
i, unused_v, unused_s, unused_s_v, run_step = state
|
|
61
|
-
return jnp.logical_and(i < num_iters, run_step)
|
|
62
|
-
|
|
63
|
-
def _iter_body(state):
|
|
64
|
-
"""One step of power iteration."""
|
|
65
|
-
i, new_v, s, s_v, unused_run_step = state
|
|
66
|
-
new_v = new_v / jnp.linalg.norm(new_v)
|
|
90
|
+
A pair (eigenvalue, eigenvector), where eigenvalue is the dominant
|
|
91
|
+
eigenvalue of ``matrix`` and eigenvector is its associated eigenvector.
|
|
67
92
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
.. versionchanged:: 0.2.2
|
|
94
|
+
``matrix`` can be a callable. Reversed the order of the return parameters,
|
|
95
|
+
from (eigenvector, eigenvalue) to (eigenvalue, eigenvector).
|
|
96
|
+
"""
|
|
97
|
+
if callable(matrix):
|
|
98
|
+
mvp = matrix
|
|
99
|
+
if v0 is None:
|
|
100
|
+
# v0 must be given as we don't know the underlying pytree structure.
|
|
101
|
+
raise ValueError('v0 must be provided when `matrix` is a callable.')
|
|
102
|
+
else:
|
|
103
|
+
mvp = lambda v: jnp.matmul(matrix, v, precision=precision)
|
|
104
|
+
if v0 is None:
|
|
105
|
+
if key is None:
|
|
106
|
+
key = jax.random.PRNGKey(0)
|
|
107
|
+
# v0 is uniformly distributed in [-1, 1]
|
|
108
|
+
v0 = jax.random.uniform(
|
|
109
|
+
key,
|
|
110
|
+
shape=matrix.shape[-1:],
|
|
111
|
+
dtype=matrix.dtype,
|
|
112
|
+
minval=-1.0,
|
|
113
|
+
maxval=1.0,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
v0 = _normalize_tree(v0)
|
|
117
|
+
|
|
118
|
+
cond_fun = functools.partial(
|
|
119
|
+
_power_iteration_cond_fun,
|
|
120
|
+
error_tolerance,
|
|
121
|
+
num_iters,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def _body_fun(loop_vars):
|
|
125
|
+
_, z, _, iter_num = loop_vars
|
|
126
|
+
eigvec = _normalize_tree(z)
|
|
127
|
+
z = mvp(eigvec)
|
|
128
|
+
eig = otu.tree_vdot(eigvec, z)
|
|
129
|
+
return eigvec, z, eig, iter_num + 1
|
|
130
|
+
|
|
131
|
+
init_vars = (v0, mvp(v0), jnp.asarray(0.0), jnp.asarray(0))
|
|
132
|
+
_, unormalized_eigenvector, dominant_eigenvalue, _ = (
|
|
133
|
+
jax.lax.while_loop(cond_fun, _body_fun, init_vars)
|
|
134
|
+
)
|
|
135
|
+
normalized_eigenvector = _normalize_tree(unormalized_eigenvector)
|
|
136
|
+
return dominant_eigenvalue, normalized_eigenvector
|
|
81
137
|
|
|
82
138
|
|
|
83
139
|
def matrix_inverse_pth_root(matrix: chex.Array,
|
|
@@ -117,7 +173,7 @@ def matrix_inverse_pth_root(matrix: chex.Array,
|
|
|
117
173
|
matrix_size = matrix.shape[0]
|
|
118
174
|
alpha = jnp.asarray(-1.0 / p, jnp.float32)
|
|
119
175
|
identity = jnp.eye(matrix_size, dtype=jnp.float32)
|
|
120
|
-
|
|
176
|
+
max_ev, _ = power_iteration(
|
|
121
177
|
matrix=matrix, num_iters=100,
|
|
122
178
|
error_tolerance=1e-6, precision=precision)
|
|
123
179
|
ridge_epsilon = ridge_epsilon * jnp.maximum(max_ev, 1e-16)
|