Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit 0705952

Browse files
AurelienJaquierJaquier Aurélien Tristan
andauthored
New stagnation stopping criterion (#13)
* tmp commit; implementation of stopping crits and other stuff * test new stopping criterion * revert check_termination * clean optimisation_CMA.py * clean stoppingCriteria.py * revert simulators.py * cleanup * some fixes --------- Co-authored-by: Jaquier Aurélien Tristan <[email protected]>
1 parent 5e4b75e commit 0705952

File tree

4 files changed

+88
-27
lines changed

4 files changed

+88
-27
lines changed

bluepyopt/deapext/CMA_MO.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from deap import base
3131
from deap import cma
3232

33-
from .stoppingCriteria import MaxNGen
33+
from .stoppingCriteria import MaxNGen, Stagnationv2
3434
from . import utils
3535
from . import hype
3636

@@ -160,10 +160,13 @@ def __init__(
160160
self.active = True
161161
if max_ngen <= 0:
162162
max_ngen = 100 + 50 * (self.problem_size + 3) ** 2 / numpy.sqrt(
163-
self.lambda_
163+
lambda_
164164
)
165165

166-
self.stopping_conditions = [MaxNGen(max_ngen)]
166+
self.stopping_conditions = [
167+
MaxNGen(max_ngen),
168+
Stagnationv2(lambda_, self.problem_size),
169+
]
167170

168171
def _select(self, candidates):
169172
"""Select the best candidates of the population
@@ -242,6 +245,6 @@ def check_termination(self, gen):
242245
if c.criteria_met:
243246
logger.info(
244247
"CMA stopped because of termination criteria: " +
245-
" ".join(c.name)
248+
"" + " ".join(c.name)
246249
)
247250
self.active = False

bluepyopt/deapext/CMA_SO.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from .stoppingCriteria import (
3333
MaxNGen,
34-
Stagnation,
34+
Stagnationv2,
3535
TolHistFun,
3636
EqualFunVals,
3737
NoEffectAxis,
@@ -63,17 +63,17 @@ def __init__(
6363
"""Constructor
6464
6565
Args:
66-
centroid (list): initial guess used as the starting point of
67-
the CMA-ES
68-
offspring_size (int): number of offspring individuals in each
69-
generation
70-
sigma (float): initial standard deviation of the distribution
71-
max_ngen (int): total number of generation to run
72-
IndCreator (fcn): function returning an individual of the pop
73-
RandIndCreator (fcn): function creating a random individual.
74-
map_function (map): function used to map (parallelize) the
75-
evaluation function calls
76-
use_scoop (bool): use scoop map for parallel computation
66+
centroid (list): initial guess used as the starting point of
67+
the CMA-ES
68+
offspring_size (int): number of offspring individuals in each
69+
generation
70+
sigma (float): initial standard deviation of the distribution
71+
max_ngen (int): total number of generation to run
72+
IndCreator (fcn): function returning an individual of the pop
73+
RandIndCreator (fcn): function creating a random individual.
74+
map_function (map): function used to map (parallelize) the
75+
evaluation function calls
76+
use_scoop (bool): use scoop map for parallel computation
7777
"""
7878

7979
if offspring_size is None:
@@ -103,14 +103,14 @@ def __init__(
103103
self.active = True
104104
if max_ngen <= 0:
105105
max_ngen = 100 + 50 * (self.problem_size + 3) ** 2 / numpy.sqrt(
106-
self.lambda_
106+
lambda_
107107
)
108108

109109
self.stopping_conditions = [
110110
MaxNGen(max_ngen),
111-
Stagnation(self.lambda_, self.problem_size),
112-
TolHistFun(self.lambda_, self.problem_size),
113-
EqualFunVals(self.lambda_, self.problem_size),
111+
Stagnationv2(lambda_, self.problem_size),
112+
TolHistFun(lambda_, self.problem_size),
113+
EqualFunVals(lambda_, self.problem_size),
114114
NoEffectAxis(self.problem_size),
115115
TolUpSigma(float(self.sigma)),
116116
TolX(),
@@ -220,6 +220,6 @@ def check_termination(self, gen):
220220
if c.criteria_met:
221221
logger.info(
222222
"CMA stopped because of termination criteria: " +
223-
" ".join(c.name)
223+
"" + " ".join(c.name)
224224
)
225225
self.active = False

bluepyopt/deapext/optimisationsCMA.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
def _ind_convert_space(ind, convert_fcn):
4242
"""util function to pass the individual from normalized to real space and
4343
inversely"""
44-
4544
return [f(x) for f, x in zip(convert_fcn, ind)]
4645

4746

@@ -359,7 +358,7 @@ def run(
359358
pickle.dump(cp, open(cp_filename_tmp, "wb"))
360359
if os.path.isfile(cp_filename_tmp):
361360
shutil.copy(cp_filename_tmp, cp_filename)
362-
logger.debug('Wrote checkpoint to %s', cp_filename)
361+
logger.debug("Wrote checkpoint to %s", cp_filename)
363362

364363
CMA_es.map_function = temp_mf
365364

bluepyopt/deapext/stoppingCriteria.py

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ def check(self, kwargs):
7575
fitness = [ind.fitness.reduce for ind in population]
7676
fitness.sort()
7777

78-
self.best.append(fitness[0])
79-
self.median.append(fitness[int(round(len(fitness) / 2.0))])
78+
# condition to avoid duplicates when re-starting
79+
if len(self.best) < ngen:
80+
self.best.append(fitness[0])
81+
self.median.append(fitness[int(round(len(fitness) / 2.0))])
8082
self.stagnation_iter = int(
8183
numpy.ceil(
8284
0.2 * ngen + 120 + 30.0 * self.problem_size / self.lambda_
@@ -95,6 +97,64 @@ def check(self, kwargs):
9597
self.criteria_met = True
9698

9799

100+
class Stagnationv2(bluepyopt.stoppingCriteria.StoppingCriteria):
101+
"""Stagnation stopping criteria class"""
102+
103+
name = "Stagnationv2"
104+
105+
def __init__(
106+
self, lambda_, problem_size, threshold=0.01, std_threshold=0.02
107+
):
108+
"""Constructor
109+
110+
Args:
111+
lambda_ (int): offspring size
112+
problem_size (int): problem size
113+
threshold (float): 1st criterion is triggered if best fitness
114+
improves less than this threshold for 100 generations
115+
std_threshold (float): 2nd criterion is triggered if
116+
standard deviation of the best fitness over
117+
the last 20 generations is below the best fitness multiplied
118+
by this threshold
119+
"""
120+
super(Stagnationv2, self).__init__()
121+
122+
self.lambda_ = lambda_
123+
self.problem_size = problem_size
124+
self.stagnation_iter = int(
125+
numpy.ceil(
126+
0.2 * ngen + 120 + 30.0 * self.problem_size / self.lambda_
127+
)
128+
)
129+
self.threshold = threshold
130+
self.std_threshold = std_threshold
131+
132+
self.best = []
133+
134+
def check(self, kwargs):
135+
"""Check if best model fitness does not improve over 1% over 100 gens
136+
and is not noisy in the last 20 generations
137+
"""
138+
ngen = kwargs.get("gen")
139+
population = kwargs.get("population")
140+
fitness = [ind.fitness.reduce for ind in population]
141+
fitness.sort()
142+
143+
# condition to avoid duplicates when re-starting
144+
if len(self.best) < ngen:
145+
self.best.append(fitness[0])
146+
147+
crit1 = len(self.best) > self.stagnation_iter
148+
crit2 = numpy.median(self.best[-20:]) * (1 + self.threshold) \
149+
> numpy.median(self.best[-120:-100])
150+
crit3 = numpy.std(self.best[-20:]) < (
151+
self.std_threshold * self.best[-1]
152+
)
153+
154+
if crit1 and crit2 and crit3:
155+
self.criteria_met = True
156+
157+
98158
class TolHistFun(bluepyopt.stoppingCriteria.StoppingCriteria):
99159
"""TolHistFun stopping criteria class"""
100160

@@ -105,8 +165,7 @@ def __init__(self, lambda_, problem_size):
105165
super(TolHistFun, self).__init__()
106166
self.tolhistfun = 10 ** -12
107167
self.mins = deque(
108-
maxlen=10 + int(numpy.ceil(30.0 * problem_size / lambda_))
109-
)
168+
maxlen=10 + int(numpy.ceil(30.0 * problem_size / lambda_)))
110169

111170
def check(self, kwargs):
112171
"""Check if the range of the best values is smaller than

0 commit comments

Comments
 (0)