Skip to content

Commit a4ec8b5

Browse files
authored
Normalize step size to the batch size used (#431)
* Normalize step size to the batch size used. * Update history. * Don't use if constexpr. * Fix all step sizes in tests to account for new behavior. * Retune DemonSGD tests to reduce failure probability. * Fix merge artifact. * Fix MOEAD test to be more robust.
1 parent 3b4e1e2 commit a4ec8b5

28 files changed

Lines changed: 137 additions & 100 deletions

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
### ensmallen ?.??.?: "???"
22
###### ????-??-??
3+
* SGD-like optimizers now all divide the step size by the batch size so that
4+
step sizes don't need to be tuned in addition to batch sizes. If you require
5+
behavior from ensmallen 2, define the `ENS_OLD_SEPARABLE_STEP_BEHAVIOR` macro
6+
before including `ensmallen.hpp`
7+
([#431](https://github.com/mlpack/ensmallen/pull/431)).
38

49
### ensmallen 2.22.2: "E-Bike Excitement"
510
###### 2025-04-30

include/ensmallen_bits/cmaes/active_cmaes_impl.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ typename MatType::elem_type ActiveCMAES<SelectionPolicyType,
110110
BaseMatType sigma(2, 1); // sigma is vector-shaped.
111111
if (stepSize == 0)
112112
sigma(0) = transformationPolicy.InitialStepSize();
113-
else
113+
else
114114
sigma(0) = ElemType(stepSize);
115115

116116
const ElemType cs = 4 / ElemType(iterate.n_elem + 4);

include/ensmallen_bits/eve/eve_impl.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,14 @@ Eve::Optimize(SeparableFunctionType& function,
149149

150150
lastObjective = objective;
151151

152+
// TODO: remove in ensmallen 4.0.0.
153+
#if defined(ENS_OLD_SEPARABLE_STEP_BEHAVIOR)
152154
iterate -= ElemType(stepSize) / dt * (m / biasCorrection1) /
153155
(sqrt(v / biasCorrection2) + ElemType(epsilon));
156+
#else
157+
iterate -= (ElemType(stepSize) / (dt * effectiveBatchSize)) *
158+
(m / biasCorrection1) / (sqrt(v / biasCorrection2) + ElemType(epsilon));
159+
#endif
154160

155161
terminate |= Callback::StepTaken(*this, f, iterate, callbacks...);
156162

include/ensmallen_bits/lookahead/lookahead_impl.hpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ inline Lookahead<BaseOptimizerType, DecayPolicyType>::~Lookahead()
6666
instDecayPolicy.Clean();
6767
}
6868

69+
template<typename BaseOptimizerType>
70+
size_t GetBatchSize(
71+
const BaseOptimizerType& baseOptimizer,
72+
const typename std::enable_if_t<traits::HasBatchSizeSignature<
73+
BaseOptimizerType>::value>* = 0)
74+
{
75+
return baseOptimizer.BatchSize();
76+
}
77+
78+
template<typename BaseOptimizerType>
79+
size_t GetBatchSize(
80+
const BaseOptimizerType& baseOptimizer,
81+
const typename std::enable_if_t<!traits::HasBatchSizeSignature<
82+
BaseOptimizerType>::value>* = 0)
83+
{
84+
return 1;
85+
}
86+
6987
//! Optimize the function (minimize).
7088
template<typename BaseOptimizerType, typename DecayPolicyType>
7189
template<typename SeparableFunctionType,
@@ -189,11 +207,9 @@ Lookahead<BaseOptimizerType, DecayPolicyType>::Optimize(
189207
// Find the number of functions to use.
190208
const size_t numFunctions = f.NumFunctions();
191209

192-
size_t batchSize = 1;
193210
// Check if the optimizer implements the BatchSize() method and use the
194211
// parameter for the objective calculation.
195-
if (traits::HasBatchSizeSignature<BaseOptimizerType>::value)
196-
batchSize = baseOptimizer.BatchSize();
212+
size_t batchSize = GetBatchSize(baseOptimizer);
197213

198214
overallObjective = 0;
199215
for (size_t i = 0; i < numFunctions; i += batchSize)

include/ensmallen_bits/parallel_sgd/parallel_sgd_impl.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ typename MatType::elem_type>::type ParallelSGD<DecayPolicyType>::Optimize(
132132
return overallObjective;
133133
}
134134

135-
// Get the stepsize for this iteration
135+
// Get the stepsize for this iteration.
136136
double stepSize = decayPolicy.StepSize(i);
137137

138138
// Shuffle for uniform sampling of functions by each thread.
@@ -180,6 +180,8 @@ typename MatType::elem_type>::type ParallelSGD<DecayPolicyType>::Optimize(
180180

181181
// Call out to utility function to use the right type of OpenMP
182182
// lock.
183+
// TODO: if batch size support > 1 is added, `stepSize` will need to
184+
// be updated here.
183185
UpdateLocation(iterate, row, i, ElemType(stepSize) * value);
184186
}
185187
}
@@ -198,4 +200,4 @@ typename MatType::elem_type>::type ParallelSGD<DecayPolicyType>::Optimize(
198200

199201
} // namespace ens
200202

201-
#endif
203+
#endif

include/ensmallen_bits/sgd/sgd_impl.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,14 @@ SGD<UpdatePolicyType, DecayPolicyType>::Optimize(
150150
gradient, callbacks...);
151151

152152
// Use the update policy to take a step.
153+
// TODO: remove old behavior in ensmallen 4.0.0.
154+
#if defined(ENS_OLD_SEPARABLE_STEP_BEHAVIOR)
153155
instUpdatePolicy.As<InstUpdatePolicyType>().Update(iterate, stepSize,
154156
gradient);
157+
#else
158+
instUpdatePolicy.As<InstUpdatePolicyType>().Update(iterate,
159+
(stepSize / effectiveBatchSize), gradient);
160+
#endif
155161

156162
terminate |= Callback::StepTaken(*this, f, iterate, callbacks...);
157163

tests/ada_belief_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ using namespace ens::test;
2222
TEMPLATE_TEST_CASE("AdaBelief_LogisticRegressionFunction", "[AdaBelief]",
2323
ENS_ALL_TEST_TYPES)
2424
{
25-
AdaBelief adaBelief;
25+
AdaBelief adaBelief(0.032);
2626
LogisticRegressionFunctionTest<TestType, arma::Row<size_t>>(adaBelief);
2727
}
2828

@@ -31,7 +31,7 @@ TEMPLATE_TEST_CASE("AdaBelief_LogisticRegressionFunction", "[AdaBelief]",
3131
TEMPLATE_TEST_CASE("AdaBelief_LogisticRegressionFunction", "[AdaBelief]",
3232
coot::mat, coot::fmat)
3333
{
34-
AdaBelief adaBelief;
34+
AdaBelief adaBelief(0.032);
3535
LogisticRegressionFunctionTest<TestType, coot::Row<size_t>>(adaBelief);
3636
}
3737

tests/ada_bound_test.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ using namespace ens::test;
2222
TEMPLATE_TEST_CASE("AdaBound_SphereFunction", "[AdaBound]", ENS_ALL_TEST_TYPES,
2323
ENS_SPARSE_TEST_TYPES)
2424
{
25-
AdaBound optimizer(0.005, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
25+
AdaBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
2626
1e-3, false);
2727
FunctionTest<SphereFunction, TestType>(
2828
optimizer,
@@ -33,7 +33,7 @@ TEMPLATE_TEST_CASE("AdaBound_SphereFunction", "[AdaBound]", ENS_ALL_TEST_TYPES,
3333
TEMPLATE_TEST_CASE("AMSBound_SphereFunction", "[AdaBound]", ENS_ALL_TEST_TYPES,
3434
ENS_SPARSE_TEST_TYPES)
3535
{
36-
AMSBound optimizer(0.005, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
36+
AMSBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
3737
1e-3, false);
3838
FunctionTest<SphereFunction, TestType>(
3939
optimizer,
@@ -47,7 +47,7 @@ TEMPLATE_TEST_CASE("AdaBound_SphereFunctionSpMatDenseGradient", "[AdaBound]",
4747
typedef typename TestType::elem_type ElemType;
4848

4949
SphereFunction f(2);
50-
AdaBound optimizer(0.001, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
50+
AdaBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
5151
1e-3, false);
5252

5353
TestType coordinates = arma::conv_to<TestType>::from(
@@ -65,7 +65,7 @@ TEMPLATE_TEST_CASE("AMSBound_SphereFunctionSpMatDenseGradient", "[AdaBound]",
6565
typedef typename TestType::elem_type ElemType;
6666

6767
SphereFunction f(2);
68-
AMSBound optimizer(0.001, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
68+
AMSBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
6969
1e-3, false);
7070

7171
arma::sp_mat coordinates = f.GetInitialPoint<TestType>();
@@ -81,15 +81,15 @@ TEMPLATE_TEST_CASE("AMSBound_SphereFunctionSpMatDenseGradient", "[AdaBound]",
8181
TEMPLATE_TEST_CASE("AdaBound_SphereFunction", "[AdaBound]",
8282
coot::mat, coot::fmat)
8383
{
84-
AdaBound optimizer(0.001, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
84+
AdaBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
8585
1e-3, false);
8686
FunctionTest<SphereFunction, TestType>(optimizer, 0.5, 0.1);
8787
}
8888

8989
TEMPLATE_TEST_CASE("AMSBoundSphereFunctionTest", "[AdaBound]",
9090
coot::mat, coot::fmat)
9191
{
92-
AMSBound optimizer(0.001, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
92+
AMSBound optimizer(0.002, 2, 0.1, 1e-3, 0.9, 0.999, 1e-8, 500000,
9393
1e-3, false);
9494
FunctionTest<SphereFunction, TestType>(optimizer, 0.5, 0.1);
9595
}

tests/ada_delta_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ TEMPLATE_TEST_CASE("AdaDelta_LogisticRegressionFunction", "[AdaDelta]",
2929

3030
// Use a large epsilon if we are using FP16, to avoid underflow in the first
3131
// iterations.
32-
AdaDelta adaDelta(1.0, 32, 0.95, sizeof(ElemType) == 2 ? 1e-4 : 1e-6);
32+
AdaDelta adaDelta(32.0, 32, 0.95, sizeof(ElemType) == 2 ? 1e-4 : 1e-6);
3333
LogisticRegressionFunctionTest<TestType, arma::Row<size_t>>(adaDelta);
3434
}
3535

@@ -38,7 +38,7 @@ TEMPLATE_TEST_CASE("AdaDelta_LogisticRegressionFunction", "[AdaDelta]",
3838
TEMPLATE_TEST_CASE("AdaDelta_LogisticRegressionFunction", "[AdaDelta]",
3939
coot::mat, coot::fmat)
4040
{
41-
AdaDelta adaDelta;
41+
AdaDelta adaDelta(32.0);
4242
LogisticRegressionFunctionTest<TestType, coot::Row<size_t>>(
4343
adaDelta, 0.003, 0.006, 1);
4444
}

tests/ada_grad_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ using namespace ens::test;
2323
TEMPLATE_TEST_CASE("AdaGrad_LogisticRegressionFunction", "[AdaGrad]",
2424
ENS_ALL_TEST_TYPES)
2525
{
26-
AdaGrad adagrad(0.99, 32, 10 * Tolerances<TestType>::Obj, 500000,
26+
AdaGrad adagrad(31.5, 32, 10 * Tolerances<TestType>::Obj, 500000,
2727
Tolerances<TestType>::Obj, true);
2828
LogisticRegressionFunctionTest<TestType, arma::Row<size_t>>(adagrad);
2929
}
@@ -33,7 +33,7 @@ TEMPLATE_TEST_CASE("AdaGrad_LogisticRegressionFunction", "[AdaGrad]",
3333
TEMPLATE_TEST_CASE("AdaGrad_LogisticRegressionFunction", "[AdaGrad]",
3434
coot::mat, coot::fmat)
3535
{
36-
AdaGrad adagrad(0.99, 32, 1e-8, 500000, 1e-9, true);
36+
AdaGrad adagrad(31.5, 32, 1e-8, 500000, 1e-9, true);
3737
LogisticRegressionFunctionTest<TestType, coot::Row<size_t>>(adagrad);
3838
}
3939

0 commit comments

Comments
 (0)