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
CNE algorithm #753
CNE algorithm #753
Changes from 57 commits
dc27518
88788d6
6ab9add
ca73f95
30ec0ba
7fdd7ca
e4fbabd
3c8aa62
cbb79a2
65f093d
eb16ce2
c6976ac
8919bb9
ee548b6
faf47a2
adc45b2
95c2e53
1ee07a0
39ef07b
74fb4a0
d0883f4
a605eef
e059af0
6c11f32
3c10995
57d4165
6a770ec
cce115b
2a9899c
1a2cfd8
dacc2e6
2a4c715
6f901a1
a80f5f7
3db85fd
8168779
554a279
1fe7d73
ff45785
48e15c4
fc982b9
66b0f41
109f05b
8054f00
5ef61fe
f213dd8
f234ebd
96ccef5
24f9baf
4decb0e
c46436e
000d6aa
2599077
408b40f
0fc792c
847db95
10b99ab
da55507
beb8ba8
3b66431
9480379
a3f1d75
4483fc8
190e639
4cd0054
668fa6e
62d5885
e176571
dec32ac
bc178c3
432331b
0944516
d66bb14
44f44cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
build* | ||
src/mlpack/core/util/gitversion.hpp | ||
src/mlpack/core/util/arma_config.hpp | ||
|
||
.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,7 @@ set(DIRS | |
sparse_autoencoder | ||
sparse_coding | ||
nystroem_method | ||
ne | ||
) | ||
|
||
foreach(dir ${DIRS}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Define the files we need to compile. | ||
# Anything not in this list will not be compiled into mlpack. | ||
set(SOURCES | ||
link_gene.hpp | ||
neuron_gene.hpp | ||
genome.hpp | ||
species.hpp | ||
population.hpp | ||
tasks.hpp | ||
parameters.hpp | ||
utils.hpp | ||
cne.hpp | ||
neat.hpp | ||
) | ||
|
||
# Add directory name to sources. | ||
set(DIR_SRCS) | ||
foreach(file ${SOURCES}) | ||
set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file}) | ||
endforeach() | ||
# Append sources (with directory name) to list of all mlpack sources (used at | ||
# the parent scope). | ||
set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
/** | ||
* @file cne.hpp | ||
* @author Bang Liu | ||
* | ||
* Definition of CNE class. | ||
*/ | ||
#ifndef MLPACK_METHODS_NE_CNE_HPP | ||
#define MLPACK_METHODS_NE_CNE_HPP | ||
|
||
#include <cstddef> | ||
#include <cstdio> | ||
|
||
#include <mlpack/core.hpp> | ||
|
||
#include "link_gene.hpp" | ||
#include "neuron_gene.hpp" | ||
#include "genome.hpp" | ||
#include "species.hpp" | ||
#include "tasks.hpp" | ||
#include "parameters.hpp" | ||
|
||
namespace mlpack { | ||
namespace ne { | ||
|
||
/** | ||
* This class implements Conventional Neuro-evolution (CNE): weight | ||
* evolution on topologically fixed neural networks. | ||
*/ | ||
template<typename TaskType> | ||
class CNE { | ||
public: | ||
// Parametric constructor. | ||
CNE(TaskType task, Genome& seedGenome, Parameters& params) { | ||
aTask = task; | ||
aSeedGenome = seedGenome; | ||
aSpeciesSize = params.aSpeciesSize; | ||
aMaxGeneration = params.aMaxGeneration; | ||
aMutateRate = params.aMutateRate; | ||
aMutateSize = params.aMutateSize; | ||
aElitePercentage = params.aElitePercentage; | ||
} | ||
|
||
// Destructor. | ||
~CNE() {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is no need to implement a default destructor. |
||
|
||
// Soft mutation: add a random value chosen from | ||
// initialization prob distribution with probability p. | ||
// TODO: here we use uniform distribution. Can we use exponential distribution? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess for now it's reasonable to use a uniform distribution only. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree. |
||
static void MutateWeightsBiased(Genome& genome, double mutateProb, double mutateSize) { | ||
for (int i=0; i<genome.aLinkGenes.size(); ++i) { | ||
double p = mlpack::math::Random(); // rand 0~1 | ||
if (p < mutateProb) { | ||
double deltaW = mlpack::math::RandNormal(0, mutateSize); | ||
double oldW = genome.aLinkGenes[i].Weight(); | ||
genome.aLinkGenes[i].Weight(oldW + deltaW); | ||
} | ||
} | ||
} | ||
|
||
// Hard mutation: replace with a random value chosen from | ||
// initialization prob distribution with probability p. | ||
// TODO: here we use uniform distribution. Can we use exponential distribution? | ||
static void MutateWeightsUnbiased(Genome& genome, double mutateProb, double mutateSize) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think, we could use a boolean to distinguish between soft and hard mutation. In this case we could reuse the basic function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we can do this. |
||
for (int i=0; i<genome.aLinkGenes.size(); ++i) { | ||
double p = mlpack::math::Random(); | ||
if (p < mutateProb) { | ||
double weight = mlpack::math::RandNormal(0, mutateSize); | ||
genome.aLinkGenes[i].Weight(weight); | ||
} | ||
} | ||
} | ||
|
||
// Randomly select weights from one parent genome. | ||
// NOTICE: child genomes need to set genome id based on its population's max id. | ||
static void CrossoverWeights(Genome& momGenome, | ||
Genome& dadGenome, | ||
Genome& child1Genome, | ||
Genome& child2Genome) { | ||
child1Genome = momGenome; | ||
child2Genome = dadGenome; | ||
for (int i=0; i<momGenome.aLinkGenes.size(); ++i) { // assume genome are the same structure. | ||
double t = mlpack::math::RandNormal(); | ||
if (t>0) { // prob = 0.5 | ||
child1Genome.aLinkGenes[i].Weight(momGenome.aLinkGenes[i].Weight()); | ||
child2Genome.aLinkGenes[i].Weight(dadGenome.aLinkGenes[i].Weight()); | ||
} else { | ||
child1Genome.aLinkGenes[i].Weight(dadGenome.aLinkGenes[i].Weight()); | ||
child2Genome.aLinkGenes[i].Weight(momGenome.aLinkGenes[i].Weight()); | ||
} | ||
} | ||
} | ||
|
||
// Initializing the species of genomes. | ||
// It can use species's parametric constructor. | ||
// Besides, adapt its own style of initialization. | ||
void InitSpecies() { | ||
aSpecies = Species(aSeedGenome, aSpeciesSize); | ||
} | ||
|
||
// Reproduce the next species. Heart function for CNE !!! | ||
// Select parents from G(i) based on their fitness. | ||
// Apply search operators to parents and produce offspring which | ||
// form G(i + 1). | ||
void Reproduce() { | ||
// Sort species by fitness | ||
aSpecies.SortGenomes(); | ||
|
||
// Select parents from elite genomes and crossover. | ||
int numElite = floor(aElitePercentage * aSpeciesSize); | ||
int numDrop = floor((aSpeciesSize - numElite) / 2) * 2; // Make sure even number. | ||
numElite = aSpeciesSize - numDrop; | ||
for (int i=numElite; i<aSpeciesSize-1; ++i) { | ||
// Randomly select two parents from elite genomes. | ||
int idx1 = RandInt(0, numElite); | ||
int idx2 = RandInt(0, numElite); | ||
|
||
// Crossover to get two children genomes. | ||
CrossoverWeights(aSpecies.aGenomes[idx1], aSpecies.aGenomes[idx2], | ||
aSpecies.aGenomes[i], aSpecies.aGenomes[i+1]); | ||
} | ||
|
||
// Keep the best genome and mutate the rests. | ||
for (int i=1; i<aSpeciesSize; ++i) { | ||
MutateWeightsBiased(aSpecies.aGenomes[i], aMutateRate, aMutateSize); | ||
} | ||
} | ||
|
||
// Evolution of species. | ||
void Evolve() { | ||
// Generate initial species at random. | ||
int generation = 0; | ||
InitSpecies(); | ||
|
||
// Repeat | ||
while (generation < aMaxGeneration) { | ||
// Evaluate all genomes in the species. | ||
for (int i=0; i<aSpecies.SpeciesSize(); ++i) { | ||
double fitness = aTask.EvalFitness(aSpecies.aGenomes[i]); | ||
aSpecies.aGenomes[i].Fitness(fitness); | ||
} | ||
aSpecies.SetBestFitnessAndGenome(); | ||
|
||
// Output some information. | ||
printf("Generation: %zu\tBest fitness: %f\n", generation, aSpecies.BestFitness()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to use Log::Info here, in that case a user can disable any output e.g:
or something like that, the important thing is a user can disable the output. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, I will revise. |
||
if (aTask.Success()) { | ||
printf("Task succeed in %zu iterations.\n", generation); | ||
exit(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would terminate the complete process, I would go with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revised for both algorithm: cne and neat. |
||
} | ||
|
||
// Reproduce next generation. | ||
Reproduce(); | ||
++generation; | ||
} | ||
} | ||
|
||
private: | ||
// Task. | ||
TaskType aTask; | ||
|
||
// Seed genome. It is used for init species. | ||
Genome aSeedGenome; | ||
|
||
// Species to evolve. | ||
Species aSpecies; | ||
|
||
// Species size. | ||
int aSpeciesSize; | ||
|
||
// Max number of generation to evolve. | ||
int aMaxGeneration; | ||
|
||
// Mutation rate. | ||
double aMutateRate; | ||
|
||
// Mutate size. For normal distribution, it is mutate variance. | ||
double aMutateSize; | ||
|
||
// Elite percentage. | ||
double aElitePercentage; | ||
|
||
}; | ||
|
||
} // namespace ne | ||
} // namespace mlpack | ||
|
||
#endif // MLPACK_METHODS_NE_CNE_HPP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if you could provide a Constructor that allows a user to directly set the parameters, instead of passing a parameter object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be useful to use
const
for all parameters.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for algorithms the parameters are a lot. Users can set parameter by construct an Parameters object without put them into a constructor function in a specific sequence. And this also makes the algorithm constructor be concise. If we have more parameters in the future, we just add new member for Parameters class, no need to change the function headers of constructors. Besides, different NE algorithm will have similar functions for constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, that for some methods you have to specify a bunch of parameters, so I think the parameter class comes in handy. On the other side, I like to provide a constructor that works right out of the box and starts with some reasonable parameters. I think in the case of the CNE method, we can specify such parameters. Maybe we can provide an constructor for such a situation, what do you think?