add details on how to produce future generations

This commit is contained in:
Michael Peters 2024-08-12 22:26:43 -07:00
parent 7a986b3296
commit a26dd0b4bd
3 changed files with 185 additions and 128 deletions

View File

@ -95,6 +95,7 @@
* E = # of Excess Genes * E = # of Excess Genes
* D = # of Disjoint Genes * D = # of Disjoint Genes
* W = Average Weight Difference of Matching Genes * W = Average Weight Difference of Matching Genes
* N = # of Genes in Larger Genome
* c1, c2, c3 = Provided coefficients to adjust importance of each factor * c1, c2, c3 = Provided coefficients to adjust importance of each factor
* *
* Explicit Fitness Sharing: * Explicit Fitness Sharing:
@ -107,38 +108,70 @@
* δ_t = Adjustable Compatibility Distance Threshold * δ_t = Adjustable Compatibility Distance Threshold
* δ(i, j) = Compatibility Distance between i and j * δ(i, j) = Compatibility Distance between i and j
* sh(δ(i, j)) = Fitness Sharing Function - 1 if δ(i, j) < δ_t else 0 * sh(δ(i, j)) = Fitness Sharing Function - 1 if δ(i, j) < δ_t else 0
* The sum of this function counts the number of organisms in i's species * The sum of this function is equal to the number of organisms in i's species
* *
* --- Iteration ---------------------------------------------------------------
* *
* TODO: species bucketing -- species from previous generations are considered in the same species forever * 1. Create the initial genome as a "complete bipartite" graph between input and output nodes
* * 2. Generate an initial population using the initial genome
* - use random values connection weights
* - all nodes should be marked "enabled"
* - all organisms in the initial population will be part of species #1
* - this implies that randomized connection weights should be chosen such that c3*W < δ_t for all organisms
* 3. Training Loop
* a) Compute fitness f_i for all organisms[i]
* b) Find adjusted f_adj_i based on each organism's species
* c) Mating
* - The next generation's organisms are computed as a function of the previous generation's organisms
* - The next generation will have the same population as the previous generation
* - Filtering:
* - Organisms that do not meet the Survival Threshold are not allowed to mate
* - Species that have not improved in fitness for stag_lim generations are not allowed to mate
* - Selection:
* - The "champion" (highest fitness) of each species with at least champ_sp networks is cloned into the next generation
* - while more population is required:
* - for each organism that meets the survival threshold (mom):
* - select a mate (dad):
* - r_asex it is the same organism
* - else r_int_sp it is from a random other organism from any other species
* - else it is from a random other organism from the same species
* - compute new genome (baby) for mom x dad
* - dis_odds chance for a node disabled in either parent to become enabled in the child
* - conditionally mutate baby's genome:
* - mut_odds chance to mutate each existing connection weight
* - if mutated, mut_w_p chance to "uniformly perturb" -> x' = x + K * rand(-1,1)
* mut_w_r chance to assign random value -> x' = J * rand(-1,1)
* - r_mut_nn chance to add a new node (src->new) = 1, (new->dst) = weight(src->dst)
* - r_mut_nc chance to add a new connection of random weight
* - TODO: figure out how future speciation works... does it just keep species based on the champions?
* *
* --- Parameters -------------------------------------------------------------- * --- Parameters --------------------------------------------------------------
* *
* To learn XOR and DPV (Double-pole balancing, with velocities), Stanley and Miikulainen used the following parameters * To learn XOR and DPV (Double-pole balancing, with velocities), Stanley and Miikulainen used the following parameters
* +-------+------- * +-------+----------+----
* | paper | desc * | paper | variable | desc
* +-------+------- * +-------+----------+----
* | 150 | Simulation Population * | 150 | pop | Simulation Population
* +-------+------- * +-------+----------+----
* | 3.0 | δ_t - Compatibility Distance Threshold * | 3.0 | δ_t | Compatibility Distance Threshold
* | 1.0 | c1 - CDF Excess genes coefficient * | 1.0 | c1 | CDF Excess genes coefficient
* | 1.0 | c2 - CDF Disjoint genes coefficient * | 1.0 | c2 | CDF Disjoint genes coefficient
* | 0.4 | c3 - CDF Average matching weight coefficient * | 0.4 | c3 | CDF Average matching weight coefficient
* +-------+------- * +-------+----------+----
* | 80% | Chance for a child to have its connection weights mutated * | 80% | mut_odds | Chance for a child to have its connection weights mutated
* | 90% | Chance for, given a child weights mutation, a weight to be "uniformly perturbed" (x' = x + K * rand()) * | 90% | mut_w_p | Chance for, given a child weights mutation, a weight to be "uniformly perturbed" (x' = x + K * rand())
* | 10% | Chance for, given a child weights mutation, a weight to be assigned a random value * | 10% | mut_w_r | Chance for, given a child weights mutation, a weight to be assigned a random value
* | 25% | Chance for a gene disabled in at least one parent to become enabled in the child * | 25% | dis_odds | Chance for a gene disabled in at least one parent to become enabled in the child
* +-------+------- * +-------+----------+----
* | 15 | After this many generations of stagnant fitness growth, a species is barred from reproduction (considered "found its peak") * | 5 | champ_sp | Species with at least this many networks will have their champions cloned into the next generation
* +-------+------- * | 15 | stag_lim | After this many generations of stagnant fitness growth, a species is barred from reproduction (considered "found its peak")
* | 25% | Non-crossover offspring (mutation only (and guaranteed)) * +-------+----------+----
* | 0.001 | Interspecies mating rate (chance for a mating event to involve different species) * | 25% | r_asex | Non-crossover offspring (mutation only (and guaranteed))
* +-------+------- * | 0.001 | r_int_sp | Interspecies mating rate (chance for a mating event to involve different species)
* | 3% | Probability of adding a new node * +-------+----------+----
* | 5% | Probability of adding a new connection * | 3% | mut_nn | Probability of adding a new node
* +-------+------- * | 5% | mut_nc | Probability of adding a new connection
* +-------+----------+----
* *
* All nodes used a "slightly steepened" sigmoidal transfer function "for more fine tuning at extreme activations" * All nodes used a "slightly steepened" sigmoidal transfer function "for more fine tuning at extreme activations"
* "It is optimized to be close to linear duing its steepest ascent between activations, -0.5 and 0.5" * "It is optimized to be close to linear duing its steepest ascent between activations, -0.5 and 0.5"
@ -159,6 +192,16 @@
* | 30% | Probability of adding a new connection * | 30% | Probability of adding a new connection
* +-------+------- * +-------+-------
* *
* --- Additional Parameters ---------------------------------------------------
*
* While not described specifically in the paper, these parameters are still important to the simulation
*
* +-------+-------
* | value | desc
* +-------+-------
* | 20% | Survival Threshold (your adjusted fitness must be in the top x% to survive to the next generation)
* +-------+-------
*
* --- TODO -------------------------------------------------------------------- * --- TODO --------------------------------------------------------------------
* *
* - Determine reproduction algo * - Determine reproduction algo
@ -173,8 +216,7 @@
* - Effectively pass data from inputs, through hidden nodes, to outputs * - Effectively pass data from inputs, through hidden nodes, to outputs
*/ */
import IOSet from "./ioset"; import { Network, Node, NodeID } from './network';
import { Network, Node, NodeID } from "./network";
interface Gene { interface Gene {
innovation: number; innovation: number;
@ -188,9 +230,10 @@ type GeneNode = Gene & { src: Node<GeneNode>; dst: Node<GeneNode> };
type Genome = Gene[]; type Genome = Gene[];
interface GenomeAlignment { interface GenomeAlignment {
matching: { a: Gene, b: Gene }[], matching: { a: Gene; b: Gene }[];
disjoint: { a: Gene | null, b: Gene | null }[], disjoint: { a: Gene | null; b: Gene | null }[];
excess: { a: Gene | null, b: Gene | null }[], excess: { a: Gene | null; b: Gene | null }[];
genomes: { a: Genome; b: Genome };
} }
interface CompatibilityDistanceConfig { interface CompatibilityDistanceConfig {
@ -221,7 +264,7 @@ export function alignGenome(a: Gene[], b: Gene[]): GenomeAlignment {
const bMaxInnov = b[b.length - 1]!.innovation; const bMaxInnov = b[b.length - 1]!.innovation;
const excessStart = Math.min(aMaxInnov, bMaxInnov); const excessStart = Math.min(aMaxInnov, bMaxInnov);
const alignment: GenomeAlignment = { matching: [], disjoint: [], excess: [] }; const alignment: GenomeAlignment = { matching: [], disjoint: [], excess: [], genomes: { a, b } };
let isDisjoint = false; let isDisjoint = false;
const innovationsOrder = Array.from(innovations).sort((a, b) => a - b); const innovationsOrder = Array.from(innovations).sort((a, b) => a - b);
@ -231,35 +274,33 @@ export function alignGenome(a: Gene[], b: Gene[]): GenomeAlignment {
if (geneA === null || geneB === null) isDisjoint = true; if (geneA === null || geneB === null) isDisjoint = true;
const pair = { a: geneA, b: geneB } const pair = { a: geneA, b: geneB };
if (innovation > excessStart) alignment.excess.push(pair); if (innovation > excessStart) alignment.excess.push(pair);
else if (isDisjoint) alignment.disjoint.push(pair); else if (isDisjoint) alignment.disjoint.push(pair);
else alignment.matching.push(pair as { a: Gene, b: Gene }); else alignment.matching.push(pair as { a: Gene; b: Gene });
} }
return alignment; return alignment;
} }
export function compatibilityDistance(alignment: GenomeAlignment, { c1, c2, c3 }: CompatibilityDistanceConfig) { export function compatibilityDistance(alignment: GenomeAlignment, { c1, c2, c3 }: CompatibilityDistanceConfig) {
const totalLength = alignment.excess.length + alignment.disjoint.length + alignment.matching.length; const maxLength = Math.max(alignment.genomes.a.length, alignment.genomes.b.length);
const avgWeightDiff = alignment.matching const avgWeightDiff =
.map(({ a, b }) => Math.abs(a.weight - b.weight)) alignment.matching.map(({ a, b }) => Math.abs(a.weight - b.weight)).reduce((p, c) => p + c) /
.reduce((p, c) => p + c) / alignment.matching.length; alignment.matching.length;
const distance = ( const distance =
c1 * alignment.excess.length / totalLength (c1 * alignment.excess.length) / maxLength + (c2 * alignment.disjoint.length) / maxLength + c3 * avgWeightDiff;
+ c2 * alignment.disjoint.length / totalLength
+ c3 * avgWeightDiff
);
return distance; return distance;
} }
// TODO: update this to support "given previous generation's species, what species (or a new species) should this genome be in" // TODO: update this to support "given previous generation's species, what species (or a new species) should this genome be in"
export function speciate( export function speciate(
genomes: Genome[], species: Map<SpeciesID, Genome[]>,
newGenomes: Genome[],
cdc: CompatibilityDistanceConfig, cdc: CompatibilityDistanceConfig,
cdt: CompatibilityDistanceThreshold, cdt: CompatibilityDistanceThreshold,
) { ) {
const species = new Map<SpeciesID, Genome[]>(); // const species = new Map<SpeciesID, Genome[]>();
} }
function activate(n: number) { function activate(n: number) {

View File

@ -1,3 +1,4 @@
// TODO: remove this, Set is already insertion-ordered in javascript!
/** an insertion ordered set */ /** an insertion ordered set */
export default class IOSet<T> { export default class IOSet<T> {
// NOTE: this data structure could be improved to have O(1) // NOTE: this data structure could be improved to have O(1)
@ -10,7 +11,7 @@ export default class IOSet<T> {
this.map = new Map(); this.map = new Map();
this.list = []; this.list = [];
if (values !== undefined) this.extend(values) if (values !== undefined) this.extend(values);
} }
has(v: T) { has(v: T) {

View File

@ -77,17 +77,32 @@ function testCompatibilityDistance() {
const alignment = alignGenome(genomeA, genomeB); const alignment = alignGenome(genomeA, genomeB);
/*
* compatibility distance function (CDF):
*
* c1*E + c2*D
* δ = ------------- + c3*W
* N
*
* δ = Compatibility Distance
* E = # of Excess Genes
* D = # of Disjoint Genes
* W = Average Weight Difference of Matching Genes
* N = # of Genes in Larger Genome
* c1, c2, c3 = Provided coefficients to adjust importance of each factor
*/
const dist1 = compatibilityDistance(alignment, { c1: 1, c2: 0, c3: 0 }); const dist1 = compatibilityDistance(alignment, { c1: 1, c2: 0, c3: 0 });
const dist2 = compatibilityDistance(alignment, { c1: 0, c2: 1, c3: 0 }); const dist2 = compatibilityDistance(alignment, { c1: 0, c2: 1, c3: 0 });
const dist3 = compatibilityDistance(alignment, { c1: 0, c2: 0, c3: 1 }); const dist3 = compatibilityDistance(alignment, { c1: 0, c2: 0, c3: 1 });
assert(dist1 === 2 / 10); assert(dist1 === 2 / 9);
assert(dist2 === 3 / 10); assert(dist2 === 3 / 9);
// |8 - 4| + |1 - 0| + |2 - 3| + |9 - 9| + |3 - 5| // |8 - 4| + |1 - 0| + |2 - 3| + |9 - 9| + |3 - 5|
// 4 + 1 + 1 + 0 + 2 // 4 + 1 + 1 + 0 + 2
// 8 / 5 // 8 / 5
assert(dist3 === 8 / 5); assert(dist3 === 8 / 5);
const distCombo = compatibilityDistance(alignment, { c1: 2, c2: 3, c3: 4 }); const distCombo = compatibilityDistance(alignment, { c1: 2, c2: 3, c3: 4 });
assert(distCombo === 2 * (2 / 10) + 3 * (3 / 10) + 4 * (8 / 5)); assert(distCombo === 2 * (2 / 9) + 3 * (3 / 9) + 4 * (8 / 5));
} }
addTest(testCompatibilityDistance); addTest(testCompatibilityDistance);