Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
nsga2.cpp
Go to the documentation of this file.
1#include "nsga2.h"
2
3namespace Brush {
4namespace Sel {
5
6using namespace Brush;
7using namespace Pop;
8using namespace Sel;
9
10template<ProgramType T>
12{
13 this->name = "nsga2";
14 this->survival = surv;
15}
16
17template<ProgramType T>
18size_t NSGA2<T>::tournament(Population<T>& pop, size_t i, size_t j) const
19{
20 // gets two individuals and compares them. i and j bhould be within island range
21 const Individual<T>& ind1 = pop[i];
22 const Individual<T>& ind2 = pop[j];
23
24 int flag = ind1.fitness.dominates(ind2.fitness);
25
26 if (flag == 1) // ind1 dominates ind2
27 return i;
28 else if (flag == -1) // ind2 dominates ind1
29 return j;
30 else if (ind1.fitness.crowding_dist > ind2.fitness.crowding_dist)
31 return i;
32 else if (ind2.fitness.crowding_dist > ind1.fitness.crowding_dist)
33 return j;
34 else
35 return i;
36}
37
38template<ProgramType T>
39vector<size_t> NSGA2<T>::select(Population<T>& pop, int island,
40 const Parameters& params)
41{
42 // tournament selection. TODO: move this to tournament selection file, and throw not implemented error in nsga.
43 auto island_pool = pop.get_island_indexes(island);
44
45 // if this is first generation, just return indices to pop
46 if (params.current_gen==0)
47 return island_pool;
48
49 // i am not sure if I need this update of rank and crowding distance (bc first generation is ignored by if above, and the other generations will always have individuals that went through survival, which already calculates this information. TODO: in the final algorithm, I need to make sure this is correct)
50 auto front = fast_nds(pop, island_pool);
51 for (size_t i = 0; i< front.size(); i++)
52 {
53 crowding_distance(pop, front, i);
54 }
55
56 vector<size_t> selected(0);
57 for (int i = 0; i < island_pool.size(); ++i) // selecting based on island_pool size
58 {
59 size_t winner = tournament(pop,
60 *r.select_randomly(island_pool.begin(), island_pool.end()),
61 *r.select_randomly(island_pool.begin(), island_pool.end()));
62
63 selected.push_back(winner);
64 }
65 return selected;
66}
67
68template<ProgramType T>
69vector<size_t> NSGA2<T>::survive(Population<T>& pop, int island,
70 const Parameters& params)
71{
72 size_t idx_start = std::floor(island*params.pop_size/params.num_islands);
73 size_t idx_end = std::floor((island+1)*params.pop_size/params.num_islands);
74
75 // TODO: survive should be unified across islands, and stop taking island as argument. This was already implemented, I need to update to remove island argument only
76 // auto original_size = idx_end - idx_start; // original island size (survive must be called with an island with offfspring)
77
78 auto original_size = params.pop_size;
79
80 // TODO: clean up comments mess here
81 // auto island_pool = pop.get_island_indexes(island);
82
83 std::vector<size_t> island_pool;
84 for (int i = 0; i < params.num_islands; ++i) {
85 auto indexes = pop.get_island_indexes(i);
86 island_pool.insert(island_pool.end(), indexes.begin(), indexes.end());
87 }
88
89 // fast non-dominated sort
90 auto front = fast_nds(pop, island_pool);
91
92 // Push back selected individuals until full
93 vector<size_t> selected;
94 selected.resize(0);
95
96 int i = 0;
97 while (
98 i < front.size()
99 && ( selected.size() + front.at(i).size() < original_size )
100 )
101 {
102 std::vector<int>& Fi = front.at(i); // indices in front i
103
104 crowding_distance(pop, front, i); // calculate crowding in Fi
105
106 for (int j = 0; j < Fi.size(); ++j) // Pt+1 = Pt+1 U Fi
107 selected.push_back(Fi.at(j));
108
109 ++i;
110 }
111
112 // fmt::print("crowding distance\n");
113 crowding_distance(pop, front, i); // calculate crowding in final front to include
114 std::sort(front.at(i).begin(),front.at(i).end(),sort_n(pop));
115
116 // fmt::print("adding last front)\n");
117 const int extra = original_size - selected.size();
118 for (int j = 0; j < extra; ++j) // Pt+1 = Pt+1 U Fi[1:N-|Pt+1|]
119 selected.push_back(front.at(i).at(j));
120
121 // fmt::print("returning\n");
122 return selected;
123}
124
125template<ProgramType T>
126vector<vector<int>> NSGA2<T>::fast_nds(Population<T>& pop, vector<size_t>& island_pool)
127{
128 // this will update pareto dominance attributes in fitness class
129 // based on the population
130
131 //< the Pareto fronts
132 vector<vector<int>> front;
133
134 front.resize(1);
135 front.at(0).clear();
136
137 for (int i = 0; i < island_pool.size(); ++i) {
138
139 std::vector<unsigned int> dom;
140 int dcount = 0;
141
142 auto p = pop.individuals.at(island_pool[i]);
143
144 for (int j = 0; j < island_pool.size(); ++j) {
145
146 const Individual<T>& q = pop[island_pool[j]];
147
148 int compare = p->fitness.dominates(q.fitness);
149 if (compare == 1) { // p dominates q
150 //p.dominated.push_back(j);
151 dom.push_back(island_pool[j]);
152 } else if (compare == -1) { // q dominates p
153 //p.dcounter += 1;
154 dcount += 1;
155 }
156 }
157 p->fitness.dcounter = dcount;
158 p->fitness.dominated.clear();
159 p->fitness.dominated = dom; // dom will have values already referring to island indexes
160 p->fitness.set_crowding_dist(0.0f);
161
162 if (p->fitness.dcounter == 0) {
163 // fmt::print("pushing {}...\n", island_pool[i]);
164 p->fitness.set_rank(1);
165 // front will have values already referring to island indexes
166 front.at(0).push_back(island_pool[i]);
167 }
168
169 }
170
171 // fmt::print("First front size {}...\n", front.at(0).size());
172
173 // using OpenMP can have different orders in the front.at(0)
174 // so let's sort it so that the algorithm is deterministic
175 // given a seed
176 std::sort(front.at(0).begin(), front.at(0).end());
177
178 int fi = 1;
179 while (front.at(fi-1).size() > 0) {
180 std::vector<int>& fronti = front.at(fi-1);
181 std::vector<int> Q;
182 for (int i = 0; i < fronti.size(); ++i) {
183
184 const Individual<T>& p = pop[fronti.at(i)];
185
186 // iterating over dominated individuals
187 for (int j = 0; j < p.fitness.dominated.size() ; ++j) {
188 // fmt::print("decreased counter of ind {} for {} to {} \n", j, p.fitness.dominated.at(j), pop.individuals.at(p.fitness.dominated.at(j))->fitness.dcounter);
189
190 auto q = pop.individuals.at(p.fitness.dominated.at(j));
191
192 // fmt::print("decreased counter \n");
193 q->fitness.dcounter -= 1;
194
195 if (q->fitness.dcounter == 0) {
196 // fmt::print("updated counter for ind {} \n", j);
197
198 q->fitness.set_rank(fi+1);
199 Q.push_back(p.fitness.dominated.at(j));
200 }
201 }
202 }
203
204 front.push_back(Q);
205
206 fi += 1;
207 }
208 return front;
209}
210
211template<ProgramType T>
212void NSGA2<T>::crowding_distance(Population<T>& pop, vector<vector<int>>& front, int fronti)
213{
214
215 // fmt::print("inside crowding distance for front {}...\n", fronti);
216
217 std::vector<int> F = front.at(fronti);
218 if (F.size() == 0 ){
219 // fmt::print("empty front\n");
220 return;
221 }
222
223 const int fsize = F.size();
224 // fmt::print("front size is {}...\n", fsize);
225
226 for (int i = 0; i < fsize; ++i)
227 pop.individuals.at(F.at(i))->fitness.set_crowding_dist(0.0f);
228
229 // fmt::print("reseted crowding distance for individuals in this front\n");
230
231 const int limit = pop.individuals.at(0)->fitness.get_wvalues().size();
232 // fmt::print("limit is {}\n", limit);
233
234 for (int m = 0; m < limit; ++m) {
235 // fmt::print("m {}\n", m);
236
237 std::sort(F.begin(), F.end(), comparator_obj(pop,m));
238
239 // in the paper dist=INF for the first and last, in the code
240 // this is only done to the first one or to the two first when size=2
241 pop.individuals.at(F.at(0))->fitness.crowding_dist = std::numeric_limits<float>::max();
242 if (fsize > 1)
243 pop.individuals.at(F.at(fsize-1))->fitness.crowding_dist = std::numeric_limits<float>::max();
244
245 float first_of_front = pop.individuals.at(F.at(0))->fitness.get_wvalues().at(m);
246 float last_of_front = pop.individuals.at(F.at(fsize-1))->fitness.get_wvalues().at(m);
247 for (int i = 1; i < fsize-1; ++i)
248 {
249 if (pop.individuals.at(F.at(i))->fitness.crowding_dist != std::numeric_limits<float>::max())
250 {
251 float next_of_front = pop.individuals.at(F.at(i+1))->fitness.get_wvalues().at(m);
252 float prev_of_front = pop.individuals.at(F.at(i-1))->fitness.get_wvalues().at(m);
253
254 // updating the value by aggregating crowd dist for each objective
255 pop.individuals.at(F.at(i))->fitness.crowding_dist +=
256 (next_of_front - prev_of_front) / (last_of_front - first_of_front);
257 }
258 }
259 }
260}
261
262} // selection
263} // Brush
Fitness fitness
aggregate fitness score
Definition individual.h:37
vector< size_t > get_island_indexes(int island)
Definition population.h:39
vector< std::shared_ptr< Individual< T > > > individuals
Definition population.h:19
void crowding_distance(Population< T > &, vector< vector< int > > &, int)
Definition nsga2.cpp:212
size_t tournament(Population< T > &pop, size_t i, size_t j) const
Definition nsga2.cpp:18
vector< size_t > survive(Population< T > &pop, int island, const Parameters &p)
survival according to the survival scheme of NSGA-II
Definition nsga2.cpp:69
NSGA2(bool surv=false)
Definition nsga2.cpp:11
vector< vector< int > > fast_nds(Population< T > &, vector< size_t > &)
Definition nsga2.cpp:126
vector< size_t > select(Population< T > &pop, int island, const Parameters &p)
selection according to the survival scheme of NSGA-II
Definition nsga2.cpp:39
static Rnd & r
Definition rnd.h:174
< nsga2 selection operator for getting the front
Definition bandit.cpp:4
float crowding_dist
crowding distance on the Pareto front
Definition fitness.h:50
vector< unsigned int > dominated
individual indices this dominates
Definition fitness.h:48
int dominates(const Fitness &b) const
set obj vector given a string of objective names
Definition fitness.cpp:43
unsigned int current_gen
Definition params.h:29
sort based on objective m
Definition nsga2.h:65
sort based on rank, breaking ties with crowding distance
Definition nsga2.h:44