create NEAT app repo

This commit is contained in:
swaits committed 2010-09-30 15:23:17 -07:00
commit 9cd2d19fb9
34 files changed
+4454

No files matched your search

+330
View File
@@ -0,0 +1,330 @@
#include "Genotype.h"
#include <cassert>
#include <cmath>
#include "IteratorHelper.h"
#include "MathHelper.h"
#include "NEAT.h"
#include "Random.h"
Genotype::Genotype(Innovator& innovator, int num_inputs, int num_outputs)
{
assert(num_inputs > 0 && num_inputs > 0);
// create bias neuron
Neurons.push_back( NeuronGene(innovator) );
// create input neurons
for (int i=0;i<num_inputs;++i)
{
Neurons.push_back( NeuronGene(innovator,NeuronGene::INPUT,i,num_inputs) );
}
// create output neurons
for (int i=0;i<num_outputs;++i)
{
Neurons.push_back( NeuronGene(innovator,NeuronGene::OUTPUT,i,num_outputs) );
}
// connect the bias and each input to each output
for (int i=0;i<(num_inputs+1);++i)
{
for (int j=(num_inputs+1);j<(num_inputs+num_outputs+1);++j)
{
Connections.push_back( ConnectionGene(innovator,Neurons[i].GetInnovation(),Neurons[j].GetInnovation()) );
}
}
}
Genotype::Genotype(const Genotype& mom, const Genotype& dad)
{
// assuming that the mom is the more fit parent, NEAT crossover can
// be simplified to:
// 1. copy the mom
// 2. for each shared mom/dad connection gene, randomly replace that gene in the mom with the one from the dad
// step one, copy the mom directly
Neurons = mom.Neurons;
Connections = mom.Connections;
// step two, randomly replace shared connection genes from dad into mom
iterate_each ( std::vector<ConnectionGene>::const_iterator, it_dad, dad.Connections )
{
// see if mom has matching gene, via brute force search
iterate_each( std::vector<ConnectionGene>::iterator, it_mom, Connections )
{
// does this one match? and, does the PRNG say we should do it?
if ( RandomBool() && (*it_mom) == (*it_dad) )
{
// yes, there's a match, make the swap
(*it_mom) = (*it_dad);
break;
}
}
}
// TODO - handle case where mom & dad have the same fitness
}
void Genotype::MutateConnectionWeights()
{
// mutate every connection
iterate_each ( std::vector<ConnectionGene>::iterator, it, Connections )
{
(*it).Mutate();
}
}
bool Genotype::MutateAddNeuron(Innovator& innovator, int retries)
{
// pointers to the two neurons we're adding a neuron in between
NeuronGene* input;
NeuronGene* output;
// figure out a connection to split
size_t connection_id;
int i;
for (i=0;i<retries;++i)
{
// choose a random connection
connection_id = RandomRange(0,(int)Connections.size()-1);
// make certain this connection is enabled
if ( !Connections[connection_id].IsEnabled() )
{
// don't split disabled connections
continue;
}
// get the new neurons on either side of the chosen connection
input = GetNeuronByInnovation(Connections[connection_id].GetInputNeuron());
output = GetNeuronByInnovation(Connections[connection_id].GetOutputNeuron());
assert(input && output);
// see if that node exists and is already in this genotype
int new_id = innovator.FindNeuronInnovation((*input).GetInnovation(),(*output).GetInnovation());
if ( new_id >= 0 && GetNeuronByInnovation(new_id) )
{
// neuron already exists in this individual
continue;
}
// make sure we're not splitting a recurrent link
// TODO
// make sure we're not splitting a looped link
if ( (*input) == (*output) )
{
// no sense in splitting a looped link
continue;
}
// make sure we're not splitting a bias neuron
if ( input->GetType() == NeuronGene::BIAS )
{
// no sense in splitting bias neurons
continue;
}
// if we get here, we have a good split
break;
}
// if we found nothing, bail out
if ( i >= retries )
{
return false;
}
// disable it
Connections[connection_id].Disable();
// create a nueron splitting these two nodes
NeuronGene new_neuron = NeuronGene(innovator,*input,*output);
// link the new node in with two new connections
float orig_weight = Connections[connection_id].GetWeight();
Connections.push_back( ConnectionGene(innovator,(*input).GetInnovation(),new_neuron.GetInnovation(),1.0f) );
Connections.push_back( ConnectionGene(innovator,new_neuron.GetInnovation(),(*output).GetInnovation(),orig_weight) );
// finally, add neuron to our list
// this invalidates the input, output pointers used above
Neurons.push_back(new_neuron);
return true;
}
bool Genotype::MutateAddConnection(Innovator& innovator, int retries)
{
// decide if this will be a looped, recurrent, or forward connection
while (retries--)
{
// choose two random neurons
int neuron_a_id = RandomRange(0,(int)Neurons.size()-1);
int neuron_b_id = RandomRange(0,(int)Neurons.size()-1);
// make sure target is not bias or input neuron
if ( Neurons[neuron_b_id].GetType() == NeuronGene::BIAS || Neurons[neuron_b_id].GetType() == NeuronGene::INPUT )
{
// TODO could skip this if we'd just select from (m_num_inputs+1,num_neurons) instead of (0,num_neurons)
continue;
}
// check for loop
if ( neuron_a_id == neuron_b_id )
{
// TODO this could actually be allowed
continue;
}
// check for recurrent
if ( IsRecurrent(neuron_a_id,neuron_b_id) )
{
// make forward
std::swap(neuron_a_id,neuron_b_id);
}
// see if this connection exists
if ( GetConnectionByNeuron(Neurons[neuron_a_id].GetInnovation(),Neurons[neuron_b_id].GetInnovation()) )
{
// exists
continue;
}
// we got a match, add the connection
Connections.push_back( ConnectionGene(innovator,Neurons[neuron_a_id].GetInnovation(),Neurons[neuron_b_id].GetInnovation()) );
return true;
}
// nothing found
return false;
}
NeuronGene* Genotype::GetNeuronByInnovation(int innovation)
{
// search every neuron for this innovation
iterate_each ( std::vector<NeuronGene>::iterator, it, Neurons )
{
if ( (*it).GetInnovation() == innovation )
{
return &(*it);
}
}
return 0;
}
ConnectionGene* Genotype::GetConnectionByNeuron(int from_neuron, int to_neuron)
{
// search for every connection that matches these neurons
iterate_each ( std::vector<ConnectionGene>::iterator, it, Connections )
{
if ( (*it).GetInputNeuron() == from_neuron && (*it).GetOutputNeuron() == to_neuron )
{
return &(*it);
}
}
// nothing found
return 0;
}
bool Genotype::IsRecurrent(int from_neuron, int to_neuron)
{
return
(Neurons[to_neuron].GetY() < Neurons[from_neuron].GetY()) ||
(Neurons[to_neuron].GetY() == Neurons[from_neuron].GetY() && Neurons[to_neuron].GetY() < Neurons[from_neuron].GetY());
}
void Genotype::Output()
{
static char* typestr[5] = { "unknown", "bias", "input", "output", "hidden" };
printf("\n");
for (size_t i=0;i<Neurons.size();++i)
{
printf("neuron %d %s\n",Neurons[i].GetInnovation(),typestr[Neurons[i].GetType()]);
}
for (size_t i=0;i<Connections.size();++i)
{
if ( Connections[i].IsEnabled() )
{
printf("connection %3d %3d %0.10f\n",Connections[i].GetInputNeuron(),Connections[i].GetOutputNeuron(),Connections[i].GetWeight());
}
}
}
void Genotype::SetFitness(float fitness)
{
Fitness = fitness;
}
float Genotype::GetFitness() const
{
return Fitness;
}
bool Genotype::operator< (const Genotype& rhs) const
{
return Fitness < rhs.Fitness;
}
float Genotype::GetCompatibility(const Genotype& genome) const
{
int disjoint_excess = 0, common = 0;
float weight_diff = 0.0f;
iterate_each ( std::vector<ConnectionGene>::const_iterator, it_lhs, Connections )
{
bool match = false;
iterate_each ( std::vector<ConnectionGene>::const_iterator, it_rhs, genome.Connections )
{
if ( (*it_lhs) == (*it_rhs) )
{
weight_diff += fabs((*it_lhs).GetWeight() - (*it_rhs).GetWeight());
match = true;
break;
}
}
if ( match )
{
++common;
}
else
{
++disjoint_excess;
}
}
size_t lhs_size = Connections.size() + Neurons.size();
size_t rhs_size = genome.Connections.size() + genome.Neurons.size();
size_t N = std::max(lhs_size, rhs_size);
float weight_diff_avg = weight_diff / (float)common;
return ((float)disjoint_excess/(float)N) + (0.4f * weight_diff_avg);
}
void Genotype::Mutate(Innovator& innovator)
{
switch ( Roulette(3,NEAT::pAddConnection,NEAT::pAddNeuron,NEAT::pWeightsMutated))
{
case 0:
MutateAddConnection(innovator);
break;
case 1:
MutateAddNeuron(innovator);
break;
case 2:
MutateConnectionWeights();
break;
default:
break;
}
}