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