472 lines
21 KiB
Python
472 lines
21 KiB
Python
# This file is part of DEAP.
|
|
#
|
|
# DEAP is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as
|
|
# published by the Free Software Foundation, either version 3 of
|
|
# the License, or (at your option) any later version.
|
|
#
|
|
# DEAP is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with DEAP. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""The :mod:`algorithms` module is intended to contain some specific algorithms
|
|
in order to execute very common evolutionary algorithms. The method used here
|
|
are more for convenience than reference as the implementation of every
|
|
evolutionary algorithm may vary infinitely. Most of the algorithms in this
|
|
module use operators registered in the toolbox. Generaly, the keyword used are
|
|
:meth:`mate` for crossover, :meth:`mutate` for mutation, :meth:`~deap.select`
|
|
for selection and :meth:`evaluate` for evaluation.
|
|
|
|
You are encouraged to write your own algorithms in order to make them do what
|
|
you really want them to do.
|
|
"""
|
|
|
|
import random
|
|
|
|
import tools
|
|
|
|
def varAnd(population, toolbox, cxpb, mutpb):
|
|
"""Part of an evolutionary algorithm applying only the variation part
|
|
(crossover **and** mutation). The modified individuals have their
|
|
fitness invalidated. The individuals are cloned so returned population is
|
|
independent of the input population.
|
|
|
|
:param population: A list of individuals to vary.
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param cxpb: The probability of mating two individuals.
|
|
:param mutpb: The probability of mutating an individual.
|
|
:returns: A list of varied individuals that are independent of their
|
|
parents.
|
|
|
|
The variation goes as follow. First, the parental population
|
|
:math:`P_\mathrm{p}` is duplicated using the :meth:`toolbox.clone` method
|
|
and the result is put into the offspring population :math:`P_\mathrm{o}`.
|
|
A first loop over :math:`P_\mathrm{o}` is executed to mate pairs of consecutive
|
|
individuals. According to the crossover probability *cxpb*, the
|
|
individuals :math:`\mathbf{x}_i` and :math:`\mathbf{x}_{i+1}` are mated
|
|
using the :meth:`toolbox.mate` method. The resulting children
|
|
:math:`\mathbf{y}_i` and :math:`\mathbf{y}_{i+1}` replace their respective
|
|
parents in :math:`P_\mathrm{o}`. A second loop over the resulting
|
|
:math:`P_\mathrm{o}` is executed to mutate every individual with a
|
|
probability *mutpb*. When an individual is mutated it replaces its not
|
|
mutated version in :math:`P_\mathrm{o}`. The resulting
|
|
:math:`P_\mathrm{o}` is returned.
|
|
|
|
This variation is named *And* beceause of its propention to apply both
|
|
crossover and mutation on the individuals. Note that both operators are
|
|
not applied systematicaly, the resulting individuals can be generated from
|
|
crossover only, mutation only, crossover and mutation, and reproduction
|
|
according to the given probabilities. Both probabilities should be in
|
|
:math:`[0, 1]`.
|
|
"""
|
|
offspring = [toolbox.clone(ind) for ind in population]
|
|
|
|
# Apply crossover and mutation on the offspring
|
|
for i in range(1, len(offspring), 2):
|
|
if random.random() < cxpb:
|
|
offspring[i-1], offspring[i] = toolbox.mate(offspring[i-1], offspring[i])
|
|
del offspring[i-1].fitness.values, offspring[i].fitness.values
|
|
|
|
for i in range(len(offspring)):
|
|
if random.random() < mutpb:
|
|
offspring[i], = toolbox.mutate(offspring[i])
|
|
del offspring[i].fitness.values
|
|
|
|
return offspring
|
|
|
|
def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None,
|
|
halloffame=None, verbose=__debug__):
|
|
"""This algorithm reproduce the simplest evolutionary algorithm as
|
|
presented in chapter 7 of [Back2000]_.
|
|
|
|
:param population: A list of individuals.
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param cxpb: The probability of mating two individuals.
|
|
:param mutpb: The probability of mutating an individual.
|
|
:param ngen: The number of generation.
|
|
:param stats: A :class:`~deap.tools.Statistics` object that is updated
|
|
inplace, optional.
|
|
:param halloffame: A :class:`~deap.tools.HallOfFame` object that will
|
|
contain the best individuals, optional.
|
|
:param verbose: Whether or not to log the statistics.
|
|
:returns: The final population and a :class:`~deap.tools.Logbook`
|
|
with the statistics of the evolution.
|
|
|
|
The algorithm takes in a population and evolves it in place using the
|
|
:meth:`varAnd` method. It returns the optimized population and a
|
|
:class:`~deap.tools.Logbook` with the statistics of the evolution (if
|
|
any). The logbook will contain the generation number, the number of
|
|
evalutions for each generation and the statistics if a
|
|
:class:`~deap.tools.Statistics` if any. The *cxpb* and *mutpb* arguments
|
|
are passed to the :func:`varAnd` function. The pseudocode goes as follow
|
|
::
|
|
|
|
evaluate(population)
|
|
for g in range(ngen):
|
|
population = select(population, len(population))
|
|
offspring = varAnd(population, toolbox, cxpb, mutpb)
|
|
evaluate(offspring)
|
|
population = offspring
|
|
|
|
As stated in the pseudocode above, the algorithm goes as follow. First, it
|
|
evaluates the individuals with an invalid fitness. Second, it enters the
|
|
generational loop where the selection procedure is applied to entirely
|
|
replace the parental population. The 1:1 replacement ratio of this
|
|
algorithm **requires** the selection procedure to be stochastic and to
|
|
select multiple times the same individual, for example,
|
|
:func:`~deap.tools.selTournament` and :func:`~deap.tools.selRoulette`.
|
|
Third, it applies the :func:`varAnd` function to produce the next
|
|
generation population. Fourth, it evaluates the new individuals and
|
|
compute the statistics on this population. Finally, when *ngen*
|
|
generations are done, the algorithm returns a tuple with the final
|
|
population and a :class:`~deap.tools.Logbook` of the evolution.
|
|
|
|
.. note::
|
|
|
|
Using a non-stochastic selection method will result in no selection as
|
|
the operator selects *n* individuals from a pool of *n*.
|
|
|
|
This function expects the :meth:`toolbox.mate`, :meth:`toolbox.mutate`,
|
|
:meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be
|
|
registered in the toolbox.
|
|
|
|
.. [Back2000] Back, Fogel and Michalewicz, "Evolutionary Computation 1 :
|
|
Basic Algorithms and Operators", 2000.
|
|
"""
|
|
logbook = tools.Logbook()
|
|
logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in population if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
if halloffame is not None:
|
|
halloffame.update(population)
|
|
|
|
record = stats.compile(population) if stats else {}
|
|
logbook.record(gen=0, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
# Begin the generational process
|
|
for gen in range(1, ngen+1):
|
|
# Select the next generation individuals
|
|
offspring = toolbox.select(population, len(population))
|
|
|
|
# Vary the pool of individuals
|
|
offspring = varAnd(offspring, toolbox, cxpb, mutpb)
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
# Update the hall of fame with the generated individuals
|
|
if halloffame is not None:
|
|
halloffame.update(offspring)
|
|
|
|
# Replace the current population by the offspring
|
|
population[:] = offspring
|
|
|
|
# Append the current generation statistics to the logbook
|
|
record = stats.compile(population) if stats else {}
|
|
logbook.record(gen=gen, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
return population, logbook
|
|
|
|
def varOr(population, toolbox, lambda_, cxpb, mutpb):
|
|
"""Part of an evolutionary algorithm applying only the variation part
|
|
(crossover, mutation **or** reproduction). The modified individuals have
|
|
their fitness invalidated. The individuals are cloned so returned
|
|
population is independent of the input population.
|
|
|
|
:param population: A list of individuals to vary.
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param lambda\_: The number of children to produce
|
|
:param cxpb: The probability of mating two individuals.
|
|
:param mutpb: The probability of mutating an individual.
|
|
:returns: A list of varied individuals that are independent of their
|
|
parents.
|
|
|
|
The variation goes as follow. On each of the *lambda_* iteration, it
|
|
selects one of the three operations; crossover, mutation or reproduction.
|
|
In the case of a crossover, two individuals are selected at random from
|
|
the parental population :math:`P_\mathrm{p}`, those individuals are cloned
|
|
using the :meth:`toolbox.clone` method and then mated using the
|
|
:meth:`toolbox.mate` method. Only the first child is appended to the
|
|
offspring population :math:`P_\mathrm{o}`, the second child is discarded.
|
|
In the case of a mutation, one individual is selected at random from
|
|
:math:`P_\mathrm{p}`, it is cloned and then mutated using using the
|
|
:meth:`toolbox.mutate` method. The resulting mutant is appended to
|
|
:math:`P_\mathrm{o}`. In the case of a reproduction, one individual is
|
|
selected at random from :math:`P_\mathrm{p}`, cloned and appended to
|
|
:math:`P_\mathrm{o}`.
|
|
|
|
This variation is named *Or* beceause an offspring will never result from
|
|
both operations crossover and mutation. The sum of both probabilities
|
|
shall be in :math:`[0, 1]`, the reproduction probability is
|
|
1 - *cxpb* - *mutpb*.
|
|
"""
|
|
assert (cxpb + mutpb) <= 1.0, ("The sum of the crossover and mutation "
|
|
"probabilities must be smaller or equal to 1.0.")
|
|
|
|
offspring = []
|
|
for _ in xrange(lambda_):
|
|
op_choice = random.random()
|
|
if op_choice < cxpb: # Apply crossover
|
|
ind1, ind2 = map(toolbox.clone, random.sample(population, 2))
|
|
ind1, ind2 = toolbox.mate(ind1, ind2)
|
|
del ind1.fitness.values
|
|
offspring.append(ind1)
|
|
elif op_choice < cxpb + mutpb: # Apply mutation
|
|
ind = toolbox.clone(random.choice(population))
|
|
ind, = toolbox.mutate(ind)
|
|
del ind.fitness.values
|
|
offspring.append(ind)
|
|
else: # Apply reproduction
|
|
offspring.append(random.choice(population))
|
|
|
|
return offspring
|
|
|
|
def eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen,
|
|
stats=None, halloffame=None, verbose=__debug__):
|
|
"""This is the :math:`(\mu + \lambda)` evolutionary algorithm.
|
|
|
|
:param population: A list of individuals.
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param mu: The number of individuals to select for the next generation.
|
|
:param lambda\_: The number of children to produce at each generation.
|
|
:param cxpb: The probability that an offspring is produced by crossover.
|
|
:param mutpb: The probability that an offspring is produced by mutation.
|
|
:param ngen: The number of generation.
|
|
:param stats: A :class:`~deap.tools.Statistics` object that is updated
|
|
inplace, optional.
|
|
:param halloffame: A :class:`~deap.tools.HallOfFame` object that will
|
|
contain the best individuals, optional.
|
|
:param verbose: Whether or not to log the statistics.
|
|
:returns: The final population and a :class:`~deap.tools.Logbook`
|
|
with the statistics of the evolution.
|
|
|
|
The algorithm takes in a population and evolves it in place using the
|
|
:meth:`varOr` method. It returns the optimized population and a
|
|
:class:`~deap.tools.Logbook` with the statistics of the evolution (if
|
|
any). The logbook will contain the generation number, the number of
|
|
evalutions for each generation and the statistics if a
|
|
:class:`~deap.tools.Statistics` if any. The *cxpb* and *mutpb* arguments
|
|
are passed to the :func:`varAnd` function. The pseudocode goes as follow
|
|
::
|
|
|
|
evaluate(population)
|
|
for g in range(ngen):
|
|
offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
|
|
evaluate(offspring)
|
|
population = select(population + offspring, mu)
|
|
|
|
First, the individuals having an invalid fitness are evaluated. Second,
|
|
the evolutionary loop begins by producing *lambda_* offspring from the
|
|
population, the offspring are generated by the :func:`varOr` function. The
|
|
offspring are then evaluated and the next generation population is
|
|
selected from both the offspring **and** the population. Finally, when
|
|
*ngen* generations are done, the algorithm returns a tuple with the final
|
|
population and a :class:`~deap.tools.Logbook` of the evolution.
|
|
|
|
.. note::
|
|
|
|
Care must be taken when the lambda:mu ratio is 1 to 1 as a non-stochastic
|
|
selection will result in no selection at all as
|
|
the operator selects *lambda* individuals from a pool of *mu*.
|
|
|
|
This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`,
|
|
:meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be
|
|
registered in the toolbox. This algorithm uses the :func:`varOr`
|
|
variation.
|
|
"""
|
|
logbook = tools.Logbook()
|
|
logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in population if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
if halloffame is not None:
|
|
halloffame.update(population)
|
|
|
|
record = stats.compile(population) if stats is not None else {}
|
|
logbook.record(gen=0, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
# Begin the generational process
|
|
for gen in range(1, ngen+1):
|
|
# Vary the population
|
|
offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
# Update the hall of fame with the generated individuals
|
|
if halloffame is not None:
|
|
halloffame.update(offspring)
|
|
|
|
# Select the next generation population
|
|
population[:] = toolbox.select(population + offspring, mu)
|
|
|
|
# Update the statistics with the new population
|
|
record = stats.compile(population) if stats is not None else {}
|
|
logbook.record(gen=gen, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
return population, logbook
|
|
|
|
def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen,
|
|
stats=None, halloffame=None, verbose=__debug__):
|
|
"""This is the :math:`(\mu~,~\lambda)` evolutionary algorithm.
|
|
|
|
:param population: A list of individuals.
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param mu: The number of individuals to select for the next generation.
|
|
:param lambda\_: The number of children to produce at each generation.
|
|
:param cxpb: The probability that an offspring is produced by crossover.
|
|
:param mutpb: The probability that an offspring is produced by mutation.
|
|
:param ngen: The number of generation.
|
|
:param stats: A :class:`~deap.tools.Statistics` object that is updated
|
|
inplace, optional.
|
|
:param halloffame: A :class:`~deap.tools.HallOfFame` object that will
|
|
contain the best individuals, optional.
|
|
:param verbose: Whether or not to log the statistics.
|
|
:returns: The final population.
|
|
|
|
First, the individuals having an invalid fitness are evaluated. Then, the
|
|
evolutionary loop begins by producing *lambda_* offspring from the
|
|
population, the offspring are generated by a crossover, a mutation or a
|
|
reproduction proportionally to the probabilities *cxpb*, *mutpb* and 1 -
|
|
(cxpb + mutpb). The offspring are then evaluated and the next generation
|
|
population is selected **only** from the offspring. Briefly, the operators
|
|
are applied as following ::
|
|
|
|
evaluate(population)
|
|
for i in range(ngen):
|
|
offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
|
|
evaluate(offspring)
|
|
population = select(offspring, mu)
|
|
|
|
This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`,
|
|
:meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be
|
|
registered in the toolbox. This algorithm uses the :func:`varOr`
|
|
variation.
|
|
"""
|
|
assert lambda_ >= mu, "lambda must be greater or equal to mu."
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in population if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
if halloffame is not None:
|
|
halloffame.update(population)
|
|
|
|
logbook = tools.Logbook()
|
|
logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
|
|
|
|
record = stats.compile(population) if stats is not None else {}
|
|
logbook.record(gen=0, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
# Begin the generational process
|
|
for gen in range(1, ngen+1):
|
|
# Vary the population
|
|
offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
|
|
|
|
# Evaluate the individuals with an invalid fitness
|
|
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
|
|
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
|
|
for ind, fit in zip(invalid_ind, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
# Update the hall of fame with the generated individuals
|
|
if halloffame is not None:
|
|
halloffame.update(offspring)
|
|
|
|
# Select the next generation population
|
|
population[:] = toolbox.select(offspring, mu)
|
|
|
|
# Update the statistics with the new population
|
|
record = stats.compile(population) if stats is not None else {}
|
|
logbook.record(gen=gen, nevals=len(invalid_ind), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
|
|
return population, logbook
|
|
|
|
def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None,
|
|
verbose=__debug__):
|
|
"""This is algorithm implements the ask-tell model proposed in
|
|
[Colette2010]_, where ask is called `generate` and tell is called `update`.
|
|
|
|
:param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
|
|
operators.
|
|
:param ngen: The number of generation.
|
|
:param stats: A :class:`~deap.tools.Statistics` object that is updated
|
|
inplace, optional.
|
|
:param halloffame: A :class:`~deap.tools.HallOfFame` object that will
|
|
contain the best individuals, optional.
|
|
:param verbose: Whether or not to log the statistics.
|
|
|
|
:returns: The final population.
|
|
|
|
The toolbox should contain a reference to the generate and the update method
|
|
of the chosen strategy.
|
|
|
|
.. [Colette2010] Collette, Y., N. Hansen, G. Pujol, D. Salazar Aponte and
|
|
R. Le Riche (2010). On Object-Oriented Programming of Optimizers -
|
|
Examples in Scilab. In P. Breitkopf and R. F. Coelho, eds.:
|
|
Multidisciplinary Design Optimization in Computational Mechanics,
|
|
Wiley, pp. 527-565;
|
|
|
|
"""
|
|
logbook = tools.Logbook()
|
|
logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
|
|
|
|
for gen in xrange(ngen):
|
|
# Generate a new population
|
|
population = toolbox.generate()
|
|
# Evaluate the individuals
|
|
fitnesses = toolbox.map(toolbox.evaluate, population)
|
|
for ind, fit in zip(population, fitnesses):
|
|
ind.fitness.values = fit
|
|
|
|
if halloffame is not None:
|
|
halloffame.update(population)
|
|
|
|
# Update the strategy with the evaluated individuals
|
|
toolbox.update(population)
|
|
|
|
record = stats.compile(population) if stats is not None else {}
|
|
logbook.record(gen=gen, nevals=len(population), **record)
|
|
if verbose:
|
|
print logbook.stream
|
|
|
|
return population, logbook |