-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor import paths in DeDuplicator.ts, CreatureUUID.ts, Breed.ts, ExperimentStore.ts, and Creature.ts * Refactor sortCreaturesByScore function and add verbose logging in ElitismUtils.ts * Refactor checkAndAdd function in Neat.ts to improve readability and fix error handling * Refactor checkAndAdd function in Neat.ts to improve readability and error handling * Refactor Genus class to add findClosestMatchingSpecies method * Refactor Genus class to add findClosestMatchingSpecies method * Fix error handling in Creature.ts and CreatureState.ts * Fix bias range check in Propagate/STEP.ts and add missing synapses in FineTune.ts --------- Co-authored-by: Nigel Leck <[email protected]>
- Loading branch information
Showing
19 changed files
with
829 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { assert } from "https://deno.land/[email protected]/assert/mod.ts"; | ||
import { Creature, CreatureUtil } from "../../mod.ts"; | ||
import { Genus } from "./Genus.ts"; | ||
import { fineTuneImprovement } from "../architecture/FineTune.ts"; | ||
import { Species } from "./Species.ts"; | ||
import { Neat } from "./Neat.ts"; | ||
|
||
export class FindTunePopulation { | ||
private neat: Neat; | ||
constructor(neat: Neat) { | ||
this.neat = neat; | ||
} | ||
|
||
async make( | ||
fittest: Creature, | ||
previousFittest: Creature | undefined, | ||
genus: Genus, | ||
) { | ||
assert(fittest, "Fittest creature mandatory"); | ||
const fittestUUID = await CreatureUtil.makeUUID(fittest); | ||
|
||
const uniqueUUID = new Set<string>([fittestUUID]); | ||
|
||
const tmpFineTunePopulation = []; | ||
|
||
// Add previousFittest first if it's different from fittest and not null | ||
if ( | ||
previousFittest | ||
) { | ||
const previousUUID = await CreatureUtil.makeUUID(previousFittest); | ||
if (!uniqueUUID.has(previousUUID)) { | ||
tmpFineTunePopulation.push(previousFittest); | ||
uniqueUUID.add(previousUUID); | ||
} | ||
} | ||
|
||
// Add remaining creatures from the population excluding fittest and previousFittest | ||
for (const creature of this.neat.population) { | ||
const creatureUUID = await CreatureUtil.makeUUID(creature); | ||
if (!uniqueUUID.has(creatureUUID)) { | ||
tmpFineTunePopulation.push(creature); | ||
uniqueUUID.add(creatureUUID); | ||
} | ||
} | ||
|
||
const tmpPreviousFittest = tmpFineTunePopulation.shift(); | ||
|
||
let fineTunedPopulation: Creature[] = []; | ||
if (!tmpPreviousFittest) { | ||
console.warn("Failed to find previous fittest creature"); | ||
} else { | ||
/** 20% of population or those that just died, leave one for the extended */ | ||
const fineTunePopSize = Math.max( | ||
Math.ceil( | ||
this.neat.config.populationSize / 5, | ||
), | ||
this.neat.config.populationSize - this.neat.population.length - | ||
this.neat.config.elitism - | ||
this.neat.trainingComplete.length, | ||
); | ||
|
||
const tunedUUID = new Set<string>(); | ||
|
||
tunedUUID.add(fittestUUID); | ||
|
||
tunedUUID.add(tmpPreviousFittest.uuid ?? "UNKNOWN"); | ||
fineTunedPopulation = await fineTuneImprovement( | ||
fittest, | ||
tmpPreviousFittest, | ||
fineTunePopSize - 1, | ||
this.neat.config.verbose, | ||
); | ||
|
||
for (let attempts = 0; attempts < 12; attempts++) { | ||
/** | ||
* Now, after we do the fine tuning of the fittest versus the previous fittest, | ||
* I want to find another creature from the same species of the fittest creature ( but not the fittest or previous fittest creatures) | ||
* and perform the fine tuning comparing the fittest creature to another within the species. | ||
* | ||
* We should favor the highest score creatures in that species. | ||
*/ | ||
|
||
const speciesFineTunePopSize = fineTunePopSize - | ||
fineTunedPopulation.length; | ||
|
||
if (speciesFineTunePopSize < 1) break; | ||
|
||
const speciesKey = await Species.calculateKey(fittest); | ||
const species = genus.speciesMap.get(speciesKey); | ||
|
||
if (species) { | ||
if (species.creatures.length > 0) { // Ensure there's more than one to choose from | ||
let eligibleCreatures = species.creatures.filter((creature) => | ||
!tunedUUID.has(creature.uuid ?? "UNKNOWN") | ||
); | ||
|
||
/** If there is no eligible creatures try find the closest species. */ | ||
if (eligibleCreatures.length == 0) { | ||
const closestSpecies = genus.findClosestMatchingSpecies(fittest); | ||
if (closestSpecies) { | ||
if (closestSpecies && closestSpecies.creatures.length > 0) { | ||
eligibleCreatures = closestSpecies.creatures.filter( | ||
(creature) => !tunedUUID.has(creature.uuid ?? "UNKNOWN"), | ||
); | ||
} | ||
} | ||
} | ||
|
||
if (eligibleCreatures.length > 0) { | ||
// Introduce random selection, weighted towards higher score creatures | ||
const nextBestCreature = this.weightedRandomSelect( | ||
eligibleCreatures, | ||
); | ||
|
||
tunedUUID.add(nextBestCreature.uuid ?? "UNKNOWN"); | ||
const extendedTunedPopulation = await fineTuneImprovement( | ||
fittest, | ||
nextBestCreature, | ||
speciesFineTunePopSize, | ||
false, | ||
); | ||
|
||
fineTunedPopulation.push(...extendedTunedPopulation); | ||
} | ||
} | ||
} else { | ||
throw new Error(`No species found for key ${speciesKey}`); | ||
} | ||
|
||
const extendedFineTunePopSize = fineTunePopSize - | ||
fineTunedPopulation.length; | ||
if (extendedFineTunePopSize > 0 && tmpFineTunePopulation.length > 0) { | ||
/* Choose a creature from near the top of the list. */ | ||
const location = Math.floor( | ||
tmpFineTunePopulation.length * Math.random() * Math.random(), | ||
); | ||
|
||
const extendedPreviousFittest = tmpFineTunePopulation[location]; | ||
if (!extendedPreviousFittest) { | ||
throw new Error( | ||
`No creature found at location ${location} in tmpFineTunePopulation.`, | ||
); | ||
} | ||
tunedUUID.add(extendedPreviousFittest.uuid ?? "UNKNOWN"); | ||
const extendedTunedPopulation = await fineTuneImprovement( | ||
fittest, | ||
extendedPreviousFittest, | ||
extendedFineTunePopSize, | ||
false, | ||
); | ||
|
||
fineTunedPopulation.push(...extendedTunedPopulation); | ||
|
||
/* Remove the chosen creature from the array */ | ||
tmpFineTunePopulation.splice(location, 1); | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return fineTunedPopulation; | ||
} | ||
|
||
/* Assuming weightedRandomSelect selects based on score, weighting higher scores more heavily.*/ | ||
|
||
weightedRandomSelect(creatures: Creature[]) { | ||
const totalWeight = creatures.reduce( | ||
(sum, creature) => sum + 1 / (creatures.indexOf(creature) + 1), | ||
0, | ||
); | ||
let random = Math.random() * totalWeight; | ||
|
||
for (const creature of creatures) { | ||
random -= 1 / (creatures.indexOf(creature) + 1); | ||
if (random <= 0) { | ||
return creature; | ||
} | ||
} | ||
return creatures[0]; // Fallback to the first creature if no selection occurs | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { assert } from "https://deno.land/[email protected]/assert/mod.ts"; | ||
import { Creature } from "../../mod.ts"; | ||
import { Species } from "./Species.ts"; | ||
|
||
export class Genus { | ||
readonly speciesMap: Map<string, Species>; | ||
readonly creatureToSpeciesMap: Map<string, string>; | ||
|
||
constructor() { | ||
this.speciesMap = new Map(); | ||
this.creatureToSpeciesMap = new Map(); | ||
} | ||
|
||
async addCreature(creature: Creature): Promise<Species> { | ||
if (creature === undefined || creature.uuid === undefined) { | ||
throw new Error(`creature ${creature.uuid} is undefined`); | ||
} | ||
|
||
const key = await Species.calculateKey(creature); | ||
|
||
let species = this.speciesMap.get(key); | ||
if (!species) { | ||
species = new Species(key); | ||
this.speciesMap.set(key, species); | ||
} | ||
species.addCreature(creature); | ||
|
||
this.creatureToSpeciesMap.set(creature.uuid, key); | ||
|
||
return species; | ||
} | ||
|
||
findSpeciesByCreatureUUID(uuid: string): Species { | ||
const speciesKey = this.creatureToSpeciesMap.get(uuid); | ||
|
||
if (!speciesKey) { | ||
throw new Error(`Could not find species for creature ${uuid}`); | ||
} | ||
const species = this.speciesMap.get(speciesKey); | ||
|
||
if (!species) { | ||
throw new Error(`Could not find species ${speciesKey}`); | ||
} | ||
|
||
return species; | ||
} | ||
|
||
findClosestMatchingSpecies(creature: Creature): Species | null { | ||
assert(creature.uuid, "creature.uuid is undefined"); | ||
const creatureSpeciesKey = this.creatureToSpeciesMap.get(creature.uuid); | ||
const creatureNeuronCount = creature.neurons.length; | ||
let closestSpecies: Species | null = null; | ||
let smallestDifference = Infinity; | ||
let largestPopulation = 0; | ||
|
||
this.speciesMap.forEach((species, key) => { | ||
if (key === creatureSpeciesKey) return; // Skip the creature's current species | ||
const exampleCreature = species.creatures[0]; // Assume at least one creature per species | ||
const difference = Math.abs( | ||
creatureNeuronCount - exampleCreature.neurons.length, | ||
); | ||
|
||
// Check if this species is closer or if it's equally close but more populated | ||
if ( | ||
difference < smallestDifference || | ||
(difference === smallestDifference && | ||
species.creatures.length > largestPopulation) | ||
) { | ||
closestSpecies = species; | ||
smallestDifference = difference; | ||
largestPopulation = species.creatures.length; | ||
} | ||
}); | ||
|
||
return closestSpecies; | ||
} | ||
} |
Oops, something went wrong.