Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvise NSGA2 #263

Merged
merged 54 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e850696
Changes (See below)
jonpsy Mar 20, 2021
01dbfa3
Merge branch 'master' into nsga2-better
jonpsy Mar 22, 2021
b2f525f
allow floatonly
jonpsy Mar 22, 2021
b12d050
a minor change to spot error(will be removed)
jonpsy Mar 22, 2021
e508278
single threaded
jonpsy Mar 22, 2021
31e96e2
make class template
jonpsy Mar 22, 2021
69906e1
test appveyor
jonpsy Mar 23, 2021
eec6cad
Update .appveyor.yml
jonpsy Mar 23, 2021
1c290c3
back2square1
jonpsy Mar 23, 2021
8f6af30
appveyor validated
jonpsy Mar 23, 2021
6254ac1
remove some inlines
jonpsy Mar 23, 2021
a43c447
dont build!
jonpsy Mar 23, 2021
a691584
dont run test as well
jonpsy Mar 23, 2021
f76c2f2
on finish
jonpsy Mar 23, 2021
c19c5f6
MSVC-15 fails back_inserter
jonpsy Mar 23, 2021
a1ef11a
some more removals
jonpsy Mar 23, 2021
1b0b51f
appveyor to normal
jonpsy Mar 23, 2021
0256405
check error
jonpsy Mar 28, 2021
6c388a0
appveyor checks again
jonpsy Mar 28, 2021
679d904
cxx11 compiler error?
jonpsy Mar 28, 2021
d18192e
building works, now just test fails on double
jonpsy Mar 28, 2021
1d16313
build in debug
jonpsy Mar 28, 2021
2689d5c
use absolute diff
jonpsy Mar 28, 2021
d9c0496
maybe the error should be ok now
jonpsy Mar 28, 2021
38f88ab
minor
jonpsy Mar 28, 2021
1ad7181
everyone use IsInBounds
jonpsy Mar 28, 2021
70bfcee
Reason for changing:
jonpsy Mar 29, 2021
d3b2ce9
Use params from paper
jonpsy Mar 29, 2021
c98b16a
SchafferN1 double debug Appveyor
jonpsy Mar 29, 2021
a975138
appveyor old
jonpsy Mar 29, 2021
1561856
introduced tolerance
jonpsy Mar 30, 2021
12c513d
appveyor debug to check tolerance level
jonpsy Mar 30, 2021
68157ee
build in debug
jonpsy Mar 30, 2021
bad9062
increase tolerance
jonpsy Mar 31, 2021
b986687
fix merge conflicts
jonpsy Mar 31, 2021
d9f3e7b
Update nsga2_impl.hpp
jonpsy Mar 31, 2021
252c500
indent fix
jonpsy Mar 31, 2021
f58e0eb
fix windows indentation
jonpsy Mar 31, 2021
846b01f
appveyor debug
jonpsy Mar 31, 2021
36e43ae
run only nsga2 test and use random seeds in main.cpp
jonpsy Mar 31, 2021
400720a
appveyor print when test fails
jonpsy Mar 31, 2021
f3f699a
run appveyor test 3000 times
jonpsy Mar 31, 2021
fb5f23d
revert appveyor and ctest settings
jonpsy Mar 31, 2021
95bc44f
rm appveyor pass
jonpsy Mar 31, 2021
5c31f6a
stress test on appveyor proofs => run 3k times
jonpsy Apr 1, 2021
7eafb8a
Revert changes from RDP
jonpsy Apr 2, 2021
02d8d94
appveyor from master
jonpsy Apr 2, 2021
c6e76e2
indent fix
jonpsy Apr 7, 2021
850612f
sanity fix
jonpsy Apr 8, 2021
6714a17
Merge branch 'nsga2-better' of https://github.com/jonpsy/ensmallen in…
jonpsy Apr 8, 2021
6be8403
Merge branch 'nsga2-better' of https://github.com/jonpsy/ensmallen in…
jonpsy Apr 8, 2021
a4f320a
Merge branch 'master' into nsga2-better
jonpsy Apr 10, 2021
9463b15
Update HISTORY.md
jonpsy Apr 10, 2021
33974ce
check arbitrary
jonpsy Apr 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
* CheckArbitraryFunctionTypeAPI extended for MOO support
([#283](https://github.com/mlpack/ensmallen/pull/283)).

* Refactor NSGA2
([#263](https://github.com/mlpack/ensmallen/pull/263)).

### ensmallen 2.16.1: "Severely Dented Can Of Polyurethane"
###### 2021-03-02
* Fix test compilation issue when `ENS_USE_OPENMP` is set
Expand Down
23 changes: 15 additions & 8 deletions include/ensmallen_bits/nsga2/nsga2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,16 @@ class NSGA2 {
typename std::enable_if<I == sizeof...(ArbitraryFunctionType), void>::type
EvaluateObjectives(std::vector<MatType>&,
std::tuple<ArbitraryFunctionType...>&,
std::vector<arma::Col<double> >&);
std::vector<arma::Col<typename MatType::elem_type> >&);

template<std::size_t I = 0,
typename MatType,
typename ...ArbitraryFunctionType>
typename std::enable_if<I < sizeof...(ArbitraryFunctionType), void>::type
EvaluateObjectives(std::vector<MatType>& population,
std::tuple<ArbitraryFunctionType...>& objectives,
std::vector<arma::Col<double> >& calculatedObjectives);
std::vector<arma::Col<typename MatType::elem_type> >&
calculatedObjectives);

/**
* Reproduce candidates from the elite population to generate a new
Expand Down Expand Up @@ -281,11 +282,15 @@ class NSGA2 {
* Assigns crowding distance metric for sorting.
*
* @param front The previously generated Pareto fronts.
* @param objectives The set of objectives.
* @param crowdingDistance The previously calculated objectives.
* @param calculatedObjectives The previously calculated objectives.
* @param crowdingDistance The crowding distance for each individual in
* the population.
*/
void CrowdingDistanceAssignment(const std::vector<size_t>& front,
std::vector<double>& crowdingDistance);
template <typename MatType>
void CrowdingDistanceAssignment(
const std::vector<size_t>& front,
std::vector<arma::Col<typename MatType::elem_type>>& calculatedObjectives,
std::vector<typename MatType::elem_type>& crowdingDistance);

/**
* The operator used in the crowding distance based sorting.
Expand All @@ -299,13 +304,15 @@ class NSGA2 {
* @param idxQ The index of the second cadidate from the elite population
* being sorted.
* @param ranks The previously calculated ranks.
* @param crowdingDistance The previously calculated objectives.
* @param crowdingDistance The crowding distance for each individual in
* the population.
* @return true if the first candidate is preferred, otherwise, false.
*/
template<typename MatType>
bool CrowdingOperator(size_t idxP,
size_t idxQ,
const std::vector<size_t>& ranks,
const std::vector<double>& crowdingDistance);
const std::vector<typename MatType::elem_type>& crowdingDistance);

//! The number of objectives being optimised for.
size_t numObjectives;
Expand Down
155 changes: 104 additions & 51 deletions include/ensmallen_bits/nsga2/nsga2_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ inline NSGA2::NSGA2(const size_t populationSize,
const double epsilon,
const arma::vec& lowerBound,
const arma::vec& upperBound) :
numObjectives(0),
numVariables(0),
populationSize(populationSize),
maxGenerations(maxGenerations),
crossoverProb(crossoverProb),
Expand All @@ -45,6 +47,8 @@ inline NSGA2::NSGA2(const size_t populationSize,
const double epsilon,
const double lowerBound,
const double upperBound) :
numObjectives(0),
numVariables(0),
populationSize(populationSize),
maxGenerations(maxGenerations),
crossoverProb(crossoverProb),
Expand All @@ -61,7 +65,7 @@ template<typename MatType,
typename... CallbackTypes>
typename MatType::elem_type NSGA2::Optimize(
std::tuple<ArbitraryFunctionType...>& objectives,
MatType& iterate,
MatType& iterateIn,
CallbackTypes&&... callbacks)
{
// Make sure for evolution to work at least four candidates are present.
Expand All @@ -71,6 +75,17 @@ typename MatType::elem_type NSGA2::Optimize(
" least 4, and, a multiple of 4!");
}

// Convenience typedefs.
typedef typename MatType::elem_type ElemType;
typedef typename MatTypeTraits<MatType>::BaseMatType BaseMatType;

BaseMatType& iterate = (BaseMatType&) iterateIn;

// Make sure that we have the methods that we need. Long name...
traits::CheckArbitraryFunctionTypeAPI<ArbitraryFunctionType...,
BaseMatType>();
RequireDenseFloatingPointType<BaseMatType>();

// Check if lower bound is a vector of a single dimension.
if (lowerBound.n_rows == 1)
lowerBound = lowerBound(0, 0) * arma::ones(iterate.n_rows, iterate.n_cols);
Expand All @@ -85,9 +100,6 @@ typename MatType::elem_type NSGA2::Optimize(
assert(upperBound.n_rows == iterate.n_rows && "The dimensions of "
"upperBound are not the same as the dimensions of iterate.");

// Convenience typedefs.
typedef typename MatType::elem_type ElemType;

numObjectives = sizeof...(ArbitraryFunctionType);
numVariables = iterate.n_rows;

Expand All @@ -98,13 +110,14 @@ typename MatType::elem_type NSGA2::Optimize(

// Population size reserved to 2 * populationSize + 1 to accommodate
// for the size of intermediate candidate population.
std::vector<MatType> population;
std::vector<BaseMatType> population;
population.reserve(2 * populationSize + 1);

// Pareto fronts, initialized during non-dominated sorting.
// Stores indices of population belonging to a certain front.
std::vector<std::vector<size_t> > fronts;
// Initialised in CrowdingDistanceAssignment.
std::vector<double> crowdingDistance;
std::vector<ElemType> crowdingDistance;
// Initialised during non-dominated sorting.
std::vector<size_t> ranks;

Expand All @@ -115,8 +128,17 @@ typename MatType::elem_type NSGA2::Optimize(
// starting point.
for (size_t i = 0; i < populationSize; i++)
{
population.push_back(arma::randu<MatType>(iterate.n_rows,
population.push_back(arma::randu<BaseMatType>(iterate.n_rows,
iterate.n_cols) - 0.5 + iterate);

// Constrain all genes to be between bounds.
for (size_t geneIdx = 0; geneIdx < numVariables; geneIdx++)
{
if (population[i](geneIdx) < lowerBound(geneIdx))
population[i](geneIdx) = lowerBound(geneIdx);
else if (population[i](geneIdx) > upperBound(geneIdx))
population[i](geneIdx) = upperBound(geneIdx);
}
}

Info << "NSGA2 initialized successfully. Optimization started." << std::endl;
Expand Down Expand Up @@ -146,56 +168,63 @@ typename MatType::elem_type NSGA2::Optimize(

// Perform fast non dominated sort on P_t ∪ G_t.
ranks.resize(population.size());
FastNonDominatedSort<MatType>(fronts, ranks, calculatedObjectives);
FastNonDominatedSort<BaseMatType>(fronts, ranks, calculatedObjectives);

// Perform crowding distance assignment.
crowdingDistance.resize(population.size());

std::fill(crowdingDistance.begin(), crowdingDistance.end(), 0.);
for (size_t fNum = 0; fNum < fronts.size(); fNum++)
{
CrowdingDistanceAssignment(fronts[fNum], crowdingDistance);
CrowdingDistanceAssignment<BaseMatType>(
fronts[fNum], calculatedObjectives, crowdingDistance);
}

// Sort based on crowding distance.
std::sort(population.begin(), population.end(),
[this, ranks, crowdingDistance, population](MatType candidateP,
MatType candidateQ)
{
size_t idxP, idxQ;
for (size_t i = 0; i < population.size(); i++)
{
if (arma::approx_equal(population[i], candidateP, "absdiff", epsilon))
idxP = i;

if (arma::approx_equal(population[i], candidateQ, "absdiff", epsilon))
idxQ = i;
}

return CrowdingOperator(idxP, idxQ, ranks, crowdingDistance);
}
[this, ranks, crowdingDistance, population]
(BaseMatType candidateP, BaseMatType candidateQ)
{
size_t idxP{}, idxQ{};
for (size_t i = 0; i < population.size(); i++)
{
if (arma::approx_equal(population[i], candidateP, "absdiff", epsilon))
idxP = i;

if (arma::approx_equal(population[i], candidateQ, "absdiff", epsilon))
idxQ = i;
}

return CrowdingOperator<BaseMatType>(idxP, idxQ, ranks, crowdingDistance);
}
);

// Yield a new population P_{t+1} of size populationSize.
// Discards unfit population from the R_{t} to yield P_{t+1}.
population.resize(populationSize);
}

// Set the candidates from the best front as the output.
std::vector<MatType> front;
std::vector<BaseMatType> front;

for (size_t f: fronts[0])
front.push_back(population[f]);

bestFront.resize(front.size());
// bestFront is stored, can be obtained by the Front() getter.
bestFront = front;
std::transform(front.begin(), front.end(), bestFront.begin(),
[&](const BaseMatType& individual)
{
return arma::conv_to<arma::mat>::from(individual);
});

// Assign iterate to first element of the best front.
iterate = bestFront[0];
iterate = front[0];

Callback::EndOptimization(*this, objectives, iterate, callbacks...);

ElemType performance = std::numeric_limits<ElemType>::max();

for(arma::Col<ElemType> objective: calculatedObjectives)
for (const arma::Col<ElemType>& objective: calculatedObjectives)
if (arma::accu(objective) < performance)
performance = arma::accu(objective);

Expand All @@ -210,7 +239,7 @@ typename std::enable_if<I == sizeof...(ArbitraryFunctionType), void>::type
NSGA2::EvaluateObjectives(
std::vector<MatType>&,
std::tuple<ArbitraryFunctionType...>&,
std::vector<arma::Col<double> >&)
std::vector<arma::Col<typename MatType::elem_type> >&)
{
// Nothing to do here.
}
Expand All @@ -223,7 +252,7 @@ typename std::enable_if<I < sizeof...(ArbitraryFunctionType), void>::type
NSGA2::EvaluateObjectives(
std::vector<MatType>& population,
std::tuple<ArbitraryFunctionType...>& objectives,
std::vector<arma::Col<double> >& calculatedObjectives)
std::vector<arma::Col<typename MatType::elem_type> >& calculatedObjectives)
{
for (size_t i = 0; i < populationSize; i++)
{
Expand Down Expand Up @@ -344,7 +373,7 @@ inline void NSGA2::FastNonDominatedSort(

size_t i = 0;

while (fronts[i].size() > 0)
while (!fronts[i].empty())
{
std::vector<size_t> nextFront;

Expand All @@ -365,6 +394,8 @@ inline void NSGA2::FastNonDominatedSort(
i++;
fronts.push_back(nextFront);
}
// Remove the empty final set.
fronts.pop_back();
jonpsy marked this conversation as resolved.
Show resolved Hide resolved
}

//! Check if a candidate Pareto dominates another candidate.
Expand Down Expand Up @@ -393,37 +424,59 @@ inline bool NSGA2::Dominates(
}

//! Assign crowding distance to the population.
inline void NSGA2::CrowdingDistanceAssignment(const std::vector<size_t>& front,
std::vector<double>& crowdingDistance)
template <typename MatType>
inline void NSGA2::CrowdingDistanceAssignment(
const std::vector<size_t>& front,
std::vector<arma::Col<typename MatType::elem_type>>& calculatedObjectives,
std::vector<typename MatType::elem_type>& crowdingDistance)
{
if (front.size() > 0)
{
for (size_t elem: front)
jonpsy marked this conversation as resolved.
Show resolved Hide resolved
crowdingDistance[elem] = 0;
// Convenience typedefs.
typedef typename MatType::elem_type ElemType;

size_t fSize = front.size();
size_t fSize = front.size();
// Stores the sorted indices of the fronts.
arma::uvec sortedIdx = arma::regspace<arma::uvec>(0, 1, fSize - 1);

for (size_t m = 0; m < numObjectives; m++)
{
crowdingDistance[front[0]] = std::numeric_limits<double>::max();
crowdingDistance[front[fSize - 1]] = std::numeric_limits<double>::max();
for (size_t m = 0; m < numObjectives; m++)
{
// Cache fValues of individuals for current objective.
arma::Col<ElemType> fValues(fSize);
std::transform(front.begin(), front.end(), fValues.begin(),
[&](const size_t& individual)
{
return calculatedObjectives[individual](m);
});

for (size_t i = 1; i < fSize - 1 ; i++)
{
crowdingDistance[front[i]] += (crowdingDistance[front[i - 1]] -
crowdingDistance[front[i + 1]]) /
(std::numeric_limits<double>::max() -
std::numeric_limits<double>::min());
}
// Sort front indices by ascending fValues for current objective.
std::sort(sortedIdx.begin(), sortedIdx.end(),
[&](const size_t& frontIdxA, const size_t& frontIdxB)
{
return (fValues(frontIdxA) < fValues(frontIdxB));
});

crowdingDistance[front[sortedIdx(0)]] =
std::numeric_limits<ElemType>::max();
crowdingDistance[front[sortedIdx(fSize - 1)]] =
std::numeric_limits<ElemType>::max();
ElemType minFval = fValues(sortedIdx(0));
ElemType maxFval = fValues(sortedIdx(fSize - 1));
ElemType scale =
std::abs(maxFval - minFval) == 0. ? 1. : std::abs(maxFval - minFval);

for (size_t i = 1; i < fSize - 1; i++)
{
crowdingDistance[front[sortedIdx(i)]] +=
(fValues(sortedIdx(i + 1)) - fValues(sortedIdx(i - 1))) / scale;
}
}
}

//! Comparator for crowding distance based sorting.
template<typename MatType>
inline bool NSGA2::CrowdingOperator(size_t idxP,
size_t idxQ,
const std::vector<size_t>& ranks,
const std::vector<double>& crowdingDistance)
const std::vector<typename MatType::elem_type>& crowdingDistance)
{
if (ranks[idxP] < ranks[idxQ])
return true;
Expand Down
Loading