Networks

Avik Jain edited this page Oct 27, 2016 · 6 revisions

Networks are basically an array of layers. They have an input layer, a number of hidden layers, and an output layer. Networks can project and gate connections, activate and propagate in the same fashion as Layers do. Networks can also be optimized, extended, exported to JSON, converted to Workers or standalone Functions, and cloned.

var inputLayer = new Layer(4);
var hiddenLayer = new Layer(6);
var outputLayer = new Layer(2);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

project

A network can project a connection to another, or gate a connection between two others networks in the same way Layers do. You have to provide the network that you want to connect to and the connectionType:

myNetwork.project(otherNetwork, Layer.connectionType.ALL_TO_ALL); 
/*  
    All the neurons in myNetwork's output layer now project a connection
    to all the neurons in otherNetwork's input layer.
*/

There are two connectionType's:

  • Layer.connectionType.ALL_TO_ALL: It connects every neuron from layer A, to every neuron in layer B.
  • Layer.connectionType.ONE_TO_ONE: It connects each neuron from layer A, to one neuron in layer B. Both layers must be the same size in order to work.
  • Layer.connectionType.ALL_TO_ELSE: Useful only in self-connections. It connects every neuron from a layer to all the other neurons in that same layer, except with itself. If this connectionType is used in a connection between different layers, it produces the same result as ALL_TO_ALL.

If not specified, the connection type is always Layer.connectionType.ALL_TO_ALL.

The method project returns a LayerConnection object, that can be gated by another network or layer.

gate

A Network can gate a connection between two other Networks or Layers, or a Layers's self-connection.

var connection = A.project(B);
C.gate(connection, Layer.gateType.INPUT_GATE); // now C's output layer gates the connection between A's output layer and B's input layer (input gate)

There are three gateType's:

  • Layer.gateType.INPUT_GATE: If network C is gating connections between network A and B, all the neurons from C's output layer gate all the input connections to B's input layer.

  • Layer.gateType.OUTPUT_GATE: If network C is gating connections between network A and B, all the neurons from C's output layer gate all the output connections from A's output layer.

  • Layer.gateType.ONE_TO_ONE: If network C is gating connections between network A and B, each neuron from C's output layer gates one connection from A's output layer to B's input layer. To use this kind of gateType, A's output layer, B's input layer and C's output layer must be the same size.

activate

When a network is activated, an input must be provided to activate the input layer, then all the hidden layers are activated in order, and finally the output layer is activated and its activation is returned.

var inputLayer = new Layer(4);
var hiddenLayer = new Layer(6);
var outputLayer = new Layer(2);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

myNetwork.activate([1,0,1,0]); // [0.5200553602396137, 0.4792707231811006]

propagate

You can provide a target value and a learning rate to a network and backpropagate the error from the output layer to all the hidden layers in reverse order until reaching the input layer. For example, this is how you train a network how to solve an XOR:

// create the network
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

// train the network
var learningRate = .3;
for (var i = 0; i < 20000; i++)
{
    // 0,0 => 0
    myNetwork.activate([0,0]);
    myNetwork.propagate(learningRate, [0]);

    // 0,1 => 1
    myNetwork.activate([0,1]);
    myNetwork.propagate(learningRate, [1]);

    // 1,0 => 1
    myNetwork.activate([1,0]);
    myNetwork.propagate(learningRate, [1]);

    // 1,1 => 0
    myNetwork.activate([1,1]);
    myNetwork.propagate(learningRate, [0]);
}


// test the network
myNetwork.activate([0,0]); // [0.015020775950893527]
myNetwork.activate([0,1]); // [0.9815816381088985]
myNetwork.activate([1,0]); // [0.9871822457132193]
myNetwork.activate([1,1]); // [0.012950087641929467]

optimize

Networks get optimized automatically on the fly after its first activation, if you print in the console the activate or propagate methods of your Network instance after activating it, it will look something like this:

function (input){
F[1] = input[0];
 F[2] = input[1];
 F[3] = input[2];
 F[4] = input[3];
 F[6] = F[7];
 F[7] = F[8];
 F[7] += F[1] * F[9];
 F[7] += F[2] * F[10];
 F[7] += F[3] * F[11];
 F[7] += F[4] * F[12];
 F[5] = (1 / (1 + Math.exp(-F[7])));
 F[13] = F[5] * (1 - F[5]);
 ...

This improves the performance of the network dramatically.

extend

You can see how to extend a network in the Examples section.

toJSON/fromJSON

Networks can be stored as JSON's and then restored back:

var exported = myNetwork.toJSON();
var imported = Network.fromJSON(exported);

imported will be a new instance of Network that is an exact clone of myNetwork

worker

The network can be converted into a WebWorker. This feature doesn't work in node.js, and it's not supported on every browser (it must support Blob).

// training set
var learningRate = .3;
var trainingSet = [
    {
        input: [0,0],
        output: [0]
    },
    {
        input: [0,1],
        output: [1]
    },
    {
        input: [1,0],
        output: [1]
    },
    {
        input: [1,1],
        output: [0]
    },
];

// create a network
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

// create a worker
var myWorker = myNetwork.worker();

// activate the network
function activateWorker(input)
{
    myWorker.postMessage({ 
        action: "activate",
        input: input,
        memoryBuffer: myNetwork.optimized.memory
    }, [myNetwork.optimized.memory.buffer]);
}

// backpropagate the network
function propagateWorker(target){
    myWorker.postMessage({ 
        action: "propagate",
        target: target,
        rate: learningRate,
        memoryBuffer: myNetwork.optimized.memory
    }, [myNetwork.optimized.memory.buffer]);
}

// train the worker
myWorker.onmessage = function(e){
    // give control of the memory back to the network - this is mandatory!
    myNetwork.optimized.ownership(e.data.memoryBuffer);

    if (e.data.action == "propagate")
    {
        if (index >= 4)
        {
            index = 0;
            iterations++;
            if (iterations % 100 == 0)
            {
                var output00 = myNetwork.activate([0,0]);
                var output01 = myNetwork.activate([0,1]);
                var output10 = myNetwork.activate([1,0]);
                var output11 = myNetwork.activate([1,1]);

                console.log("0,0 => ", output00);
                console.log("0,1 => ", output01);
                console.log("1,0 => ", output10);
                console.log("1,1 => ", output11, "\n");
            }
        }

        activateWorker(trainingSet[index].input);
    }

    if (e.data.action == "activate")
    {
        propagateWorker(trainingSet[index].output); 
        index++;
    }
}

// kick it
var index = 0;
var iterations = 0;
activateWorker(trainingSet[index].input);

standalone

The network can be exported to a single javascript Function. This can be useful when your network is already trained and you just need to use it, since the standalone functions is just one javascript function with an array and operations within, with no dependencies on Synaptic or any other library.

var inputLayer = new Layer(4);
var hiddenLayer = new Layer(6);
var outputLayer = new Layer(2);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

var standalone = myNetwork.standalone();

myNetwork.activate([1,0,1,0]);  // [0.5466397925108878, 0.5121246668637663]
standalone([1,0,1,0]);   // [0.5466397925108878, 0.5121246668637663]

clone

A network can be cloned to a completely new instance, with the same connections and traces.

var inputLayer = new Layer(4);
var hiddenLayer = new Layer(6);
var outputLayer = new Layer(2);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

var clone = myNetwork.clone();

myNetwork.activate([1,0,1,0]);  // [0.5466397925108878, 0.5121246668637663]
clone.activate([1,0,1,0]);   // [0.5466397925108878, 0.5121246668637663]

neurons

The method neurons() return an array with all the neurons in the network, in activation order.

set

The method set(layers) receives an object with layers in the same format as the constructor of Network and sets them as the layers of the Network, this is useful when you are extending the Network class to create your own architectures. See the examples section.

var inputLayer = new Layer(4);
var hiddenLayer = new Layer(6);
var outputLayer = new Layer(2);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network();

myNetwork.set({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

clear

The method clear() clears the networks contextual memory, while leaving the network's weights unmodified.

This can be useful in LSTM's when the network needs to be activated with a new sequence of data that should not use context from previous activations.