liquidcarrot / carrot

🥕 Evolutionary Neural Networks in JavaScript
https://liquidcarrot.io/carrot/
MIT License
295 stars 34 forks source link

Inputs and outputs are not being tracked correctly by Network #163

Open christianechevarria opened 5 years ago

christianechevarria commented 5 years ago

Description

As the code currently stands, Network relies on two sets input_nodes and output_nodes to keep track of inputs and outputs respectively and make decisions on how to operate on Networks.

It's imperative that we have a way to detect what neurons are actually inputs and neurons to properly mutate, merge, activate, and otherwise do anything with our architecture-free Networks. This is possibly a long-standing bug that can be contributing to https://github.com/liquidcarrot/carrot/issues/51

This bug was (re)discovered when @teh-mICON reported he was getting un-normalized output like @dan-ryan did before

This is the output from Network.toJSON showing the structure of the neurons and connections of @teh-mICON 's network:

{
  "nodes": [
  {
    "bias": 0.09503197925769324,
    "squash": "TANH",
    "mask": 1,
    "index": 0
  },
  {
    "bias": 0.13779783091838294,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 1
  },
  {
    "bias": 0.10046853249309917,
    "squash": "INVERSE",
    "mask": 1,
    "index": 2
  },
  {
    "bias": 0.24744855279574018,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 3
  },
  {
    "bias": -0.20052607167794267,
    "squash": "SINUSOID",
    "mask": 1,
    "index": 4
  },
  {
    "bias": -0.09105453041194123,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 5
  },
  {
    "bias": 0.07804478165427353,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 6
  },
  {
    "bias": 0.4098613875791224,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 7
  },
  {
    "bias": -0.10260134476556315,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 8
  },
  {
    "bias": -0.09105453041194123,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 9
  },
  {
    "bias": -0.9188545171845655,
    "squash": "SELU",
    "mask": 1,
    "index": 10
  },
  {
    "bias": -0.09105453041194123,
    "squash": "LOGISTIC",
    "mask": 1,
    "index": 11
  }],
  "connections": [
  {
    "weight": 0.20762993440469124,
    "from": 9,
    "to": 11,
    "gater": null
  },
  {
    "weight": -1.6017292711240334,
    "from": 7,
    "to": 11,
    "gater": null
  },
  {
    "weight": 2.1207859110855507,
    "from": 8,
    "to": 9,
    "gater": null
  },
  {
    "weight": 0.9397945993867647,
    "from": 6,
    "to": 11,
    "gater": null
  },
  {
    "weight": -1.6017292711240334,
    "from": 7,
    "to": 9,
    "gater": null
  },
  {
    "weight": -2.062494586539331,
    "from": 5,
    "to": 11,
    "gater": null
  },
  {
    "weight": -0.5624448323743709,
    "from": 6,
    "to": 9,
    "gater": null
  },
  {
    "weight": 0.9220719725967439,
    "from": 4,
    "to": 11,
    "gater": null
  },
  {
    "weight": -2.062494586539331,
    "from": 5,
    "to": 9,
    "gater": null
  },
  {
    "weight": -0.5758182765217281,
    "from": 3,
    "to": 11,
    "gater": null
  },
  {
    "weight": 0.6568345798712394,
    "from": 4,
    "to": 9,
    "gater": null
  },
  {
    "weight": 0.8679286348183202,
    "from": 2,
    "to": 11,
    "gater": null
  },
  {
    "weight": -0.5758182765217281,
    "from": 3,
    "to": 9,
    "gater": null
  },
  {
    "weight": -1.567223021276047,
    "from": 1,
    "to": 11,
    "gater": null
  },
  {
    "weight": 0.8679286348183202,
    "from": 2,
    "to": 9,
    "gater": null
  },
  {
    "weight": 1.2598001286174136,
    "from": 0,
    "to": 11,
    "gater": null
  },
  {
    "weight": -1.567223021276047,
    "from": 1,
    "to": 9,
    "gater": null
  },
  {
    "weight": 1.6194117802506345,
    "from": 0,
    "to": 9,
    "gater": null
  },
  {
    "weight": 0.23318443542656864,
    "from": 8,
    "to": 10,
    "gater": null
  },
  {
    "weight": -0.6619424143505284,
    "from": 10,
    "to": 11,
    "gater": null
  }],
  "input_nodes": [0, 1, 2, 3, 4, 5, 6, 7, 8],
  "output_nodes": [9],
  "input_size": 9,
  "output_size": 1,
  "input": 9,
  "output": 1
}

If you look closely the output_nodes shows that # 9 is an output node, but in one of the connections you can also see that # 9 is a "from" neuron to neuron # 11 which is then the true output

This situation may have come about because of a mutation that added a connection from 9 to 11, something that should not have happened by default which then in turn caused this bug, but even still we should be adaptive enough to update our output node indexes whenever we add connections to former output neurons

Below you'll find a visualization of the network

Screenshots

Screenshot 2019-09-27 at 10 25 51 PM

Files

test/units/architecture/network.1.test.js src/methods/mutation.js src/architecture/Network.js

To Reproduce

Build a network, evolve it with methods.mutation.FFW as allowed methods, keep checking the output until it falls outside of the expected range

Tasks

christianechevarria commented 5 years ago

Interesting idea: a "quick and dirty" way to pinpoint what is causing the bug could be to iteratively restrict the mutation methods to discover if some subset of mutation methods is the culprit

christianechevarria commented 5 years ago

Update:

Wrote a test that calls Network.mutate(methods.mutation.ADD_NODE) on a network with no hidden neurons and it reliably shows that output neurons are being converted into hidden

image

Exploring a fix now, will update with more discoveries

christianechevarria commented 5 years ago

Another thing that the tests show is that when neurons are added they're being added to the end of the neurons array which is not in and of itself a bad thing, but I know there are a couple spots where we assume the order of the neurons will be indicative of whether they are inputs, hidden, or outputs so this can also be causing issues.