1 /**
2 * @file cne_impl.hpp
3 * @author Marcus Edel
4 * @author Kartik Nighania
5 *
6 * Conventional Neural Evolution
7 * An optimizer that works like biological evolution which selects best
8 * candidates based on their fitness scores and creates new generation by
9 * mutation and crossover of population.
10 *
11 * ensmallen is free software; you may redistribute it and/or modify it under
12 * the terms of the 3-clause BSD license. You should have received a copy of
13 * the 3-clause BSD license along with ensmallen. If not, see
14 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
15 */
16 #ifndef ENSMALLEN_CNE_CNE_IMPL_HPP
17 #define ENSMALLEN_CNE_CNE_IMPL_HPP
18
19 #include "cne.hpp"
20
21 namespace ens {
22
CNE(const size_t populationSize,const size_t maxGenerations,const double mutationProb,const double mutationSize,const double selectPercent,const double tolerance)23 inline CNE::CNE(const size_t populationSize,
24 const size_t maxGenerations,
25 const double mutationProb,
26 const double mutationSize,
27 const double selectPercent,
28 const double tolerance) :
29 populationSize(populationSize),
30 maxGenerations(maxGenerations),
31 mutationProb(mutationProb),
32 mutationSize(mutationSize),
33 selectPercent(selectPercent),
34 tolerance(tolerance),
35 numElite(0),
36 elements(0)
37 { /* Nothing to do here. */ }
38
39 //! Optimize the function.
40 template<typename ArbitraryFunctionType,
41 typename MatType,
42 typename... CallbackTypes>
Optimize(ArbitraryFunctionType & function,MatType & iterateIn,CallbackTypes &&...callbacks)43 typename MatType::elem_type CNE::Optimize(ArbitraryFunctionType& function,
44 MatType& iterateIn,
45 CallbackTypes&&... callbacks)
46 {
47 // Convenience typedefs.
48 typedef typename MatType::elem_type ElemType;
49 typedef typename MatTypeTraits<MatType>::BaseMatType BaseMatType;
50
51 // Make sure that we have the methods that we need. Long name...
52 traits::CheckArbitraryFunctionTypeAPI<ArbitraryFunctionType,
53 BaseMatType>();
54 RequireDenseFloatingPointType<BaseMatType>();
55
56 // Vector of fitness values corresponding to each candidate.
57 BaseMatType fitnessValues;
58 //! Index of sorted fitness values.
59 arma::uvec index;
60
61 // Make sure for evolution to work at least four candidates are present.
62 if (populationSize < 4)
63 {
64 throw std::logic_error("CNE::Optimize(): population size should be at least"
65 " 4!");
66 }
67
68 // Find the number of elite canditates from population.
69 numElite = floor(selectPercent * populationSize);
70
71 // Making sure we have even number of candidates to remove and create.
72 if ((populationSize - numElite) % 2 != 0)
73 numElite--;
74
75 // Terminate if two parents can not be created.
76 if (numElite < 2)
77 {
78 throw std::logic_error("CNE::Optimize(): unable to select two parents. "
79 "Increase selection percentage.");
80 }
81
82 // Terminate if at least two childs are not possible.
83 if ((populationSize - numElite) < 2)
84 {
85 throw std::logic_error("CNE::Optimize(): no space to accomodate even 2 "
86 "children. Increase population size.");
87 }
88
89 BaseMatType& iterate = (BaseMatType&) iterateIn;
90
91 // Generate the population based on a Gaussian distribution around the given
92 // starting point.
93 std::vector<BaseMatType> population;
94 for (size_t i = 0 ; i < populationSize; ++i)
95 {
96 population.push_back(arma::randn<BaseMatType>(iterate.n_rows,
97 iterate.n_cols) + iterate);
98 }
99
100 // Store the number of elements in the objective matrix.
101 elements = iterate.n_rows * iterate.n_cols;
102
103 // Initialize helper variables.
104 fitnessValues.set_size(populationSize);
105
106 // Controls early termination of the optimization process.
107 bool terminate = false;
108
109 Info << "CNE initialized successfully. Optimization started."
110 << std::endl;
111
112 // Find the fitness before optimization using given iterate parameters.
113 ElemType lastBestFitness = function.Evaluate(iterate);
114 Callback::Evaluate(*this, function, iterate, lastBestFitness, callbacks...);
115
116 // Iterate until maximum number of generations is obtained.
117 terminate |= Callback::BeginOptimization(*this, function, iterate,
118 callbacks...);
119 for (size_t gen = 1; gen <= maxGenerations && !terminate; gen++)
120 {
121 // Calculating fitness values of all candidates.
122 for (size_t i = 0; i < populationSize; i++)
123 {
124 // Select a candidate and insert the parameters in the function.
125 iterate = population[i];
126 terminate |= Callback::StepTaken(*this, function, iterate,
127 callbacks...);
128
129 // Find fitness of candidate.
130 fitnessValues[i] = function.Evaluate(iterate);
131
132 Callback::Evaluate(*this, function, iterate, fitnessValues[i],
133 callbacks...);
134 }
135
136 Info << "Generation number: " << gen << " best fitness = "
137 << fitnessValues.min() << std::endl;
138
139 // Create next generation of species.
140 Reproduce(population, fitnessValues, index);
141
142 // Check for termination criteria.
143 if (std::abs(lastBestFitness - fitnessValues.min()) < tolerance)
144 {
145 Info << "CNE: minimized within tolerance " << tolerance << "; "
146 << "terminating optimization." << std::endl;
147 break;
148 }
149
150 // Store the best fitness of present generation.
151 lastBestFitness = fitnessValues.min();
152 }
153
154 // Set the best candidate into the network parameters.
155 iterateIn = population[index(0)];
156
157 const ElemType objective = function.Evaluate(iterate);
158 Callback::Evaluate(*this, function, iterate, objective, callbacks...);
159
160 Callback::EndOptimization(*this, function, iterate, callbacks...);
161 return objective;
162 }
163
164 //! Reproduce candidates to create the next generation.
165 template<typename MatType>
Reproduce(std::vector<MatType> & population,const MatType & fitnessValues,arma::uvec & index)166 inline void CNE::Reproduce(std::vector<MatType>& population,
167 const MatType& fitnessValues,
168 arma::uvec& index)
169 {
170 // Sort fitness values. Smaller fitness value means better performance.
171 index = arma::sort_index(fitnessValues);
172
173 // First parent.
174 size_t mom;
175
176 // Second parent.
177 size_t dad;
178
179 for (size_t i = numElite; i < populationSize - 1; i++)
180 {
181 // Select 2 different parents from elite group randomly [0, numElite).
182 mom = arma::as_scalar(arma::randi<arma::uvec>(
183 1, arma::distr_param(0, numElite - 1)));
184
185 dad = arma::as_scalar(arma::randi<arma::uvec>(
186 1, arma::distr_param(0, numElite - 1)));
187
188 // Making sure both parents are not the same.
189 if (mom == dad)
190 {
191 if (dad != numElite - 1)
192 {
193 dad++;
194 }
195 else
196 {
197 dad--;
198 }
199 }
200
201 // Parents generate 2 children replacing the dropped-out candidates.
202 // Also finding the index of these candidates in the population matrix.
203 Crossover(population, index[mom], index[dad], index[i], index[i + 1]);
204 }
205
206 // Mutating the weights with small noise values.
207 // This is done to bring change in the next generation.
208 Mutate(population, index);
209 }
210
211 //! Crossover parents to create new children.
212 template<typename MatType>
Crossover(std::vector<MatType> & population,const size_t mom,const size_t dad,const size_t child1,const size_t child2)213 inline void CNE::Crossover(std::vector<MatType>& population,
214 const size_t mom,
215 const size_t dad,
216 const size_t child1,
217 const size_t child2)
218 {
219 // Replace the candidates with parents at their place.
220 population[child1] = population[mom];
221 population[child2] = population[dad];
222
223 // Randomly alter mom and dad genome weights to get two different children.
224 for (size_t i = 0; i < elements; i++)
225 {
226 // Using it to alter the weights of the children.
227 const double random = arma::randu<typename MatType::elem_type>();
228 if (random > 0.5)
229 {
230 population[child1](i) = population[mom](i);
231 population[child2](i) = population[dad](i);
232 }
233 else
234 {
235 population[child1](i) = population[dad](i);
236 population[child2](i) = population[mom](i);
237 }
238 }
239 }
240
241 //! Modify weights with some noise for the evolution of next generation.
242 template<typename MatType>
Mutate(std::vector<MatType> & population,arma::uvec & index)243 inline void CNE::Mutate(std::vector<MatType>& population, arma::uvec& index)
244 {
245 // Mutate the whole matrix with the given rate and probability.
246 // The best candidate is not altered.
247 for (size_t i = 1; i < populationSize; i++)
248 {
249 population[index(i)] += (arma::randu<MatType>(population[index(i)].n_rows,
250 population[index(i)].n_cols) < mutationProb) %
251 (mutationSize * arma::randn<MatType>(population[index(i)].n_rows,
252 population[index(i)].n_cols));
253 }
254 }
255
256 } // namespace ens
257
258 #endif
259