UNPKG

@alenaksu/neatjs

Version:

NEAT (Neuroevolution of Augmenting Topologies) implementation in JavaScript

144 lines 5.49 kB
import { rnd, getRandomItems, mutateGenome, getRandomSpecies, compatibility, crossover, speciateOrganism, descending } from '../utils'; export class Species { constructor() { /** * The organisms of the species */ this.organisms = []; /** * Mark the species for extinction */ this.extinct = false; /** * Species' age */ this.age = 0; /** * Age from last improvement */ this.ageOfLastImprovement = 0; /** * Max fitness ever */ this.maxFitness = 0; /** * Average fitness */ this.averageFitness = 0; /** * Number of expected offspring in proportion to the sum of adjusted fitnesses */ this.expectedOffspring = 0; } addOrganism(organism) { if (!this.specimen) this.specimen = organism; this.organisms.push(organism); organism.species = this; } removeOrganism(organism) { const index = this.organisms.indexOf(organism); if (~index) this.organisms.splice(index, 1); } getSpecimen() { return this.specimen; } getChampion() { return this.organisms[0]; } adjustFitness(config) { let totalFitness = 0; this.extinct = !!(this.age - this.ageOfLastImprovement + 1 > config.dropoffAge); this.organisms.forEach((organism) => { organism.originalFitness = organism.fitness; if (this.extinct) { // Penalty for a long period of stagnation (divide fitness by 100) organism.fitness *= 0.01; } if (this.age <= 10) { // boost young organisms organism.fitness *= config.ageSignificance; } organism.fitness = Math.max(organism.fitness, 0.0001) / this.organisms.length; totalFitness += organism.originalFitness; }); this.organisms.sort(descending((i) => i.fitness)); [this.specimen] = getRandomItems(this.organisms, 1); this.averageFitness = totalFitness / this.organisms.length; // update age of last improvement if (this.organisms[0].originalFitness > this.maxFitness) { this.maxFitness = this.organisms[0].originalFitness; this.ageOfLastImprovement = this.age; } const removeFrom = Math.floor(this.organisms.length * config.survivalThreshold + 1); for (let i = removeFrom; i < this.organisms.length; i++) this.organisms[i].kill = true; } /** * Perform mating and mutation to form next generation. * The sorted_species is ordered to have best species in the beginning. * Returns list of baby organisms as a result of reproduction of all organisms in this species. * @param generation */ reproduce(config, generation, population, sortedSpecies) { const { organisms, expectedOffspring } = this; if (expectedOffspring && !organisms.length) return; const [...babies] = organisms; const champ = babies[0]; let champAdded = false; // TODO stolen babies let superChamp = population.getSuperChamp(); for (let i = 0; i < expectedOffspring; i++) { let baby; if (superChamp && superChamp === champ && superChamp.expectedOffspring > 0) { // If we have a population champion, finish off some special clones let organism = superChamp.copy(0, generation); if (superChamp.expectedOffspring === 1) organism = mutateGenome(config, organism); superChamp.expectedOffspring--; baby = organism; } else if (!champAdded && expectedOffspring > 5) { // Champion of species with more than 5 networks is copied unchanged baby = champ.copy(0, generation); champAdded = true; } else if (rnd() < config.mutateOnlyProbability) { // Mutate only const [mom] = getRandomItems(babies, 1); baby = mutateGenome(config, mom.copy(0, generation)); } else { // mate const [mom] = getRandomItems(babies, 1); let dad; if (rnd() > config.interspeciesMateRate) { [dad] = getRandomItems(babies, 1); } else { // Interspecies mate let tries = 0, randomSpecies = this; while (randomSpecies === this && tries++ < 5) { const species = getRandomSpecies(sortedSpecies); if (species.organisms.length) randomSpecies = species; } dad = randomSpecies.getChampion(); } baby = crossover(dad, mom, config); if (rnd() < config.mutateOnlyProbability || compatibility(mom, dad, config) === 0) mutateGenome(config, baby); } baby.generation = generation; speciateOrganism(config, baby, population.species); } } } //# sourceMappingURL=Species.js.map