From 70252c2ef37ddf974349fa092dce92782ffd302a Mon Sep 17 00:00:00 2001 From: sanine Date: Mon, 30 Oct 2023 02:14:17 -0500 Subject: add genome creation & mutation trial --- src/genome/genome.js | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/genome/trial.js | 37 ++++++++++++++++++++++ src/mind/topology.js | 2 +- src/util.js | 6 ++++ 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/genome/trial.js diff --git a/src/genome/genome.js b/src/genome/genome.js index d2650b5..bc569ff 100644 --- a/src/genome/genome.js +++ b/src/genome/genome.js @@ -1,6 +1,7 @@ 'use strict'; -import { network } from '../mind/topology'; +import { random_choice } from '../util.js'; +import { network } from '../mind/topology.js'; // check if a given genome is valid and compute its size @@ -25,6 +26,7 @@ export function get_size(num_input, num_output, genome) { } +// parse a genome into a useable neural net export function parse_genome(num_input, num_output, genome) { const size = get_size(num_input, num_output, genome); if (size < 0) { @@ -49,6 +51,7 @@ function clamp(value, min, max) { return value; } +// adjust the source input of a gene export function mut_gene_source(n_input, n_internal, n_output, gene, r) { const [source, sink, weight] = gene; @@ -62,6 +65,7 @@ export function mut_gene_source(n_input, n_internal, n_output, gene, r) { } +// adjust the sink target of a gene export function mut_gene_sink(n_input, n_internal, n_output, gene, r) { const [source, sink, weight] = gene; @@ -75,6 +79,9 @@ export function mut_gene_sink(n_input, n_internal, n_output, gene, r) { } +// modify a gene's weight +// only adjusts the weight by performing a weighted average, so as to +// more gently modify the generated net export function mut_gene_weight(weight_max, gene, r) { const [source, sink, weight] = gene; @@ -90,6 +97,9 @@ export function mut_gene_weight(weight_max, gene, r) { } + +// expand the size of the neural net encoded by the genome +// relabels internal indices so that there is one extra internal neuron export function mut_genome_expand( [n_input, n_internal, n_output, genome], r ) { @@ -106,11 +116,16 @@ export function mut_genome_expand( } +// contract the size of the neural net encoded by the genome +// relabels internal indices so that there is one less internal neuron export function mut_genome_contract( [n_input, n_internal, n_output, genome], r ) { const contract_idx = Math.floor(n_internal * r) + n_input; + + // decrement sources on the contract index too, to prevent invalid genomes const new_source = (source) => source >= contract_idx ? source-1 : source; + // decrement sinks only after the contract index const new_sink = (sink) => sink > contract_idx ? sink-1 : sink; const new_genome = genome.map(([source, sink, weight]) => [ @@ -125,6 +140,7 @@ export function mut_genome_contract( } +// append a newly generated gene to the end of the genome export function mut_genome_insert( [n_input, n_internal, n_output, genome], weight_max, @@ -141,6 +157,7 @@ export function mut_genome_insert( } +// delete a gene from the genome export function mut_genome_delete( [n_input, n_internal, n_output, genome], r ) { @@ -148,3 +165,71 @@ export function mut_genome_delete( const new_genome = genome.filter((_, idx) => idx != del_idx); return [n_input, n_internal, n_output, new_genome]; } + + +function mut_gene( + [n_input, n_internal, n_output, genome], + weight_max, r1, r2, r3 +) { + const gene_idx = Math.floor(genome.length * r1); + const mod = random_choice(['source', 'sink', 'weight'], r2); + let new_gene; + if (mod == 'source') { + new_gene = mut_gene_source( + n_input, n_internal, n_output, + genome[gene_idx], + r3 + ); + } else if (mod == 'sink') { + new_gene = mut_gene_sink( + n_input, n_internal, n_output, + genome[gene_idx], + r3 + ); + } else { + new_gene = mut_gene_weight( + weight_max, genome[gene_idx], r3 + ); + } + + const new_genome = genome.map((gene, idx) => { + if (idx == gene_idx) { return new_gene; } + return gene; + }); + + return [ + n_input, n_internal, n_output, new_genome + ]; +} + + +export function mutate_genome(obj, weight_max) { + const mut = random_choice([ + 'gene', 'gene', 'gene', + 'gene', 'gene', 'gene', + 'gene', 'gene', 'gene', + 'insert', 'delete', + 'insert', 'delete', + 'expand', 'contract', + ], Math.random()); + + if (mut == 'gene') { + return mut_gene( + obj, weight_max, + Math.random(), Math.random(), Math.random() + ); + } else if (mut == 'insert') { + return mut_genome_insert( + obj, weight_max, + Math.random(), Math.random(), Math.random() + ); + } else if (mut == 'delete') { + return mut_genome_delete(obj, Math.random()); + } else if (mut == 'expand') { + return mut_genome_expand(obj, Math.random()); + } else if (mut == 'contract') { + return mut_genome_contract(obj, Math.random()); + } else { + throw new Error(`bad mut value: ${mut}`); + } +} diff --git a/src/genome/trial.js b/src/genome/trial.js new file mode 100644 index 0000000..2ce23bf --- /dev/null +++ b/src/genome/trial.js @@ -0,0 +1,37 @@ +import { + get_size, + mut_genome_insert, mutate_genome, +} from './genome.js'; + +const recurse = (f, x0, n) => { + if (n == 0) { + return x0; + } else { + return f(recurse(f, x0, n-1)); + } +}; + + +const n_input = 5; +const n_output = 5; + + +const [_1, _2, _3, genome] = recurse( + s => mut_genome_insert( + s, 4, + Math.random(), Math.random(), Math.random() + ), + [n_input, 10, n_output, []], + 20); + + +const n_internal = get_size(n_input, n_output, genome) - n_input - n_output; +console.log([n_input, n_internal, n_output, genome]); + +const mutation = recurse( + s => mutate_genome(s, 4), + [n_input, n_internal, n_output, genome], + 40 +); + +console.log(mutation); diff --git a/src/mind/topology.js b/src/mind/topology.js index 576ee83..127351e 100644 --- a/src/mind/topology.js +++ b/src/mind/topology.js @@ -1,6 +1,6 @@ 'use strict'; -import { create } from '../util'; +import { create } from '../util.js'; const DEFAULT_WEIGHT_MAX = 4; diff --git a/src/util.js b/src/util.js index 9f52551..4e23d9d 100644 --- a/src/util.js +++ b/src/util.js @@ -8,3 +8,9 @@ export function create(obj, proto=Object.prototype) { return Object.create(proto, props); }; + + +export function random_choice(collection, r) { + const idx = Math.floor(collection.length * r); + return collection[idx]; +} -- cgit v1.2.1