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.
Files changed (92) hide show
  1. {optax-0.2.0 → optax-0.2.2}/PKG-INFO +3 -2
  2. {optax-0.2.0 → optax-0.2.2}/optax/__init__.py +5 -1
  3. {optax-0.2.0 → optax-0.2.2}/optax/_src/alias.py +136 -11
  4. {optax-0.2.0 → optax-0.2.2}/optax/_src/alias_test.py +23 -13
  5. {optax-0.2.0 → optax-0.2.2}/optax/_src/combine.py +23 -8
  6. {optax-0.2.0 → optax-0.2.2}/optax/_src/linear_algebra.py +98 -42
  7. optax-0.2.2/optax/_src/linear_algebra_test.py +217 -0
  8. {optax-0.2.0 → optax-0.2.2}/optax/_src/linesearch.py +109 -91
  9. {optax-0.2.0 → optax-0.2.2}/optax/_src/linesearch_test.py +1 -1
  10. {optax-0.2.0 → optax-0.2.2}/optax/_src/transform.py +53 -11
  11. {optax-0.2.0 → optax-0.2.2}/optax/_src/transform_test.py +30 -2
  12. {optax-0.2.0 → optax-0.2.2}/optax/_src/utils.py +7 -76
  13. {optax-0.2.0 → optax-0.2.2}/optax/_src/utils_test.py +3 -40
  14. optax-0.2.2/optax/contrib/__init__.py +35 -0
  15. optax-0.2.0/optax/contrib/complex_valued_test.py → optax-0.2.2/optax/contrib/_complex_valued_test.py +3 -3
  16. optax-0.2.0/optax/contrib/mechanic_test.py → optax-0.2.2/optax/contrib/_mechanic_test.py +3 -3
  17. optax-0.2.0/optax/contrib/privacy_test.py → optax-0.2.2/optax/contrib/_privacy_test.py +6 -6
  18. optax-0.2.0/optax/contrib/reduce_on_plateau.py → optax-0.2.2/optax/contrib/_reduce_on_plateau.py +68 -38
  19. optax-0.2.0/optax/contrib/reduce_on_plateau_test.py → optax-0.2.2/optax/contrib/_reduce_on_plateau_test.py +45 -39
  20. optax-0.2.0/optax/contrib/sam_test.py → optax-0.2.2/optax/contrib/_sam_test.py +5 -5
  21. {optax-0.2.0 → optax-0.2.2}/optax/losses/__init__.py +1 -0
  22. {optax-0.2.0 → optax-0.2.2}/optax/losses/_classification.py +69 -3
  23. {optax-0.2.0 → optax-0.2.2}/optax/losses/_classification_test.py +43 -2
  24. optax-0.2.2/optax/losses/_fenchel_young.py +63 -0
  25. optax-0.2.2/optax/losses/_fenchel_young_test.py +63 -0
  26. {optax-0.2.0 → optax-0.2.2}/optax/losses/_regression.py +5 -1
  27. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_inject.py +2 -2
  28. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_schedule.py +15 -3
  29. {optax-0.2.0 → optax-0.2.2}/optax/tree_utils/__init__.py +6 -0
  30. optax-0.2.2/optax/tree_utils/_state_utils.py +786 -0
  31. optax-0.2.2/optax/tree_utils/_state_utils_test.py +599 -0
  32. {optax-0.2.0 → optax-0.2.2}/optax/tree_utils/_tree_math.py +5 -0
  33. {optax-0.2.0 → optax-0.2.2}/pyproject.toml +2 -1
  34. optax-0.2.0/optax/_src/linear_algebra_test.py +0 -62
  35. optax-0.2.0/optax/contrib/__init__.py +0 -35
  36. optax-0.2.0/optax/tree_utils/_state_utils.py +0 -119
  37. optax-0.2.0/optax/tree_utils/_state_utils_test.py +0 -273
  38. {optax-0.2.0 → optax-0.2.2}/LICENSE +0 -0
  39. {optax-0.2.0 → optax-0.2.2}/README.md +0 -0
  40. {optax-0.2.0 → optax-0.2.2}/optax/_src/base.py +0 -0
  41. {optax-0.2.0 → optax-0.2.2}/optax/_src/base_test.py +0 -0
  42. {optax-0.2.0 → optax-0.2.2}/optax/_src/clipping.py +0 -0
  43. {optax-0.2.0 → optax-0.2.2}/optax/_src/clipping_test.py +0 -0
  44. {optax-0.2.0 → optax-0.2.2}/optax/_src/combine_test.py +0 -0
  45. {optax-0.2.0 → optax-0.2.2}/optax/_src/constrain.py +0 -0
  46. {optax-0.2.0 → optax-0.2.2}/optax/_src/constrain_test.py +0 -0
  47. {optax-0.2.0 → optax-0.2.2}/optax/_src/factorized.py +0 -0
  48. {optax-0.2.0 → optax-0.2.2}/optax/_src/factorized_test.py +0 -0
  49. {optax-0.2.0 → optax-0.2.2}/optax/_src/float64_test.py +0 -0
  50. {optax-0.2.0 → optax-0.2.2}/optax/_src/lookahead.py +0 -0
  51. {optax-0.2.0 → optax-0.2.2}/optax/_src/lookahead_test.py +0 -0
  52. {optax-0.2.0 → optax-0.2.2}/optax/_src/numerics.py +0 -0
  53. {optax-0.2.0 → optax-0.2.2}/optax/_src/numerics_test.py +0 -0
  54. {optax-0.2.0 → optax-0.2.2}/optax/_src/schedule.py +0 -0
  55. {optax-0.2.0 → optax-0.2.2}/optax/_src/update.py +0 -0
  56. {optax-0.2.0 → optax-0.2.2}/optax/_src/update_test.py +0 -0
  57. {optax-0.2.0 → optax-0.2.2}/optax/_src/wrappers.py +0 -0
  58. {optax-0.2.0 → optax-0.2.2}/optax/_src/wrappers_test.py +0 -0
  59. /optax-0.2.0/optax/contrib/cocob.py → /optax-0.2.2/optax/contrib/_cocob.py +0 -0
  60. /optax-0.2.0/optax/contrib/common_test.py → /optax-0.2.2/optax/contrib/_common_test.py +0 -0
  61. /optax-0.2.0/optax/contrib/complex_valued.py → /optax-0.2.2/optax/contrib/_complex_valued.py +0 -0
  62. /optax-0.2.0/optax/contrib/dadapt_adamw.py → /optax-0.2.2/optax/contrib/_dadapt_adamw.py +0 -0
  63. /optax-0.2.0/optax/contrib/mechanic.py → /optax-0.2.2/optax/contrib/_mechanic.py +0 -0
  64. /optax-0.2.0/optax/contrib/privacy.py → /optax-0.2.2/optax/contrib/_privacy.py +0 -0
  65. /optax-0.2.0/optax/contrib/prodigy.py → /optax-0.2.2/optax/contrib/_prodigy.py +0 -0
  66. /optax-0.2.0/optax/contrib/sam.py → /optax-0.2.2/optax/contrib/_sam.py +0 -0
  67. {optax-0.2.0 → optax-0.2.2}/optax/losses/_ranking.py +0 -0
  68. {optax-0.2.0 → optax-0.2.2}/optax/losses/_ranking_test.py +0 -0
  69. {optax-0.2.0 → optax-0.2.2}/optax/losses/_regression_test.py +0 -0
  70. {optax-0.2.0 → optax-0.2.2}/optax/losses/_smoothing.py +0 -0
  71. {optax-0.2.0 → optax-0.2.2}/optax/losses/_smoothing_test.py +0 -0
  72. {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/__init__.py +0 -0
  73. {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/control_variates.py +0 -0
  74. {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/control_variates_test.py +0 -0
  75. {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/stochastic_gradient_estimators.py +0 -0
  76. {optax-0.2.0 → optax-0.2.2}/optax/monte_carlo/stochastic_gradient_estimators_test.py +0 -0
  77. {optax-0.2.0 → optax-0.2.2}/optax/optax_test.py +0 -0
  78. {optax-0.2.0 → optax-0.2.2}/optax/projections/__init__.py +0 -0
  79. {optax-0.2.0 → optax-0.2.2}/optax/projections/_projections.py +0 -0
  80. {optax-0.2.0 → optax-0.2.2}/optax/projections/_projections_test.py +0 -0
  81. {optax-0.2.0 → optax-0.2.2}/optax/schedules/__init__.py +0 -0
  82. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_inject_test.py +0 -0
  83. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_join.py +0 -0
  84. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_join_test.py +0 -0
  85. {optax-0.2.0 → optax-0.2.2}/optax/schedules/_schedule_test.py +0 -0
  86. {optax-0.2.0 → optax-0.2.2}/optax/schedules/inject.py +0 -0
  87. {optax-0.2.0 → optax-0.2.2}/optax/second_order/__init__.py +0 -0
  88. {optax-0.2.0 → optax-0.2.2}/optax/second_order/_base.py +0 -0
  89. {optax-0.2.0 → optax-0.2.2}/optax/second_order/_fisher.py +0 -0
  90. {optax-0.2.0 → optax-0.2.2}/optax/second_order/_hessian.py +0 -0
  91. {optax-0.2.0 → optax-0.2.2}/optax/second_order/_hessian_test.py +0 -0
  92. {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.0
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.7
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.0"
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 Adam and contains the same number 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`` respectievly. The learning rate is
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
- It has been found that adding noise to the gradients can improve
998
- both the training error and the generalization error in very deep networks.
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, the
1029
- variance decays according to `(1+t)^-\gamma`.
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
- @jax.grad
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, get_updates
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
- @jax.grad
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, get_updates
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, get_updates = target(dtype)
122
+ initial_params, final_params, objective = target(dtype)
123
123
 
124
124
  @jax.jit
125
125
  def step(params, state):
126
- updates = get_updates(params)
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
- updates, state = opt.update(updates, state, params)
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
- updates, new_state = self.variant(opt.update)(grads, state, params)
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
- Given a sequence of chainable transforms, `chain` returns an `init_fn`
31
- that constructs a `state` by concatenating the states of the individual
32
- transforms, and returns an `update_fn` which chains the update transformations
33
- feeding the appropriate state to each.
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 ``GradientTransformationExtraArgs``, created by chaining the input
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
- ``optax.multi_transform`` with :func:`optax.masked`.
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
- An ``optax.GradientTransformation``.
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 numpy as np
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 power_iteration(matrix: chex.Array,
34
- num_iters: int = 100,
35
- error_tolerance: float = 1e-6,
36
- precision: lax.Precision = lax.Precision.HIGHEST):
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
- The power iteration algorithm takes a symmetric PSD matrix `A`, and produces
40
- a scalar `\lambda` , which is the greatest (in absolute value) eigenvalue
41
- of `A`, and a vector v, which is the corresponding eigenvector of `A`.
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
- [Wikipedia, 2021](https://en.wikipedia.org/wiki/Power_iteration)
66
+ Wikipedia contributors. `Power iteration
67
+ <https://en.wikipedia.org/w/index.php?tit0le=Power_iteration>`_.
45
68
 
46
69
  Args:
47
- matrix: the symmetric PSD matrix.
48
- num_iters: Number of iterations.
49
- error_tolerance: Iterative exit condition.
50
- precision: precision XLA related flag, the available options are:
51
- a) lax.Precision.DEFAULT (better step time, but not precise);
52
- b) lax.Precision.HIGH (increased precision, slower);
53
- c) lax.Precision.HIGHEST (best possible precision, slowest).
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
- eigen vector, eigen value
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
- s_v = jnp.einsum('ij,j->i', matrix, new_v, precision=precision)
69
- s_new = jnp.einsum('i,i->', new_v, s_v, precision=precision)
70
- return (i + 1, s_v, s_new, s_v,
71
- jnp.greater(jnp.abs(s_new - s), error_tolerance))
72
-
73
- # Figure out how to use step as seed for random.
74
- v_0 = np.random.uniform(-1.0, 1.0, matrix_size).astype(matrix.dtype)
75
-
76
- init_state = tuple([0, v_0, jnp.zeros([], dtype=matrix.dtype), v_0, True])
77
- _, v_out, s_out, _, _ = lax.while_loop(
78
- _iter_condition, _iter_body, init_state)
79
- v_out = v_out / jnp.linalg.norm(v_out)
80
- return v_out, s_out
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
- _, max_ev = power_iteration(
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)