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

Commit f03ed3a

Browse files
Merge pull request #450 from AurelienJaquier/stopcrit-PR-clean
New stagnation stopping criterion
2 parents 2f3a548 + 1c61a7c commit f03ed3a

File tree

4 files changed

+108
-27
lines changed

4 files changed

+108
-27
lines changed

bluepyopt/deapext/CMA_MO.py

Lines changed: 13 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

@@ -90,6 +90,7 @@ def __init__(
9090
weight_hv=0.5,
9191
map_function=None,
9292
use_scoop=False,
93+
use_stagnation_criterion=True,
9394
):
9495
"""Constructor
9596
@@ -109,6 +110,8 @@ def __init__(
109110
map_function (map): function used to map (parallelize) the
110111
evaluation function calls
111112
use_scoop (bool): use scoop map for parallel computation
113+
use_stagnation_criterion (bool): whether to use the stagnation
114+
stopping criterion on top of the maximum generation criterion
112115
"""
113116

114117
if offspring_size is None:
@@ -160,10 +163,16 @@ def __init__(
160163
self.active = True
161164
if max_ngen <= 0:
162165
max_ngen = 100 + 50 * (self.problem_size + 3) ** 2 / numpy.sqrt(
163-
self.lambda_
166+
lambda_
164167
)
165168

166-
self.stopping_conditions = [MaxNGen(max_ngen)]
169+
self.stopping_conditions = [
170+
MaxNGen(max_ngen),
171+
]
172+
if use_stagnation_criterion:
173+
self.stopping_conditions.append(
174+
Stagnationv2(lambda_, self.problem_size)
175+
)
167176

168177
def _select(self, candidates):
169178
"""Select the best candidates of the population
@@ -242,6 +251,6 @@ def check_termination(self, gen):
242251
if c.criteria_met:
243252
logger.info(
244253
"CMA stopped because of termination criteria: " +
245-
" ".join(c.name)
254+
"" + " ".join(c.name)
246255
)
247256
self.active = False

bluepyopt/deapext/CMA_SO.py

Lines changed: 23 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,
@@ -59,21 +59,24 @@ def __init__(
5959
RandIndCreator,
6060
map_function=None,
6161
use_scoop=False,
62+
use_stagnation_criterion=True,
6263
):
6364
"""Constructor
6465
6566
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
67+
centroid (list): initial guess used as the starting point of
68+
the CMA-ES
69+
offspring_size (int): number of offspring individuals in each
70+
generation
71+
sigma (float): initial standard deviation of the distribution
72+
max_ngen (int): total number of generation to run
73+
IndCreator (fcn): function returning an individual of the pop
74+
RandIndCreator (fcn): function creating a random individual.
75+
map_function (map): function used to map (parallelize) the
76+
evaluation function calls
77+
use_scoop (bool): use scoop map for parallel computation
78+
use_stagnation_criterion (bool): whether to use the stagnation
79+
stopping criterion on top of the maximum generation criterion
7780
"""
7881

7982
if offspring_size is None:
@@ -103,20 +106,23 @@ def __init__(
103106
self.active = True
104107
if max_ngen <= 0:
105108
max_ngen = 100 + 50 * (self.problem_size + 3) ** 2 / numpy.sqrt(
106-
self.lambda_
109+
lambda_
107110
)
108111

109112
self.stopping_conditions = [
110113
MaxNGen(max_ngen),
111-
Stagnation(self.lambda_, self.problem_size),
112-
TolHistFun(self.lambda_, self.problem_size),
113-
EqualFunVals(self.lambda_, self.problem_size),
114+
TolHistFun(lambda_, self.problem_size),
115+
EqualFunVals(lambda_, self.problem_size),
114116
NoEffectAxis(self.problem_size),
115117
TolUpSigma(float(self.sigma)),
116118
TolX(),
117119
ConditionCov(),
118120
NoEffectCoor(),
119121
]
122+
if use_stagnation_criterion:
123+
self.stopping_conditions.append(
124+
Stagnationv2(lambda_, self.problem_size)
125+
)
120126

121127
def update(self, population):
122128
"""Update the current covariance matrix strategy from the
@@ -220,6 +226,6 @@ def check_termination(self, gen):
220226
if c.criteria_met:
221227
logger.info(
222228
"CMA stopped because of termination criteria: " +
223-
" ".join(c.name)
229+
"" + " ".join(c.name)
224230
)
225231
self.active = False

bluepyopt/deapext/optimisationsCMA.py

Lines changed: 7 additions & 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

@@ -62,6 +61,7 @@ def __init__(
6261
selector_name="single_objective",
6362
weight_hv=0.5,
6463
fitness_reduce=numpy.sum,
64+
use_stagnation_criterion=True,
6565
):
6666
"""Constructor
6767
@@ -86,6 +86,8 @@ def __init__(
8686
is computed as 1 - weight_hv.
8787
fitness_reduce (fcn): function used to reduce the objective values
8888
to a single fitness score
89+
use_stagnation_criterion (bool): whether to use the stagnation
90+
stopping criterion on top of the maximum generation criterion
8991
"""
9092

9193
super(DEAPOptimisationCMA, self).__init__(evaluator=evaluator)
@@ -120,6 +122,8 @@ def __init__(
120122
"{}".format(self.selector_name)
121123
)
122124

125+
self.use_stagnation_criterion = use_stagnation_criterion
126+
123127
# Number of objective values
124128
self.problem_size = len(self.evaluator.params)
125129

@@ -287,6 +291,7 @@ def run(
287291
RandIndCreator=self.toolbox.RandomInd,
288292
map_function=self.map_function,
289293
use_scoop=self.use_scoop,
294+
use_stagnation_criterion=self.use_stagnation_criterion,
290295
)
291296

292297
if self.selector_name == "multi_objective":
@@ -359,7 +364,7 @@ def run(
359364
pickle.dump(cp, open(cp_filename_tmp, "wb"))
360365
if os.path.isfile(cp_filename_tmp):
361366
shutil.copy(cp_filename_tmp, cp_filename)
362-
logger.debug('Wrote checkpoint to %s', cp_filename)
367+
logger.debug("Wrote checkpoint to %s", cp_filename)
363368

364369
CMA_es.map_function = temp_mf
365370

bluepyopt/deapext/stoppingCriteria.py

Lines changed: 65 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,66 @@ 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 = None
125+
self.threshold = threshold
126+
self.std_threshold = std_threshold
127+
128+
self.best = []
129+
130+
def check(self, kwargs):
131+
"""Check if best model fitness does not improve over 1% over 100 gens
132+
and is not noisy in the last 20 generations
133+
"""
134+
ngen = kwargs.get("gen")
135+
population = kwargs.get("population")
136+
fitness = [ind.fitness.reduce for ind in population]
137+
fitness.sort()
138+
139+
# condition to avoid duplicates when re-starting
140+
if len(self.best) < ngen:
141+
self.best.append(fitness[0])
142+
143+
self.stagnation_iter = int(
144+
numpy.ceil(
145+
0.2 * ngen + 120 + 30.0 * self.problem_size / self.lambda_
146+
)
147+
)
148+
149+
crit1 = len(self.best) > self.stagnation_iter
150+
crit2 = numpy.median(self.best[-20:]) * (1 + self.threshold) \
151+
> numpy.median(self.best[-120:-100])
152+
crit3 = numpy.std(self.best[-20:]) < (
153+
self.std_threshold * self.best[-1]
154+
)
155+
156+
if crit1 and crit2 and crit3:
157+
self.criteria_met = True
158+
159+
98160
class TolHistFun(bluepyopt.stoppingCriteria.StoppingCriteria):
99161
"""TolHistFun stopping criteria class"""
100162

@@ -105,8 +167,7 @@ def __init__(self, lambda_, problem_size):
105167
super(TolHistFun, self).__init__()
106168
self.tolhistfun = 10 ** -12
107169
self.mins = deque(
108-
maxlen=10 + int(numpy.ceil(30.0 * problem_size / lambda_))
109-
)
170+
maxlen=10 + int(numpy.ceil(30.0 * problem_size / lambda_)))
110171

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

0 commit comments

Comments
 (0)