Files
2010-09-30 15:23:17 -07:00

322 lines
8.7 KiB
C++

#include "NEAT.h"
#include <algorithm>
#include <cassert>
#include "IteratorHelper.h"
#include "NeuralNetwork.h"
#include "Random.h"
const int NEAT::kPopulation = 150; // size of population
const float NEAT::kC1 = 1.0f; // coefficient for excess genes in compatibility equation
const float NEAT::kC2 = 1.0f; // coefficient for disjoint genes in compatibility equation
const float NEAT::kC3 = 0.4f; // coefficient for weight delta in compatibility equation
const float NEAT::kCompatibilityThreshold = 3.0f; // used in speciation
const int NEAT::kNumUnimprovedGensAllowed = 15; // # generations a species is allowed to not improve
const int NEAT::kMinSpeciesSizeForChampionReproduction = 5; // species must be at least this big
const float NEAT::pWeightsMutated = 0.8f; // chance a genome has its weights mutated
const float NEAT::pUniformWeightPerturbation = 0.9f; // each weight's chance of uniform perturbation
const float NEAT::pAssignNewWeight = 0.1f; // each weight's chance of getting replaced
const float NEAT::pInheritedGeneDisabled = 0.75f; // chance gene disabled if it was disabled in either parent
const float NEAT::pMutationOnly = 0.25f; // chance of reproduction through mutation, without crossover
const float NEAT::kInterSpeciesRate = 0.001f; // inter-species mating rate
const float NEAT::pAddNeuron = 0.03f; // chance of adding a neuron (mutation)
const float NEAT::pAddConnection = 0.05f; // chance of adding a new connection (mutation)
const float NEAT::kMaxWeightPerturbation = 0.1f; // +/- weight perturbation (mutation)
const float NEAT::kMaxNewWeight = 0.5f; // +/- range for new weights
/*
trait_param_mut_prob 0.5
trait_mutation_power 1.0
linktrait_mut_sig 1.0
nodetrait_mut_sig 0.5
weigh_mut_power 2.5
recur_prob 0.00
disjoint_coeff 1.0
excess_coeff 1.0
mutdiff_coeff 0.4
compat_thresh 3.0
age_significance 1.0
survival_thresh 0.20
mutate_only_prob 0.25
mutate_random_trait_prob 0.1
mutate_link_trait_prob 0.1
mutate_node_trait_prob 0.1
mutate_link_weights_prob 0.9
mutate_toggle_enable_prob 0.00
mutate_gene_reenable_prob 0.000
mutate_add_node_prob 0.03
mutate_add_link_prob 0.05
interspecies_mate_rate 0.001
mate_multipoint_prob 0.6
mate_multipoint_avg_prob 0.4
mate_singlepoint_prob 0.0
mate_only_prob 0.2
recur_only_prob 0.0
pop_size 150
dropoff_age 15
newlink_tries 20
print_every 30
babies_stolen 0
num_runs 100
*/
NEAT::NEAT(int num_inputs, int num_outputs) :
m_next_species_id(0)
{
// create initial population
iterate_times(NEAT::kPopulation)
{
m_population.push_back( Genotype(m_innovator, num_inputs, num_outputs) );
}
}
void NEAT::SortPopulation()
{
std::sort(m_population.begin(),m_population.end());
}
void NEAT::SaveBestGenomes()
{
// add best of current population to best ever
assert(m_population.size() >= 4);
for( unsigned int i = 0; i < 4; ++i )
{
m_best.push_back( m_population[i] );
}
// sort
std::sort(m_best.begin(),m_best.end());
// kill off all but 4
while ( m_best.size() > 4 )
{
m_best.pop_back();
}
}
void NEAT::SpeciatePopulation()
{
// clear old members out of all species, and choose a representative member in each species
iterate_each( std::vector<Species>::iterator, it_species, m_species )
{
// make sure species is not dead
if ( !(*it_species).member_ids.empty() )
{
// has members, just choose random as representative for next generation
int rep_id = RandomRange(0,(int)(*it_species).member_ids.size()-1);
(*it_species).representative = m_population[rep_id];
(*it_species).member_ids.clear();
}
}
// add each genome into a species, or create a new species if necessary
for ( int i=0; i<(int)m_population.size(); ++i )
{
// test every species for compatibility with genome i
bool added = false;
iterate_each( std::vector<Species>::iterator, it_species, m_species )
{
// test genotype for compatibility with this species
if ( (*it_species).representative.GetCompatibility(m_population[i]) < NEAT::kCompatibilityThreshold )
{
// add it
(*it_species).member_ids.push_back(i);
added = true;
break;
}
}
// if we didn't add this genome to any species, create one now
if ( !added )
{
m_species.push_back(Species(m_population[i],i,m_next_species_id++));
}
}
// kill off any dead species
iterate_each( std::vector<Species>::iterator, it_species, m_species )
{
if ( (*it_species).member_ids.empty() )
{
// erase it
// TODO - need to actually remove it???
}
}
printf("%d species\n",m_species.size());
}
void NEAT::AdjustAndShareFitness()
{
// fitness for each genome is divided by the total number of genomes in its species
iterate_each( std::vector<Species>::iterator, it_species, m_species )
{
float divisor = (float)((*it_species).member_ids.size());
iterate_each( std::vector<int>::iterator, it_member_id, (*it_species).member_ids )
{
float old_fitness = m_population[*it_member_id].GetFitness();
float new_fitness = old_fitness / divisor;
m_population[*it_member_id].SetFitness( new_fitness );
}
}
}
void NEAT::Reproduce()
{
// get population size (we'll make sure it ends up with this many)
size_t pop_size = m_population.size();
// waste old population
std::vector<Genotype> old_population(m_population);
m_population.clear();
// get total fitness (assumes already adjusted)
float fitness_total = 0.0f;
iterate_each( std::vector<Genotype>::iterator, it, old_population )
{
fitness_total += (*it).GetFitness();
}
// now have each species spawn amounts based on its share population average
iterate_each( std::vector<Species>::iterator, it_species, m_species )
{
// ignore this species if it's dead
if ( (*it_species).member_ids.empty() )
{
continue;
}
// sum total fitness for this species
float species_fitness = 0.0f;
iterate_each( std::vector<int>::iterator, it_member_id, (*it_species).member_ids )
{
species_fitness += old_population[*it_member_id].GetFitness();
}
// calculate how many to spawn
float fitness_share = species_fitness / fitness_total;
size_t num_to_spawn = (int)(fitness_share * (float)pop_size + 0.5f);
// now do the reproductions
// first one is simply the best in the species
m_population.push_back( old_population[(*it_species).member_ids[0]] );
// and fill up the rest with random based reproduction
iterate_times(num_to_spawn-1)
{
// determine if this is sexual or asexual
if ( RandomChance(NEAT::pMutationOnly) )
{
// asexual reproduction
// choose one random genome from the species
int survivor_count = (int)(*it_species).member_ids.size();
if ( survivor_count < 1 )
{
survivor_count = 1;
}
int a = RandomZeroBiased(survivor_count-1);
// copy
Genotype g = old_population[a];
// mutate
g.Mutate(m_innovator);
// add to population
m_population.push_back(g);
}
else
{
// sexual reproduction
// choose two random genomes from the species
int survivor_count = (int)(*it_species).member_ids.size();
if ( survivor_count < 1 )
{
survivor_count = 1;
}
int a = RandomZeroBiased(survivor_count-1);
int b = RandomZeroBiased(survivor_count-1);
if ( b < a )
{
std::swap(a,b);
}
// mate them
Genotype g = Genotype(old_population[a], old_population[b]);
// mutate
g.Mutate(m_innovator);
// add to population
m_population.push_back( g );
}
}
}
}
void NEAT::Run()
{
iterate_times(10000)
{
// update the innovator
m_innovator.Update();
// build networks from genomes
std::vector<NeuralNetwork> networks;
iterate_each( std::vector<Genotype>::iterator, it_genotype, m_population )
{
networks.push_back( NeuralNetwork(*it_genotype) );
}
// test networks
std::vector<float> fitness;
bool finished = TestNetworks(networks,fitness);
// store results
assert(fitness.size() == m_population.size());
for( unsigned int i = 0; i < fitness.size(); ++i )
{
m_population[i].SetFitness( fitness[i] );
}
// sort by fitness
SortPopulation();
// save off the best of the population
SaveBestGenomes();
// now quit if TestNetworks(..) returned true
if ( finished )
{
return;
}
// speciate
SpeciatePopulation();
// adjust & share fitness
AdjustAndShareFitness();
// finally, create the new population
Reproduce();
printf("best fitness = %0.10f\n",m_best[0].GetFitness());
m_best[0].Output();
}
}
NEAT::Species::Species(const Genotype& originator, int originator_id, int species_id) :
species_id(species_id),
representative(originator)
{
member_ids.push_back(originator_id);
}