summaryrefslogtreecommitdiff
path: root/src/genome
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-10-30 02:14:17 -0500
committersanine <sanine.not@pm.me>2023-10-30 02:14:17 -0500
commit70252c2ef37ddf974349fa092dce92782ffd302a (patch)
tree746b648674bb2b7befbd17c38d9e2d01ff4adfb3 /src/genome
parenta32853e60029fa7f08d4d713ee613ee03196fbef (diff)
add genome creation & mutation trial
Diffstat (limited to 'src/genome')
-rw-r--r--src/genome/genome.js87
-rw-r--r--src/genome/trial.js37
2 files changed, 123 insertions, 1 deletions
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);