From efa6567359ad0df0269f5aada9f718550b6f688b Mon Sep 17 00:00:00 2001 From: nojhan Date: Sun, 5 Jul 2020 19:03:22 +0200 Subject: [PATCH] add FastGA foundry and eoStandardBitMutation variants --- eo/src/eo | 1 + eo/src/eoAlgoFoundry.h | 4 +- eo/src/eoAlgoFoundryFastGA.h | 251 ++++++++++++++++++++++++++++++ eo/src/eoEasyEA.h | 2 + eo/src/eoMergeReduce.h | 3 +- eo/src/eoOpContainer.h | 7 + eo/src/ga.h | 1 + eo/src/ga/eoStandardBitMutation.h | 213 +++++++++++++++++++++++++ eo/src/utils/eoRNG.h | 14 +- eo/test/CMakeLists.txt | 2 + eo/test/t-eoFoundryFastGA.cpp | 104 +++++++++++++ 11 files changed, 598 insertions(+), 4 deletions(-) create mode 100644 eo/src/eoAlgoFoundryFastGA.h create mode 100644 eo/src/ga/eoStandardBitMutation.h create mode 100644 eo/test/t-eoFoundryFastGA.cpp diff --git a/eo/src/eo b/eo/src/eo index 5cf749603..a296c7091 100644 --- a/eo/src/eo +++ b/eo/src/eo @@ -162,6 +162,7 @@ #include "eoForge.h" #include "eoAlgoFoundry.h" #include "eoAlgoFoundryEA.h" +#include "eoAlgoFoundryFastGA.h" #include "eoEvalFoundryEA.h" //----------------------------------------------------------------------------- diff --git a/eo/src/eoAlgoFoundry.h b/eo/src/eoAlgoFoundry.h index 9c815e157..0efa18a6d 100644 --- a/eo/src/eoAlgoFoundry.h +++ b/eo/src/eoAlgoFoundry.h @@ -32,7 +32,7 @@ * and hold the link to the encoding. @see eoAlgoFoundryEA * * As with eoForgeVector, adding a managed operator - *is done through public member variable's `add` method, + * is done through public member variable's `add` method, * which takes the class name as template and its constructor's parameters * as arguments. For example: * @code @@ -74,7 +74,7 @@ class eoOperatorFoundry : public eoForgeVector< Itf > * @endcode * * In a second step, the operators to be used should be selected - * by indicating their index, just like if the foundry was a array: + * by indicating their index, just like if the foundry was an array: * @code * foundry = {0, 1, 2}; * // ^ ^ ^ diff --git a/eo/src/eoAlgoFoundryFastGA.h b/eo/src/eoAlgoFoundryFastGA.h new file mode 100644 index 000000000..f9ab98f00 --- /dev/null +++ b/eo/src/eoAlgoFoundryFastGA.h @@ -0,0 +1,251 @@ + +/* + This library 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; + version 2 of the License. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + © 2020 Thales group + + Authors: + Johann Dreo +*/ + +#ifndef _eoAlgoFoundryFastGA_H_ +#define _eoAlgoFoundryFastGA_H_ + +#include +#include +#include + +/** A class that assemble an eoEasyEA on the fly, given a combination of available operators. + * + * The foundry should first be set up with sets of operators + * for the main modules of an EA: + * continuators, crossovers, mutations, selection and replacement operators. + * + * This is done through public member variable's `add` method, + * which takes the class name as template and its constructor's parameters + * as arguments. For example: + * @code + * foundry.selectors.add< eoStochTournamentSelect >( 0.5 ); + * @endcode + * + * @warning If the constructor takes a reference YOU SHOULD ABSOLUTELY wrap it + * in a `std::ref`, or it will silently be passed as a copy, + * which would effectively disable any link between operators. + * + * In a second step, the operators to be used should be selected + * by indicating their index, passing an array of eight elements: + * @code + * foundry.select({0, 1, 2, 3, 4, 5, 6, 7}); + * @endcode + * + * @note: by default, the firsts of the eight operators are selected. + * + * If you don't (want to) recall the order of the operators in the encoding, + * you can use the `index()` member, for example: + * @code + * foundry.at(foundry.continuators.index()) = 2; // select the third continuator + * @endcode + * + * Now, you can call the fourdry just like any eoAlgo, by passing it an eoPop: + * @code + * foundry(pop); + * @encode + * It will instantiate the needed operators (only) and the algorithm itself on-the-fly, + * and then run it. + * + * @note: Thanks to the underlying eoOperatorFoundry, not all the added operators are instantiated. + * Every instantiation is deferred upon actual use. That way, you can still reconfigure them + * at any time with `eoForgeOperator::setup`, for example: + * @code + * foundry.selector.at(0).setup(0.5); // using constructor's arguments + * @endcode + * + * @ingroup Foundry + * @ingroup Algorithms + */ +template +class eoAlgoFoundryFastGA : public eoAlgoFoundry +{ + public: + /** The constructon only take an eval, because all other operators + * are stored in the public containers. + */ + eoAlgoFoundryFastGA( eoInit & init, eoEvalFunc& eval, size_t max_evals = 10000, size_t max_restarts = std::numeric_limits::max() ) : + eoAlgoFoundry(8), + continuators(0, true), // Always re-instantiate continuators, because they hold a state. + crossover_rates(1, false), + crossovers(2, false), + mutation_rates(3, false), + mutations(4, false), + selectors(5, false), + pop_sizes(6, false), + replacements(7, false), + _eval(eval), + _init(init), + _max_evals(max_evals), + _max_restarts(max_restarts) + { } + + public: + + /* Operators containers @{ */ + eoOperatorFoundry< eoContinue > continuators; + eoOperatorFoundry< double > crossover_rates; + eoOperatorFoundry< eoQuadOp > crossovers; + eoOperatorFoundry< double > mutation_rates; + eoOperatorFoundry< eoMonOp > mutations; + eoOperatorFoundry< eoSelectOne > selectors; + eoOperatorFoundry< size_t > pop_sizes; + eoOperatorFoundry< eoReplacement > replacements; + /* @} */ + + /** instantiate and call the pre-selected algorithm. + */ + void operator()(eoPop& pop) + { + assert(continuators.size() > 0); assert(this->at(continuators.index()) < continuators.size()); + assert( crossover_rates.size() > 0); assert(this->at( crossover_rates.index()) < crossover_rates.size()); + assert( crossovers.size() > 0); assert(this->at( crossovers.index()) < crossovers.size()); + assert( mutation_rates.size() > 0); assert(this->at( mutation_rates.index()) < mutation_rates.size()); + assert( mutations.size() > 0); assert(this->at( mutations.index()) < mutations.size()); + assert( selectors.size() > 0); assert(this->at( selectors.index()) < selectors.size()); + assert( pop_sizes.size() > 0); assert(this->at( pop_sizes.index()) < pop_sizes.size()); + assert(replacements.size() > 0); assert(this->at(replacements.index()) < replacements.size()); + + // Crossover or clone + double cross_rate = this->crossover_rate(); + eoProportionalOp cross; + // Cross-over that produce only one offspring, + // made by wrapping the quad op (which produce 2 offsprings) + // in a bin op (which ignore the second offspring). + eoQuad2BinOp single_cross(this->crossover()); + cross.add(single_cross, cross_rate); + eoBinCloneOp cross_clone; + cross.add(cross_clone, 1 - cross_rate); // Clone + + // Mutation or clone + double mut_rate = this->mutation_rate(); + eoProportionalOp mut; + mut.add(this->mutation(), mut_rate); + eoMonCloneOp mut_clone; + mut.add(mut_clone, 1 - mut_rate); // FIXME TBC + + // Apply mutation after cross-over. + eoSequentialOp variator; + variator.add(cross,1.0); + variator.add(mut,1.0); + + // All variatiors + double lambda = this->pop_size(); + eoGeneralBreeder breeder(this->selector(), variator, lambda, /*as rate*/false); + + // Objective function calls counter + eoEvalCounterThrowException eval(_eval, _max_evals); + eoPopLoopEval pop_eval(eval); + + // Algorithm itself + eoEasyEA algo = eoEasyEA(this->continuator(), pop_eval, breeder, this->replacement()); + + // Restart wrapper + eoAlgoPopReset reset_pop(_init, pop_eval); + eoGenContinue restart_cont(_max_restarts); + eoAlgoRestart restart(eval, algo, restart_cont, reset_pop); + + try { + restart(pop); + } catch(eoMaxEvalException e) { + // In case some solutions were not evaluated when max eval occured. + eoPopLoopEval pop_last_eval(_eval); + pop_last_eval(pop,pop); + } + } + + /** Return an approximate name of the selected algorithm. + * + * @note: does not take into account parameters of the operators, + * only show class names. + */ + std::string name() + { + std::ostringstream name; + name << this->at(continuators.index()) << " (" << this->continuator().className() << ") + "; + name << this->at(crossover_rates.index()) << " (" << this->crossover_rate().className() << ") + "; + name << this->at(crossovers.index()) << " (" << this->crossover().className() << ") + "; + name << this->at(mutation_rates.index()) << " (" << this->mutation_rate().className() << ") + "; + name << this->at(mutations.index()) << " (" << this->mutation().className() << ") + "; + name << this->at(selectors.index()) << " (" << this->selector().className() << ") + "; + name << this->at(pop_sizes.index()) << " (" << this->pop_size().className() << ")"; + name << this->at(replacements.index()) << " (" << this->replacement().className() << ")"; + return name.str(); + } + + protected: + eoEvalFunc& _eval; + eoInit& _init; + const size_t _max_evals; + const size_t _max_restarts; + + public: + eoContinue& continuator() + { + assert(this->at(continuators.index()) < continuators.size()); + return continuators.instantiate(this->at(continuators.index())); + } + + double& crossover_rate() + { + assert(this->at(crossover_rates.index()) < crossover_rates.size()); + return crossover_rates.instantiate(this->at(crossover_rates.index())); + } + + eoQuadOp& crossover() + { + assert(this->at(crossovers.index()) < crossovers.size()); + return crossovers.instantiate(this->at(crossovers.index())); + } + + double& mutation_rate() + { + assert(this->at(mutation_rates.index()) < mutation_rates.size()); + return mutation_rates.instantiate(this->at(mutation_rates.index())); + } + + eoMonOp& mutation() + { + assert(this->at(mutations.index()) < mutations.size()); + return mutations.instantiate(this->at(mutations.index())); + } + + eoSelectOne& selector() + { + assert(this->at(selectors.index()) < selectors.size()); + return selectors.instantiate(this->at(selectors.index())); + } + + size_t& pop_size() + { + assert(this->at(pop_sizes.index()) < pop_sizes.size()); + return pop_sizes.instantiate(this->at(pop_sizes.index())); + } + + eoReplacement& replacement() + { + assert(this->at(replacements.index()) < replacements.size()); + return replacements.instantiate(this->at(replacements.index())); + } + +}; + +#endif // _eoAlgoFoundryFastGA_H_ diff --git a/eo/src/eoEasyEA.h b/eo/src/eoEasyEA.h index effe81a14..fc2ebd6b2 100644 --- a/eo/src/eoEasyEA.h +++ b/eo/src/eoEasyEA.h @@ -272,6 +272,8 @@ template class eoEasyEA: public eoAlgo replace(_pop, offspring); // after replace, the new pop. is in _pop + std::cout << _pop << std::endl; + if (pSize > _pop.size()) throw eoException("Population shrinking!"); else if (pSize < _pop.size()) diff --git a/eo/src/eoMergeReduce.h b/eo/src/eoMergeReduce.h index 94ad7edbc..060657301 100644 --- a/eo/src/eoMergeReduce.h +++ b/eo/src/eoMergeReduce.h @@ -97,7 +97,8 @@ class eoCommaReplacement : public eoMergeReduce virtual void operator()(eoPop& _parents, eoPop& _offspring) { // There must be more offsprings than parents, or else an exception will be raised - assert( _offspring.size() >= _parents.size() ); + // Removed this assertion, which do not hold in some special case of singe indivudal algorithms. + //assert( _offspring.size() >= _parents.size() ); eoMergeReduce::operator()( _parents, _offspring ); } diff --git a/eo/src/eoOpContainer.h b/eo/src/eoOpContainer.h index 41e8b74b8..0636bd0c3 100644 --- a/eo/src/eoOpContainer.h +++ b/eo/src/eoOpContainer.h @@ -106,6 +106,7 @@ public: for (size_t i = 0; i < rates.size(); ++i) { _pop.seekp(pos); do { + // std::clog << "Before:" << _pop.offspring().size() << " offsprings" << std::endl; if (eo::rng.flip(rates[i])) { // try // { @@ -126,6 +127,11 @@ public: if (!_pop.exhausted()) ++_pop; + + // std::clog << "After:" << _pop.offspring().size() << " offsprings" << std::endl; + // std::clog << _pop.offspring() << std::endl; + // std::clog << std::endl; + } while (!_pop.exhausted()); } @@ -155,6 +161,7 @@ public: try { + // std::clog << "\t" << ops[i]->className() << std::endl; (*ops[i])(_pop); ++_pop; } diff --git a/eo/src/ga.h b/eo/src/ga.h index 2f20dc879..843b2064b 100644 --- a/eo/src/ga.h +++ b/eo/src/ga.h @@ -34,6 +34,7 @@ // the operators #include "ga/eoBitOp.h" +#include "ga/eoStandardBitMutation.h" // #include to be corrected - thanks someone! diff --git a/eo/src/ga/eoStandardBitMutation.h b/eo/src/ga/eoStandardBitMutation.h new file mode 100644 index 000000000..50ad7873e --- /dev/null +++ b/eo/src/ga/eoStandardBitMutation.h @@ -0,0 +1,213 @@ + +#ifndef _eoStandardBitMutation_h_ +#define _eoStandardBitMutation_h_ + +#include "../utils/eoRNG.h" + +/** Standard bit mutation with mutation rate p: + * choose k from the binomial distribution Bin(n,p) and apply flip_k(x). + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoStandardBitMutation : public eoMonOp +{ + public: + eoStandardBitMutation(double rate = 0.5) : + _rate(rate), + _nb(1), + _bitflip(_nb) + {} + + virtual bool operator()(EOT& chrom) + { + _nb = eo::rng.binomial(chrom.size(),_rate); + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return _bitflip(chrom); + } + + protected: + double _rate; + unsigned _nb; + eoDetSingleBitFlip _bitflip; +}; + +/** Uniform bit mutation with mutation rate p: + * choose k from the uniform distribution U(0,n) and apply flip_k(x). + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoUniformBitMutation : public eoMonOp +{ + public: + eoUniformBitMutation(double rate = 0.5) : + _rate(rate), + _nb(1), + _bitflip(_nb) + {} + + virtual bool operator()(EOT& chrom) + { + _nb = eo::rng.random(chrom.size()); + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return _bitflip(chrom); + } + + protected: + double _rate; + unsigned _nb; + eoDetSingleBitFlip _bitflip; +}; + + +/** Conditional standard bit mutation with mutation rate p: + * choose k from the binomial distribution Bin(n,p) until k >0 + * and apply flip_k(x). + * + * This is identical to sampling k from the conditional binomial + * distribution Bin>0(n,p) which re-assigns the probability to sample + * a 0 proportionally to all values i ∈ [1..n]. + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoConditionalBitMutation : public eoStandardBitMutation +{ + public: + eoConditionalBitMutation(double rate = 0.5) : + eoStandardBitMutation(rate) + {} + + virtual bool operator()(EOT& chrom) + { + assert(chrom.size()>0); + this->_nb = eo::rng.binomial(chrom.size()-1,this->_rate); + this->_nb++; + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return this->_bitflip(chrom); + } +}; + +/** Shifted standard bit mutation with mutation rate p: + * choose k from the binomial distribution Bin(n,p). + * When k= 0, set k= 1. Apply flip_k(x). + * + * This is identical to sampling k from the conditional binomial + * distribution Bin0→1(n,p) which re-assigns the probability to + * sample a 0 to sampling k= 1. + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoShiftedBitMutation : public eoStandardBitMutation +{ + public: + eoShiftedBitMutation(double rate = 0.5) : + eoStandardBitMutation(rate) + {} + + virtual bool operator()(EOT& chrom) + { + assert(chrom.size()>0); + this->_nb = eo::rng.binomial(chrom.size()-1,this->_rate); + if(this->_nb == 0) { + this->_nb = 1; + } + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return this->_bitflip(chrom); + } +}; + +/** Mutation which size is sample in a gaussian. + * + * sample k from the normal distribution N(pn,σ^2) + * and apply flip_k(x). + * + * From: + * Furong Ye, Carola Doerr, and Thomas Back. + * Interpolating local and global search by controllingthe variance of standard bit mutation. + * In 2019 IEEE Congress on Evolutionary Computation(CEC), pages 2292–2299. + * + * In contrast to standard bit mutation, this operators allows to scale + * the variance of the mutation strength independently of the mean. + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoNormalBitMutation : public eoStandardBitMutation +{ + public: + eoNormalBitMutation(double rate = 0.5, double variance = 1) : + eoStandardBitMutation(rate), + _variance(variance) + {} + + virtual bool operator()(EOT& chrom) + { + this->_nb = eo::rng.normal(this->_rate * chrom.size(), _variance); + if(this->_nb >= chrom.size()) { + this->_nb = eo::rng.random(chrom.size()); + } + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return this->_bitflip(chrom); + } + + protected: + double _variance; +}; + +/** Fast mutation which size is sampled from an adaptive power law. + * + * From: + * Benjamin Doerr, Huu Phuoc Le, Régis Makhmara, and Ta Duy Nguyen. + * Fast genetic algorithms. + * In Proc. of Genetic and Evolutionary Computation Conference (GECCO’17), pages 777–784.ACM, 2017. + * + * @ingroup Bitstrings + * @ingroup Variators + */ +template +class eoFastBitMutation : public eoStandardBitMutation +{ + public: + eoFastBitMutation(double rate = 0.5, double beta = 1.5) : + eoStandardBitMutation(rate), + _beta(beta) + { + assert(beta > 1); + } + + virtual bool operator()(EOT& chrom) + { + this->_nb = powerlaw(chrom.size(),_beta); + // BitFlip operator is bound to the _nb reference, + // thus one don't need to re-instantiate. + return this->_bitflip(chrom); + } + + protected: + + double powerlaw(unsigned n, double beta) + { + double cnb = 0; + for(unsigned i=1; i +#include + +#include +#include +#include +#include "../../problems/eval/oneMaxEval.h" + +using Particle = eoRealParticle; +using Bits = eoBit; + +// Generate a search space of 5,232,000 algorithms, +// by enumerating candidate operators and their parameters. +eoAlgoFoundryFastGA& make_foundry(eoFunctorStore& store, eoInit& init, eoEvalFunc& eval_onemax) +{ + auto& foundry = store.pack< eoAlgoFoundryFastGA >(init, eval_onemax, 20,10); + + /***** Continuators ****/ + for(size_t i=10; i < 100; i+=2 ) { + foundry.continuators.add< eoSteadyFitContinue >(10,i); + } + + for(double i=0.1; i<1.0; i+=0.1) { + foundry.crossover_rates.add(i); + foundry.mutation_rates.add(i); + } + + for(size_t i=5; i<100; i+=10) { + foundry.pop_sizes.add(i); + } + + /***** Crossovers ****/ + for(double i=0.1; i<0.9; i+=0.1) { + foundry.crossovers.add< eoUBitXover >(i); // preference over 1 + } + for(size_t i=1; i < 11; i+=1) { + foundry.crossovers.add< eoNPtsBitXover >(i); // nb of points + } + foundry.crossovers.add< eo1PtBitXover >(); + + /***** Mutations ****/ + double p = 1.0; // Probability of flipping eath bit. + foundry.mutations.add< eoUniformBitMutation >(p); // proba of flipping k bits, k drawn in uniform distrib + foundry.mutations.add< eoStandardBitMutation >(p); // proba of flipping k bits, k drawn in binomial distrib + foundry.mutations.add< eoConditionalBitMutation >(p); // proba of flipping k bits, k drawn in binomial distrib, minus zero + foundry.mutations.add< eoShiftedBitMutation >(p); // proba of flipping k bits, k drawn in binomial distrib, changing zeros to one + foundry.mutations.add< eoNormalBitMutation >(p); // proba of flipping k bits, k drawn in normal distrib + foundry.mutations.add< eoFastBitMutation >(p); // proba of flipping k bits, k drawn in powerlaw distrib + for(size_t i=1; i < 11; i+=1) { + foundry.mutations.add< eoDetSingleBitFlip >(i); // mutate k bits without duplicates + } + + /***** Selectors *****/ + foundry.selectors.add< eoRandomSelect >(); + foundry.selectors.add< eoSequentialSelect >(); + foundry.selectors.add< eoProportionalSelect >(); + for(size_t i=2; i < 10; i+=1) { // Tournament size. + foundry.selectors.add< eoDetTournamentSelect >(i); + } + for(double i=0.51; i<0.91; i+=0.1) { // Tournament size as perc of pop. + foundry.selectors.add< eoStochTournamentSelect >(i); + } + + /***** Replacements ****/ + foundry.replacements.add< eoPlusReplacement >(); + foundry.replacements.add< eoCommaReplacement >(); + foundry.replacements.add< eoSSGAWorseReplacement >(); + for(double i=0.51; i<0.91; i+=0.1) { + foundry.replacements.add< eoSSGAStochTournamentReplacement >(i); + } + for(size_t i=2; i < 10; i+=1) { + foundry.replacements.add< eoSSGADetTournamentReplacement >(i); + } + + return foundry; +} + + +int main(int /*argc*/, char** /*argv*/) +{ + eo::log << eo::setlevel(eo::warnings); + eoFunctorStore store; + + oneMaxEval onemax_eval; + + eoBooleanGenerator gen(0.5); + eoInitFixedLength init(/*bitstring size=*/5, gen); + + auto& foundry = make_foundry(store, init, onemax_eval); + + size_t n = foundry.continuators.size() * foundry.crossovers.size() * foundry.mutations.size() * foundry.selectors.size() * foundry.replacements.size()* foundry.crossover_rates.size() * foundry.mutation_rates.size() * foundry.pop_sizes.size(); + std::clog << n << " possible algorithms instances." << std::endl; + + eoPop pop; + pop.append(5,init); + ::apply(onemax_eval,pop); + + foundry.select({0,0,0,0,0,0,0,0}); + foundry(pop); + + std::cout << "Done" << std::endl; + std::cout << pop << std::endl; + std::cout << pop.best_element() << std::endl; +}