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.
This commit is contained in:
Johann Dreo 2020-03-30 17:37:04 +02:00
commit a436fb3fa0
8 changed files with 558 additions and 17 deletions

View file

@ -160,7 +160,7 @@
#include "eoForge.h"
#include "eoAlgoFoundryEA.h"
#include "eoEvalFoundryEA.h"
//-----------------------------------------------------------------------------
// to be continued ...

208
eo/src/eoAlgoFoundryEA.h Normal file
View file

@ -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 <johann.dreo@thalesgroup.com>
*/
#ifndef _eoAlgoFoundryEA_H_
#define _eoAlgoFoundryEA_H_
#include <array>
#include <tuple>
/** 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<EOT> >( 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 EOT>
class eoAlgoFoundryEA : public eoAlgo<EOT>
{
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<EOT>& 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<size_t,dim> a)
{
_encoding = a;
}
/* Operators containers @{ */
eoForgeVector< eoContinue<EOT> > continuators;
eoForgeVector< eoQuadOp<EOT> > crossovers;
eoForgeVector< eoMonOp<EOT> > mutations;
eoForgeVector< eoSelectOne<EOT> > selectors;
eoForgeVector< eoReplacement<EOT> > replacements;
/* @} */
/** Instanciate and call the pre-selected algorithm.
*/
void operator()(eoPop<EOT>& 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<EOT> variator;
variator.add(this->crossover(), 1.0);
variator.add(this->mutation(), 1.0);
eoGeneralBreeder<EOT> breeder(this->selector(), variator, 1.0);
eoGenContinue<EOT> common_cont(_max_gen);
eoCombinedContinue<EOT> gen_cont(common_cont);
gen_cont.add(this->continuator());
eoEasyEA<EOT> algo = eoEasyEA<EOT>(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<EOT>& _eval;
const size_t _max_gen;
std::array<size_t, dim> _encoding;
public:
eoContinue<EOT>& continuator()
{
assert(_encoding.at(index_of.continuators) < continuators.size());
return continuators.instanciate(_encoding.at(index_of.continuators));
}
eoQuadOp<EOT>& crossover()
{
assert(_encoding.at(index_of.crossovers) < crossovers.size());
return crossovers.instanciate(_encoding.at(index_of.crossovers));
}
eoMonOp<EOT>& mutation()
{
assert(_encoding.at(index_of.mutations) < mutations.size());
return mutations.instanciate(_encoding.at(index_of.mutations));
}
eoSelectOne<EOT>& selector()
{
assert(_encoding.at(index_of.selectors) < selectors.size());
return selectors.instanciate(_encoding.at(index_of.selectors));
}
eoReplacement<EOT>& replacement()
{
assert(_encoding.at(index_of.replacements) < replacements.size());
return replacements.instanciate(_encoding.at(index_of.replacements));
}
};
#endif // _eoAlgoFoundryEA_H_

162
eo/src/eoEvalFoundryEA.h Normal file
View file

@ -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 <johann.dreo@thalesgroup.com>
*/
#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 EOT, class SUB>
class eoEvalFoundryEA : public eoEvalFunc<EOT>
{
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<SUB>& foundry,
eoInit<SUB>& subpb_init,
const size_t pop_size,
eoPopEvalFunc<SUB>& 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<SUB>::Indices::continuators;
static const size_t i_cros = eoAlgoFoundryEA<SUB>::Indices::crossovers ;
static const size_t i_muta = eoAlgoFoundryEA<SUB>::Indices::mutations ;
static const size_t i_sele = eoAlgoFoundryEA<SUB>::Indices::selectors ;
static const size_t i_repl = eoAlgoFoundryEA<SUB>::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<size_t,eoAlgoFoundryEA<SUB>::dim> decode( const EOT& sol )
{
// Denormalize
size_t cont = static_cast<size_t>(std::ceil( sol[i_cont] * _foundry.continuators.size() ));
size_t cros = static_cast<size_t>(std::ceil( sol[i_cros] * _foundry.crossovers .size() ));
size_t muta = static_cast<size_t>(std::ceil( sol[i_muta] * _foundry.mutations .size() ));
size_t sele = static_cast<size_t>(std::ceil( sol[i_sele] * _foundry.selectors .size() ));
size_t repl = static_cast<size_t>(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<SUB> 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<SUB>& _subpb_init;
eoPopEvalFunc<SUB>& _subpb_eval;
eoAlgoFoundryEA<SUB>& _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<class EOT, class SUB>
eoEvalFoundryEA<EOT,SUB>&
make_eoEvalFoundryEA(
eoInit<SUB>& subpb_init,
eoPopEvalFunc<SUB>& subpb_eval,
eoAlgoFoundryEA<SUB>& foundry,
const typename SUB::Fitness penalization,
const size_t pop_size
)
{
return *(new eoEvalFoundryEA<EOT,SUB>(subpb_init, subpb_eval, foundry, penalization, pop_size));
}
#endif // _eoEvalFoundryEA_H_

View file

@ -25,6 +25,31 @@
#include <string>
#include <tuple>
template<typename Type, unsigned N, unsigned Last>
struct tuple_printer {
static void print(std::ostream& out, const Type& value) {
out << std::get<N>(value) << ", ";
tuple_printer<Type, N + 1, Last>::print(out, value);
}
};
template<typename Type, unsigned N>
struct tuple_printer<Type, N, N> {
static void print(std::ostream& out, const Type& value) {
out << std::get<N>(value);
}
};
template<typename... Types>
std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
out << "(";
tuple_printer<std::tuple<Types...>, 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<eoselect<EOT>,eoRankMuSelect<EOT>> forge(mu);
// ^ desired ^ to-be-instanciated ^ operator's
// interface operator parameters
// ^ desired ^ to-be-instanciated ^ operator's
// interface operator parameters
// Actual instanciation:
eoSelect<EOT>& select = forge.instanciate();
* @endcode
@ -69,8 +93,8 @@ template<class Itf, class Op, typename... Args>
class eoForgeOperator : public eoForgeInterface<Itf>
{
public:
eoForgeOperator(Args&&... args) :
_args(std::forward<Args>(args)...),
eoForgeOperator(Args... args) :
_args(args...),
_instanciated(nullptr)
{ }
@ -118,8 +142,9 @@ class eoForgeOperator : public eoForgeInterface<Itf>
Itf* _instanciated;
};
//! Partial specialization for constructors without any argument.
template<class Itf, class Op>
class eoForgeOperator<Itf,Op> : public eoForgeInterface<Itf>
class eoForgeOperator<Itf,Op> : public eoForgeInterface<Itf>
{
public:
eoForgeOperator() :
@ -149,14 +174,14 @@ class eoForgeOperator<Itf,Op> : public eoForgeInterface<Itf>
* with different parametrization (or not).
*
* @code
eoForgeVector<eoSelect<EOT>> named_factories;
eoForgeVector<eoSelect<EOT>> factories;
// Capture constructor's parameters and defer instanciation.
named_factories.add<eoRankMuSelect<EOT>>(1);
named_factories.setup<eoRankMuSelect<EOT>>(0, 5); // Edit
factories.add<eoRankMuSelect<EOT>>(1);
factories.setup<eoRankMuSelect<EOT>>(0, 5); // Edit
// Actually instanciate.
eoSelect<EOT>& op = named_factories.instanciate("RMS");
eoSelect<EOT>& op = factories.instanciate(0);
// Call.
op();
@ -169,9 +194,9 @@ class eoForgeVector : public std::vector<eoForgeInterface<Itf>*>
{
public:
template<class Op, typename... Args>
void add(Args&&... args)
void add(Args... args)
{
auto pfo = new eoForgeOperator<Itf,Op,Args...>(std::forward<Args>(args)...);
auto pfo = new eoForgeOperator<Itf,Op,Args...>(args...);
this->push_back(pfo);
}
@ -183,11 +208,11 @@ class eoForgeVector : public std::vector<eoForgeInterface<Itf>*>
}
template<class Op, typename... Args>
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<Itf,Op,Args...>(std::forward<Args>(args)...);
auto pfo = new eoForgeOperator<Itf,Op,Args...>(args...);
this->at(index) = pfo;
}

View file

@ -75,7 +75,7 @@ template<class Chrom> class eoDetBitFlip: public eoMonOp<Chrom>
* @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"; }

View file

@ -74,6 +74,7 @@ set (TEST_LIST
t-operator-forge
t-forge-algo
t-algo-forged
t-algo-forged-search
)

View file

@ -0,0 +1,145 @@
#include <iostream>
#include <string>
#include <eo>
#include <ga.h>
#include "../../problems/eval/oneMaxEval.h"
#include <eo>
#include <utils/checkpointing>
using Particle = eoRealParticle<eoMaximizingFitness>;
using Bits = eoBit<double>;
// Generate a search space of 5,232,000 algorithms,
// by enumerating candidate operators and their parameters.
eoAlgoFoundryEA<Bits>& make_foundry(eoFunctorStore& store, eoPopEvalFunc<Bits>& eval_onemax)
{
auto& foundry = store.pack< eoAlgoFoundryEA<Bits> >(eval_onemax, 100);
/***** Continuators ****/
for(size_t i=10; i < 30; i+=2 ) {
foundry.continuators.add< eoSteadyFitContinue<Bits> >(10,i);
}
/***** Crossovers ****/
foundry.crossovers.add< eo1PtBitXover<Bits> >();
for(double i=0.1; i<0.9; i+=0.1) {
foundry.crossovers.add< eoUBitXover<Bits> >(i); // preference over 1
}
for(size_t i=1; i < 11; i+=1) {
foundry.crossovers.add< eoNPtsBitXover<Bits> >(i); // nb of points
}
/***** Mutations ****/
for(double i=0.01; i<1.0; i+=0.01) {
foundry.mutations.add< eoBitMutation<Bits> >(i); // proba of flipping any bit
}
for(size_t i=1; i < 11; i+=1) {
foundry.mutations.add< eoDetBitFlip<Bits> >(i); // mutate k bits
}
/***** Selectors *****/
for(double i=0.51; i<0.91; i+=0.1) {
foundry.selectors.add< eoStochTournamentSelect<Bits> >(i);
}
foundry.selectors.add< eoSequentialSelect<Bits> >();
foundry.selectors.add< eoProportionalSelect<Bits> >();
for(size_t i=2; i < 10; i+=1) {
foundry.selectors.add< eoDetTournamentSelect<Bits> >(i);
}
/***** Replacements ****/
foundry.replacements.add< eoCommaReplacement<Bits> >();
foundry.replacements.add< eoPlusReplacement<Bits> >();
foundry.replacements.add< eoSSGAWorseReplacement<Bits> >();
for(double i=0.51; i<0.91; i+=0.1) {
foundry.replacements.add< eoSSGAStochTournamentReplacement<Bits> >(i);
}
for(size_t i=2; i < 10; i+=1) {
foundry.replacements.add< eoSSGADetTournamentReplacement<Bits> >(i);
}
return foundry;
}
// A basic PSO algorithm.
std::pair< eoAlgo<Particle>*, eoPop<Particle>* >
make_pso(eoFunctorStore& store, eoEvalFunc<Particle>& eval_foundry)
{
const size_t dim = eoAlgoFoundryEA<Bits>::dim;
auto& gen_pos = store.pack< eoUniformGenerator<double> >(0.1,0.9);
auto& random_pos = store.pack< eoInitFixedLength<Particle> >(dim, gen_pos);
auto pop = new eoPop<Particle>();
pop->append(100, random_pos); // pop size
auto& gen_minus = store.pack< eoUniformGenerator<double> >(-0.05, 0.05);
auto& random_velo = store.pack< eoVelocityInitFixedLength<Particle> >(dim, gen_minus);
auto& local_init = store.pack< eoFirstIsBestInit<Particle> >();
auto topology = new eoLinearTopology<Particle>(5); // neighborhood size
auto& init = store.pack< eoInitializer<Particle> >(eval_foundry, random_velo, local_init, *topology, *pop);
init();
auto bounds = new eoRealVectorBounds(dim, 0, 0.999999);
auto& velocity = store.pack< eoStandardVelocity<Particle> >(*topology, 1, 1.6, 2, *bounds);
auto& flight = store.pack< eoStandardFlight<Particle> >();
auto& cont_gen = store.pack< eoGenContinue<Particle> >(50);
auto& cont = store.pack< eoCombinedContinue<Particle> >(cont_gen);
auto& checkpoint = store.pack< eoCheckPoint<Particle> >(cont);
auto& best = store.pack< eoBestFitnessStat<Particle> >();
checkpoint.add(best);
auto& monitor = store.pack< eoOStreamMonitor >(std::clog);
monitor.add(best);
checkpoint.add(monitor);
auto& pso = store.pack< eoEasyPSO<Particle> >(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<Bits> evalfunc;
eoPopLoopEval<Bits> 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<Bits> onemax_init(/*bitstring size=*/500, gen);
eoEvalFoundryEA<Particle,Bits> eval_foundry(foundry,
onemax_init, /*pop_size=*/ 10,
onemax_eval, /*penalization=*/ 0);
/***** Algorithm selection stuff (PSO on foundry) *****/
eoAlgo<Particle>* pso;
eoPop<Particle>* 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;
}

View file

@ -21,7 +21,7 @@ int main(int /*argc*/, char** /*argv*/)
eoBooleanGenerator gen(0.5);
eoInitFixedLength<EOT> init(dim, gen);
eoAlgoFoundryEA<EOT> foundry(eval);
eoAlgoFoundryEA<EOT> foundry(eval, 10);
/***** Continuators ****/
for(size_t i=10; i < 30; i+=10 ) {