Skip to content

Instantly share code, notes, and snippets.

@adrianseeley
Created February 27, 2014 18:43
Show Gist options
  • Save adrianseeley/9256385 to your computer and use it in GitHub Desktop.
Save adrianseeley/9256385 to your computer and use it in GitHub Desktop.
GATO.PLINKO
function Plinko () {
var Ref = {
Train: function (Training_Set, Train_Till_Errors, Train_Till_Iterations, Max_Medium_Population, Nerf_Rate) {
// Define processes
var Processes = [
function Process_Identity (Self) {
// Distribute all inputs to all outputs
for (var a = 0; a < Self.Outputs.length; a++) for (var b = 0; b < Self.Inputs.length; b++) Ref.Medium[Self.Outputs[a].Layer][Self.Outputs[a].Node].Inputs.push(Self.Inputs[b]);
},
function Process_Summation (Self) {
// Distribute sum of all inputs to all outputs
var Sum = 0; for (var a = 0; a < Self.Inputs.length; a++) Sum += Self.Inputs[a];
for (var a = 0; a < Self.Outputs.length; a++) Ref.Medium[Self.Outputs[a].Layer][Self.Outputs[a].Node].Inputs.push(Sum);
,
function Process_Average (Self) {
// Distribute average of all inputs to all outputs
var Average = 0; for (var a = 0; a < Self.Inputs.length; a++) Average += Self.Inputs[a]; Average /= Self.Inputs.length;
for (var a = 0; a < Self.Outputs.length; a++) Ref.Medium[Self.Outputs[a].Layer][Self.Outputs[a].Node].Inputs.push(Average);
}
];
// Define training set evaluator
function Evaluate_Training_Cases (Medium, Training_Set) {
// Calculate class output pairs for training set
var Class_Output_Pairs = []; for (var a = 0; a < Training_Set.length; a++) Class_Output_Pairs.push({Class: Training_Set[a].Output, Output: Evaluate_Medium(Medium, Training_Set[a].Inputs)});
return Class_Output_Pairs;
};
// Define medium evaluation function
function Evaluate_Medium (Medium, Inputs) {
// Reset medium
for (var a = 0; a < Medium.length; a++) for (var b = 0; b < Medium[a].length; b++) for (var c = 0; c < Medium[a][b].Inputs.length; c++) Medium[a][b].Inputs[c] = [];
// Stimulate medium with inputs
for (var a = 0; a < Inputs.length; a++) Medium[0][a].Inputs.push(Inputs[a]);
// Process medium (note last layer is summation layer and is skipped)
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) Medium[a][b].Process(Medium[a][b]);
// Calculate the summation of the output layer
var Sum = 0; for (var a = 0; a < Medium[Medium.length - 1][0].Inputs.length; a++) Sum += Medium[Medium.length - 1][0].Inputs[a];
// Return evaluated sum
return Sum;
};
// Define fitness function
function Fitness (Class_Output_Pairs) {
// Calculate the divergence and convergence of all pairs
var Divergence = 0; var Convergence = 0;
for (var a = 0; a < Class_Output_Pairs.length; a++) for (var b = 0; b < Class_Output_Pairs.length; b++) {
if (a == b) continue;
if (Class_Output_Pairs[a].Class == Class_Output_Pairs[b].Class) Convergence += Math.abs(Class_Output_Pairs[b].Output - Class_Output_Pairs[a].Output);
else Divergence += Math.abs(Class_Output_Pairs[b].Output - Class_Output_Pairs[a].Output);
}
// Return the divergence convergence ratio as the fitness
return Convergence / Divergence;
};
// Define medium mutation function
function Mutate (Medium) {
// First duplicate the medium
var Mutant = JSON.parse(JSON.stringify(Medium));
// There are 6 types of mutation, collect them all
var Available_Mutations = [];
// 1. Remove any genetic node, and possibly a genetic row if it is the last node in that row
for (var a = 1; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) Available_Mutations.push({Type: 'Remove Genetic Node', Layer: a, Node: b});
// 2. Add a genetic node to an existing genetic row
for (var a = 1; a < Medium.length - 1; a++) Available_Mutations.push({Type: 'Add Genetic Node', Layer: a});
// 3. Shuffle the process of any input or genetic node
for (var a = 1; a < Medium.length; a++) for (var b = 0; b < Medium[a].length; b++) Available_Mutations.push({Type: 'Shuffle Genetic Node Process', Layer: a, Node: b});
// 4. Add an additional output to any genetic or input node
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) for (var c = a + 1; c < Medium.length; c++) for (var d = 0; d < Medium[c].length; d++) Available_Mutations.push({Type: 'Add Input Or Genetic Node Output', Layer: a, Node: b, Output_Layer: c, Output_Node: d});
// 5. Remove an output from any genetic or input node, cannot remove the last output from a node
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) if (Medium[a][b].Outputs.length > 1) for (var c = 0; c < Medium[a][b].Outputs.length; c++) Available_Mutations.push({Type: 'Remove Input Or Genetic Node Output', Layer: a, Node: b, Remove_Output_Index: c});
// 6. Add a new genetic row with a new genetic node in it, has no preconditions
for (var a = 1; a < Medium.length; a++) Available_Mutations.push({Type: 'Add Genetic Row', Layer: a});
// Choose one of the available mutations
var Mutation = Available_Mutations[Math.floor(Math.random() * Available_Mutations.length)];
// Branch by mutation type
switch (Mutation.Type) {
case 'Remove Genetic Node':
// Ensure any other node pointing at this node gains the outputs of the removed genetic node in it's place
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) for (var c = 0; c < Medium[a][b].Outputs.length; c++) if (Medium[a][b].Outputs[c].Layer == Mutation.Layer && Medium[a][b].Outputs[c].Node == Mutation.Node) Medium[a][b].Outputs = Medium[a][b].slice(0, c).concat(Medium[Mutation.Layer][Mutation.Node].Outputs).concat(Medium[a][b].slice(c + 1));
// Ensure any other node pointing at the genetic layer of the mutation but at a later genetic node has it's node value decremented to match the removal of a node in front of it
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) for (var c = 0; c < Medium[a][b].Outputs.length; c++) if (Medium[a][b].Outputs[c].Layer == Mutation.Layer && Medium[a][b].Outputs[c].Node > Mutation.Node) Medium[a][b].Outputs[c].Node--;
// Now that this genetic node has no inputs, and we shifted any pointers that were affected, remove node
Medium[Mutation.Layer].splice(Mutation.Node, 1);
// If genetic row has been left empty, it must be removed
if (Medium[Mutation.Layer].length == 0) {
// Decrement any row pointers after the row to be removed to accomodate the removed row
for (var a = 0; a < Medium.length - 1; a++) for (var b = 0; b < Medium[a].length; b++) for (var c = 0; c < Medium[a][b].Outputs.length; c++) if (Medium[a][b].Outputs[c].Layer > Mutation.Layer) Medium[a][b].Outputs[c].Layer--;
// Remove empty genetic row
Medium.splice(Mutation.Layer, 1);
}
break;
case 'Add Genetic Node':
// Genetic nodes always get added at the end of the layer to avoid pointer breaking
Medium[Mutation.Layer].push({Inputs: [], Outputs: [{Layer: Mutation.Layer + 1, Node: 0}], Process: Math.floor(Math.random() * Processes.length)});
break;
case 'Shuffle Genetic Node Process':
// Shuffle the input or genetic nodes process
Medium[Mutation.Layer][Mutation.Node].Process = Math.floor(Math.random() * Processes.length);
break;
case 'Add Input Or Genetic Node Output':
// Add the input or genetic node output
Medium[Mutation.Layer][Mutation.Node].Outputs.push({Layer: Mutation.Output_Layer, Node: Mutation.Output_Node});
break;
case 'Remove Input Or Genetic Node Output':
// Remove the input or genetic node output by index
Medium[Mutation.Layer][Mutation.Node].Outputs.splice(Mutation.Remove_Output_Index, 1);
break;
case 'Add Genetic Row':
// Add a new genetic row with a new genetic node in it
Medium.splice(Mutation.Layer, 0, [{Inputs: [], Outputs: [{Layer: Mutation.Layer + 1, Node: 0}], Process: Math.floor(Math.random() * Processes.length)}]);
break;
default: throw 'Unknown Mutation Type: ' + JSON.stringify(Mutation);
}
// Return mutant
return Mutant;
};
// Create medium population with a single medium
var Medium_Population = [[]];
// Create input layer in starting medium population
Medium_Population[0].push([]);
for (var a = 0; a < Training_Set[0].Inputs.length; a++) Medium_Population[0][0].push({Inputs: [], Outputs: [{Layer: 1, Node: 0}], Process: 0});
// Create output layer in starting medium population
Medium_Population[0].push([{Inputs: []}]);
// Measure fitness of starting medium population
Medium_Population[0].Fitness = Fitness(Evaluate_Training_Cases(Medium_Population[0], Training_Set));
// Provide a nerf to the starting medium population
Medium_Population[0].Nerf = 1.0;
// Mutation loop
for (var a = 0; a < Train_Till_Iterations && Medium_Population[0].Fitness > Train_Till_Errors; a++) {
// Mutate the queen and add it to the population
Medium_Population.push(Mutate(Medium_Population[0]));
// Measure fitness of mutant
Medium_Population[Medium_Population.length - 1].Fitness = Fitness(Evaluate_Training_Cases(Medium_Population[Medium_Population.length - 1], Training_Set));
// Provide a nerf to mutant
Medium_Population[Medium_Population.length - 1].Nerf = 1.0;
// If queen failed to produce a more fit mutant, nerf queen
if (Medium_Population[Medium_Population.length - 1].Fitness >= Medium_Population[0].Fitness) Medium_Population[0].Nerf *= Nerf_Rate;
// Sort medium population by fitness * nerf
Medium_Population.sort(function (Left_Medium, Right_Medium) { return (Left_Medium.Fitness * Left_Medium.Nerf) - (Right_Medium.Fitness * Right_Medium.Nerf); });
// Cull any overpopulation
while (Medium_Population.length > Max_Medium_Population) Medium_Population.pop();
}
}
};
return Ref;
};
var p = Plinko();
console.log(p);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment