322 lines
8.7 KiB
C++
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);
|
|
}
|
|
|
|
|