Why doesn't this neural network give (only) the full 3-layer path?

This function is a rendering of the neural network shown on the right side of this image based on a fairly simple data structure.

Attempt at evolution:

enter image description here

After evolution:

enter image description here

Each point represents a neuron, each line represents a connection. There's a problem with which neurons and connections arise and which ones do not, and I have been struggling with this problem for 5 hours straight, without coffee breaks. One of you will probably point out one tiny stupid mistake that caused the problem, and I will most likely keep pulling out my hair.

Simply put: connections are made from top to bottom. I only want to make full paths from top to bottom. No points (neurons) in the middle should be displayed if no connections lead to them from above. If a neuron in the upper layer connects to a neuron in the middle layer, but this neuron does not interact with the neural layer of the lower layer, that connection does nothing, so it should not be rendered.

As you can see, there are top-level neurons that are rendered with no connections at all, and mid-level neurons that are rendered without a vertex connection. All connections are from top to bottom, no connection flows upward. In other words, the network is forwarding.

The data structure passed to this function is brain

, which is the same brain

one passed to the following constructors Neuron

and Connection

, which are listed in abbreviated form, only displaying properties related to the function in question (edited from the original with further attempts to solve the problem:

Neuron

function Neuron(brain, layer) {
    var that = this;
    brain.counter++;
    brain.globalReferenceNeurons[brain.counter] = this;
    this.active = true; //as the brain mutates, some neurons and 
    //connections are disabled via this property
    this.layer = layer;
    this.id = brain.counter;
    this.connected = {};
    this.connections = {};
    this.connect = function(target) {
        if (that.active == true) {
            new Connection(brain, this, target, function(id, connection) {
                brain.globalReferenceConnections[id] = connection;
                that.connections[id] = connection;
            });
        }
    };
}

      

Compound

function Connection(brain, source, target, callback) {
    if (source.layer < target.layer) {
        brain.counter++;
        brain.globalReferenceConnections[brain.counter] = this;
        this.active = true; //as the brain mutates, some neurons and 
        //connections are disabled via this property
        this.id = brain.counter;
        this.source = source;
        this.target = target;
        target.connected[this.id] = this; //connected references 
        //incoming connections to a neuron
        callback(this.id, this);
    }
}

      

As you can see, it brain.globalReferenceNeurons

contains the data needed to render the neural network in the picture.

And here's the render function in question (updated again):

function renderBrain(brain, context, canvas) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  var width = canvas.width;
  var height = canvas.height;
  var layers = brain.layers;
  var heightDivision = height / layers;
  var layerList = [];
  for (var i1 = 0; i1 < brain.layers; i1++) {
    layerList.push([]);
    for (var prop1 in brain.globalReferenceNeurons) {
      if (brain.globalReferenceNeurons[prop1].layer === i1) {
        layerList[i1].push(brain.globalReferenceNeurons[prop1]);
      }
    }
  }

  function renderLayer(layer, layerCount, layerTotal) {
    var length = layer.length;
    var widthDivision = width / length;
    var neuronCount = 0;
    for (var i1 = 0; i1 < layer.length; i1++) {
      neuronCount++;
      const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)

      function hasActivePathAhead(obj, count) {
        if (!count) {
          count = 0;
        }
        if (obj.active) {
          var targets = getActiveProps(obj.connections);
          if (obj.layer === 2) {
            return true;
          } else if (obj.connections[targets[count]]) {
            for (var i1 = 0; i1 < targets.length; i1++) {
              var result = hasActivePathAhead(obj.connections[targets[count]].target, 
                  count + 1);
              return result;
            }
            return false;
          } else {
            return false;
          }
        } else {
          return false;
        }
      }

      function hasActivePathBehind(obj, count) {
        if (!count) {
          count = 0;
        }
        if (obj.active) {
          var sources = getActiveProps(obj.connected);
          if (obj.layer === 0) {
            return true;
          } else if (obj.connected[sources[count]]) {
            for (var i1 = 0; i1 < sources.length; i1++) {
              var result = 
                  hasActivePathBehind(obj.connected[sources[count]].source, count + 1);
              return result;
            }
            return false;
          } else {
            return false;
          }
        } else {
          return false;
        }
      }
      if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
        context.beginPath();
        context.arc((widthDivision * neuronCount) 
            - (0.5 * widthDivision), 
            (heightDivision * layerCount) 
            - (heightDivision * 0.5), 
            5, 0, 2 * Math.PI, false);
        context.fillStyle = '#adf442';
        context.fill();
        context.lineWidth = 2;
        context.strokeStyle = '#56cc41';
        context.stroke();
        var connectionCount = 0;
        for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
          var connection = 
              layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
          if (hasActivePathAhead(connection.target) 
              && hasActivePathBehind(connection.target)) {
            var targetLayer = connection.target.layer;
            var index = layerList[targetLayer].findIndex(function(e) {
              return e == connection.target
            });
            if (index > -1) {
              var targetLayerLength = Object.keys(layerList[targetLayer]).length;
              var targetLayerWidthDivision = width / targetLayerLength;
              var p1 = {
                x: (widthDivision * neuronCount) - (0.5 * widthDivision),
                y: (heightDivision * layerCount) - (heightDivision * 0.5)
              };
              var p2 = {
                x: (index * targetLayerWidthDivision) 
                    + (0.5 * targetLayerWidthDivision),
                y: (targetLayer * heightDivision) 
                    + (heightDivision * 0.5)
              };
              connectionCount++;
              context.beginPath();
              context.moveTo(p1.x, p1.y);
              context.lineTo(p2.x, p2.y);
              context.lineWidth = 1;
              context.stroke();
            }
          }
        }
      }
    }
  }
  var layerCount = 0;
  for (i1 = 0; i1 < layerList.length; i1++) {
    layerCount++;
    renderLayer(layerList[i1], layerCount, layerList.length);
  }
}

      

"Working" example test: https://jsfiddle.net/au2Lt6na/4/

For 5 hours I fiddled with this function trying to figure out the problem and for the life of me I was unable to figure it out. Can anyone tell me what is causing the rendering of non-external neural pathways?


Note. I've spent many hours over the past few days trying to fix this, writing brand new ways to figure out which paths are completed from top to bottom, and it still suffers from the same problems as before. I am missing something.

+3


source to share


2 answers


Couldn't fit into a comment and since you don't have a working example we can only guess.

Your recursion doesn't suit me. The variable count doesn't make sense and you have multiple levels of redundancy checking active 3 times for each iteration and don't check when indexing into key arrays with count

.

No working code, and since your variable nomenclature is confusing, this is just a guess on how to fix it. The same applies to hasActivePathBehind

Strike>

Ignore the following code

 function hasActivePathAhead(obj) {
    if (obj.active) {
        if (obj.layer === 2) {
            return true;
        }
        var targets = getActiveProps(obj.connections);
        for (var i = 0; i < targets.length; i++) {
            if(hasActivePathAhead(obj.connections[targets[i]].target)){
                return true;
            }
        }
    }
    return false;
 }
      

Run code



UPDATE and work fix.



Update , because the accepted answer doesn't work , it should have used a stricter test.

Since you provided the script in the comments, I saw that you removed the counter from the functions. While the functions are still wrong and not working, the bug is elsewhere in the code. The code is too complex, hiding the nature of the error.

There is too much trouble in the details, so I just started from scratch.

Instead of checking each node for a backward and forward line, I traverse the layers forward from top to bottom. This negates the need to check the reverse connections. I have a function that checks if a node is connected to the bottom, a function that takes all nodes from a node to the bottom, and a function that draws all active nodes in the level (the active one is connected to this layer down)

You can optimize it by adding a flag to the nodes, indicating that they have already been rendered as code that can render some nodes multiple times. But I didn't add that as I didn't want to change the data structure that you had. Or you can add Map

that contains the node pairs that have been rendered and check to see if a node pair is needed.

Using fiddle as a template here is a working version using randomized paths as mentioned in the fiddle.

function Neuron(brain, layer) {
    var that = this;
    brain.counter++;
    brain.globalReferenceNeurons[brain.counter] = this;
    this.active = true; //as the brain mutates, some neurons and 
    //connections are disabled via this property
    this.layer = layer;
    this.id = brain.counter;
    this.connected = {};
    this.connections = {};
    this.connect = function (target) {
        if (that.active == true) {
            new Connection(brain, this, target, function (id, connection) {
                brain.globalReferenceConnections[id] = connection;
                that.connections[id] = connection;
            });
        }
    };
}

function Connection(brain, source, target, callback) {
    if (source.layer < target.layer) {
        brain.counter++;
        brain.globalReferenceConnections[brain.counter] = this;
        this.active = true; //as the brain mutates, some neurons and 
        //connections are disabled via this property
        this.id = brain.counter;
        this.source = source;
        this.target = target;
        target.connected[this.id] = this;
        callback(this.id, this);
    }
}


function renderBrain(brain, ctx, canvas) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    var width = canvas.width;
    var height = canvas.height;
    var layers = brain.layers;
    var heightDiv = height / layers;
    var layerList = [];
    for (var i1 = 0; i1 < brain.layers; i1++) {
        layerList.push([]);
        for (var prop1 in brain.globalReferenceNeurons) {
            if (brain.globalReferenceNeurons[prop1].layer === i1) {
                layerList[i1].push(brain.globalReferenceNeurons[prop1]);
            }
        }
    }
    var coord;  // to hold node coordinates defined here to prevent pointless memory allocation dealocation cycle
    // Gets the node position based on its ID and layer position
    function nodePosition(node,coord = {}){
        var pos;
        pos = node.id - layerList[node.layer][0].id; // get pos from node id (if this does not hold true you should include the node position in the node data is it is important)
        coord.x = (width / layerList[node.layer].length) * (pos + 0.5);
        coord.y = heightDiv * (node.layer + 0.5);
        return coord;
    }        
    // draws a node
    function drawNode(node){
        ctx.strokeStyle = '#56cc41';
        ctx.fillStyle = '#adf442';
        ctx.lineWidth = 2;
        coord = nodePosition(node,coord);
        ctx.beginPath();
        ctx.arc(coord.x,coord.y, 5, 0, 2 * Math.PI);
        ctx.fill();
        ctx.stroke();
    }
    // draws a link between two nodes
    function drawLink(node,node1){
        ctx.strokeStyle = '#56cc41';
        ctx.lineWidth = 1;
        coord = nodePosition(node,coord);
        ctx.beginPath();
        ctx.moveTo(coord.x,coord.y);
        coord = nodePosition(node1,coord);
        ctx.lineTo(coord.x,coord.y);
        ctx.stroke();
    }
    // returns true if the path from this node jas a connection that leads to the end
    function isPathActive(node){
        var paths, i, nextNode;
        if(node.active){
            if(node.layer === 2){ // is node at end
                return true;
            }
            paths = Object.keys(node.connections).map(key => node.connections[key]);
            for(i = 0; i < paths.length; i ++){
                nextNode = paths[i].target;
                if(nextNode.active){
                    if(nextNode.layer === 2){
                        return true;
                    }
                    if(isPathActive(nextNode)){
                        return true;
                    }
                }
            }
        }
        return false;
    }
    // renders from a node all active pathes to end
    function renderPath(node){
        var i;
        paths = Object.keys(node.connections).map(key => node.connections[key]);
        for(i = 0; i < paths.length; i ++){
            nextNode = paths[i].target;
            if(isPathActive(nextNode)){
                drawLink(node,nextNode)
                renderPath(nextNode);
            }
        }
        drawNode(node,i+ 1)
    }
    // renders from top layer all active paths
    function renderActivePaths(layer){
        var i;
        for(i = 0; i < layer.length; i ++){
            if(isPathActive(layer[i])){
                renderPath(layer[i])
            }
        }
    }
    renderActivePaths(layerList[0]);
}





var brain = {
    counter: 0,
    layers: 3,
    globalReferenceNeurons: {},
    globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),
    new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0), 
    new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0), 
    new Neuron(brain, 0)]; //10

var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1), 
    new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1), 
    new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
    new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
    new Neuron(brain, 1)]; //13

var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2)]; //2

layer0[0].connect(layer1[0]);
layer0[1].connect(layer1[1]);
layer0[2].connect(layer1[2]);
layer0[3].connect(layer1[3]);
layer0[4].connect(layer1[4]);
layer0[5].connect(layer1[5]);
layer0[6].connect(layer1[6]);
layer0[7].connect(layer1[7]);
layer0[8].connect(layer1[8]);
layer0[9].connect(layer1[9]);
layer0[0].connect(layer1[3]);
layer0[1].connect(layer1[4]);
layer0[2].connect(layer1[5]);
layer0[3].connect(layer1[6]);
layer0[4].connect(layer1[7]);
layer0[5].connect(layer1[8]);
layer0[6].connect(layer1[9]);
layer0[7].connect(layer1[10]);
layer0[8].connect(layer1[11]);
layer0[9].connect(layer1[12]);

layer1[0].connect(layer2[0]);
layer1[1].connect(layer2[1]);
layer1[2].connect(layer2[0]);
layer1[3].connect(layer2[1]);
layer1[4].connect(layer2[0]);
layer1[5].connect(layer2[1]);
layer1[6].connect(layer2[0]);
layer1[7].connect(layer2[1]);
layer1[8].connect(layer2[0]);
layer1[9].connect(layer2[1]);
layer1[10].connect(layer2[0]);
layer1[11].connect(layer2[1]);
layer1[12].connect(layer2[0]);

//works! until...

function getRandomInt(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

for (prop in brain.globalReferenceNeurons) {
	var rand = getRandomInt(1,6);
  var neuron = brain.globalReferenceNeurons[prop];
  if (rand == 1 && neuron.layer != 2) neuron.active = false;
}
for (prop in brain.globalReferenceConnections) {
	var rand = getRandomInt(1,6);
  var connection = brain.globalReferenceConnections[prop];
  if (rand == 1) connection.active = false;
}



renderBrain(brain, canvas.getContext("2d"), canvas);
      

<canvas id="canvas" width= 512 height = 200></canvas>
      

Run code


+1


source


for (var i1 = 0; i1 < targets.length; i1++) {
    var result = hasActivePathAhead(obj.connections[targets[count]].target, 
              count + 1);
    return result;
}

      

This piece is strange. You might need this:

for (var i1 = 0; i1 < targets.length; i1++) {
    var result = hasActivePathAhead(obj.connections[targets[count]].target, 
              count + 1);
    if(result){
        return true;
    }        
}

      

And the use is count

strange. I think count

it shouldn't be passed as a parameter as it was used to index the target or source.

I think the snippet should be like this:



else if (targets.length) {
    var target;
    for (var i1 = 0; i1 < targets.length; i1++) {
        target=targets[i1];
        var result = hasActivePathAhead(obj.connections[target].target);
        if(result){
             return true;
        }
    }
    return false;
}

      

Live demo here:

function Neuron(brain, layer) {
    var that = this;
    brain.counter++;
    brain.globalReferenceNeurons[brain.counter] = this;
    this.active = true; //as the brain mutates, some neurons and 
    //connections are disabled via this property
    this.layer = layer;
    this.id = brain.counter;
    this.connected = {};
    this.connections = {};
    this.connect = function (target) {
        if (that.active == true) {
            new Connection(brain, this, target, function (id, connection) {
                brain.globalReferenceConnections[id] = connection;
                that.connections[id] = connection;
            });
        }
    };
}

function Connection(brain, source, target, callback) {
    if (source.layer < target.layer) {
        brain.counter++;
        brain.globalReferenceConnections[brain.counter] = this;
        this.active = true; //as the brain mutates, some neurons and 
        //connections are disabled via this property
        this.id = brain.counter;
        this.source = source;
        this.target = target;
        target.connected[this.id] = this;
        callback(this.id, this);
    }
}


function renderBrain(brain, context, canvas) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    var width = canvas.width;
    var height = canvas.height;
    var layers = brain.layers;
    var heightDivision = height / layers;
    var layerList = [];
    for (var i1 = 0; i1 < brain.layers; i1++) {
        layerList.push([]);
        for (var prop1 in brain.globalReferenceNeurons) {
            if (brain.globalReferenceNeurons[prop1].layer === i1) {
                layerList[i1].push(brain.globalReferenceNeurons[prop1]);
            }
        }
    }

    function renderLayer(layer, layerCount, layerTotal) {
        var length = layer.length;
        var widthDivision = width / length;
        var neuronCount = 0;
        for (var i1 = 0; i1 < layer.length; i1++) {
            neuronCount++;
            const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)

            function hasActivePathAhead(obj) {
                if (obj.active) {
                    var targets = getActiveProps(obj.connections);
                    if (obj.layer === 2) {
                        return true;
                    } else if (targets.length) {
                        var target;
                        for (var i1 = 0; i1 < targets.length; i1++) {
                            target = targets[i1];
                            var result = hasActivePathAhead(obj.connections[target].target);
                            if (result) {
                                return true;
                            }
                        }
                        return false;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }

            function hasActivePathBehind(obj) {
                if (obj.active) {
                    var sources = getActiveProps(obj.connected);
                    if (obj.layer === 0) {
                        return true;
                    } else if (sources.length) {
                        var source;
                        for (var i1 = 0; i1 < sources.length; i1++) {
                            source = sources[i1];
                            var result =
                                hasActivePathBehind(obj.connected[source].source);
                            return result;
                        }
                        return false;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
            if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
                context.beginPath();
                context.arc((widthDivision * neuronCount) -
                    (0.5 * widthDivision),
                    (heightDivision * layerCount) -
                    (heightDivision * 0.5),
                    5, 0, 2 * Math.PI, false);
                context.fillStyle = '#adf442';
                context.fill();
                context.lineWidth = 2;
                context.strokeStyle = '#56cc41';
                context.stroke();
                var connectionCount = 0;
                for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
                    var connection =
                        layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
                    if (hasActivePathAhead(connection.target) &&
                        hasActivePathBehind(connection.target)) {
                        var targetLayer = connection.target.layer;
                        var index = layerList[targetLayer].findIndex(function (e) {
                            return e == connection.target
                        });
                        if (index > -1) {
                            var targetLayerLength = Object.keys(layerList[targetLayer]).length;
                            var targetLayerWidthDivision = width / targetLayerLength;
                            var p1 = {
                                x: (widthDivision * neuronCount) - (0.5 * widthDivision),
                                y: (heightDivision * layerCount) - (heightDivision * 0.5)
                            };
                            var p2 = {
                                x: (index * targetLayerWidthDivision) +
                                    (0.5 * targetLayerWidthDivision),
                                y: (targetLayer * heightDivision) +
                                    (heightDivision * 0.5)
                            };
                            connectionCount++;
                            context.beginPath();
                            context.moveTo(p1.x, p1.y);
                            context.lineTo(p2.x, p2.y);
                            context.lineWidth = 1;
                            context.stroke();
                        }
                    }
                }
            }
        }
    }
    var layerCount = 0;
    for (i1 = 0; i1 < layerList.length; i1++) {
        layerCount++;
        renderLayer(layerList[i1], layerCount, layerList.length);
    }
}





var brain = {
    counter: 0,
    layers: 3,
    globalReferenceNeurons: {},
    globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0), new Neuron(brain, 0)];
var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1)];
var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2)];

layer0[0].connect(layer1[1]);
layer0[1].connect(layer1[0]);
layer0[3].connect(layer1[0]);
layer1[0].connect(layer2[0]);

layer1[2].connect(layer2[2]);
layer1[1].connect(layer2[3]);



var canvas = document.getElementById('cav');

var ctx = canvas.getContext('2d');
renderBrain(brain, ctx, canvas);
      

<canvas id="cav" width="600" height="400"></canvas>
      

Run code


+3


source







All Articles