diff --git a/HISTORY.md b/HISTORY.md index 09a4b74da..8d06b21d2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -27,13 +27,16 @@ * Make Callback flexible for MultiObjective Optimizers ([#289](https://github.com/mlpack/ensmallen/pull/289)). - + * Add ZDT Test Suite ([#273](https://github.com/mlpack/ensmallen/pull/273)). * Add MOEA-D/DE Optimizer ([#269](https://github.com/mlpack/ensmallen/pull/269)). + * Introduce Policy Methods for MOEA/D-DE + ([#293](https://github.com/mlpack/ensmallen/pull/293)). + ### ensmallen 2.16.1: "Severely Dented Can Of Polyurethane" ###### 2021-03-02 * Fix test compilation issue when `ENS_USE_OPENMP` is set diff --git a/doc/optimizers.md b/doc/optimizers.md index 103ff855d..8587b9413 100644 --- a/doc/optimizers.md +++ b/doc/optimizers.md @@ -1573,8 +1573,29 @@ decomposed to form a Single Objective Problem. A diversity preserving mechanism a varied set of solution. #### Constructors -* `MOEAD()` -* `MOEAD(`_`populationSize, maxGenerations, crossoverProb, neighborProb, neighborSize, distributionIndex, differentialWeight, maxReplace, epsilon, lowerBound, upperBound`_`)` +* `MOEAD<`_`InitPolicyType, DecompPolicyType`_`>()` +* `MOEAD<`_`InitPolicyType, DecompPolicyType`_`>(`_`populationSize, maxGenerations, crossoverProb, neighborProb, neighborSize, distributionIndex, differentialWeight, maxReplace, epsilon, lowerBound, upperBound`_`)` + +The _`InitPolicyType`_ template parameter refers to the strategy used to +initialize the reference directions. + +The following types are available: + + * **`BayesianBootstrap`** + +The _`DecompPolicyType`_ template parameter refers to the strategy used to +decompose the weight vectors to form a scalar objective function. + +The following types are available: + + * **`Tchebycheff`** + * **`WeightedAverage`** + * **`PenaltyBoundaryIntersection`** + +For convenience the following types can be used: + + * **`DefaultMOEAD`** (equivalent to `MOEAD`): utilizes BayesianBootstrap for weight init + and Tchebycheff for weight decomposition. #### Attributes @@ -1591,10 +1612,12 @@ a varied set of solution. | `double` | **`epsilon`** | Handles numerical stability after weight initialization. | `1E-10`| | `double`, `arma::vec` | **`lowerBound`** | Lower bound of the coordinates on the coordinates of the whole population during the search process. | `0` | | `double`, `arma::vec` | **`upperBound`** | Lower bound of the coordinates on the coordinates of the whole population during the search process. | `1` | +| `InitPolicyType` | **`initPolicy`** | Instantiated init policy used to initialize weights. | `InitPolicyType()` | +| `DecompPolicyType` | **`decompPolicy`** | Instantiated decomposition policy used to create scalar objective problem. | `DecompPolicyType()` | Attributes of the optimizer may also be changed via the member methods `PopulationSize()`, `MaxGenerations()`, `CrossoverRate()`, `NeighborProb()`, `NeighborSize()`, `DistributionIndex()`, -`DifferentialWeight()`, `MaxReplace()`, `Epsilon()`, `LowerBound()` and `UpperBound()`. +`DifferentialWeight()`, `MaxReplace()`, `Epsilon()`, `LowerBound()`, `UpperBound()`, `InitPolicy()` and `DecompPolicy()`. #### Examples: @@ -1606,7 +1629,7 @@ Attributes of the optimizer may also be changed via the member methods SchafferFunctionN1 SCH; arma::vec lowerBound("-10 -10"); arma::vec upperBound("10 10"); -MOEAD opt(150, 300, 1.0, 0.9, 20, 20, 0.5, 2, 1E-10, lowerBound, upperBound); +DefaultMOEAD opt(150, 300, 1.0, 0.9, 20, 20, 0.5, 2, 1E-10, lowerBound, upperBound); typedef decltype(SCH.objectiveA) ObjectiveTypeA; typedef decltype(SCH.objectiveB) ObjectiveTypeB; arma::mat coords = SCH.GetInitialPoint(); diff --git a/include/ensmallen_bits/moead/decomposition_policies/pbi_decomposition.hpp b/include/ensmallen_bits/moead/decomposition_policies/pbi_decomposition.hpp new file mode 100644 index 000000000..1b7c24659 --- /dev/null +++ b/include/ensmallen_bits/moead/decomposition_policies/pbi_decomposition.hpp @@ -0,0 +1,81 @@ +/** + * @file pbi_decomposition.hpp + * @author Nanubala Gnana Sai + * + * The Penalty Based Boundary Intersection (PBI) decomposition policy. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef ENSMALLEN_MOEAD_PBI_HPP +#define ENSMALLEN_MOEAD_PBI_HPP + +namespace ens { + +/** + * Penalty Based Boundary Intersection (PBI) method is a weight decomposition method, + * it tries to find the intersection between bottom-most boundary of the attainable + * objective set with the reference directions. + * + * The goal is to minimize the distance between objective vectors with the ideal point + * along the reference direction. To handle equality constraints, a penalty parameter + * theta is used. + * + * For more information, see the following: + * @code + * article{zhang2007moea, + * title={MOEA/D: A multiobjective evolutionary algorithm based on decomposition}, + * author={Zhang, Qingfu and Li, Hui}, + * journal={IEEE Transactions on evolutionary computation}, + * pages={712--731}, + * year={2007} + * @endcode + */ +class PenaltyBoundaryIntersection +{ + public: + /** + * Constructor for Penalty Based Boundary Intersection decomposition + * policy. + * + * @param theta The penalty value. + */ + PenaltyBoundaryIntersection(const double theta = 5) : + theta(theta) + { + /* Nothing to do. */ + } + + /** + * Decompose the weight vectors. + * + * @tparam VecType The type of the vector used in the decommposition. + * @param weight The weight vector corresponding to a subproblem. + * @param idealPoint The reference point in the objective space. + * @param candidateFitness The objective vector of the candidate. + */ + template + typename VecType::elem_type Apply(const VecType& weight, + const VecType& idealPoint, + const VecType& candidateFitness) + { + typedef typename VecType::elem_type ElemType; + //! A unit vector in the same direction as the provided weight vector. + const VecType referenceDirection = weight / arma::norm(weight, 1); + //! Distance of F(x) from the idealPoint along the reference direction. + const ElemType d1 = arma::dot(candidateFitness - idealPoint, referenceDirection); + //! The perpendicular distance of F(x) from reference direction. + const ElemType d2 = arma::norm(candidateFitness - (idealPoint + d1 * referenceDirection), 1); + + return d1 + static_cast(theta) * d2; + } + + private: + double theta; +}; + +} // namespace ens + +#endif diff --git a/include/ensmallen_bits/moead/decomposition_policies/tchebycheff_decomposition.hpp b/include/ensmallen_bits/moead/decomposition_policies/tchebycheff_decomposition.hpp new file mode 100644 index 000000000..0507c03d3 --- /dev/null +++ b/include/ensmallen_bits/moead/decomposition_policies/tchebycheff_decomposition.hpp @@ -0,0 +1,66 @@ +/** + * @file tchebycheff_decomposition.hpp + * @author Nanubala Gnana Sai + * + * The Tchebycheff Weight decomposition policy. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef ENSMALLEN_MOEAD_TCHEBYCHEFF_HPP +#define ENSMALLEN_MOEAD_TCHEBYCHEFF_HPP + +namespace ens { + +/** + * The Tchebycheff method works by taking the maximum of element-wise product + * between reference direction and the line connecting objective vector and + * ideal point. + * + * Under mild conditions, for each Pareto Optimal point there exists a reference + * direction such that the given point is also the optimal solution + * to this scalar objective. + * + * For more information, see the following: + * @code + * article{zhang2007moea, + * title={MOEA/D: A multiobjective evolutionary algorithm based on decomposition}, + * author={Zhang, Qingfu and Li, Hui}, + * journal={IEEE Transactions on evolutionary computation}, + * pages={712--731}, + * year={2007} + * @endcode + */ +class Tchebycheff +{ + public: + /** + * Constructor for Tchebycheff decomposition policy. + */ + Tchebycheff() + { + /* Nothing to do. */ + } + + /** + * Decompose the weight vectors. + * + * @tparam VecType The type of the vector used in the decommposition. + * @param weight The weight vector corresponding to a subproblem. + * @param idealPoint The reference point in the objective space. + * @param candidateFitness The objective vector of the candidate. + */ + template + typename VecType::elem_type Apply(const VecType& weight, + const VecType& idealPoint, + const VecType& candidateFitness) + { + return arma::max(weight % arma::abs(candidateFitness - idealPoint)); + } +}; + +} // namespace ens + +#endif diff --git a/include/ensmallen_bits/moead/decomposition_policies/weighted_decomposition.hpp b/include/ensmallen_bits/moead/decomposition_policies/weighted_decomposition.hpp new file mode 100644 index 000000000..80073953c --- /dev/null +++ b/include/ensmallen_bits/moead/decomposition_policies/weighted_decomposition.hpp @@ -0,0 +1,62 @@ +/** + * @file weighted_decomposition.hpp + * @author Nanubala Gnana Sai + * + * The Weighted Average decomposition policy. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef ENSMALLEN_MOEAD_WEIGHTED_HPP +#define ENSMALLEN_MOEAD_WEIGHTED_HPP + +namespace ens { + +/** + * The Weighted average method of decomposition. The working principle is to + * minimize the dot product between reference direction and the line connecting + * objective vector and ideal point. + * + * For more information, see the following: + * @code + * article{zhang2007moea, + * title={MOEA/D: A multiobjective evolutionary algorithm based on decomposition}, + * author={Zhang, Qingfu and Li, Hui}, + * journal={IEEE Transactions on evolutionary computation}, + * pages={712--731}, + * year={2007} + * @endcode + */ +class WeightedAverage +{ + public: + /** + * Constructor for Weighted Average decomposition policy. + */ + WeightedAverage() + { + /* Nothing to do. */ + } + + /** + * Decompose the weight vectors. + * + * @tparam VecType The type of the vector used in the decommposition. + * @param weight The weight vector corresponding to a subproblem. + * @param idealPoint The reference point in the objective space. + * @param candidateFitness The objective vector of the candidate. + */ + template + typename VecType::elem_type Apply(const VecType& weight, + const VecType& /* idealPoint */, + const VecType& candidateFitness) + { + return arma::dot(weight, candidateFitness); + } +}; + +} // namespace ens + +#endif diff --git a/include/ensmallen_bits/moead/moead.hpp b/include/ensmallen_bits/moead/moead.hpp index 9fa34cc09..fb52a4a16 100644 --- a/include/ensmallen_bits/moead/moead.hpp +++ b/include/ensmallen_bits/moead/moead.hpp @@ -1,13 +1,10 @@ /** * @file moead.hpp - * @author Utkarsh Rai * @author Nanubala Gnana Sai * - * MOEA/D, Multi Objective Evolutionary Algorithm based on Decompositon is a - * multi objective optimization algorithm. It employs evolutionary algorithms, - * to find better solutions by iterating on the previous solutions and - * decomposition approaches, to convert the multi objective problem to a single - * objective one, to find the best Pareto Front for the given problem. + * MOEA/D-DE is a multi objective optimization algorithm. MOEA/D-DE + * uses genetic algorithms along with a set of reference directions + * to drive the population towards the Optimal Front. * * ensmallen is free software; you may redistribute it and/or modify it under * the terms of the 3-clause BSD license. You should have received a copy of @@ -18,35 +15,38 @@ #ifndef ENSMALLEN_MOEAD_MOEAD_HPP #define ENSMALLEN_MOEAD_MOEAD_HPP +//! Decomposition policies. +#include "decomposition_policies/tchebycheff_decomposition.hpp" +#include "decomposition_policies/weighted_decomposition.hpp" +#include "decomposition_policies/pbi_decomposition.hpp" + +//! Weight initialization policies. +#include "weight_init_policies/bbs_init.hpp" namespace ens { /** - * This class implements the MOEA/D algorithm with Differential Evolution - * crossover. Step numbers used in different parts of the implementation - * correspond to the step number used in the original algorithm by the author. + * MOEA/D-DE (Multi Objective Evolutionary Algorithm based on Decompositon - + * Differential Variant) is a multiobjective optimization algorithm. This class + * implements the said optimizer. + * + * The algorithm works by generating a candidate population from a fixed starting point. + * Reference directions are generated to guide the optimization process towards the Pareto Front. + * Further, a decomposition function is defined to decompose the problem to a scalar optimization + * objective. Utilizing genetic operators, offsprings are generated with better decomposition values + * to replace the neighboring parent solutions. * * For more information, see the following: * @code - * @article{article, - * author = {Zhang, Qingfu and Li, Hui}, - * year = {2008}, - * pages = {712 - 731}, - * title = {MOEA/D: A Multiobjective Evolutionary Algorithm Based on - * Decomposition}, - * journal = {Evolutionary Computation, IEEE Transactions on}, - * - * @article{4633340, - * author={H. {Li} and Q. {Zhang}}, - * year={2009}, - * pages={284-302},} - * title={Multiobjective Optimization Problems With Complicated Pareto Sets, MOEA/D and NSGA-II}, - * journal={IEEE Transactions on Evolutionary Computation}, + * @article{li2008multiobjective, + * title={Multiobjective optimization problems with complicated Pareto sets, MOEA/D and NSGA-II}, + * author={Li, Hui and Zhang, Qingfu}, + * journal={IEEE transactions on evolutionary computation}, + * pages={284--302}, + * year={2008}, * @endcode - * - * MOEA/D can optimize arbitrary multi-objective functions. For more details, - * see the documentation on function types included with this distribution or - * on the ensmallen website. */ +template class MOEAD { public: /** @@ -82,7 +82,9 @@ class MOEAD { const size_t maxReplace = 2, const double epsilon = 1E-10, const arma::vec& lowerBound = arma::zeros(1, 1), - const arma::vec& upperBound = arma::ones(1, 1)); + const arma::vec& upperBound = arma::ones(1, 1), + const InitPolicyType initPolicy = InitPolicyType(), + const DecompPolicyType decompPolicy = DecompPolicyType()); /** * Constructor for the MOEA/D optimizer. This constructor is provides an @@ -119,7 +121,9 @@ class MOEAD { const size_t maxReplace = 2, const double epsilon = 1E-10, const double lowerBound = 0, - const double upperBound = 1); + const double upperBound = 1, + const InitPolicyType initPolicy = InitPolicyType(), + const DecompPolicyType decompPolicy = DecompPolicyType()); /** * Optimize a set of objectives. The initial population is generated @@ -202,6 +206,15 @@ class MOEAD { //! `Optimize()` has been called. const arma::cube& ParetoFront() const { return paretoFront; } + //! Get the weight initialization policy. + const InitPolicyType& InitPolicy() const { return initPolicy; } + //! Modify the weight initialization policy. + InitPolicyType& InitPolicy() { return initPolicy; } + + //! Get the weight decomposition policy. + const DecompPolicyType& DecompPolicy() const { return decompPolicy; } + //! Modify the weight decomposition policy. + DecompPolicyType& DecompPolicy() { return decompPolicy; } private: /** @@ -212,8 +225,8 @@ class MOEAD { * @return std::tuple The chosen pair of indices. */ std::tuple Mating(size_t subProblemIdx, - const arma::umat& neighborSize, - bool sampleNeighbor); + const arma::umat& neighborSize, + bool sampleNeighbor); /** * Mutate the child formed by the crossover of two random members of the @@ -232,19 +245,6 @@ class MOEAD { const MatType& lowerBound, const MatType& upperBound); - /** - * Decompose the multi objective problem to a single objective problem. - * - * @param subProblemWeight The Decomposition weight vector of the current subproblem. - * @param idealPoint The reference point z for a decomposition problem. - * @param candidateFitness The fitness value of the candidate. - * @return The real value obtained from the decomposed function. - */ - template - ElemType DecomposeObjectives(const arma::Col& subProblemWeight, - const arma::Col& idealPoint, - const arma::Col& candidateFitness); - /** * Evaluate objectives for the elite population. * @@ -316,8 +316,15 @@ class MOEAD { //! The set of all the Pareto optimal objective vectors. //! Stored after Optimize() is called. arma::cube paretoFront; + + //! Policy to initialize the reference directions (weights) matrix. + InitPolicyType initPolicy; + + //! Policy to decompose the weights. + DecompPolicyType decompPolicy; }; +using DefaultMOEAD = MOEAD; } // namespace ens // Include implementation. diff --git a/include/ensmallen_bits/moead/moead_impl.hpp b/include/ensmallen_bits/moead/moead_impl.hpp index 26d08aa49..63c7d5e70 100644 --- a/include/ensmallen_bits/moead/moead_impl.hpp +++ b/include/ensmallen_bits/moead/moead_impl.hpp @@ -1,6 +1,5 @@ /** * @file moead_impl.hpp - * @author Utkarsh Rai * @author Nanubala Gnana Sai * * Implementation of the MOEA/D-DE algorithm. Used for multi-objective @@ -19,18 +18,21 @@ #include namespace ens { - -inline MOEAD::MOEAD(const size_t populationSize, - const size_t maxGenerations, - const double crossoverProb, - const double neighborProb, - const size_t neighborSize, - const double distributionIndex, - const double differentialWeight, - const size_t maxReplace, - const double epsilon, - const arma::vec& lowerBound, - const arma::vec& upperBound) : +template +inline MOEAD:: +MOEAD(const size_t populationSize, + const size_t maxGenerations, + const double crossoverProb, + const double neighborProb, + const size_t neighborSize, + const double distributionIndex, + const double differentialWeight, + const size_t maxReplace, + const double epsilon, + const arma::vec& lowerBound, + const arma::vec& upperBound, + const InitPolicyType initPolicy, + const DecompPolicyType decompPolicy) : populationSize(populationSize), maxGenerations(maxGenerations), crossoverProb(crossoverProb), @@ -41,20 +43,26 @@ inline MOEAD::MOEAD(const size_t populationSize, maxReplace(maxReplace), epsilon(epsilon), lowerBound(lowerBound), - upperBound(upperBound) + upperBound(upperBound), + initPolicy(initPolicy), + decompPolicy(decompPolicy) { /* Nothing to do here. */ } -inline MOEAD::MOEAD(const size_t populationSize, - const size_t maxGenerations, - const double crossoverProb, - const double neighborProb, - const size_t neighborSize, - const double distributionIndex, - const double differentialWeight, - const size_t maxReplace, - const double epsilon, - const double lowerBound, - const double upperBound) : +template +inline MOEAD:: +MOEAD(const size_t populationSize, + const size_t maxGenerations, + const double crossoverProb, + const double neighborProb, + const size_t neighborSize, + const double distributionIndex, + const double differentialWeight, + const size_t maxReplace, + const double epsilon, + const double lowerBound, + const double upperBound, + const InitPolicyType initPolicy, + const DecompPolicyType decompPolicy) : populationSize(populationSize), maxGenerations(maxGenerations), crossoverProb(crossoverProb), @@ -65,16 +73,20 @@ inline MOEAD::MOEAD(const size_t populationSize, maxReplace(maxReplace), epsilon(epsilon), lowerBound(lowerBound * arma::ones(1, 1)), - upperBound(upperBound * arma::ones(1, 1)) + upperBound(upperBound * arma::ones(1, 1)), + initPolicy(initPolicy), + decompPolicy(decompPolicy) { /* Nothing to do here. */ } //! Optimize the function. +template template -typename MatType::elem_type MOEAD::Optimize(std::tuple& objectives, - MatType& iterateIn, - CallbackTypes&&... callbacks) +typename MatType::elem_type MOEAD:: +Optimize(std::tuple& objectives, + MatType& iterateIn, + CallbackTypes&&... callbacks) { // Population Size must be at least 3 for MOEA/D-DE to work. if (populationSize < 3) @@ -136,8 +148,8 @@ typename MatType::elem_type MOEAD::Optimize(std::tuple bool terminate = false; // The weight matrix. Each vector represents a decomposition subproblem (M X N). - const BaseMatType weights = BaseMatType(numObjectives, populationSize, - arma::fill::randu) + epsilon; + const BaseMatType weights = initPolicy.template Generate( + numObjectives, populationSize, epsilon); // 1.1 Storing the indices of nearest neighbors of each weight vector. arma::umat neighborIndices(neighborSize, populationSize); @@ -155,7 +167,7 @@ typename MatType::elem_type MOEAD::Optimize(std::tuple std::vector population(populationSize); for (BaseMatType& individual : population) { - individual = arma::randu( + individual = arma::randu( iterate.n_rows, iterate.n_cols) - 0.5 + iterate; // Constrain all genes to be within bounds. @@ -256,10 +268,10 @@ typename MatType::elem_type MOEAD::Optimize(std::tuple const size_t pick = sampleNeighbor ? neighborIndices(idx, subProblemIdx) : idx; - const ElemType candidateDecomposition = DecomposeObjectives( - weights.col(pick), idealPoint, candidateFitness); - const ElemType parentDecomposition = DecomposeObjectives( - weights.col(pick), idealPoint, populationFitness[pick]); + const ElemType candidateDecomposition = decompPolicy.template + Apply>(weights.col(pick), idealPoint, candidateFitness); + const ElemType parentDecomposition = decompPolicy.template + Apply>(weights.col(pick), idealPoint, populationFitness[pick]); if (candidateDecomposition < parentDecomposition) { @@ -314,10 +326,12 @@ typename MatType::elem_type MOEAD::Optimize(std::tuple } //! Randomly chooses to select from parents or neighbors. +template inline std::tuple -MOEAD::Mating(size_t subProblemIdx, - const arma::umat& neighborIndices, - bool sampleNeighbor) +MOEAD:: +Mating(size_t subProblemIdx, + const arma::umat& neighborIndices, + bool sampleNeighbor) { //! Indexes of two points from the sample space. size_t pointA = sampleNeighbor @@ -344,11 +358,13 @@ MOEAD::Mating(size_t subProblemIdx, } //! Perform Polynomial mutation of the candidate. +template template -inline void MOEAD::Mutate(MatType& candidate, - double mutationRate, - const MatType& lowerBound, - const MatType& upperBound) +inline void MOEAD:: +Mutate(MatType& candidate, + double mutationRate, + const MatType& lowerBound, + const MatType& upperBound) { const size_t numVariables = candidate.n_rows; for (size_t geneIdx = 0; geneIdx < numVariables; ++geneIdx) @@ -383,35 +399,29 @@ inline void MOEAD::Mutate(MatType& candidate, candidate = arma::min(arma::max(candidate, lowerBound), upperBound); } -//! Calculate the output for single objective function using the Tchebycheff -//! approach. -template -inline ElemType MOEAD::DecomposeObjectives(const arma::Col& subProblemWeight, - const arma::Col& idealPoint, - const arma::Col& candidateFitness) -{ - return arma::max(subProblemWeight % arma::abs(candidateFitness - idealPoint)); -} - //! No objectives to evaluate. +template template typename std::enable_if::type -MOEAD::EvaluateObjectives( +MOEAD:: +EvaluateObjectives( std::vector&, std::tuple&, std::vector >&) { - // Nothing to do here. + // Nothing to do here. } //! Evaluate the objectives for the entire population. +template template typename std::enable_if::type -MOEAD::EvaluateObjectives( +MOEAD:: +EvaluateObjectives( std::vector& population, std::tuple& objectives, std::vector >& calculatedObjectives) diff --git a/include/ensmallen_bits/moead/weight_init_policies/bbs_init.hpp b/include/ensmallen_bits/moead/weight_init_policies/bbs_init.hpp new file mode 100644 index 000000000..b877dd4a9 --- /dev/null +++ b/include/ensmallen_bits/moead/weight_init_policies/bbs_init.hpp @@ -0,0 +1,76 @@ +/** + * @file bbs_init.hpp + * @author Nanubala Gnana Sai + * + * The Bayesian Bootstrap (BBS) method of Weight Initialization. + * + * ensmallen is free software; you may redistribute it and/or modify it under + * the terms of the 3-clause BSD license. You should have received a copy of + * the 3-clause BSD license along with ensmallen. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef ENSMALLEN_MOEAD_BBS_HPP +#define ENSMALLEN_MOEAD_BBS_HPP + +namespace ens { + +/** + * The Bayesian Bootstrap method for initializing weights. Samples are randomly picked from uniform + * distribution followed by sorting and finding adjacent difference. This gives you a list of + * numbers which is guaranteed to sum up to 1. + * + * @code + * @article{rubin1981bayesian, + * title={The bayesian bootstrap}, + * author={Rubin, Donald B}, + * journal={The annals of statistics}, + * pages={130--134}, + * year={1981}, + * @endcode + * + */ +class BayesianBootstrap +{ + public: + /** + * Constructor for Bayesian Bootstrap policy. + */ + BayesianBootstrap() + { + /* Nothing to do. */ + } + + /** + * Generate the reference direction matrix. + * + * @tparam MatType The type of the matrix used for constructing weights. + * @param numObjectives The dimensionality of objective space. + * @param numPoints The number of reference directions requested. + * @param epsilon Handle numerical stability after weight initialization. + */ + template + MatType Generate(const size_t numObjectives, + const size_t numPoints, + const double epsilon) + { + typedef typename MatType::elem_type ElemType; + typedef typename arma::Col VecType; + + MatType weights(numObjectives, numPoints); + for (size_t pointIdx = 0; pointIdx < numPoints; ++pointIdx) + { + VecType referenceDirection(numObjectives + 1, arma::fill::randu); + referenceDirection(0) = 0; + referenceDirection(numObjectives) = 1; + referenceDirection = arma::sort(referenceDirection); + referenceDirection = arma::diff(referenceDirection); + weights.col(pointIdx) = std::move(referenceDirection) + epsilon; + } + + return weights; + } +}; + +} // namespace ens + +#endif diff --git a/tests/callbacks_test.cpp b/tests/callbacks_test.cpp index 924278f38..25f8c20ef 100644 --- a/tests/callbacks_test.cpp +++ b/tests/callbacks_test.cpp @@ -406,7 +406,7 @@ TEST_CASE("MOEADCallbacksFullFunctionTest", "[CallbackTest]") { arma::vec lowerBound = {-1000}; arma::vec upperBound = {1000}; - MOEAD optimizer(150, 300, 1.0, 0.9, 20, 20, 0.5, 2, 1E-10, lowerBound, upperBound); + DefaultMOEAD optimizer(150, 300, 1.0, 0.9, 20, 20, 0.5, 2, 1E-10, lowerBound, upperBound); CallbacksFullMultiobjectiveFunctionTest(optimizer, false, false, false, false, true, true, false, false, false, true); } diff --git a/tests/moead_test.cpp b/tests/moead_test.cpp index 8e89872b0..98dd73bb6 100644 --- a/tests/moead_test.cpp +++ b/tests/moead_test.cpp @@ -1,7 +1,5 @@ /** * @file moead_test.cpp - * @author Sayan Goswami - * @author Utkarsh Rai * @author Nanubala Gnana Sai * * ensmallen is free software; you may redistribute it and/or modify it under @@ -47,7 +45,7 @@ TEST_CASE("MOEADSchafferN1DoubleTest", "[MOEADTest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -109,7 +107,7 @@ TEST_CASE("MOEADSchafferN1TestVectorDoubleBounds", "[MOEADTest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -169,7 +167,7 @@ TEST_CASE("MOEADFonsecaFlemingDoubleTest", "[MOEADTest]") const double expectedLowerBound = -1.0 / sqrt(3); const double expectedUpperBound = 1.0 / sqrt(3); - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -224,7 +222,7 @@ TEST_CASE("MOEADFonsecaFlemingTestVectorDoubleBounds", "[MOEADTest]") const double expectedLowerBound = -1.0 / sqrt(3); const double expectedUpperBound = 1.0 / sqrt(3); - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -279,7 +277,7 @@ TEST_CASE("MOEADSchafferN1FloatTest", "[MOEADTest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -341,7 +339,7 @@ TEST_CASE("MOEADSchafferN1TestVectorFloatBounds", "[MOEADTest]") const double expectedLowerBound = 0.0; const double expectedUpperBound = 2.0; - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -401,7 +399,7 @@ TEST_CASE("MOEADFonsecaFlemingFloatTest", "[MOEADTest]") const float expectedLowerBound = -1.0 / sqrt(3); const float expectedUpperBound = 1.0 / sqrt(3); - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -456,7 +454,7 @@ TEST_CASE("MOEADFonsecaFlemingTestVectorFloatBounds", "[MOEADTest]") const float expectedLowerBound = -1.0 / sqrt(3); const float expectedUpperBound = 1.0 / sqrt(3); - MOEAD opt( + DefaultMOEAD opt( 150, // Population size. 300, // Max generations. 1.0, // Crossover probability. @@ -498,3 +496,48 @@ TEST_CASE("MOEADFonsecaFlemingTestVectorFloatBounds", "[MOEADTest]") REQUIRE(allInRange); } + +/** + * Test against the first problem of ZDT Test Suite. ZDT-1 is a 30 + * variable-2 objective problem with a convex Pareto Front. + * + * NOTE: For the sake of runtime, only ZDT-1 is tested against the + * algorithm. Others have been tested separately. + */ +TEST_CASE("MOEADZDTONETest", "[MOEADTest]") +{ + //! Parameters taken from original ZDT Paper. + ZDT1<> ZDT_ONE(100); + const double lowerBound = 0; + const double upperBound = 1; + + DefaultMOEAD opt( + 150, // Population size. + 300, // Max generations. + 1.0, // Crossover probability. + 0.9, // Probability of sampling from neighbor. + 20, // Neighborhood size. + 20, // Perturbation index. + 0.5, // Differential weight. + 2, // Max childrens to replace parents. + 1E-10, // epsilon. + lowerBound, // Lower bound. + upperBound // Upper bound. + ); + + typedef decltype(ZDT_ONE.objectiveF1) ObjectiveTypeA; + typedef decltype(ZDT_ONE.objectiveF2) ObjectiveTypeB; + + arma::mat coords = ZDT_ONE.GetInitialPoint(); + std::tuple objectives = ZDT_ONE.GetObjectives(); + + opt.Optimize(objectives, coords); + + //! Refer the ZDT_ONE implementation for g objective implementation. + //! The optimal g value is taken from the docs of ZDT_ONE. + size_t numVariables = coords.size(); + double sum = arma::accu(coords(arma::span(1, numVariables - 1), 0)); + double g = 1. + 9. * sum / (static_cast(numVariables - 1)); + + REQUIRE(g == Approx(1.0).margin(0.99)); +} \ No newline at end of file