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

Commit a0c8010

Browse files
authored
Merge pull request #383 from BlueBrain/CMA_rebase
Add the CMA optimisation strategy
2 parents 7dfae69 + 88d3344 commit a0c8010

File tree

19 files changed

+1854
-54
lines changed

19 files changed

+1854
-54
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ x86_64
1111
/cov_reports
1212
.coverage
1313
coverage.xml
14+
.idea/

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2016-2020, EPFL/Blue Brain Project
1+
# Copyright (c) 2016-2022, EPFL/Blue Brain Project
22
#
33
# This file is part of BluePyOpt <https://github.com/BlueBrain/BluePyOpt>
44
#

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Examples and test are BSD-licensed.
66
External dependencies are either LGPL or BSD-licensed.
77
See file ACKNOWLEDGEMENTS.txt and AUTHORS.txt for further details.
88

9-
Copyright (c) Blue Brain Project/EPFL 2016-2021.
9+
Copyright (c) Blue Brain Project/EPFL 2016-2022.
1010

1111
This program is free software: you can redistribute it and/or modify it under
1212
the terms of the GNU Lesser General Public License as published by the

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ Funding
198198
This work has been partially funded by the European Union Seventh Framework Program (FP7/2007­2013) under grant agreement no. 604102 (HBP), the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 720270, 785907 (Human Brain Project SGA1/SGA2) and by the EBRAINS research infrastructure, funded from the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 945539 (Human Brain Project SGA3).
199199
This project/research was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government’s ETH Board of the Swiss Federal Institutes of Technology.
200200

201-
Copyright (c) 2016-2021 Blue Brain Project/EPFL
201+
Copyright (c) 2016-2022 Blue Brain Project/EPFL
202202

203203
..
204204
The following image is also defined in the index.rst file, as the relative path is

bluepyopt/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Init script"""
22

33
"""
4-
Copyright (c) 2016-2020, EPFL/Blue Brain Project
4+
Copyright (c) 2016-2022, EPFL/Blue Brain Project
55
66
This file is part of BluePyOpt <https://github.com/BlueBrain/BluePyOpt>
77
@@ -32,6 +32,7 @@
3232
import bluepyopt.deapext.algorithms
3333
import bluepyopt.stoppingCriteria
3434
import bluepyopt.deapext.optimisations
35+
import bluepyopt.deapext.optimisationsCMA
3536

3637
# Add some backward compatibility for the time when DEAPoptimisation not in
3738
# deapext yet

bluepyopt/deapext/CMA_MO.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
"""Multi Objective CMA-es class"""
2+
3+
"""
4+
Copyright (c) 2016-2022, EPFL/Blue Brain Project
5+
6+
This file is part of BluePyOpt <https://github.com/BlueBrain/BluePyOpt>
7+
8+
This library is free software; you can redistribute it and/or modify it under
9+
the terms of the GNU Lesser General Public License version 3.0 as published
10+
by the Free Software Foundation.
11+
12+
This library is distributed in the hope that it will be useful, but WITHOUT
13+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14+
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15+
details.
16+
17+
You should have received a copy of the GNU Lesser General Public License
18+
along with this library; if not, write to the Free Software Foundation, Inc.,
19+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20+
"""
21+
22+
# pylint: disable=R0912, R0914
23+
24+
import logging
25+
import numpy
26+
import copy
27+
from math import log
28+
29+
import deap
30+
from deap import base
31+
from deap import cma
32+
33+
from .stoppingCriteria import MaxNGen
34+
from . import utils
35+
from . import hype
36+
37+
logger = logging.getLogger("__main__")
38+
39+
40+
def get_hyped(pop, ubound_score=250., threshold_improvement=240.):
41+
"""Compute the hypervolume contribution of each individual.
42+
The fitness space is first bounded and all dimension who do not show
43+
improvement are ignored.
44+
"""
45+
46+
# Cap the obj at 250
47+
points = numpy.array([ind.fitness.values for ind in pop])
48+
points[points > ubound_score] = ubound_score
49+
lbounds = numpy.min(points, axis=0)
50+
ubounds = numpy.max(points, axis=0)
51+
52+
# Remove the dimensions that do not show any improvement
53+
to_remove = []
54+
for i, lb in enumerate(lbounds):
55+
if lb >= threshold_improvement:
56+
to_remove.append(i)
57+
points = numpy.delete(points, to_remove, axis=1)
58+
lbounds = numpy.delete(lbounds, to_remove)
59+
ubounds = numpy.delete(ubounds, to_remove)
60+
61+
if not len(lbounds):
62+
logger.warning("No dimension along which to compute the hypervolume.")
63+
return [0.] * len(pop)
64+
65+
# Rescale the objective space
66+
# Note: 2 here is a magic number used to make the hypercube larger than it
67+
# really is. It makes sure that the individual always have a non-zero
68+
# hyper-volume contribution and improves the results while avoiding an
69+
# edge case.
70+
points = (points - lbounds) / numpy.max(ubounds.flatten())
71+
ubounds = numpy.max(points, axis=0) + 2.0
72+
73+
hv = hype.hypeIndicatorSampled(
74+
points=points, bounds=ubounds, k=5, nrOfSamples=200000
75+
)
76+
return hv
77+
78+
79+
class CMA_MO(cma.StrategyMultiObjective):
80+
"""Multiple objective covariance matrix adaption"""
81+
82+
def __init__(
83+
self,
84+
centroids,
85+
offspring_size,
86+
sigma,
87+
max_ngen,
88+
IndCreator,
89+
RandIndCreator,
90+
weight_hv=0.5,
91+
map_function=None,
92+
use_scoop=False,
93+
):
94+
"""Constructor
95+
96+
Args:
97+
centroid (list): initial guess used as the starting point of
98+
the CMA-ES
99+
offspring_size (int): number of offspring individuals in each
100+
generation
101+
sigma (float): initial standard deviation of the distribution
102+
max_ngen (int): total number of generation to run
103+
IndCreator (fcn): function returning an individual of the pop
104+
RandIndCreator (fcn): function creating a random individual.
105+
weight_hv (float): between 0 and 1. Weight given to the
106+
hypervolume contribution when computing the score of an
107+
individual in MO-CMA. The weight of the fitness contribution
108+
is computed as 1 - weight_hv.
109+
map_function (map): function used to map (parallelize) the
110+
evaluation function calls
111+
use_scoop (bool): use scoop map for parallel computation
112+
"""
113+
114+
if offspring_size is None:
115+
lambda_ = int(4 + 3 * log(len(RandIndCreator())))
116+
else:
117+
lambda_ = offspring_size
118+
119+
if centroids is None:
120+
starters = [RandIndCreator() for i in range(lambda_)]
121+
else:
122+
if len(centroids) != lambda_:
123+
from itertools import cycle
124+
125+
generator = cycle(centroids)
126+
starters = [
127+
copy.deepcopy(next(generator)) for i in range(lambda_)
128+
]
129+
else:
130+
starters = centroids
131+
132+
cma.StrategyMultiObjective.__init__(
133+
self, starters, sigma, mu=int(lambda_ * 0.5), lambda_=lambda_
134+
)
135+
136+
self.population = []
137+
self.problem_size = len(starters[0])
138+
139+
self.weight_hv = weight_hv
140+
141+
self.map_function = map_function
142+
self.use_scoop = use_scoop
143+
144+
# Toolbox specific to this CMA-ES
145+
self.toolbox = base.Toolbox()
146+
self.toolbox.register("generate", self.generate, IndCreator)
147+
self.toolbox.register("update", self.update)
148+
149+
if self.use_scoop:
150+
if self.map_function:
151+
raise Exception(
152+
"Impossible to use scoop and provide self defined map "
153+
"function: %s" % self.map_function
154+
)
155+
from scoop import futures
156+
157+
self.map_function = futures.map
158+
159+
# Set termination conditions
160+
self.active = True
161+
if max_ngen <= 0:
162+
max_ngen = 100 + 50 * (self.problem_size + 3) ** 2 / numpy.sqrt(
163+
self.lambda_
164+
)
165+
166+
self.stopping_conditions = [MaxNGen(max_ngen)]
167+
168+
def _select(self, candidates):
169+
"""Select the best candidates of the population
170+
171+
Fill the next population (chosen) with the Pareto fronts until there
172+
is not enough space. When an entire front does not fit in the space
173+
left we rely on a mixture of hypervolume and fitness. The respective
174+
weights of hypervolume and fitness are "hv" and "1-hv". The remaining
175+
fronts are explicitly not chosen"""
176+
177+
if self.weight_hv == 0.0:
178+
fit = [numpy.sum(ind.fitness.values) for ind in candidates]
179+
idx_scores = list(numpy.argsort(fit))
180+
181+
elif self.weight_hv == 1.0:
182+
hv = get_hyped(candidates)
183+
idx_scores = list(numpy.argsort(hv))[::-1]
184+
185+
else:
186+
hv = get_hyped(candidates)
187+
idx_hv = list(numpy.argsort(hv))[::-1]
188+
fit = [numpy.sum(ind.fitness.values) for ind in candidates]
189+
idx_fit = list(numpy.argsort(fit))
190+
scores = []
191+
for i in range(len(candidates)):
192+
score = (self.weight_hv * idx_hv.index(i)) + (
193+
(1.0 - self.weight_hv) * idx_fit.index(i)
194+
)
195+
scores.append(score)
196+
idx_scores = list(numpy.argsort(scores))
197+
198+
chosen = [candidates[i] for i in idx_scores[: self.mu]]
199+
not_chosen = [candidates[i] for i in idx_scores[self.mu:]]
200+
return chosen, not_chosen
201+
202+
def get_population(self, to_space):
203+
"""Returns the population in the original parameter space"""
204+
pop = copy.deepcopy(self.population)
205+
for i, ind in enumerate(pop):
206+
for j, v in enumerate(ind):
207+
pop[i][j] = to_space[j](v)
208+
return pop
209+
210+
def get_parents(self, to_space):
211+
"""Returns the population in the original parameter space"""
212+
pop = copy.deepcopy(self.parents)
213+
for i, ind in enumerate(pop):
214+
for j, v in enumerate(ind):
215+
pop[i][j] = to_space[j](v)
216+
return pop
217+
218+
def generate_new_pop(self, lbounds, ubounds):
219+
"""Generate a new population bounded in the normalized space"""
220+
self.population = self.toolbox.generate()
221+
return utils.bound(self.population, lbounds, ubounds)
222+
223+
def update_strategy(self):
224+
self.toolbox.update(self.population)
225+
226+
def set_fitness(self, fitnesses):
227+
for f, ind in zip(fitnesses, self.population):
228+
ind.fitness.values = f
229+
230+
def set_fitness_parents(self, fitnesses):
231+
for f, ind in zip(fitnesses, self.parents):
232+
ind.fitness.values = f
233+
234+
def check_termination(self, gen):
235+
stopping_params = {
236+
"gen": gen,
237+
"population": self.population,
238+
}
239+
240+
[c.check(stopping_params) for c in self.stopping_conditions]
241+
for c in self.stopping_conditions:
242+
if c.criteria_met:
243+
logger.info(
244+
"CMA stopped because of termination criteria: " +
245+
" ".join(c.name)
246+
)
247+
self.active = False

0 commit comments

Comments
 (0)