From a436fb3fa09ada64f3d2d0e21ad5d033d3671e9a Mon Sep 17 00:00:00 2001 From: nojhan Date: Mon, 30 Mar 2020 17:37:04 +0200 Subject: [PATCH] feat: add algo foundry and eval foundry Necessary machinery to perform automatic algorithm selection based on the grammar defined by EasyEA and the vocabulary defined by sets of parametrized operators. --- eo/src/eo | 2 +- eo/src/eoAlgoFoundryEA.h | 208 +++++++++++++++++++++++++++++++ eo/src/eoEvalFoundryEA.h | 162 ++++++++++++++++++++++++ eo/src/eoForge.h | 53 +++++--- eo/src/ga/eoBitOp.h | 2 +- eo/test/CMakeLists.txt | 1 + eo/test/t-algo-forged-search.cpp | 145 +++++++++++++++++++++ eo/test/t-algo-forged.cpp | 2 +- 8 files changed, 558 insertions(+), 17 deletions(-) create mode 100644 eo/src/eoAlgoFoundryEA.h create mode 100644 eo/src/eoEvalFoundryEA.h create mode 100644 eo/test/t-algo-forged-search.cpp diff --git a/eo/src/eo b/eo/src/eo index edf9bc79b..819ec2b1e 100644 --- a/eo/src/eo +++ b/eo/src/eo @@ -160,7 +160,7 @@ #include "eoForge.h" #include "eoAlgoFoundryEA.h" - +#include "eoEvalFoundryEA.h" //----------------------------------------------------------------------------- // to be continued ... diff --git a/eo/src/eoAlgoFoundryEA.h b/eo/src/eoAlgoFoundryEA.h new file mode 100644 index 000000000..5868c5771 --- /dev/null +++ b/eo/src/eoAlgoFoundryEA.h @@ -0,0 +1,208 @@ + +/* + 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 _eoAlgoFoundryEA_H_ +#define _eoAlgoFoundryEA_H_ + +#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 + * + * In a second step, the operators to be used should be selected + * by indicating their index, just like the foundry was a array of five elements: + * @code + * foundry = {0, 1, 2, 0, 3}; + * // ^ continue + * // ^ crossover + * // ^ mutation + * // ^ selection + * // ^ replacement + * @endcode + * + * @note: by default, the firsts of the five operators are selected. + * + * If you don't (want to) recall the order of the operators in the encoding, + * you can use the `index_of` member, for example: + * @code + * foundry.at(foundry.index_of.continuators) = 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 instanciate the needed operators (only) and the algorithm itself on-the-fly, + * and then run it. + * + * @note: Thanks to the underlying eoForgeVector, not all the added operators are instanciated. + * Every instanciation 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 eoAlgoFoundryEA : public eoAlgo +{ + public: + static const size_t dim = 5; + + struct Indices + { + static const size_t continuators = 0; + static const size_t crossovers = 1; + static const size_t mutations = 2; + static const size_t selectors = 3; + static const size_t replacements = 4; + }; + + /** Helper for keeping track of the indices of the underlying encoding. */ + const Indices index_of; + + /** The constructon only take an eval, because all other operators + * are stored in the public containers. + */ + eoAlgoFoundryEA( eoPopEvalFunc& eval, size_t max_gen ) : + index_of(), + _eval(eval), + _max_gen(max_gen) + { + _encoding = { 0 }; // dim * 0 + } + + /** Access to the index of the currently selected operator. + */ + size_t& at(size_t i) + { + return _encoding.at(i); + } + + /** Select indices of all the operators. + */ + void operator=( std::array a) + { + _encoding = a; + } + + /* Operators containers @{ */ + eoForgeVector< eoContinue > continuators; + eoForgeVector< eoQuadOp > crossovers; + eoForgeVector< eoMonOp > mutations; + eoForgeVector< eoSelectOne > selectors; + eoForgeVector< eoReplacement > replacements; + /* @} */ + + /** Instanciate and call the pre-selected algorithm. + */ + void operator()(eoPop& pop) + { + assert(continuators.size() > 0); assert(_encoding.at(index_of.continuators) < continuators.size()); + assert( crossovers.size() > 0); assert(_encoding.at(index_of.crossovers) < crossovers.size()); + assert( mutations.size() > 0); assert(_encoding.at(index_of.mutations) < mutations.size()); + assert( selectors.size() > 0); assert(_encoding.at(index_of.selectors) < selectors.size()); + assert(replacements.size() > 0); assert(_encoding.at(index_of.replacements) < replacements.size()); + + eoSequentialOp variator; + variator.add(this->crossover(), 1.0); + variator.add(this->mutation(), 1.0); + + eoGeneralBreeder breeder(this->selector(), variator, 1.0); + + eoGenContinue common_cont(_max_gen); + eoCombinedContinue gen_cont(common_cont); + gen_cont.add(this->continuator()); + + eoEasyEA algo = eoEasyEA(gen_cont, _eval, breeder, this->replacement()); + + algo(pop); + } + + /** Return an approximate name of the seected algorithm. + * + * @note: does not take into account parameters of the operators, + * only show class names. + */ + std::string name() + { + std::ostringstream name; + name << _encoding.at(index_of.continuators) << " (" << this->continuator().className() << ") + "; + name << _encoding.at(index_of.crossovers) << " (" << this->crossover().className() << ") + "; + name << _encoding.at(index_of.mutations) << " (" << this->mutation().className() << ") + "; + name << _encoding.at(index_of.selectors) << " (" << this->selector().className() << ") + "; + name << _encoding.at(index_of.replacements) << " (" << this->replacement().className() << ")"; + return name.str(); + } + + protected: + eoPopEvalFunc& _eval; + const size_t _max_gen; + std::array _encoding; + + public: + eoContinue& continuator() + { + assert(_encoding.at(index_of.continuators) < continuators.size()); + return continuators.instanciate(_encoding.at(index_of.continuators)); + } + + eoQuadOp& crossover() + { + assert(_encoding.at(index_of.crossovers) < crossovers.size()); + return crossovers.instanciate(_encoding.at(index_of.crossovers)); + } + + eoMonOp& mutation() + { + assert(_encoding.at(index_of.mutations) < mutations.size()); + return mutations.instanciate(_encoding.at(index_of.mutations)); + } + + eoSelectOne& selector() + { + assert(_encoding.at(index_of.selectors) < selectors.size()); + return selectors.instanciate(_encoding.at(index_of.selectors)); + } + + eoReplacement& replacement() + { + assert(_encoding.at(index_of.replacements) < replacements.size()); + return replacements.instanciate(_encoding.at(index_of.replacements)); + } + +}; + +#endif // _eoAlgoFoundryEA_H_ diff --git a/eo/src/eoEvalFoundryEA.h b/eo/src/eoEvalFoundryEA.h new file mode 100644 index 000000000..b5f87c2a5 --- /dev/null +++ b/eo/src/eoEvalFoundryEA.h @@ -0,0 +1,162 @@ + +/* + 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 _eoEvalFoundryEA_H_ +#define _eoEvalFoundryEA_H_ + +#include "eoEvalFunc.h" +#include "eoAlgoFoundryEA.h" +#include "eoInit.h" +#include "eoPopEvalFunc.h" + +/** Evaluate an algorithm assembled by an eoAlgoFoundryEA, encoded as a numeric vector. + * + * Allows to plug another search algorithm on top of an eoAlgoFoundryEA, + * so as to find the best configuration. + * + * The first template EOT is the encoding of the high-level algorithm selection problem, + * the second template SUB is the encoding of the low-level generic problem. + * + * @ingroup Evaluation + * @ingroup Foundry + */ +template +class eoEvalFoundryEA : public eoEvalFunc +{ +public: + /** Takes the necessary parameters to perform a search on the sub-problem. + * + * @param foundry The set of algorithms among which to select. + * @param subpb_init An initilizer for sub-problem encoding. + * @param pop_size Population size for the sub-problem solver. + * @param subpb_eval The sub-problem itself. + * @param penalization If any solution to the high-level algorithm selection problem is out of bounds, set it to this value. + */ + eoEvalFoundryEA( + eoAlgoFoundryEA& foundry, + eoInit& subpb_init, + const size_t pop_size, + eoPopEvalFunc& subpb_eval, + const typename SUB::Fitness penalization + ) : + _subpb_init(subpb_init), + _subpb_eval(subpb_eval), + _foundry(foundry), + _penalization(penalization), + _pop_size(pop_size) + { } + + static const size_t i_cont = eoAlgoFoundryEA::Indices::continuators; + static const size_t i_cros = eoAlgoFoundryEA::Indices::crossovers ; + static const size_t i_muta = eoAlgoFoundryEA::Indices::mutations ; + static const size_t i_sele = eoAlgoFoundryEA::Indices::selectors ; + static const size_t i_repl = eoAlgoFoundryEA::Indices::replacements; + + /** Decode the high-level problem encoding as an array of indices. + * + * May be useful for getting a solution back into an eoAlgoFoundryEA. + * @code + * foundry = eval.decode(pop.best_element()); + * std::cout << foundry.name() << std::endl; + * auto& cont = foundry.continuator(); // Get the configured operator + * @encode + */ + std::array::dim> decode( const EOT& sol ) + { + // Denormalize + size_t cont = static_cast(std::ceil( sol[i_cont] * _foundry.continuators.size() )); + size_t cros = static_cast(std::ceil( sol[i_cros] * _foundry.crossovers .size() )); + size_t muta = static_cast(std::ceil( sol[i_muta] * _foundry.mutations .size() )); + size_t sele = static_cast(std::ceil( sol[i_sele] * _foundry.selectors .size() )); + size_t repl = static_cast(std::ceil( sol[i_repl] * _foundry.replacements.size() )); + + return {cont, cros, muta, repl}; + } + + /** Perform a sub-problem search with the configuration encoded in the given solution + * and set its (high-level) fitness to the best (low-level) fitness found. + * + * You may want to overload this to perform multiple runs or solve multiple sub-problems. + */ + virtual void operator()(EOT& sol) + { + if(not sol.invalid()) { + return; + } + + eoPop pop; + pop.append(_pop_size, _subpb_init); + _subpb_eval(pop,pop); + + auto config = decode(sol); + double cont = config[i_cont]; + double cros = config[i_cros]; + double muta = config[i_muta]; + double sele = config[i_sele]; + double repl = config[i_repl]; + + if( + 0 <= cont and cont < _foundry.continuators.size() + and 0 <= cros and cros < _foundry.crossovers .size() + and 0 <= muta and muta < _foundry.mutations .size() + and 0 <= sele and sele < _foundry.selectors .size() + and 0 <= repl and repl < _foundry.replacements.size() + ) { + _foundry = config; + + // Actually perform a search + _foundry(pop); + + sol.fitness( pop.best_element().fitness() ); + } else { + sol.fitness( _penalization ); // penalization + } + } + +protected: + eoInit& _subpb_init; + eoPopEvalFunc& _subpb_eval; + eoAlgoFoundryEA& _foundry; + const typename EOT::Fitness _penalization; + const size_t _pop_size; +}; + +/** Helper function to instanciate an eoEvalFoundryEA without having to indicate the template for the sub-problem encoding. + * + * The template is deduced from the constructor's parameters. + * Not sure it's more concise than a classical instanciation… + */ +template +eoEvalFoundryEA& + make_eoEvalFoundryEA( + eoInit& subpb_init, + eoPopEvalFunc& subpb_eval, + eoAlgoFoundryEA& foundry, + const typename SUB::Fitness penalization, + const size_t pop_size + ) +{ + return *(new eoEvalFoundryEA(subpb_init, subpb_eval, foundry, penalization, pop_size)); +} + +#endif // _eoEvalFoundryEA_H_ + diff --git a/eo/src/eoForge.h b/eo/src/eoForge.h index 952a02586..90678de90 100644 --- a/eo/src/eoForge.h +++ b/eo/src/eoForge.h @@ -25,6 +25,31 @@ #include #include +template +struct tuple_printer { + + static void print(std::ostream& out, const Type& value) { + out << std::get(value) << ", "; + tuple_printer::print(out, value); + } +}; + +template +struct tuple_printer { + + static void print(std::ostream& out, const Type& value) { + out << std::get(value); + } + +}; + +template +std::ostream& operator<<(std::ostream& out, const std::tuple& value) { + out << "("; + tuple_printer, 0, sizeof...(Types) - 1>::print(out, value); + out << ")"; + return out; +} /** * @defgroup Foundry Tools for automatic algorithms assembling, selection and search. @@ -56,9 +81,8 @@ class eoForgeInterface * * @code eoForgeOperator,eoRankMuSelect> forge(mu); - // ^ desired ^ to-be-instanciated ^ operator's - // interface operator parameters - + // ^ desired ^ to-be-instanciated ^ operator's + // interface operator parameters // Actual instanciation: eoSelect& select = forge.instanciate(); * @endcode @@ -69,8 +93,8 @@ template class eoForgeOperator : public eoForgeInterface { public: - eoForgeOperator(Args&&... args) : - _args(std::forward(args)...), + eoForgeOperator(Args... args) : + _args(args...), _instanciated(nullptr) { } @@ -118,8 +142,9 @@ class eoForgeOperator : public eoForgeInterface Itf* _instanciated; }; +//! Partial specialization for constructors without any argument. template -class eoForgeOperator : public eoForgeInterface +class eoForgeOperator : public eoForgeInterface { public: eoForgeOperator() : @@ -149,14 +174,14 @@ class eoForgeOperator : public eoForgeInterface * with different parametrization (or not). * * @code - eoForgeVector> named_factories; + eoForgeVector> factories; // Capture constructor's parameters and defer instanciation. - named_factories.add>(1); - named_factories.setup>(0, 5); // Edit + factories.add>(1); + factories.setup>(0, 5); // Edit // Actually instanciate. - eoSelect& op = named_factories.instanciate("RMS"); + eoSelect& op = factories.instanciate(0); // Call. op(); @@ -169,9 +194,9 @@ class eoForgeVector : public std::vector*> { public: template - void add(Args&&... args) + void add(Args... args) { - auto pfo = new eoForgeOperator(std::forward(args)...); + auto pfo = new eoForgeOperator(args...); this->push_back(pfo); } @@ -183,11 +208,11 @@ class eoForgeVector : public std::vector*> } template - void setup(size_t index, Args&&... args) + void setup(size_t index, Args... args) { assert(this->at(index) != nullptr); delete this->at(index); - auto pfo = new eoForgeOperator(std::forward(args)...); + auto pfo = new eoForgeOperator(args...); this->at(index) = pfo; } diff --git a/eo/src/ga/eoBitOp.h b/eo/src/ga/eoBitOp.h index b5436327f..0e88b492f 100644 --- a/eo/src/ga/eoBitOp.h +++ b/eo/src/ga/eoBitOp.h @@ -75,7 +75,7 @@ template class eoDetBitFlip: public eoMonOp * @param _num_bit The number of bits to change * default is one - equivalent to eoOneBitFlip then */ - eoDetBitFlip(const unsigned& _num_bit = 1): num_bit(_num_bit) {} + eoDetBitFlip(const unsigned/*&*/ _num_bit = 1): num_bit(_num_bit) {} /// The class name. virtual std::string className() const { return "eoDetBitFlip"; } diff --git a/eo/test/CMakeLists.txt b/eo/test/CMakeLists.txt index b392df824..87d898a44 100644 --- a/eo/test/CMakeLists.txt +++ b/eo/test/CMakeLists.txt @@ -74,6 +74,7 @@ set (TEST_LIST t-operator-forge t-forge-algo t-algo-forged + t-algo-forged-search ) diff --git a/eo/test/t-algo-forged-search.cpp b/eo/test/t-algo-forged-search.cpp new file mode 100644 index 000000000..721b8a7c7 --- /dev/null +++ b/eo/test/t-algo-forged-search.cpp @@ -0,0 +1,145 @@ +#include +#include + +#include +#include +#include "../../problems/eval/oneMaxEval.h" + +#include +#include + +using Particle = eoRealParticle; +using Bits = eoBit; + +// Generate a search space of 5,232,000 algorithms, +// by enumerating candidate operators and their parameters. +eoAlgoFoundryEA& make_foundry(eoFunctorStore& store, eoPopEvalFunc& eval_onemax) +{ + auto& foundry = store.pack< eoAlgoFoundryEA >(eval_onemax, 100); + + /***** Continuators ****/ + for(size_t i=10; i < 30; i+=2 ) { + foundry.continuators.add< eoSteadyFitContinue >(10,i); + } + + /***** Crossovers ****/ + foundry.crossovers.add< eo1PtBitXover >(); + 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 + } + + /***** Mutations ****/ + for(double i=0.01; i<1.0; i+=0.01) { + foundry.mutations.add< eoBitMutation >(i); // proba of flipping any bit + } + for(size_t i=1; i < 11; i+=1) { + foundry.mutations.add< eoDetBitFlip >(i); // mutate k bits + } + + /***** Selectors *****/ + for(double i=0.51; i<0.91; i+=0.1) { + foundry.selectors.add< eoStochTournamentSelect >(i); + } + foundry.selectors.add< eoSequentialSelect >(); + foundry.selectors.add< eoProportionalSelect >(); + for(size_t i=2; i < 10; i+=1) { + foundry.selectors.add< eoDetTournamentSelect >(i); + } + + /***** Replacements ****/ + foundry.replacements.add< eoCommaReplacement >(); + foundry.replacements.add< eoPlusReplacement >(); + 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; +} + +// A basic PSO algorithm. +std::pair< eoAlgo*, eoPop* > + make_pso(eoFunctorStore& store, eoEvalFunc& eval_foundry) +{ + const size_t dim = eoAlgoFoundryEA::dim; + + auto& gen_pos = store.pack< eoUniformGenerator >(0.1,0.9); + auto& random_pos = store.pack< eoInitFixedLength >(dim, gen_pos); + + auto pop = new eoPop(); + pop->append(100, random_pos); // pop size + + auto& gen_minus = store.pack< eoUniformGenerator >(-0.05, 0.05); + auto& random_velo = store.pack< eoVelocityInitFixedLength >(dim, gen_minus); + + auto& local_init = store.pack< eoFirstIsBestInit >(); + + auto topology = new eoLinearTopology(5); // neighborhood size + + auto& init = store.pack< eoInitializer >(eval_foundry, random_velo, local_init, *topology, *pop); + init(); + + auto bounds = new eoRealVectorBounds(dim, 0, 0.999999); + + auto& velocity = store.pack< eoStandardVelocity >(*topology, 1, 1.6, 2, *bounds); + + auto& flight = store.pack< eoStandardFlight >(); + + auto& cont_gen = store.pack< eoGenContinue >(50); + auto& cont = store.pack< eoCombinedContinue >(cont_gen); + + auto& checkpoint = store.pack< eoCheckPoint >(cont); + auto& best = store.pack< eoBestFitnessStat >(); + checkpoint.add(best); + auto& monitor = store.pack< eoOStreamMonitor >(std::clog); + monitor.add(best); + checkpoint.add(monitor); + + auto& pso = store.pack< eoEasyPSO >(init, checkpoint, eval_foundry, velocity, flight); + + return std::make_pair(&pso,pop); +} + + +int main(int /*argc*/, char** /*argv*/) +{ + eo::log << eo::setlevel(eo::warnings); + eoFunctorStore store; + + /***** Sub-problem stuff (GA on OneMax) *****/ + + oneMaxEval evalfunc; + eoPopLoopEval onemax_eval(evalfunc); + + auto& foundry = make_foundry(store, onemax_eval); + + size_t n = foundry.continuators.size() * foundry.crossovers.size() * foundry.mutations.size() * foundry.selectors.size() * foundry.replacements.size(); + std::clog << n << " possible algorithms instances." << std::endl; + + // Evaluation of a forged algo on the sub-problem + eoBooleanGenerator gen(0.5); + eoInitFixedLength onemax_init(/*bitstring size=*/500, gen); + eoEvalFoundryEA eval_foundry(foundry, + onemax_init, /*pop_size=*/ 10, + onemax_eval, /*penalization=*/ 0); + + /***** Algorithm selection stuff (PSO on foundry) *****/ + + eoAlgo* pso; + eoPop* pop_foundry; + std::tie(pso, pop_foundry) = make_pso(store, eval_foundry); + + // Perform the best algorithm configuration search. + (*pso)(*pop_foundry); + + // Print a glimpse of the best algorithm found. + foundry = eval_foundry.decode(pop_foundry->best_element()); + std::cout << "Best algorithm: " << foundry.name() << std::endl; + +} diff --git a/eo/test/t-algo-forged.cpp b/eo/test/t-algo-forged.cpp index c0535c89c..6d5861ba4 100644 --- a/eo/test/t-algo-forged.cpp +++ b/eo/test/t-algo-forged.cpp @@ -21,7 +21,7 @@ int main(int /*argc*/, char** /*argv*/) eoBooleanGenerator gen(0.5); eoInitFixedLength init(dim, gen); - eoAlgoFoundryEA foundry(eval); + eoAlgoFoundryEA foundry(eval, 10); /***** Continuators ****/ for(size_t i=10; i < 30; i+=10 ) {