Skip to content

Commit b5f49dc

Browse files
Implement DeltaBarDelta using refactored GradientDescent. (#440)
* Refactor GradientDescent to GradientDescentType<UpdatePolicyType, DecayPolicyType> * Add DeltaBarDeltaUpdate * Update Docs * Add Tests * Use clamp instead of find and fill * Try to fix CI Failure * Update History.md * Simplify and Correct DeltaBarDelta Description * Revert inadvertent code change * Simplify description in optmizer docs too * initialize isInitialized :) * Address review suggestions * Add MomentumDeltaBarDelta * Change minStepSize to minGain * Address Review Comments * Increase test iterations to fix CI failure * Apply suggestions from code review Co-authored-by: Ryan Curtin <ryan@ratml.org> * Fix a typo --------- Co-authored-by: Ryan Curtin <ryan@ratml.org>
1 parent 117f8b8 commit b5f49dc

15 files changed

Lines changed: 1208 additions & 21 deletions

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
### ensmallen ?.??.?: "???"
22
###### ????-??-??
3+
* Refactor `GradientDescent` into
4+
`GradientDescentType<UpdatePolicyType, DecayPolicyType>` and
5+
add the `DeltaBarDelta` and `MomentumDeltaBarDelta` optimizers
6+
([#440](https://github.com/mlpack/ensmallen/pull/440)).
7+
38
* Fix an off-by-one bug where the actual number of executed iterations was one
49
fewer than the specified `maxIterations`
510
([#443](https://github.com/mlpack/ensmallen/pull/443)).
@@ -47,6 +52,7 @@
4752
ActiveCMAES<FullSelection, BoundaryBoxConstraint> opt(lambda,
4853
BoundaryBoxConstraint(lowerBound, upperBound), ...);
4954
```
55+
5056
* Add proximal gradient optimizers for L1-constrained and other related
5157
problems: `FBS`, `FISTA`, and `FASTA`
5258
([#427](https://github.com/mlpack/ensmallen/pull/427)). See the

doc/function_types.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ The following optimizers can be used with differentiable functions:
135135
* [Fast Adaptive Shrinkage/Thresholding Algorithm (FASTA)](#fast-adaptive-shrinkage-thresholding-algorithm-fasta) (`ens::FASTA`)
136136
* [FrankWolfe](#frank-wolfe) (`ens::FrankWolfe`)
137137
* [GradientDescent](#gradient-descent) (`ens::GradientDescent`)
138+
* [DeltaBarDelta](#deltabardelta) (`ens::DeltaBarDelta`)
139+
* [MomentumDeltaBarDelta](#momentum-deltabardelta) (`ens::MomentumDeltaBarDelta`)
138140
- Any optimizer for [arbitrary functions](#arbitrary-functions)
139141

140142
Each of these optimizers has an `Optimize()` function that is called as

doc/optimizers.md

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,6 @@ parameters.
823823
If `lambda` and `sigma` are not specified, then 0 is used as the initial value
824824
for all Lagrange multipliers and 10 is used as the initial penalty parameter.
825825

826-
</details>
827-
828826
#### Examples
829827

830828
<details open>
@@ -1261,6 +1259,70 @@ optimizer.Optimize(f, coordinates);
12611259
* [Differential Evolution in Wikipedia](https://en.wikipedia.org/wiki/Differential_Evolution)
12621260
* [Arbitrary functions](#arbitrary-functions)
12631261

1262+
## DeltaBarDelta
1263+
1264+
*An optimizer for [differentiable functions](#differentiable-functions).*
1265+
1266+
A Gradient Descent variant that adapts learning rates for each parameter to improve convergence. If the current gradient and the exponential average of past gradients corresponding to a parameter have the same sign, then the step size for that parameter is incremented by `kappa`. Otherwise, it is decreased by a proportion `phi` of its current value (additive increase, multiplicative decrease).
1267+
1268+
***Notes:***
1269+
1270+
- DeltaBarDelta is very sensitive to its parameters (`kappa` and `phi`) hence a good
1271+
hyperparameter selection is necessary as its default may not fit every case.
1272+
Typically, `kappa` should be smaller than the step size.
1273+
1274+
- This implementation uses a minStepSize parameter to set a lower bound for the learning
1275+
rate. This prevents the learning rate from dropping to zero, which can occur due to
1276+
floating-point underflow. For tasks which require extreme fine-tuning, you may need to
1277+
lower this parameter below its default value (1e-8) in order to allow for smaller
1278+
learning rates.
1279+
1280+
#### Constructors
1281+
1282+
* `DeltaBarDelta()`
1283+
* `DeltaBarDelta(`_`stepSize`_`)`
1284+
* `DeltaBarDelta(`_`stepSize, maxIterations, tolerance`_`)`
1285+
* `DeltaBarDelta(`_`stepSize, maxIterations, tolerance, kappa, phi, theta, minStepSize, resetPolicy`_`)`
1286+
1287+
#### Attributes
1288+
1289+
| **type** | **name** | **description** | **default** |
1290+
|----------|----------|-----------------|-------------|
1291+
| `double` | **`stepSize`** | Initial step size. | `1.0` |
1292+
| `size_t` | **`maxIterations`** | Maximum number of iterations allowed (0 means no limit). | `100000` |
1293+
| `double` | **`tolerance`** | Maximum absolute tolerance to terminate algorithm. | `1e-5` |
1294+
| `double` | **`kappa`** | Additive increase constant for step size when gradient signs persist. | `0.2` |
1295+
| `double` | **`phi`** | Multiplicative decrease factor for step size when gradient signs flip. | `0.2` |
1296+
| `double` | **`theta`** | Decay rate for computing the exponential average of past gradients. | `0.5` |
1297+
| `double` | **`minStepSize`** | Minimum allowed step size for any parameter. | `1e-8` |
1298+
| `bool` | **`resetPolicy`** | If true, parameters are reset before every `Optimize()` call. | `true` |
1299+
1300+
Attributes of the optimizer may also be modified via the member methods
1301+
`StepSize()`, `MaxIterations()`, `Tolerance()`, `Kappa()`, `Phi()`, `Theta()`, `MinStepSize()` and `ResetPolicy()`.
1302+
1303+
1304+
#### Examples:
1305+
1306+
<details open>
1307+
<summary>Click to collapse/expand example code.
1308+
</summary>
1309+
1310+
```c++
1311+
RosenbrockFunction f;
1312+
arma::mat coordinates = f.GetInitialPoint();
1313+
1314+
DeltaBarDelta optimizer(0.001, 0, 1e-15, 0.0001, 0.2, 0.8);
1315+
optimizer.Optimize(f, coordinates);
1316+
```
1317+
1318+
</details>
1319+
1320+
#### See also:
1321+
1322+
* [Increased rates of convergence through learning rate adaptation (pdf)](https://www.academia.edu/download/32005051/Jacobs.NN88.pdf)
1323+
* [Differentiable functions](#differentiable-functions)
1324+
* [Gradient Descent](#gradient-descent)
1325+
12641326
## DemonAdam
12651327
12661328
*An optimizer for [differentiable separable functions](#differentiable-separable-functions).*
@@ -1894,11 +1956,17 @@ Gradient Descent is a technique to minimize a function. To find a local minimum
18941956
of a function using gradient descent, one takes steps proportional to the
18951957
negative of the gradient of the function at the current point.
18961958

1959+
Note that Gradient Descent is an extremely simple optimizer. For more advanced, adaptive optimizers, consider DeltaBarDelta, MomentumDeltaBarDelta, or established stochastic variants such as Adam, RMSProp, and AdaGrad.
1960+
18971961
#### Constructors
18981962

18991963
* `GradientDescent()`
19001964
* `GradientDescent(`_`stepSize`_`)`
19011965
* `GradientDescent(`_`stepSize, maxIterations, tolerance`_`)`
1966+
* `GradientDescent(`_`stepSize, maxIterations, tolerance, updatePolicy, decayPolicy, resetPolicy`_`)`
1967+
1968+
Note that `GradientDescent` is based on the templated type
1969+
`GradientDescentType<`_`UpdatePolicyType, DecayPolicyType`_`>` with _`UpdatePolicyType`_` = VanillaUpdate` and _`DecayPolicyType`_` = NoDecay`.
19021970

19031971
#### Attributes
19041972

@@ -1909,7 +1977,9 @@ negative of the gradient of the function at the current point.
19091977
| `size_t` | **`tolerance`** | Maximum absolute tolerance to terminate algorithm. | `1e-5` |
19101978

19111979
Attributes of the optimizer may also be changed via the member methods
1912-
`StepSize()`, `MaxIterations()`, and `Tolerance()`.
1980+
`StepSize()`, `MaxIterations()`, `Tolerance()`, `UpdatePolicy()`,
1981+
`DecayPolicy()`, and `ResetPolicy()`.
1982+
19131983

19141984
#### Examples:
19151985

@@ -2396,6 +2466,67 @@ for all Lagrange multipliers and 10 is used as the initial penalty parameter.
23962466
* [Semidefinite programming on Wikipedia](https://en.wikipedia.org/wiki/Semidefinite_programming)
23972467
* [Semidefinite programs](#semidefinite-programs) (includes example usage of `PrimalDualSolver`)
23982468
2469+
## Momentum DeltaBarDelta
2470+
2471+
*An optimizer for [differentiable functions](#differentiable-functions).*
2472+
2473+
A [DeltaBarDelta](#deltabardelta) variant that incorporates the following modifications:
2474+
- In the original DeltaBarDelta, the momentum term (`delta_bar`) is used
2475+
solely for sign comparison with the current gradient and does not
2476+
participate in the parameter update. In this modified variant, the
2477+
momentum term (`velocity`) is directly used to update the parameters.
2478+
- Instead of adjusting the step size directly, each parameter maintains
2479+
a gain value initialized to 1.0. Updates apply additive increases or
2480+
multiplicative decreases to this gain. The effective step size for a
2481+
parameter is the product of its initial step size and its current gain.
2482+
2483+
Note: This variant originates from optimization of the t-SNE cost function.
2484+
2485+
#### Constructors
2486+
2487+
* `MomentumDeltaBarDelta()`
2488+
* `MomentumDeltaBarDelta(`_`stepSize`_`)`
2489+
* `MomentumDeltaBarDelta(`_`stepSize, maxIterations, tolerance`_`)`
2490+
* `MomentumDeltaBarDelta(`_`stepSize, maxIterations, tolerance, kappa, phi, momentum, minGain, resetPolicy`_`)`
2491+
2492+
#### Attributes
2493+
2494+
| **type** | **name** | **description** | **default** |
2495+
|----------|----------|-----------------|-------------|
2496+
| `double` | **`stepSize`** | Initial step size. | `1.0` |
2497+
| `size_t` | **`maxIterations`** | Maximum number of iterations allowed (0 means no limit). | `100000` |
2498+
| `double` | **`tolerance`** | Maximum absolute tolerance to terminate algorithm. | `1e-5` |
2499+
| `double` | **`kappa`** | Additive increase constant for step size. | `0.2` |
2500+
| `double` | **`phi`** | Multiplicative decrease factor for step size. | `0.8` |
2501+
| `double` | **`momentum`** | The momentum hyperparameter. | `0.5` |
2502+
| `double` | **`minGain`** | Minimum allowed gain (scaling factor) for any parameter. | `1e-8` |
2503+
| `bool` | **`resetPolicy`** | If true, parameters are reset before every `Optimize()` call. | `true` |
2504+
2505+
Attributes of the optimizer may also be modified via the member methods
2506+
`StepSize()`, `MaxIterations()`, `Tolerance()`, `Kappa()`, `Phi()`, `Momentum()`, `MinGain()`, and `ResetPolicy()`.
2507+
2508+
#### Examples:
2509+
2510+
<details open>
2511+
<summary>Click to collapse/expand example code.
2512+
</summary>
2513+
2514+
```c++
2515+
RosenbrockFunction f;
2516+
arma::mat coordinates = f.GetInitialPoint();
2517+
2518+
MomentumDeltaBarDelta optimizer(0.001, 0, 1e-15, 0.2, 0.8, 0.5);
2519+
optimizer.Optimize(f, coordinates);
2520+
```
2521+
2522+
</details>
2523+
2524+
#### See also:
2525+
* [t-SNE Implementations](https://lvdmaaten.github.io/tsne/)
2526+
* [Increased rates of convergence through learning rate adaptation (pdf)](https://www.academia.edu/download/32005051/Jacobs.NN88.pdf)
2527+
* [Differentiable functions](#differentiable-functions)
2528+
* [Gradient Descent](#gradient-descent)
2529+
23992530
## Momentum SGD
24002531

24012532
*An optimizer for [differentiable separable functions](#differentiable-separable-functions).*

include/ensmallen.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@
120120
#include "ensmallen_bits/cd/cd.hpp"
121121
#include "ensmallen_bits/cne/cne.hpp"
122122
#include "ensmallen_bits/de/de.hpp"
123+
#include "ensmallen_bits/delta_bar_delta/delta_bar_delta.hpp"
124+
#include "ensmallen_bits/delta_bar_delta/momentum_delta_bar_delta.hpp"
123125
#include "ensmallen_bits/eve/eve.hpp"
124126
#include "ensmallen_bits/fasta/fasta.hpp"
125127
#include "ensmallen_bits/fbs/fbs.hpp"
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* @file delta_bar_delta.hpp
3+
* @author Ranjodh Singh
4+
*
5+
* Class wrapper for the DeltaBarDelta update policy.
6+
*
7+
* ensmallen is free software; you may redistribute it and/or modify it under
8+
* the terms of the 3-clause BSD license. You should have received a copy of
9+
* the 3-clause BSD license along with ensmallen. If not, see
10+
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
11+
*/
12+
#ifndef ENSMALLEN_DELTA_BAR_DELTA_HPP
13+
#define ENSMALLEN_DELTA_BAR_DELTA_HPP
14+
15+
#include <ensmallen_bits/gradient_descent/gradient_descent.hpp>
16+
#include "update_policies/delta_bar_delta_update.hpp"
17+
18+
namespace ens {
19+
20+
/**
21+
* DeltaBarDelta optimizer.
22+
*
23+
* A heuristic designed to accelerate convergence by
24+
* adapting the learning rate of each parameter individually.
25+
*
26+
* According to the Delta-Bar-Delta update:
27+
*
28+
* - If the current gradient and the exponential average of
29+
* past gradients corresponding to a parameter have the same
30+
* sign, then the step size for that parameter is incremented by
31+
* \f$\kappa\f$. Otherwise, it is decreased by a proportion \f$\phi\f$
32+
* of its current value (additive increase, multiplicative decrease).
33+
*
34+
* @note This implementation uses a minStepSize parameter to set a lower
35+
* bound for the learning rate. This prevents the learning rate from
36+
* dropping to zero, which can occur due to floating-point underflow.
37+
* For tasks which require extreme fine-tuning, you may need to lower
38+
* this parameter below its default value (1e-8) in order to allow for
39+
* smaller learning rates.
40+
*
41+
* @code
42+
* @article{jacobs1988increased,
43+
* title = {Increased Rates of Convergence Through Learning Rate
44+
* Adaptation},
45+
* author = {Jacobs, Robert A.},
46+
* journal = {Neural Networks},
47+
* volume = {1},
48+
* number = {4},
49+
* pages = {295--307},
50+
* year = {1988},
51+
* publisher = {Pergamon}
52+
* }
53+
* @endcode
54+
*/
55+
class DeltaBarDelta
56+
{
57+
public:
58+
/**
59+
* Construct the DeltaBarDelta optimizer with the given function and
60+
* parameters. DeltaBarDelta is very sensitive to its parameters (kappa
61+
* and phi) hence a good hyperparameter selection is necessary as its
62+
* default may not fit every case.
63+
*
64+
* @param stepSize Step size (initial).
65+
* @param maxIterations Maximum number of iterations allowed (0 means no
66+
* limit).
67+
* @param tolerance Maximum absolute tolerance to terminate algorithm.
68+
* @param kappa Additive increase constant for step size.
69+
* @param phi Multiplicative decrease factor for step size.
70+
* @param theta Decay rate for the exponential moving average.
71+
* @param minStepSize Minimum allowed step size for any parameter
72+
* (default: 1e-8).
73+
* @param resetPolicy If true, parameters are reset before every Optimize
74+
* call; otherwise, their values are retained.
75+
*/
76+
DeltaBarDelta(const double stepSize = 1.0,
77+
const size_t maxIterations = 100000,
78+
const double tolerance = 1e-5,
79+
const double kappa = 0.2,
80+
const double phi = 0.2,
81+
const double theta = 0.5,
82+
const double minStepSize = 1e-8,
83+
const bool resetPolicy = true);
84+
85+
/**
86+
* Optimize the given function using DeltaBarDelta.
87+
* The given starting point will be modified to store the finishing
88+
* point of the algorithm, and the final objective value is returned.
89+
*
90+
* @tparam SeparableFunctionType Type of the function to optimize.
91+
* @tparam MatType Type of matrix to optimize with.
92+
* @tparam GradType Type of matrix to use to represent function gradients.
93+
* @tparam CallbackTypes Types of callback functions.
94+
* @param function Function to optimize.
95+
* @param iterate Starting point (will be modified).
96+
* @param callbacks Callback functions.
97+
* @return Objective value of the final point.
98+
*/
99+
template<typename SeparableFunctionType,
100+
typename MatType,
101+
typename GradType,
102+
typename... CallbackTypes>
103+
typename std::enable_if<IsMatrixType<GradType>::value,
104+
typename MatType::elem_type>::type
105+
Optimize(SeparableFunctionType& function,
106+
MatType& iterate,
107+
CallbackTypes&&... callbacks)
108+
{
109+
return optimizer.Optimize<SeparableFunctionType, MatType, GradType,
110+
CallbackTypes...>(function, iterate,
111+
std::forward<CallbackTypes>(callbacks)...);
112+
}
113+
114+
//! Forward the MatType as GradType.
115+
template<typename SeparableFunctionType,
116+
typename MatType,
117+
typename... CallbackTypes>
118+
typename MatType::elem_type Optimize(SeparableFunctionType& function,
119+
MatType& iterate,
120+
CallbackTypes&&... callbacks)
121+
{
122+
return Optimize<SeparableFunctionType, MatType, MatType,
123+
CallbackTypes...>(function, iterate,
124+
std::forward<CallbackTypes>(callbacks)...);
125+
}
126+
127+
//! Get the initial step size.
128+
double StepSize() const { return optimizer.StepSize(); }
129+
//! Modify the initial step size.
130+
double& StepSize() { return optimizer.StepSize(); }
131+
132+
//! Get the maximum number of iterations (0 indicates no limit).
133+
size_t MaxIterations() const { return optimizer.MaxIterations(); }
134+
//! Modify the maximum number of iterations (0 indicates no limit).
135+
size_t& MaxIterations() { return optimizer.MaxIterations(); }
136+
137+
//! Get the additive increase constant for step size.
138+
double Kappa() const { return optimizer.UpdatePolicy().Kappa(); }
139+
//! Modify the additive increase constant for step size.
140+
double& Kappa() { return optimizer.UpdatePolicy().Kappa(); }
141+
142+
//! Get the multiplicative decrease factor for step size.
143+
double Phi() const { return optimizer.UpdatePolicy().Phi(); }
144+
//! Modify the multiplicative decrease factor for step size.
145+
double& Phi() { return optimizer.UpdatePolicy().Phi(); }
146+
147+
//! Get the decay rate for the exponential moving average.
148+
double Theta() const { return optimizer.UpdatePolicy().Theta(); }
149+
//! Modify the decay rate for the exponential moving average.
150+
double& Theta() { return optimizer.UpdatePolicy().Theta(); }
151+
152+
//! Get the minimum allowed step size for any parameter.
153+
double MinStepSize() const { return optimizer.UpdatePolicy().MinStepSize(); }
154+
//! Modify the minimum allowed step size for any parameter.
155+
double& MinStepSize() { return optimizer.UpdatePolicy().MinStepSize(); }
156+
157+
//! Get the tolerance for termination.
158+
double Tolerance() const { return optimizer.Tolerance(); }
159+
//! Modify the tolerance for termination.
160+
double& Tolerance() { return optimizer.Tolerance(); }
161+
162+
//! Get whether or not the update policy parameters are reset before
163+
//! Optimize call.
164+
bool ResetPolicy() const { return optimizer.ResetPolicy(); }
165+
//! Modify whether or not the update policy parameters are reset before
166+
//! Optimize call.
167+
bool& ResetPolicy() { return optimizer.ResetPolicy(); }
168+
169+
private:
170+
//! The GradientDescentType object with DeltaBarDelta policy.
171+
GradientDescentType<DeltaBarDeltaUpdate, NoDecay> optimizer;
172+
};
173+
174+
} // namespace ens
175+
176+
// Include implementation.
177+
#include "delta_bar_delta_impl.hpp"
178+
179+
#endif // ENSMALLEN_DELTA_BAR_DELTA_HPP

0 commit comments

Comments
 (0)