// returns an individual randomly selected from the best N% Genome Species::GetIndividual(Parameters& a_Parameters, RNG& a_RNG) const { ASSERT(m_Individuals.size() > 0); // Make a pool of only evaluated individuals! std::vector<Genome> t_Evaluated; for(unsigned int i=0; i<m_Individuals.size(); i++) { if (m_Individuals[i].IsEvaluated()) t_Evaluated.push_back( m_Individuals[i] ); } ASSERT(t_Evaluated.size() > 0); if (t_Evaluated.size() == 1) { return (t_Evaluated[0]); } else if (t_Evaluated.size() == 2) { return (t_Evaluated[ Rounded(a_RNG.RandFloat()) ]); } // Warning!!!! The individuals must be sorted by best fitness for this to work int t_chosen_one=0; // Here might be introduced better selection scheme, but this works OK for now if (!a_Parameters.RouletteWheelSelection) { //start with the last one just for comparison sake int temp_genome; int t_num_parents = static_cast<int>( floor((a_Parameters.SurvivalRate * (static_cast<double>(t_Evaluated.size())))+1.0)); ASSERT(t_num_parents>0); if (t_num_parents>=t_Evaluated.size()) t_num_parents = t_Evaluated.size()-1; t_chosen_one = a_RNG.RandInt(0, t_num_parents); for (unsigned int i = 0; i < a_Parameters.TournamentSize; i++) { temp_genome = a_RNG.RandInt(0, t_num_parents); if (m_Individuals[temp_genome].GetFitness() > m_Individuals[t_chosen_one].GetFitness()) { t_chosen_one = temp_genome; } } } else { // roulette wheel selection std::vector<double> t_probs; for(unsigned int i=0; i<t_Evaluated.size(); i++) t_probs.push_back( t_Evaluated[i].GetFitness() ); t_chosen_one = a_RNG.Roulette(t_probs); } return (t_Evaluated[t_chosen_one]); }
// Reproduce mates & mutates the individuals of the species // It may access the global species list in the population // because some babies may turn out to belong in another species // that have to be created. // Also calls Birth() for every new baby void Species::Reproduce(Population &a_Pop, Parameters& a_Parameters, RNG& a_RNG) { Genome t_baby; // temp genome for reproduction int t_offspring_count = Rounded(GetOffspringRqd()); // no offspring?! yikes.. dead species! if (t_offspring_count == 0) { // maybe do something else? return; } ////////////////////////// // Reproduction // Spawn t_offspring_count babies bool t_champ_chosen = false; bool t_baby_exists_in_pop = false; while(t_offspring_count--) { bool t_new_individual = true; // if the champ was not chosen, do it now.. if (!t_champ_chosen) { t_baby = m_Individuals[0]; t_champ_chosen = true; t_new_individual = false; } // or if it was, then proceed with the others else { do // - while the baby already exists somewhere in the new population { // this tells us if the baby is a result of mating bool t_mated = false; // There must be individuals there.. ASSERT(NumIndividuals() > 0); // for a species of size 1 we can only mutate // NOTE: but does it make sense since we know this is the champ? if (NumIndividuals() == 1) { t_baby = GetIndividual(a_Parameters, a_RNG); t_mated = false; } // else we can mate else { do // keep trying to mate until a good offspring is produced { Genome t_mom = GetIndividual(a_Parameters, a_RNG); // choose whether to mate at all // Do not allow crossover when in simplifying phase if ((a_RNG.RandFloat() < a_Parameters.CrossoverRate) && (a_Pop.GetSearchMode() != SIMPLIFYING)) { // get the father Genome t_dad; bool t_interspecies = false; // There is a probability that the father may come from another species if ((a_RNG.RandFloat() < a_Parameters.InterspeciesCrossoverRate) && (a_Pop.m_Species.size()>1)) { // Find different species (random one) // !!!!!!!!!!!!!!!!! int t_diffspec = a_RNG.RandInt(0, static_cast<int>(a_Pop.m_Species.size()-1)); t_dad = a_Pop.m_Species[t_diffspec].GetIndividual(a_Parameters, a_RNG); t_interspecies = true; } else { // Mate within species t_dad = GetIndividual(a_Parameters, a_RNG); // The other parent should be a different one // number of tries to find different parent int t_tries = 32; if (!a_Parameters.AllowClones) while(((t_mom.GetID() == t_dad.GetID()) || (t_mom.CompatibilityDistance(t_dad, a_Parameters) < 0.00001) ) && (t_tries--)) { t_dad = GetIndividual(a_Parameters, a_RNG); } else while(((t_mom.GetID() == t_dad.GetID()) ) && (t_tries--)) { t_dad = GetIndividual(a_Parameters, a_RNG); } t_interspecies = false; } // OK we have both mom and dad so mate them // Choose randomly one of two types of crossover if (a_RNG.RandFloat() < a_Parameters.MultipointCrossoverRate) { t_baby = t_mom.Mate( t_dad, false, t_interspecies, a_RNG); } else { t_baby = t_mom.Mate( t_dad, true, t_interspecies, a_RNG); } t_mated = true; } // don't mate - reproduce the mother asexually else { t_baby = t_mom; t_mated = false; } } while (t_baby.HasDeadEnds() || (t_baby.NumLinks() == 0)); // in case of dead ends after crossover we will repeat crossover // until it works } // Mutate the baby if ((!t_mated) || (a_RNG.RandFloat() < a_Parameters.OverallMutationRate)) MutateGenome(t_baby_exists_in_pop, a_Pop, t_baby, a_Parameters, a_RNG); // Check if this baby is already present somewhere in the offspring // we don't want that t_baby_exists_in_pop = false; // Unless of course, we want if (!a_Parameters.AllowClones) { for(unsigned int i=0; i<a_Pop.m_TempSpecies.size(); i++) { for(unsigned int j=0; j<a_Pop.m_TempSpecies[i].m_Individuals.size(); j++) { if ( (t_baby.CompatibilityDistance(a_Pop.m_TempSpecies[i].m_Individuals[j], a_Parameters) < 0.00001) // identical genome? ) { t_baby_exists_in_pop = true; break; } } } } } while (t_baby_exists_in_pop); // end do } // Final place to test for problems // If there is anything wrong here, we will just // pick a random individual and leave him unchanged if ((t_baby.NumLinks() == 0) || t_baby.HasDeadEnds()) { t_baby = GetIndividual(a_Parameters, a_RNG); t_new_individual = false; } if (t_new_individual) { // We have a new offspring now // give the offspring a new ID t_baby.SetID(a_Pop.GetNextGenomeID()); a_Pop.IncrementNextGenomeID(); // sort the baby's genes t_baby.SortGenes(); // clear the baby's fitness t_baby.SetFitness(0); t_baby.SetAdjFitness(0); t_baby.SetOffspringAmount(0); t_baby.ResetEvaluated(); } ////////////////////////////////// // put the baby to its species // ////////////////////////////////// // before Reproduce() is invoked, it is assumed that a // clone of the population exists with the name of m_TempSpecies // we will store results there. // after all reproduction completes, the original species will be replaced back bool t_found = false; std::vector<Species>::iterator t_cur_species = a_Pop.m_TempSpecies.begin(); // No species yet? if (t_cur_species == a_Pop.m_TempSpecies.end()) { // create the first species and place the baby there a_Pop.m_TempSpecies.push_back( Species(t_baby, a_Pop.GetNextSpeciesID())); a_Pop.IncrementNextSpeciesID(); } else { // try to find a compatible species Genome t_to_compare = t_cur_species->GetRepresentative(); t_found = false; while((t_cur_species != a_Pop.m_TempSpecies.end()) && (!t_found)) { if (t_baby.IsCompatibleWith( t_to_compare, a_Parameters)) { // found a compatible species t_cur_species->AddIndividual(t_baby); t_found = true; // the search is over } else { // keep searching for a matching species t_cur_species++; if (t_cur_species != a_Pop.m_TempSpecies.end()) { t_to_compare = t_cur_species->GetRepresentative(); } } } // if couldn't find a match, make a new species if (!t_found) { a_Pop.m_TempSpecies.push_back( Species(t_baby, a_Pop.GetNextSpeciesID())); a_Pop.IncrementNextSpeciesID(); } } } }
//------------------------------------- Epoch ---------------------------- // // This function performs one epoch of the genetic algorithm and returns // a vector of pointers to the new phenotypes //------------------------------------------------------------------------ vector<CNeuralNet*> Cga::Epoch(const vector<double> &FitnessScores) { //first check to make sure we have the correct amount of fitness scores if (FitnessScores.size() != m_vecGenomes.size()) { MessageBox(NULL,"Cga::Epoch(scores/ genomes mismatch)!","Error", MB_OK); } //reset appropriate values and kill off the existing phenotypes and //any poorly performing species ResetAndKill(); //update the genomes with the fitnesses scored in the last run for (int gen=0; gen<m_vecGenomes.size(); ++gen) { m_vecGenomes[gen].SetFitness(FitnessScores[gen]); } //sort genomes and keep a record of the best performers SortAndRecord(); //separate the population into species of similar topology, adjust //fitnesses and calculate spawn levels SpeciateAndCalculateSpawnLevels(); //this will hold the new population of genomes vector<CGenome> NewPop; //request the offspring from each species. The number of children to //spawn is a double which we need to convert to an int. int NumSpawnedSoFar = 0; CGenome baby; //now to iterate through each species selecting offspring to be mated and //mutated for (int spc=0; spc<m_vecSpecies.size(); ++spc) { //because of the number to spawn from each species is a double //rounded up or down to an integer it is possible to get an overflow //of genomes spawned. This statement just makes sure that doesn't //happen if (NumSpawnedSoFar < CParams::iNumSweepers) { //this is the amount of offspring this species is required to // spawn. Rounded simply rounds the double up or down. int NumToSpawn = Rounded(m_vecSpecies[spc].NumToSpawn()); bool bChosenBestYet = false; while (NumToSpawn--) { //first grab the best performing genome from this species and transfer //to the new population without mutation. This provides per species //elitism if (!bChosenBestYet) { baby = m_vecSpecies[spc].Leader(); bChosenBestYet = true; } else { //if the number of individuals in this species is only one //then we can only perform mutation if (m_vecSpecies[spc].NumMembers() == 1) { //spawn a child baby = m_vecSpecies[spc].Spawn(); } //if greater than one we can use the crossover operator else { //spawn1 CGenome g1 = m_vecSpecies[spc].Spawn(); if (RandFloat() < CParams::dCrossoverRate) { //spawn2, make sure it's not the same as g1 CGenome g2 = m_vecSpecies[spc].Spawn(); //number of attempts at finding a different genome int NumAttempts = 5; while ( (g1.ID() == g2.ID()) && (NumAttempts--) ) { g2 = m_vecSpecies[spc].Spawn(); } if (g1.ID() != g2.ID()) { baby = Crossover(g1, g2); } } else { baby = g1; } } ++m_iNextGenomeID; baby.SetID(m_iNextGenomeID); //now we have a spawned child lets mutate it! First there is the //chance a neuron may be added if (baby.NumNeurons() < CParams::iMaxPermittedNeurons) { baby.AddNeuron(CParams::dChanceAddNode, *m_pInnovation, CParams::iNumTrysToFindOldLink); } //now there's the chance a link may be added baby.AddLink(CParams::dChanceAddLink, CParams::dChanceAddRecurrentLink, *m_pInnovation, CParams::iNumTrysToFindLoopedLink, CParams::iNumAddLinkAttempts); //mutate the weights baby.MutateWeights(CParams::dMutationRate, CParams::dProbabilityWeightReplaced, CParams::dMaxWeightPerturbation); baby.MutateActivationResponse(CParams::dActivationMutationRate, CParams::dMaxActivationPerturbation); } //sort the babies genes by their innovation numbers baby.SortGenes(); //add to new pop NewPop.push_back(baby); ++NumSpawnedSoFar; if (NumSpawnedSoFar == CParams::iNumSweepers) { NumToSpawn = 0; } }//end while }//end if }//next species //if there is an underflow due to the rounding error and the amount //of offspring falls short of the population size additional children //need to be created and added to the new population. This is achieved //simply, by using tournament selection over the entire population. if (NumSpawnedSoFar < CParams::iNumSweepers) { //calculate amount of additional children required int Rqd = CParams::iNumSweepers - NumSpawnedSoFar; //grab them while (Rqd--) { NewPop.push_back(TournamentSelection(m_iPopSize/5)); } } //replace the current population with the new one m_vecGenomes = NewPop; //create the new phenotypes vector<CNeuralNet*> new_phenotypes; for (gen=0; gen<m_vecGenomes.size(); ++gen) { //calculate max network depth int depth = CalculateNetDepth(m_vecGenomes[gen]); CNeuralNet* phenotype = m_vecGenomes[gen].CreatePhenotype(depth); new_phenotypes.push_back(phenotype); } //increase generation counter ++m_iGeneration; return new_phenotypes; }