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
Neural evolution algorithms implementation (CNE, NEAT, HyperNEAT) #686
Changes from 1 commit
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
4483fc8
190e639
4cd0054
668fa6e
62d5885
e176571
dec32ac
bc178c3
432331b
0944516
d66bb14
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 |
---|---|---|
|
@@ -132,22 +132,26 @@ class NEAT { | |
// Whether mutate or not. | ||
double p = mlpack::math::Random(); | ||
if (p > mutateAddLinkProb) return; | ||
|
||
printf("addlink 1\n"); | ||
// Select from neuron | ||
ssize_t fromNeuronIdx = mlpack::math::RandInt(0, genome.aNeuronGenes.size()); | ||
ssize_t fromNeuronId = genome.aNeuronGenes[fromNeuronIdx].Id(); | ||
|
||
printf("addlink 2\n"); | ||
// Select to neuron which cannot be input. | ||
printf("genome num input is: %d\n", genome.NumInput()); | ||
ssize_t toNeuronIdx = mlpack::math::RandInt(genome.NumInput(), genome.aNeuronGenes.size()); | ||
printf("toNeuronIdx is: %d\n", toNeuronIdx); | ||
printf("num of neuron is : %d\n", genome.aNeuronGenes.size()); | ||
ssize_t toNeuronId = genome.aNeuronGenes[toNeuronIdx].Id(); | ||
|
||
if (fromNeuronId == toNeuronId) return; | ||
printf("addlink 3\n"); | ||
// Check link already exist or not. | ||
ssize_t linkIdx = IsLinkExist(genome, fromNeuronId, toNeuronId); | ||
if (linkIdx != -1) { | ||
genome.aLinkGenes[linkIdx].Enabled(true); | ||
return; | ||
} | ||
|
||
printf("addlink 4\n"); | ||
// Check innovation already exist or not. | ||
ssize_t innovIdx = CheckLinkInnovation(fromNeuronId, toNeuronId); | ||
if (innovIdx != -1) { | ||
|
@@ -159,7 +163,7 @@ class NEAT { | |
genome.AddLink(linkGene); | ||
return; | ||
} | ||
|
||
printf("addlink 5\n"); | ||
// If new link and new innovation, create it, push new innovation. | ||
LinkInnovation linkInnov = AddLinkInnovation(fromNeuronId, toNeuronId); | ||
LinkGene linkGene(fromNeuronId, | ||
|
@@ -293,74 +297,80 @@ class NEAT { | |
// If not, we will need to change CrossoverLinkAndNeuron, and Disjoint, and WeightDiff. | ||
void CrossoverLinkAndNeuron(Genome& momGenome, Genome& dadGenome, Genome& childGenome) { | ||
// Add input and output neuron genes to child genome. | ||
printf("crossover 0\n"); | ||
for (ssize_t i=0; i<(momGenome.NumInput() + momGenome.NumOutput()); ++i) { | ||
childGenome.aNeuronGenes.push_back(momGenome.aNeuronGenes[i]); | ||
printf("crossover 0.5\n"); | ||
} | ||
|
||
childGenome.NumInput(momGenome.NumInput()); | ||
childGenome.NumOutput(momGenome.NumOutput()); | ||
childGenome.GenomeDepth(); | ||
|
||
printf("crossover 1\n"); | ||
// Iterate to add link genes and neuron genes to child genome. | ||
for (ssize_t i=0; i<momGenome.NumLink(); ++i) { | ||
ssize_t innovId = momGenome.aLinkGenes[i].InnovationId(); | ||
ssize_t idx = dadGenome.GetLinkIndex(innovId); | ||
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 like the names of the genomes. 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. Thanks! |
||
bool linkContainedInDad = (idx != -1); | ||
double randNum = mlpack::math::Random(); | ||
|
||
printf("crossover 2\n"); | ||
if (!linkContainedInDad) { // exceed or disjoint | ||
childGenome.AddLink(momGenome.aLinkGenes[i]); | ||
|
||
printf("crossover 3\n"); | ||
// Add from neuron | ||
ssize_t idxInChild = childGenome.GetNeuronIndex(momGenome.aLinkGenes[i].FromNeuronId()); | ||
ssize_t idxInParent = momGenome.GetNeuronIndex(momGenome.aLinkGenes[i].FromNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(momGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 4\n"); | ||
// Add to neuron | ||
idxInChild = childGenome.GetNeuronIndex(momGenome.aLinkGenes[i].ToNeuronId()); | ||
idxInParent = momGenome.GetNeuronIndex(momGenome.aLinkGenes[i].ToNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(momGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 5\n"); | ||
continue; | ||
} | ||
|
||
if (linkContainedInDad && randNum < 0.5) { | ||
childGenome.AddLink(momGenome.aLinkGenes[i]); | ||
|
||
printf("crossover 6\n"); | ||
// Add from neuron | ||
ssize_t idxInChild = childGenome.GetNeuronIndex(momGenome.aLinkGenes[i].FromNeuronId()); | ||
ssize_t idxInParent = momGenome.GetNeuronIndex(momGenome.aLinkGenes[i].FromNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(momGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 7\n"); | ||
// Add to neuron | ||
idxInChild = childGenome.GetNeuronIndex(momGenome.aLinkGenes[i].ToNeuronId()); | ||
idxInParent = momGenome.GetNeuronIndex(momGenome.aLinkGenes[i].ToNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(momGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 8\n"); | ||
continue; | ||
} | ||
|
||
printf("crossover 9\n"); | ||
if (linkContainedInDad && randNum >= 0.5) { | ||
childGenome.AddLink(dadGenome.aLinkGenes[idx]); | ||
|
||
printf("crossover 10\n"); | ||
// Add from neuron TODO: make it a function?? check whether crossover is correct. | ||
ssize_t idxInChild = childGenome.GetNeuronIndex(dadGenome.aLinkGenes[idx].FromNeuronId()); | ||
ssize_t idxInParent = dadGenome.GetNeuronIndex(dadGenome.aLinkGenes[idx].FromNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(dadGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 11\n"); | ||
// Add to neuron | ||
idxInChild = childGenome.GetNeuronIndex(dadGenome.aLinkGenes[idx].ToNeuronId()); | ||
idxInParent = dadGenome.GetNeuronIndex(dadGenome.aLinkGenes[idx].ToNeuronId()); | ||
if (idxInChild == -1) { | ||
childGenome.AddHiddenNeuron(dadGenome.aNeuronGenes[idxInParent]); | ||
} | ||
|
||
printf("crossover 12\n"); | ||
continue; | ||
} | ||
} | ||
|
@@ -518,7 +528,9 @@ class NEAT { | |
|
||
averageRank = averageRank / speciesSize; // smaller is better. | ||
speciesAverageRank.push_back(averageRank); | ||
printf("size speciesAverage is %d \n", speciesAverageRank.size()); | ||
} | ||
printf("size speciesAverage is %d \n", speciesAverageRank.size()); | ||
} | ||
|
||
// Remove weak species. | ||
|
@@ -565,24 +577,30 @@ class NEAT { | |
// NOTICE: how we organize different mutations is kind of flexible. | ||
void Mutate(Genome& genome) { | ||
// NOTICE: we can change mutate rates here. Randomly let mutate rates be bigger or smaller. | ||
|
||
printf("mutate 1\n"); | ||
// Mutate weights. | ||
MutateWeight(genome, aMutateWeightProb, aPerturbWeightProb, aMutateWeightSize); | ||
|
||
printf("mutate 2\n"); | ||
// Mutate link. TODO: check link mutate implementation is correct or not. | ||
double p = aMutateAddLinkProb; | ||
while (p > 0) { // so p can be bigger than 1 and mutate can happen multiple times. | ||
if (mlpack::math::Random() < p) { | ||
printf("mutate 3\n"); | ||
MutateAddLink(genome, aMutateAddLinkProb); | ||
printf("mutate 4\n"); | ||
} | ||
--p; | ||
} | ||
|
||
// Mutate neuron | ||
printf("mutate 5\n"); | ||
p = aMutateAddNeuronProb; | ||
printf("mutate 6\n"); | ||
while (p > 0) { | ||
if (mlpack::math::Random() < p) { | ||
printf("mutate 7\n"); | ||
MutateAddNeuron(genome, aMutateAddNeuronProb); | ||
printf("mutate 8\n"); | ||
} | ||
--p; | ||
} | ||
|
@@ -591,7 +609,9 @@ class NEAT { | |
p = aMutateEnabledProb; | ||
while (p > 0) { | ||
if (mlpack::math::Random() < p) { | ||
printf("mutate 9\n"); | ||
MutateEnableDisable(genome, true, aMutateEnabledProb); | ||
printf("mutate 10\n"); | ||
} | ||
--p; | ||
} | ||
|
@@ -600,7 +620,9 @@ class NEAT { | |
p = aMutateDisabledProb; | ||
while (p > 0) { | ||
if (mlpack::math::Random() < p) { | ||
printf("mutate 11\n"); | ||
MutateEnableDisable(genome, false, aMutateDisabledProb); | ||
printf("mutate 12\n"); | ||
} | ||
--p; | ||
} | ||
|
@@ -609,18 +631,31 @@ class NEAT { | |
// Breed child for a species. | ||
// NOTICE: can have different ways to breed a child. | ||
void BreedChild(Species& species, Genome& childGenome, double crossoverProb) { | ||
printf("breed 1\n"); | ||
double p = mlpack::math::Random(); | ||
ssize_t speciesSize = species.aGenomes.size(); | ||
printf("species size is %d \n", speciesSize); | ||
printf("breed 2\n"); | ||
if (speciesSize == 0) | ||
return; | ||
|
||
if (p < crossoverProb) { | ||
ssize_t idx1 = mlpack::math::RandInt(0, speciesSize); | ||
ssize_t idx2 = mlpack::math::RandInt(0, speciesSize); | ||
printf("idx1 is %d\n", idx1); | ||
printf("idx2 is %d\n", idx2); | ||
printf("breed 3\n"); | ||
Crossover(species.aGenomes[idx1], species.aGenomes[idx2], childGenome); | ||
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. Is it possible to do a crossover using the same genomes? 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. |
||
printf("breed 4\n"); | ||
} else { | ||
ssize_t idx = mlpack::math::RandInt(0, speciesSize); | ||
printf("breed 5\n"); | ||
childGenome = species.aGenomes[idx]; | ||
printf("breed 6\n"); | ||
} | ||
|
||
printf("breed 7\n"); | ||
Mutate(childGenome); | ||
printf("breed 8\n"); | ||
} | ||
|
||
|
||
|
@@ -633,31 +668,46 @@ class NEAT { | |
void Reproduce() { | ||
// Remove stale species. | ||
RemoveStaleSpecies(aPopulation); | ||
|
||
printf("hehe 0\n"); // DEBUG | ||
// Remove weak genomes in each species. | ||
CullSpecies(aPopulation, aCullSpeciesPercentage); | ||
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'm not sure, is this a technique to avoid local minima? Truncating each species by 50% sounds like an counterproductive idea. Maybe I missed a reference in the NEAT paper? 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 referred to this implementation which seems performs good on Mario game: 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. Thanks for the information, maybe it's a good idea, but it sounds counterproductive to me. I think it should work without, since RemoveStaleSpecies should remove old species anyway. 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. My current plan is keep the functions, but when we do the Reproduce(), we need further decision about whether to use it. Organizing these steps in Reproduce() are flexible. 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. Sounds good, since there is a parameter (aCullSpeciesPercentage) it's super easy to disable the function. |
||
printf("hehe 1\n"); // DEBUG | ||
|
||
// Remove weak species. | ||
RemoveWeakSpecies(aPopulation); | ||
|
||
if (aPopulation.NumSpecies() > 10) { | ||
RemoveWeakSpecies(aPopulation); | ||
} | ||
printf("hehe 2\n"); // DEBUG | ||
// Breed children in each species. | ||
std::vector<double> speciesAverageRank; | ||
CalcSpeciesAverageRank(aPopulation, speciesAverageRank); | ||
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. Regarding https://groups.yahoo.com/neo/groups/neat/conversations/topics/2203 we don't use the adjusted fitness. 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. So you mean, as we are calculation the average fitness of species, we doesn't need to use the adjustedFitness, because it likes we average it for twice, right? 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. Yes, I think that's what Stanley is trying to explain. 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 now removed all adjustedFitness, even in the genome class. |
||
printf("size speciesAverageRank is %d \n", speciesAverageRank.size()); | ||
printf("hehe 3\n"); // DEBUG | ||
double totalAverageRank = std::accumulate(speciesAverageRank.begin(), speciesAverageRank.end(), 0); | ||
printf("size speciesAverageRank is %d \n", speciesAverageRank.size()); | ||
printf("hehe 3-0\n"); | ||
std::vector<Genome> childGenomes; | ||
printf("hehe 3-01\n"); | ||
for (ssize_t i=0; i<aPopulation.aSpecies.size(); ++i) { | ||
// number of child genomes by this species. | ||
printf("hehe 3-02\n"); | ||
printf("size species is %d \n", aPopulation.aSpecies.size()); | ||
printf("size speciesAverageRank is %d \n", speciesAverageRank.size()); | ||
ssize_t numBreed = std::floor(speciesAverageRank[i] * aPopulationSize / totalAverageRank) - 1; | ||
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's the right position, I would go with offspring here. |
||
|
||
printf("hehe 3-1\n"); // DEBUG | ||
for (ssize_t j=0; j<numBreed; ++j) { | ||
Genome genome; //!!!!!!!!!!!!! | ||
printf("num of species is %d \n", aPopulation.aSpecies.size()); | ||
BreedChild(aPopulation.aSpecies[i], genome, aCrossoverRate); | ||
childGenomes.push_back(genome); | ||
} | ||
printf("hehe 3-2\n"); // DEBUG | ||
} | ||
printf("hehe 4\n"); // DEBUG | ||
|
||
// Keep the best in each species. | ||
CullSpeciesToOne(aPopulation); | ||
printf("hehe 5\n"); // DEBUG | ||
|
||
// Random choose species and breed child until reach population size. | ||
while (childGenomes.size() + aPopulation.aSpecies.size() < aPopulationSize) { | ||
|
@@ -666,11 +716,13 @@ class NEAT { | |
BreedChild(aPopulation.aSpecies[speciesIndex], genome, aCrossoverRate); | ||
childGenomes.push_back(genome); | ||
} | ||
printf("hehe 6\n"); // DEBUG | ||
|
||
// Speciate genomes into new species. | ||
for (ssize_t i=0; i<childGenomes.size(); ++i) { | ||
AddGenomeToSpecies(aPopulation, childGenomes[i]); | ||
} | ||
printf("hehe 7\n"); // DEBUG | ||
|
||
// Reassign genome IDs. | ||
aPopulation.ReassignGenomeId(); | ||
|
@@ -680,7 +732,9 @@ class NEAT { | |
void Evaluate() { | ||
for (ssize_t i=0; i<aPopulation.NumSpecies(); ++i) { | ||
for (ssize_t j=0; j<aPopulation.aSpecies[i].SpeciesSize(); ++j) { | ||
printf("start eval fitness\n"); | ||
double fitness = aTask.EvalFitness(aPopulation.aSpecies[i].aGenomes[j]); | ||
printf("end eval fitness\n"); | ||
aPopulation.aSpecies[i].aGenomes[j].Fitness(fitness); | ||
} | ||
|
||
|
@@ -706,7 +760,9 @@ class NEAT { | |
// Repeat | ||
while (generation < aMaxGeneration) { | ||
// Evaluate all genomes in population. | ||
printf("start evaluate\n"); | ||
Evaluate(); | ||
printf("end evaluate\n"); | ||
|
||
// Output some information. | ||
printf("Generation: %zu\tBest fitness: %f\n", generation, aPopulation.BestFitness()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,7 +51,9 @@ class TaskXor { | |
|
||
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, using an arma::mat object to store the input is a better representation. This way, we can use all kinds of already implemented methods to preprocess the data. 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. @zoq Reasonable. I will revise it then. |
||
double fitness = 0; | ||
for (int i=0; i<4; ++i) { | ||
printf("start activate\n"); | ||
genome.Activate(inputs[i]); | ||
printf("end activate\n"); | ||
double output = genome.Output()[0]; | ||
//fitness += FitnissFunction::Error(output, outputs[i]); incorrect | ||
fitness += pow((output - outputs[i]), 2); // TODO: revise. | ||
|
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 we have to make sure, that
fromNeuronIdx != toNeuronIdx
.