169c35bb15
Signed-off-by: prescientmoon <git@moonythm.dev>
2848 lines
91 KiB
JavaScript
2848 lines
91 KiB
JavaScript
/*!
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016 Juan Cazala - juancazala.com
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE
|
|
*
|
|
*
|
|
*
|
|
* ********************************************************************************************
|
|
* SYNAPTIC (v1.0.8)
|
|
* ********************************************************************************************
|
|
*
|
|
* Synaptic is a javascript neural network library for node.js and the browser, its generalized
|
|
* algorithm is architecture-free, so you can build and train basically any type of first order
|
|
* or even second order neural network architectures.
|
|
*
|
|
* http://en.wikipedia.org/wiki/Recurrent_neural_network#Second_Order_Recurrent_Neural_Network
|
|
*
|
|
* The library includes a few built-in architectures like multilayer perceptrons, multilayer
|
|
* long-short term memory networks (LSTM) or liquid state machines, and a trainer capable of
|
|
* training any given network, and includes built-in training tasks/tests like solving an XOR,
|
|
* passing a Distracted Sequence Recall test or an Embeded Reber Grammar test.
|
|
*
|
|
* The algorithm implemented by this library has been taken from Derek D. Monner's paper:
|
|
*
|
|
*
|
|
* A generalized LSTM-like training algorithm for second-order recurrent neural networks
|
|
* http://www.overcomplete.net/papers/nn2012.pdf
|
|
*
|
|
* There are references to the equations in that paper commented through the source code.
|
|
*
|
|
*/
|
|
/******/ (function(modules) { // webpackBootstrap
|
|
/******/ // The module cache
|
|
/******/ var installedModules = {};
|
|
|
|
/******/ // The require function
|
|
/******/ function __webpack_require__(moduleId) {
|
|
|
|
/******/ // Check if module is in cache
|
|
/******/ if(installedModules[moduleId])
|
|
/******/ return installedModules[moduleId].exports;
|
|
|
|
/******/ // Create a new module (and put it into the cache)
|
|
/******/ var module = installedModules[moduleId] = {
|
|
/******/ exports: {},
|
|
/******/ id: moduleId,
|
|
/******/ loaded: false
|
|
/******/ };
|
|
|
|
/******/ // Execute the module function
|
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
|
|
/******/ // Flag the module as loaded
|
|
/******/ module.loaded = true;
|
|
|
|
/******/ // Return the exports of the module
|
|
/******/ return module.exports;
|
|
/******/ }
|
|
|
|
|
|
/******/ // expose the modules object (__webpack_modules__)
|
|
/******/ __webpack_require__.m = modules;
|
|
|
|
/******/ // expose the module cache
|
|
/******/ __webpack_require__.c = installedModules;
|
|
|
|
/******/ // __webpack_public_path__
|
|
/******/ __webpack_require__.p = "";
|
|
|
|
/******/ // Load entry module and return exports
|
|
/******/ return __webpack_require__(0);
|
|
/******/ })
|
|
/************************************************************************/
|
|
/******/ ([
|
|
/* 0 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;var Synaptic = {
|
|
Neuron: __webpack_require__(1),
|
|
Layer: __webpack_require__(3),
|
|
Network: __webpack_require__(4),
|
|
Trainer: __webpack_require__(5),
|
|
Architect: __webpack_require__(6)
|
|
};
|
|
|
|
// CommonJS & AMD
|
|
if (true)
|
|
{
|
|
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){ return Synaptic }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
|
}
|
|
|
|
// Node.js
|
|
if (typeof module !== 'undefined' && module.exports)
|
|
{
|
|
module.exports = Synaptic;
|
|
}
|
|
|
|
// Browser
|
|
if (typeof window == 'object')
|
|
{
|
|
(function(){
|
|
var oldSynaptic = window['synaptic'];
|
|
Synaptic.ninja = function(){
|
|
window['synaptic'] = oldSynaptic;
|
|
return Synaptic;
|
|
};
|
|
})();
|
|
|
|
window['synaptic'] = Synaptic;
|
|
}
|
|
|
|
|
|
/***/ },
|
|
/* 1 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
/* WEBPACK VAR INJECTION */(function(module) {// export
|
|
if (module) module.exports = Neuron;
|
|
|
|
/******************************************************************************************
|
|
NEURON
|
|
*******************************************************************************************/
|
|
|
|
function Neuron() {
|
|
this.ID = Neuron.uid();
|
|
this.label = null;
|
|
this.connections = {
|
|
inputs: {},
|
|
projected: {},
|
|
gated: {}
|
|
};
|
|
this.error = {
|
|
responsibility: 0,
|
|
projected: 0,
|
|
gated: 0
|
|
};
|
|
this.trace = {
|
|
elegibility: {},
|
|
extended: {},
|
|
influences: {}
|
|
};
|
|
this.state = 0;
|
|
this.old = 0;
|
|
this.activation = 0;
|
|
this.selfconnection = new Neuron.connection(this, this, 0); // weight = 0 -> not connected
|
|
this.squash = Neuron.squash.LOGISTIC;
|
|
this.neighboors = {};
|
|
this.bias = Math.random() * .2 - .1;
|
|
}
|
|
|
|
Neuron.prototype = {
|
|
|
|
// activate the neuron
|
|
activate: function(input) {
|
|
// activation from enviroment (for input neurons)
|
|
if (typeof input != 'undefined') {
|
|
this.activation = input;
|
|
this.derivative = 0;
|
|
this.bias = 0;
|
|
return this.activation;
|
|
}
|
|
|
|
// old state
|
|
this.old = this.state;
|
|
|
|
// eq. 15
|
|
this.state = this.selfconnection.gain * this.selfconnection.weight *
|
|
this.state + this.bias;
|
|
|
|
for (var i in this.connections.inputs) {
|
|
var input = this.connections.inputs[i];
|
|
this.state += input.from.activation * input.weight * input.gain;
|
|
}
|
|
|
|
// eq. 16
|
|
this.activation = this.squash(this.state);
|
|
|
|
// f'(s)
|
|
this.derivative = this.squash(this.state, true);
|
|
|
|
// update traces
|
|
var influences = [];
|
|
for (var id in this.trace.extended) {
|
|
// extended elegibility trace
|
|
var neuron = this.neighboors[id];
|
|
|
|
// if gated neuron's selfconnection is gated by this unit, the influence keeps track of the neuron's old state
|
|
var influence = neuron.selfconnection.gater == this ? neuron.old : 0;
|
|
|
|
// index runs over all the incoming connections to the gated neuron that are gated by this unit
|
|
for (var incoming in this.trace.influences[neuron.ID]) { // captures the effect that has an input connection to this unit, on a neuron that is gated by this unit
|
|
influence += this.trace.influences[neuron.ID][incoming].weight *
|
|
this.trace.influences[neuron.ID][incoming].from.activation;
|
|
}
|
|
influences[neuron.ID] = influence;
|
|
}
|
|
|
|
for (var i in this.connections.inputs) {
|
|
var input = this.connections.inputs[i];
|
|
|
|
// elegibility trace - Eq. 17
|
|
this.trace.elegibility[input.ID] = this.selfconnection.gain * this.selfconnection
|
|
.weight * this.trace.elegibility[input.ID] + input.gain * input.from
|
|
.activation;
|
|
|
|
for (var id in this.trace.extended) {
|
|
// extended elegibility trace
|
|
var xtrace = this.trace.extended[id];
|
|
var neuron = this.neighboors[id];
|
|
var influence = influences[neuron.ID];
|
|
|
|
// eq. 18
|
|
xtrace[input.ID] = neuron.selfconnection.gain * neuron.selfconnection
|
|
.weight * xtrace[input.ID] + this.derivative * this.trace.elegibility[
|
|
input.ID] * influence;
|
|
}
|
|
}
|
|
|
|
// update gated connection's gains
|
|
for (var connection in this.connections.gated) {
|
|
this.connections.gated[connection].gain = this.activation;
|
|
}
|
|
|
|
return this.activation;
|
|
},
|
|
|
|
// back-propagate the error
|
|
propagate: function(rate, target) {
|
|
// error accumulator
|
|
var error = 0;
|
|
|
|
// whether or not this neuron is in the output layer
|
|
var isOutput = typeof target != 'undefined';
|
|
|
|
// output neurons get their error from the enviroment
|
|
if (isOutput)
|
|
this.error.responsibility = this.error.projected = target - this.activation; // Eq. 10
|
|
|
|
else // the rest of the neuron compute their error responsibilities by backpropagation
|
|
{
|
|
// error responsibilities from all the connections projected from this neuron
|
|
for (var id in this.connections.projected) {
|
|
var connection = this.connections.projected[id];
|
|
var neuron = connection.to;
|
|
// Eq. 21
|
|
error += neuron.error.responsibility * connection.gain * connection.weight;
|
|
}
|
|
|
|
// projected error responsibility
|
|
this.error.projected = this.derivative * error;
|
|
|
|
error = 0;
|
|
// error responsibilities from all the connections gated by this neuron
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id]; // gated neuron
|
|
var influence = neuron.selfconnection.gater == this ? neuron.old : 0; // if gated neuron's selfconnection is gated by this neuron
|
|
|
|
// index runs over all the connections to the gated neuron that are gated by this neuron
|
|
for (var input in this.trace.influences[id]) { // captures the effect that the input connection of this neuron have, on a neuron which its input/s is/are gated by this neuron
|
|
influence += this.trace.influences[id][input].weight * this.trace.influences[
|
|
neuron.ID][input].from.activation;
|
|
}
|
|
// eq. 22
|
|
error += neuron.error.responsibility * influence;
|
|
}
|
|
|
|
// gated error responsibility
|
|
this.error.gated = this.derivative * error;
|
|
|
|
// error responsibility - Eq. 23
|
|
this.error.responsibility = this.error.projected + this.error.gated;
|
|
}
|
|
|
|
// learning rate
|
|
rate = rate || .1;
|
|
|
|
// adjust all the neuron's incoming connections
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
|
|
// Eq. 24
|
|
var gradient = this.error.projected * this.trace.elegibility[input.ID];
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id];
|
|
gradient += neuron.error.responsibility * this.trace.extended[
|
|
neuron.ID][input.ID];
|
|
}
|
|
input.weight += rate * gradient; // adjust weights - aka learn
|
|
}
|
|
|
|
// adjust bias
|
|
this.bias += rate * this.error.responsibility;
|
|
},
|
|
|
|
project: function(neuron, weight) {
|
|
// self-connection
|
|
if (neuron == this) {
|
|
this.selfconnection.weight = 1;
|
|
return this.selfconnection;
|
|
}
|
|
|
|
// check if connection already exists
|
|
var connected = this.connected(neuron);
|
|
if (connected && connected.type == "projected") {
|
|
// update connection
|
|
if (typeof weight != 'undefined')
|
|
connected.connection.weight = weight;
|
|
// return existing connection
|
|
return connected.connection;
|
|
} else {
|
|
// create a new connection
|
|
var connection = new Neuron.connection(this, neuron, weight);
|
|
}
|
|
|
|
// reference all the connections and traces
|
|
this.connections.projected[connection.ID] = connection;
|
|
this.neighboors[neuron.ID] = neuron;
|
|
neuron.connections.inputs[connection.ID] = connection;
|
|
neuron.trace.elegibility[connection.ID] = 0;
|
|
|
|
for (var id in neuron.trace.extended) {
|
|
var trace = neuron.trace.extended[id];
|
|
trace[connection.ID] = 0;
|
|
}
|
|
|
|
return connection;
|
|
},
|
|
|
|
gate: function(connection) {
|
|
// add connection to gated list
|
|
this.connections.gated[connection.ID] = connection;
|
|
|
|
var neuron = connection.to;
|
|
if (!(neuron.ID in this.trace.extended)) {
|
|
// extended trace
|
|
this.neighboors[neuron.ID] = neuron;
|
|
var xtrace = this.trace.extended[neuron.ID] = {};
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
xtrace[input.ID] = 0;
|
|
}
|
|
}
|
|
|
|
// keep track
|
|
if (neuron.ID in this.trace.influences)
|
|
this.trace.influences[neuron.ID].push(connection);
|
|
else
|
|
this.trace.influences[neuron.ID] = [connection];
|
|
|
|
// set gater
|
|
connection.gater = this;
|
|
},
|
|
|
|
// returns true or false whether the neuron is self-connected or not
|
|
selfconnected: function() {
|
|
return this.selfconnection.weight !== 0;
|
|
},
|
|
|
|
// returns true or false whether the neuron is connected to another neuron (parameter)
|
|
connected: function(neuron) {
|
|
var result = {
|
|
type: null,
|
|
connection: false
|
|
};
|
|
|
|
if (this == neuron) {
|
|
if (this.selfconnected()) {
|
|
result.type = 'selfconnection';
|
|
result.connection = this.selfconnection;
|
|
return result;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
for (var type in this.connections) {
|
|
for (var connection in this.connections[type]) {
|
|
var connection = this.connections[type][connection];
|
|
if (connection.to == neuron) {
|
|
result.type = type;
|
|
result.connection = connection;
|
|
return result;
|
|
} else if (connection.from == neuron) {
|
|
result.type = type;
|
|
result.connection = connection;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
// clears all the traces (the neuron forgets it's context, but the connections remain intact)
|
|
clear: function() {
|
|
|
|
for (var trace in this.trace.elegibility)
|
|
this.trace.elegibility[trace] = 0;
|
|
|
|
for (var trace in this.trace.extended)
|
|
for (var extended in this.trace.extended[trace])
|
|
this.trace.extended[trace][extended] = 0;
|
|
|
|
this.error.responsibility = this.error.projected = this.error.gated = 0;
|
|
},
|
|
|
|
// all the connections are randomized and the traces are cleared
|
|
reset: function() {
|
|
this.clear();
|
|
|
|
for (var type in this.connections)
|
|
for (var connection in this.connections[type])
|
|
this.connections[type][connection].weight = Math.random() * .2 - .1;
|
|
this.bias = Math.random() * .2 - .1;
|
|
|
|
this.old = this.state = this.activation = 0;
|
|
},
|
|
|
|
// hardcodes the behaviour of the neuron into an optimized function
|
|
optimize: function(optimized, layer) {
|
|
|
|
optimized = optimized || {};
|
|
var store_activation = [];
|
|
var store_trace = [];
|
|
var store_propagation = [];
|
|
var varID = optimized.memory || 0;
|
|
var neurons = optimized.neurons || 1;
|
|
var inputs = optimized.inputs || [];
|
|
var targets = optimized.targets || [];
|
|
var outputs = optimized.outputs || [];
|
|
var variables = optimized.variables || {};
|
|
var activation_sentences = optimized.activation_sentences || [];
|
|
var trace_sentences = optimized.trace_sentences || [];
|
|
var propagation_sentences = optimized.propagation_sentences || [];
|
|
var layers = optimized.layers || { __count: 0, __neuron: 0 };
|
|
|
|
// allocate sentences
|
|
var allocate = function(store){
|
|
var allocated = layer in layers && store[layers.__count];
|
|
if (!allocated)
|
|
{
|
|
layers.__count = store.push([]) - 1;
|
|
layers[layer] = layers.__count;
|
|
}
|
|
};
|
|
allocate(activation_sentences);
|
|
allocate(trace_sentences);
|
|
allocate(propagation_sentences);
|
|
var currentLayer = layers.__count;
|
|
|
|
// get/reserve space in memory by creating a unique ID for a variablel
|
|
var getVar = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
if (args.length == 1) {
|
|
if (args[0] == 'target') {
|
|
var id = 'target_' + targets.length;
|
|
targets.push(varID);
|
|
} else
|
|
var id = args[0];
|
|
if (id in variables)
|
|
return variables[id];
|
|
return variables[id] = {
|
|
value: 0,
|
|
id: varID++
|
|
};
|
|
} else {
|
|
var extended = args.length > 2;
|
|
if (extended)
|
|
var value = args.pop();
|
|
|
|
var unit = args.shift();
|
|
var prop = args.pop();
|
|
|
|
if (!extended)
|
|
var value = unit[prop];
|
|
|
|
var id = prop + '_';
|
|
for (var property in args)
|
|
id += args[property] + '_';
|
|
id += unit.ID;
|
|
if (id in variables)
|
|
return variables[id];
|
|
|
|
return variables[id] = {
|
|
value: value,
|
|
id: varID++
|
|
};
|
|
}
|
|
};
|
|
|
|
// build sentence
|
|
var buildSentence = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var store = args.pop();
|
|
var sentence = "";
|
|
for (var i in args)
|
|
if (typeof args[i] == 'string')
|
|
sentence += args[i];
|
|
else
|
|
sentence += 'F[' + args[i].id + ']';
|
|
|
|
store.push(sentence + ';');
|
|
};
|
|
|
|
// helper to check if an object is empty
|
|
var isEmpty = function(obj) {
|
|
for (var prop in obj) {
|
|
if (obj.hasOwnProperty(prop))
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// characteristics of the neuron
|
|
var noProjections = isEmpty(this.connections.projected);
|
|
var noGates = isEmpty(this.connections.gated);
|
|
var isInput = layer == 'input' ? true : isEmpty(this.connections.inputs);
|
|
var isOutput = layer == 'output' ? true : noProjections && noGates;
|
|
|
|
// optimize neuron's behaviour
|
|
var rate = getVar('rate');
|
|
var activation = getVar(this, 'activation');
|
|
if (isInput)
|
|
inputs.push(activation.id);
|
|
else {
|
|
activation_sentences[currentLayer].push(store_activation);
|
|
trace_sentences[currentLayer].push(store_trace);
|
|
propagation_sentences[currentLayer].push(store_propagation);
|
|
var old = getVar(this, 'old');
|
|
var state = getVar(this, 'state');
|
|
var bias = getVar(this, 'bias');
|
|
if (this.selfconnection.gater)
|
|
var self_gain = getVar(this.selfconnection, 'gain');
|
|
if (this.selfconnected())
|
|
var self_weight = getVar(this.selfconnection, 'weight');
|
|
buildSentence(old, ' = ', state, store_activation);
|
|
if (this.selfconnected())
|
|
if (this.selfconnection.gater)
|
|
buildSentence(state, ' = ', self_gain, ' * ', self_weight, ' * ',
|
|
state, ' + ', bias, store_activation);
|
|
else
|
|
buildSentence(state, ' = ', self_weight, ' * ', state, ' + ',
|
|
bias, store_activation);
|
|
else
|
|
buildSentence(state, ' = ', bias, store_activation);
|
|
for (var i in this.connections.inputs) {
|
|
var input = this.connections.inputs[i];
|
|
var input_activation = getVar(input.from, 'activation');
|
|
var input_weight = getVar(input, 'weight');
|
|
if (input.gater)
|
|
var input_gain = getVar(input, 'gain');
|
|
if (this.connections.inputs[i].gater)
|
|
buildSentence(state, ' += ', input_activation, ' * ',
|
|
input_weight, ' * ', input_gain, store_activation);
|
|
else
|
|
buildSentence(state, ' += ', input_activation, ' * ',
|
|
input_weight, store_activation);
|
|
}
|
|
var derivative = getVar(this, 'derivative');
|
|
switch (this.squash) {
|
|
case Neuron.squash.LOGISTIC:
|
|
buildSentence(activation, ' = (1 / (1 + Math.exp(-', state, ')))',
|
|
store_activation);
|
|
buildSentence(derivative, ' = ', activation, ' * (1 - ',
|
|
activation, ')', store_activation);
|
|
break;
|
|
case Neuron.squash.TANH:
|
|
var eP = getVar('aux');
|
|
var eN = getVar('aux_2');
|
|
buildSentence(eP, ' = Math.exp(', state, ')', store_activation);
|
|
buildSentence(eN, ' = 1 / ', eP, store_activation);
|
|
buildSentence(activation, ' = (', eP, ' - ', eN, ') / (', eP, ' + ', eN, ')', store_activation);
|
|
buildSentence(derivative, ' = 1 - (', activation, ' * ', activation, ')', store_activation);
|
|
break;
|
|
case Neuron.squash.IDENTITY:
|
|
buildSentence(activation, ' = ', state, store_activation);
|
|
buildSentence(derivative, ' = 1', store_activation);
|
|
break;
|
|
case Neuron.squash.HLIM:
|
|
buildSentence(activation, ' = +(', state, ' > 0)', store_activation);
|
|
buildSentence(derivative, ' = 1', store_activation);
|
|
case Neuron.squash.RELU:
|
|
buildSentence(activation, ' = ', state, ' > 0 ? ', state, ' : 0', store_activation);
|
|
buildSentence(derivative, ' = ', state, ' > 0 ? 1 : 0', store_activation);
|
|
break;
|
|
}
|
|
|
|
for (var id in this.trace.extended) {
|
|
// calculate extended elegibility traces in advance
|
|
|
|
var neuron = this.neighboors[id];
|
|
var influence = getVar('influences[' + neuron.ID + ']');
|
|
var neuron_old = getVar(neuron, 'old');
|
|
var initialized = false;
|
|
if (neuron.selfconnection.gater == this)
|
|
{
|
|
buildSentence(influence, ' = ', neuron_old, store_trace);
|
|
initialized = true;
|
|
}
|
|
for (var incoming in this.trace.influences[neuron.ID]) {
|
|
var incoming_weight = getVar(this.trace.influences[neuron.ID]
|
|
[incoming], 'weight');
|
|
var incoming_activation = getVar(this.trace.influences[neuron.ID]
|
|
[incoming].from, 'activation');
|
|
|
|
if (initialized)
|
|
buildSentence(influence, ' += ', incoming_weight, ' * ', incoming_activation, store_trace);
|
|
else {
|
|
buildSentence(influence, ' = ', incoming_weight, ' * ', incoming_activation, store_trace);
|
|
initialized = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var i in this.connections.inputs) {
|
|
var input = this.connections.inputs[i];
|
|
if (input.gater)
|
|
var input_gain = getVar(input, 'gain');
|
|
var input_activation = getVar(input.from, 'activation');
|
|
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
|
.elegibility[input.ID]);
|
|
if (this.selfconnected()) {
|
|
if (this.selfconnection.gater) {
|
|
if (input.gater)
|
|
buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
|
|
' * ', trace, ' + ', input_gain, ' * ', input_activation,
|
|
store_trace);
|
|
else
|
|
buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
|
|
' * ', trace, ' + ', input_activation, store_trace);
|
|
} else {
|
|
if (input.gater)
|
|
buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
|
|
input_gain, ' * ', input_activation, store_trace);
|
|
else
|
|
buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
|
|
input_activation, store_trace);
|
|
}
|
|
} else {
|
|
if (input.gater)
|
|
buildSentence(trace, ' = ', input_gain, ' * ', input_activation,
|
|
store_trace);
|
|
else
|
|
buildSentence(trace, ' = ', input_activation, store_trace);
|
|
}
|
|
for (var id in this.trace.extended) {
|
|
// extended elegibility trace
|
|
var neuron = this.neighboors[id];
|
|
var influence = getVar('influences[' + neuron.ID + ']');
|
|
|
|
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
|
.elegibility[input.ID]);
|
|
var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID,
|
|
this.trace.extended[neuron.ID][input.ID]);
|
|
if (neuron.selfconnected())
|
|
var neuron_self_weight = getVar(neuron.selfconnection, 'weight');
|
|
if (neuron.selfconnection.gater)
|
|
var neuron_self_gain = getVar(neuron.selfconnection, 'gain');
|
|
if (neuron.selfconnected())
|
|
if (neuron.selfconnection.gater)
|
|
buildSentence(xtrace, ' = ', neuron_self_gain, ' * ',
|
|
neuron_self_weight, ' * ', xtrace, ' + ', derivative, ' * ',
|
|
trace, ' * ', influence, store_trace);
|
|
else
|
|
buildSentence(xtrace, ' = ', neuron_self_weight, ' * ',
|
|
xtrace, ' + ', derivative, ' * ', trace, ' * ',
|
|
influence, store_trace);
|
|
else
|
|
buildSentence(xtrace, ' = ', derivative, ' * ', trace, ' * ',
|
|
influence, store_trace);
|
|
}
|
|
}
|
|
for (var connection in this.connections.gated) {
|
|
var gated_gain = getVar(this.connections.gated[connection], 'gain');
|
|
buildSentence(gated_gain, ' = ', activation, store_activation);
|
|
}
|
|
}
|
|
if (!isInput) {
|
|
var responsibility = getVar(this, 'error', 'responsibility', this.error
|
|
.responsibility);
|
|
if (isOutput) {
|
|
var target = getVar('target');
|
|
buildSentence(responsibility, ' = ', target, ' - ', activation,
|
|
store_propagation);
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
|
.elegibility[input.ID]);
|
|
var input_weight = getVar(input, 'weight');
|
|
buildSentence(input_weight, ' += ', rate, ' * (', responsibility,
|
|
' * ', trace, ')', store_propagation);
|
|
}
|
|
outputs.push(activation.id);
|
|
} else {
|
|
if (!noProjections && !noGates) {
|
|
var error = getVar('aux');
|
|
for (var id in this.connections.projected) {
|
|
var connection = this.connections.projected[id];
|
|
var neuron = connection.to;
|
|
var connection_weight = getVar(connection, 'weight');
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
if (connection.gater) {
|
|
var connection_gain = getVar(connection, 'gain');
|
|
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
|
connection_gain, ' * ', connection_weight,
|
|
store_propagation);
|
|
} else
|
|
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
|
connection_weight, store_propagation);
|
|
}
|
|
var projected = getVar(this, 'error', 'projected', this.error.projected);
|
|
buildSentence(projected, ' = ', derivative, ' * ', error,
|
|
store_propagation);
|
|
buildSentence(error, ' = 0', store_propagation);
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id];
|
|
var influence = getVar('aux_2');
|
|
var neuron_old = getVar(neuron, 'old');
|
|
if (neuron.selfconnection.gater == this)
|
|
buildSentence(influence, ' = ', neuron_old, store_propagation);
|
|
else
|
|
buildSentence(influence, ' = 0', store_propagation);
|
|
for (var input in this.trace.influences[neuron.ID]) {
|
|
var connection = this.trace.influences[neuron.ID][input];
|
|
var connection_weight = getVar(connection, 'weight');
|
|
var neuron_activation = getVar(connection.from, 'activation');
|
|
buildSentence(influence, ' += ', connection_weight, ' * ',
|
|
neuron_activation, store_propagation);
|
|
}
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
|
influence, store_propagation);
|
|
}
|
|
var gated = getVar(this, 'error', 'gated', this.error.gated);
|
|
buildSentence(gated, ' = ', derivative, ' * ', error,
|
|
store_propagation);
|
|
buildSentence(responsibility, ' = ', projected, ' + ', gated,
|
|
store_propagation);
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
var gradient = getVar('aux');
|
|
var trace = getVar(this, 'trace', 'elegibility', input.ID, this
|
|
.trace.elegibility[input.ID]);
|
|
buildSentence(gradient, ' = ', projected, ' * ', trace,
|
|
store_propagation);
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id];
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
|
|
input.ID, this.trace.extended[neuron.ID][input.ID]);
|
|
buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
|
|
xtrace, store_propagation);
|
|
}
|
|
var input_weight = getVar(input, 'weight');
|
|
buildSentence(input_weight, ' += ', rate, ' * ', gradient,
|
|
store_propagation);
|
|
}
|
|
|
|
} else if (noGates) {
|
|
buildSentence(responsibility, ' = 0', store_propagation);
|
|
for (var id in this.connections.projected) {
|
|
var connection = this.connections.projected[id];
|
|
var neuron = connection.to;
|
|
var connection_weight = getVar(connection, 'weight');
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
if (connection.gater) {
|
|
var connection_gain = getVar(connection, 'gain');
|
|
buildSentence(responsibility, ' += ', neuron_responsibility,
|
|
' * ', connection_gain, ' * ', connection_weight,
|
|
store_propagation);
|
|
} else
|
|
buildSentence(responsibility, ' += ', neuron_responsibility,
|
|
' * ', connection_weight, store_propagation);
|
|
}
|
|
buildSentence(responsibility, ' *= ', derivative,
|
|
store_propagation);
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
var trace = getVar(this, 'trace', 'elegibility', input.ID, this
|
|
.trace.elegibility[input.ID]);
|
|
var input_weight = getVar(input, 'weight');
|
|
buildSentence(input_weight, ' += ', rate, ' * (',
|
|
responsibility, ' * ', trace, ')', store_propagation);
|
|
}
|
|
} else if (noProjections) {
|
|
buildSentence(responsibility, ' = 0', store_propagation);
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id];
|
|
var influence = getVar('aux');
|
|
var neuron_old = getVar(neuron, 'old');
|
|
if (neuron.selfconnection.gater == this)
|
|
buildSentence(influence, ' = ', neuron_old, store_propagation);
|
|
else
|
|
buildSentence(influence, ' = 0', store_propagation);
|
|
for (var input in this.trace.influences[neuron.ID]) {
|
|
var connection = this.trace.influences[neuron.ID][input];
|
|
var connection_weight = getVar(connection, 'weight');
|
|
var neuron_activation = getVar(connection.from, 'activation');
|
|
buildSentence(influence, ' += ', connection_weight, ' * ',
|
|
neuron_activation, store_propagation);
|
|
}
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
buildSentence(responsibility, ' += ', neuron_responsibility,
|
|
' * ', influence, store_propagation);
|
|
}
|
|
buildSentence(responsibility, ' *= ', derivative,
|
|
store_propagation);
|
|
for (var id in this.connections.inputs) {
|
|
var input = this.connections.inputs[id];
|
|
var gradient = getVar('aux');
|
|
buildSentence(gradient, ' = 0', store_propagation);
|
|
for (var id in this.trace.extended) {
|
|
var neuron = this.neighboors[id];
|
|
var neuron_responsibility = getVar(neuron, 'error',
|
|
'responsibility', neuron.error.responsibility);
|
|
var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
|
|
input.ID, this.trace.extended[neuron.ID][input.ID]);
|
|
buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
|
|
xtrace, store_propagation);
|
|
}
|
|
var input_weight = getVar(input, 'weight');
|
|
buildSentence(input_weight, ' += ', rate, ' * ', gradient,
|
|
store_propagation);
|
|
}
|
|
}
|
|
}
|
|
buildSentence(bias, ' += ', rate, ' * ', responsibility,
|
|
store_propagation);
|
|
}
|
|
return {
|
|
memory: varID,
|
|
neurons: neurons + 1,
|
|
inputs: inputs,
|
|
outputs: outputs,
|
|
targets: targets,
|
|
variables: variables,
|
|
activation_sentences: activation_sentences,
|
|
trace_sentences: trace_sentences,
|
|
propagation_sentences: propagation_sentences,
|
|
layers: layers
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// represents a connection between two neurons
|
|
Neuron.connection = function Connection(from, to, weight) {
|
|
|
|
if (!from || !to)
|
|
throw new Error("Connection Error: Invalid neurons");
|
|
|
|
this.ID = Neuron.connection.uid();
|
|
this.from = from;
|
|
this.to = to;
|
|
this.weight = typeof weight == 'undefined' ? Math.random() * .2 - .1 :
|
|
weight;
|
|
this.gain = 1;
|
|
this.gater = null;
|
|
}
|
|
|
|
|
|
// squashing functions
|
|
Neuron.squash = {};
|
|
|
|
// eq. 5 & 5'
|
|
Neuron.squash.LOGISTIC = function(x, derivate) {
|
|
if (!derivate)
|
|
return 1 / (1 + Math.exp(-x));
|
|
var fx = Neuron.squash.LOGISTIC(x);
|
|
return fx * (1 - fx);
|
|
};
|
|
Neuron.squash.TANH = function(x, derivate) {
|
|
if (derivate)
|
|
return 1 - Math.pow(Neuron.squash.TANH(x), 2);
|
|
var eP = Math.exp(x);
|
|
var eN = 1 / eP;
|
|
return (eP - eN) / (eP + eN);
|
|
};
|
|
Neuron.squash.IDENTITY = function(x, derivate) {
|
|
return derivate ? 1 : x;
|
|
};
|
|
Neuron.squash.HLIM = function(x, derivate) {
|
|
return derivate ? 1 : x > 0 ? 1 : 0;
|
|
};
|
|
Neuron.squash.RELU = function(x, derivate) {
|
|
if (derivate)
|
|
return x > 0 ? 1 : 0;
|
|
return x > 0 ? x : 0;
|
|
};
|
|
|
|
// unique ID's
|
|
(function() {
|
|
var neurons = 0;
|
|
var connections = 0;
|
|
Neuron.uid = function() {
|
|
return neurons++;
|
|
}
|
|
Neuron.connection.uid = function() {
|
|
return connections++;
|
|
}
|
|
Neuron.quantity = function() {
|
|
return {
|
|
neurons: neurons,
|
|
connections: connections
|
|
}
|
|
}
|
|
})();
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module)))
|
|
|
|
/***/ },
|
|
/* 2 */
|
|
/***/ function(module, exports) {
|
|
|
|
module.exports = function(module) {
|
|
if(!module.webpackPolyfill) {
|
|
module.deprecate = function() {};
|
|
module.paths = [];
|
|
// module.parent = undefined by default
|
|
module.children = [];
|
|
module.webpackPolyfill = 1;
|
|
}
|
|
return module;
|
|
}
|
|
|
|
|
|
/***/ },
|
|
/* 3 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
/* WEBPACK VAR INJECTION */(function(module) {// export
|
|
if (module) module.exports = Layer;
|
|
|
|
// import
|
|
var Neuron = __webpack_require__(1)
|
|
, Network = __webpack_require__(4)
|
|
|
|
/*******************************************************************************************
|
|
LAYER
|
|
*******************************************************************************************/
|
|
|
|
function Layer(size, label) {
|
|
this.size = size | 0;
|
|
this.list = [];
|
|
this.label = label || null;
|
|
this.connectedTo = [];
|
|
|
|
while (size--) {
|
|
var neuron = new Neuron();
|
|
this.list.push(neuron);
|
|
}
|
|
}
|
|
|
|
Layer.prototype = {
|
|
|
|
// activates all the neurons in the layer
|
|
activate: function(input) {
|
|
|
|
var activations = [];
|
|
|
|
if (typeof input != 'undefined') {
|
|
if (input.length != this.size)
|
|
throw new Error("INPUT size and LAYER size must be the same to activate!");
|
|
|
|
for (var id in this.list) {
|
|
var neuron = this.list[id];
|
|
var activation = neuron.activate(input[id]);
|
|
activations.push(activation);
|
|
}
|
|
} else {
|
|
for (var id in this.list) {
|
|
var neuron = this.list[id];
|
|
var activation = neuron.activate();
|
|
activations.push(activation);
|
|
}
|
|
}
|
|
return activations;
|
|
},
|
|
|
|
// propagates the error on all the neurons of the layer
|
|
propagate: function(rate, target) {
|
|
|
|
if (typeof target != 'undefined') {
|
|
if (target.length != this.size)
|
|
throw new Error("TARGET size and LAYER size must be the same to propagate!");
|
|
|
|
for (var id = this.list.length - 1; id >= 0; id--) {
|
|
var neuron = this.list[id];
|
|
neuron.propagate(rate, target[id]);
|
|
}
|
|
} else {
|
|
for (var id = this.list.length - 1; id >= 0; id--) {
|
|
var neuron = this.list[id];
|
|
neuron.propagate(rate);
|
|
}
|
|
}
|
|
},
|
|
|
|
// projects a connection from this layer to another one
|
|
project: function(layer, type, weights) {
|
|
|
|
if (layer instanceof Network)
|
|
layer = layer.layers.input;
|
|
|
|
if (layer instanceof Layer) {
|
|
if (!this.connected(layer))
|
|
return new Layer.connection(this, layer, type, weights);
|
|
} else
|
|
throw new Error("Invalid argument, you can only project connections to LAYERS and NETWORKS!");
|
|
|
|
|
|
},
|
|
|
|
// gates a connection betwenn two layers
|
|
gate: function(connection, type) {
|
|
|
|
if (type == Layer.gateType.INPUT) {
|
|
if (connection.to.size != this.size)
|
|
throw new Error("GATER layer and CONNECTION.TO layer must be the same size in order to gate!");
|
|
|
|
for (var id in connection.to.list) {
|
|
var neuron = connection.to.list[id];
|
|
var gater = this.list[id];
|
|
for (var input in neuron.connections.inputs) {
|
|
var gated = neuron.connections.inputs[input];
|
|
if (gated.ID in connection.connections)
|
|
gater.gate(gated);
|
|
}
|
|
}
|
|
} else if (type == Layer.gateType.OUTPUT) {
|
|
if (connection.from.size != this.size)
|
|
throw new Error("GATER layer and CONNECTION.FROM layer must be the same size in order to gate!");
|
|
|
|
for (var id in connection.from.list) {
|
|
var neuron = connection.from.list[id];
|
|
var gater = this.list[id];
|
|
for (var projected in neuron.connections.projected) {
|
|
var gated = neuron.connections.projected[projected];
|
|
if (gated.ID in connection.connections)
|
|
gater.gate(gated);
|
|
}
|
|
}
|
|
} else if (type == Layer.gateType.ONE_TO_ONE) {
|
|
if (connection.size != this.size)
|
|
throw new Error("The number of GATER UNITS must be the same as the number of CONNECTIONS to gate!");
|
|
|
|
for (var id in connection.list) {
|
|
var gater = this.list[id];
|
|
var gated = connection.list[id];
|
|
gater.gate(gated);
|
|
}
|
|
}
|
|
connection.gatedfrom.push({layer: this, type: type});
|
|
},
|
|
|
|
// true or false whether the whole layer is self-connected or not
|
|
selfconnected: function() {
|
|
|
|
for (var id in this.list) {
|
|
var neuron = this.list[id];
|
|
if (!neuron.selfconnected())
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
// true of false whether the layer is connected to another layer (parameter) or not
|
|
connected: function(layer) {
|
|
// Check if ALL to ALL connection
|
|
var connections = 0;
|
|
for (var here in this.list) {
|
|
for (var there in layer.list) {
|
|
var from = this.list[here];
|
|
var to = layer.list[there];
|
|
var connected = from.connected(to);
|
|
if (connected.type == 'projected')
|
|
connections++;
|
|
}
|
|
}
|
|
if (connections == this.size * layer.size)
|
|
return Layer.connectionType.ALL_TO_ALL;
|
|
|
|
// Check if ONE to ONE connection
|
|
connections = 0;
|
|
for (var neuron in this.list) {
|
|
var from = this.list[neuron];
|
|
var to = layer.list[neuron];
|
|
var connected = from.connected(to);
|
|
if (connected.type == 'projected')
|
|
connections++;
|
|
}
|
|
if (connections == this.size)
|
|
return Layer.connectionType.ONE_TO_ONE;
|
|
},
|
|
|
|
// clears all the neuorns in the layer
|
|
clear: function() {
|
|
for (var id in this.list) {
|
|
var neuron = this.list[id];
|
|
neuron.clear();
|
|
}
|
|
},
|
|
|
|
// resets all the neurons in the layer
|
|
reset: function() {
|
|
for (var id in this.list) {
|
|
var neuron = this.list[id];
|
|
neuron.reset();
|
|
}
|
|
},
|
|
|
|
// returns all the neurons in the layer (array)
|
|
neurons: function() {
|
|
return this.list;
|
|
},
|
|
|
|
// adds a neuron to the layer
|
|
add: function(neuron) {
|
|
this.neurons[neuron.ID] = neuron || new Neuron();
|
|
this.list.push(neuron);
|
|
this.size++;
|
|
},
|
|
|
|
set: function(options) {
|
|
options = options || {};
|
|
|
|
for (var i in this.list) {
|
|
var neuron = this.list[i];
|
|
if (options.label)
|
|
neuron.label = options.label + '_' + neuron.ID;
|
|
if (options.squash)
|
|
neuron.squash = options.squash;
|
|
if (options.bias)
|
|
neuron.bias = options.bias;
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// represents a connection from one layer to another, and keeps track of its weight and gain
|
|
Layer.connection = function LayerConnection(fromLayer, toLayer, type, weights) {
|
|
this.ID = Layer.connection.uid();
|
|
this.from = fromLayer;
|
|
this.to = toLayer;
|
|
this.selfconnection = toLayer == fromLayer;
|
|
this.type = type;
|
|
this.connections = {};
|
|
this.list = [];
|
|
this.size = 0;
|
|
this.gatedfrom = [];
|
|
|
|
if (typeof this.type == 'undefined')
|
|
{
|
|
if (fromLayer == toLayer)
|
|
this.type = Layer.connectionType.ONE_TO_ONE;
|
|
else
|
|
this.type = Layer.connectionType.ALL_TO_ALL;
|
|
}
|
|
|
|
if (this.type == Layer.connectionType.ALL_TO_ALL ||
|
|
this.type == Layer.connectionType.ALL_TO_ELSE) {
|
|
for (var here in this.from.list) {
|
|
for (var there in this.to.list) {
|
|
var from = this.from.list[here];
|
|
var to = this.to.list[there];
|
|
if(this.type == Layer.connectionType.ALL_TO_ELSE && from == to)
|
|
continue;
|
|
var connection = from.project(to, weights);
|
|
|
|
this.connections[connection.ID] = connection;
|
|
this.size = this.list.push(connection);
|
|
}
|
|
}
|
|
} else if (this.type == Layer.connectionType.ONE_TO_ONE) {
|
|
|
|
for (var neuron in this.from.list) {
|
|
var from = this.from.list[neuron];
|
|
var to = this.to.list[neuron];
|
|
var connection = from.project(to, weights);
|
|
|
|
this.connections[connection.ID] = connection;
|
|
this.size = this.list.push(connection);
|
|
}
|
|
}
|
|
|
|
fromLayer.connectedTo.push(this);
|
|
}
|
|
|
|
// types of connections
|
|
Layer.connectionType = {};
|
|
Layer.connectionType.ALL_TO_ALL = "ALL TO ALL";
|
|
Layer.connectionType.ONE_TO_ONE = "ONE TO ONE";
|
|
Layer.connectionType.ALL_TO_ELSE = "ALL TO ELSE";
|
|
|
|
// types of gates
|
|
Layer.gateType = {};
|
|
Layer.gateType.INPUT = "INPUT";
|
|
Layer.gateType.OUTPUT = "OUTPUT";
|
|
Layer.gateType.ONE_TO_ONE = "ONE TO ONE";
|
|
|
|
(function() {
|
|
var connections = 0;
|
|
Layer.connection.uid = function() {
|
|
return connections++;
|
|
}
|
|
})();
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module)))
|
|
|
|
/***/ },
|
|
/* 4 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
/* WEBPACK VAR INJECTION */(function(module) {// export
|
|
if (module) module.exports = Network;
|
|
|
|
// import
|
|
var Neuron = __webpack_require__(1)
|
|
, Layer = __webpack_require__(3)
|
|
, Trainer = __webpack_require__(5)
|
|
|
|
/*******************************************************************************************
|
|
NETWORK
|
|
*******************************************************************************************/
|
|
|
|
function Network(layers) {
|
|
if (typeof layers != 'undefined') {
|
|
this.layers = layers || {
|
|
input: null,
|
|
hidden: {},
|
|
output: null
|
|
};
|
|
this.optimized = null;
|
|
}
|
|
}
|
|
Network.prototype = {
|
|
|
|
// feed-forward activation of all the layers to produce an ouput
|
|
activate: function(input) {
|
|
|
|
if (this.optimized === false)
|
|
{
|
|
this.layers.input.activate(input);
|
|
for (var layer in this.layers.hidden)
|
|
this.layers.hidden[layer].activate();
|
|
return this.layers.output.activate();
|
|
}
|
|
else
|
|
{
|
|
if (this.optimized == null)
|
|
this.optimize();
|
|
return this.optimized.activate(input);
|
|
}
|
|
},
|
|
|
|
// back-propagate the error thru the network
|
|
propagate: function(rate, target) {
|
|
|
|
if (this.optimized === false)
|
|
{
|
|
this.layers.output.propagate(rate, target);
|
|
var reverse = [];
|
|
for (var layer in this.layers.hidden)
|
|
reverse.push(this.layers.hidden[layer]);
|
|
reverse.reverse();
|
|
for (var layer in reverse)
|
|
reverse[layer].propagate(rate);
|
|
}
|
|
else
|
|
{
|
|
if (this.optimized == null)
|
|
this.optimize();
|
|
this.optimized.propagate(rate, target);
|
|
}
|
|
},
|
|
|
|
// project a connection to another unit (either a network or a layer)
|
|
project: function(unit, type, weights) {
|
|
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
|
|
if (unit instanceof Network)
|
|
return this.layers.output.project(unit.layers.input, type, weights);
|
|
|
|
if (unit instanceof Layer)
|
|
return this.layers.output.project(unit, type, weights);
|
|
|
|
throw new Error("Invalid argument, you can only project connections to LAYERS and NETWORKS!");
|
|
},
|
|
|
|
// let this network gate a connection
|
|
gate: function(connection, type) {
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
this.layers.output.gate(connection, type);
|
|
},
|
|
|
|
// clear all elegibility traces and extended elegibility traces (the network forgets its context, but not what was trained)
|
|
clear: function() {
|
|
|
|
this.restore();
|
|
|
|
var inputLayer = this.layers.input,
|
|
outputLayer = this.layers.output;
|
|
|
|
inputLayer.clear();
|
|
for (var layer in this.layers.hidden) {
|
|
var hiddenLayer = this.layers.hidden[layer];
|
|
hiddenLayer.clear();
|
|
}
|
|
outputLayer.clear();
|
|
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
},
|
|
|
|
// reset all weights and clear all traces (ends up like a new network)
|
|
reset: function() {
|
|
|
|
this.restore();
|
|
|
|
var inputLayer = this.layers.input,
|
|
outputLayer = this.layers.output;
|
|
|
|
inputLayer.reset();
|
|
for (var layer in this.layers.hidden) {
|
|
var hiddenLayer = this.layers.hidden[layer];
|
|
hiddenLayer.reset();
|
|
}
|
|
outputLayer.reset();
|
|
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
},
|
|
|
|
// hardcodes the behaviour of the whole network into a single optimized function
|
|
optimize: function() {
|
|
|
|
var that = this;
|
|
var optimized = {};
|
|
var neurons = this.neurons();
|
|
|
|
for (var i in neurons) {
|
|
var neuron = neurons[i].neuron;
|
|
var layer = neurons[i].layer;
|
|
while (neuron.neuron)
|
|
neuron = neuron.neuron;
|
|
optimized = neuron.optimize(optimized, layer);
|
|
}
|
|
for (var i in optimized.propagation_sentences)
|
|
optimized.propagation_sentences[i].reverse();
|
|
optimized.propagation_sentences.reverse();
|
|
|
|
var hardcode = "";
|
|
hardcode += "var F = Float64Array ? new Float64Array(" + optimized.memory +
|
|
") : []; ";
|
|
for (var i in optimized.variables)
|
|
hardcode += "F[" + optimized.variables[i].id + "] = " + (optimized.variables[
|
|
i].value || 0) + "; ";
|
|
hardcode += "var activate = function(input){\n";
|
|
for (var i in optimized.inputs)
|
|
hardcode += "F[" + optimized.inputs[i] + "] = input[" + i + "]; ";
|
|
for (var currentLayer in optimized.activation_sentences) {
|
|
if (optimized.activation_sentences[currentLayer].length > 0) {
|
|
for (var currentNeuron in optimized.activation_sentences[currentLayer]) {
|
|
hardcode += optimized.activation_sentences[currentLayer][currentNeuron].join(" ");
|
|
hardcode += optimized.trace_sentences[currentLayer][currentNeuron].join(" ");
|
|
}
|
|
}
|
|
}
|
|
hardcode += " var output = []; "
|
|
for (var i in optimized.outputs)
|
|
hardcode += "output[" + i + "] = F[" + optimized.outputs[i] + "]; ";
|
|
hardcode += "return output; }; "
|
|
hardcode += "var propagate = function(rate, target){\n";
|
|
hardcode += "F[" + optimized.variables.rate.id + "] = rate; ";
|
|
for (var i in optimized.targets)
|
|
hardcode += "F[" + optimized.targets[i] + "] = target[" + i + "]; ";
|
|
for (var currentLayer in optimized.propagation_sentences)
|
|
for (var currentNeuron in optimized.propagation_sentences[currentLayer])
|
|
hardcode += optimized.propagation_sentences[currentLayer][currentNeuron].join(" ") + " ";
|
|
hardcode += " };\n";
|
|
hardcode +=
|
|
"var ownership = function(memoryBuffer){\nF = memoryBuffer;\nthis.memory = F;\n};\n";
|
|
hardcode +=
|
|
"return {\nmemory: F,\nactivate: activate,\npropagate: propagate,\nownership: ownership\n};";
|
|
hardcode = hardcode.split(";").join(";\n");
|
|
|
|
var constructor = new Function(hardcode);
|
|
|
|
var network = constructor();
|
|
network.data = {
|
|
variables: optimized.variables,
|
|
activate: optimized.activation_sentences,
|
|
propagate: optimized.propagation_sentences,
|
|
trace: optimized.trace_sentences,
|
|
inputs: optimized.inputs,
|
|
outputs: optimized.outputs,
|
|
check_activation: this.activate,
|
|
check_propagation: this.propagate
|
|
}
|
|
|
|
network.reset = function() {
|
|
if (that.optimized) {
|
|
that.optimized = null;
|
|
that.activate = network.data.check_activation;
|
|
that.propagate = network.data.check_propagation;
|
|
}
|
|
}
|
|
|
|
this.optimized = network;
|
|
this.activate = network.activate;
|
|
this.propagate = network.propagate;
|
|
},
|
|
|
|
// restores all the values from the optimized network the their respective objects in order to manipulate the network
|
|
restore: function() {
|
|
if (!this.optimized)
|
|
return;
|
|
|
|
var optimized = this.optimized;
|
|
|
|
var getValue = function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
var unit = args.shift();
|
|
var prop = args.pop();
|
|
|
|
var id = prop + '_';
|
|
for (var property in args)
|
|
id += args[property] + '_';
|
|
id += unit.ID;
|
|
|
|
var memory = optimized.memory;
|
|
var variables = optimized.data.variables;
|
|
|
|
if (id in variables)
|
|
return memory[variables[id].id];
|
|
return 0;
|
|
}
|
|
|
|
var list = this.neurons();
|
|
|
|
// link id's to positions in the array
|
|
var ids = {};
|
|
for (var i in list) {
|
|
var neuron = list[i].neuron;
|
|
while (neuron.neuron)
|
|
neuron = neuron.neuron;
|
|
|
|
neuron.state = getValue(neuron, 'state');
|
|
neuron.old = getValue(neuron, 'old');
|
|
neuron.activation = getValue(neuron, 'activation');
|
|
neuron.bias = getValue(neuron, 'bias');
|
|
|
|
for (var input in neuron.trace.elegibility)
|
|
neuron.trace.elegibility[input] = getValue(neuron, 'trace',
|
|
'elegibility', input);
|
|
|
|
for (var gated in neuron.trace.extended)
|
|
for (var input in neuron.trace.extended[gated])
|
|
neuron.trace.extended[gated][input] = getValue(neuron, 'trace',
|
|
'extended', gated, input);
|
|
}
|
|
|
|
// get connections
|
|
for (var i in list) {
|
|
var neuron = list[i].neuron;
|
|
while (neuron.neuron)
|
|
neuron = neuron.neuron;
|
|
|
|
for (var j in neuron.connections.projected) {
|
|
var connection = neuron.connections.projected[j];
|
|
connection.weight = getValue(connection, 'weight');
|
|
connection.gain = getValue(connection, 'gain');
|
|
}
|
|
}
|
|
},
|
|
|
|
// returns all the neurons in the network
|
|
neurons: function() {
|
|
|
|
var neurons = [];
|
|
|
|
var inputLayer = this.layers.input.neurons(),
|
|
outputLayer = this.layers.output.neurons();
|
|
|
|
for (var neuron in inputLayer)
|
|
neurons.push({
|
|
neuron: inputLayer[neuron],
|
|
layer: 'input'
|
|
});
|
|
|
|
for (var layer in this.layers.hidden) {
|
|
var hiddenLayer = this.layers.hidden[layer].neurons();
|
|
for (var neuron in hiddenLayer)
|
|
neurons.push({
|
|
neuron: hiddenLayer[neuron],
|
|
layer: layer
|
|
});
|
|
}
|
|
for (var neuron in outputLayer)
|
|
neurons.push({
|
|
neuron: outputLayer[neuron],
|
|
layer: 'output'
|
|
});
|
|
|
|
return neurons;
|
|
},
|
|
|
|
// returns number of inputs of the network
|
|
inputs: function() {
|
|
return this.layers.input.size;
|
|
},
|
|
|
|
// returns number of outputs of hte network
|
|
outputs: function() {
|
|
return this.layers.output.size;
|
|
},
|
|
|
|
// sets the layers of the network
|
|
set: function(layers) {
|
|
|
|
this.layers = layers;
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
},
|
|
|
|
setOptimize: function(bool){
|
|
this.restore();
|
|
if (this.optimized)
|
|
this.optimized.reset();
|
|
this.optimized = bool? null : false;
|
|
},
|
|
|
|
// returns a json that represents all the neurons and connections of the network
|
|
toJSON: function(ignoreTraces) {
|
|
|
|
this.restore();
|
|
|
|
var list = this.neurons();
|
|
var neurons = [];
|
|
var connections = [];
|
|
|
|
// link id's to positions in the array
|
|
var ids = {};
|
|
for (var i in list) {
|
|
var neuron = list[i].neuron;
|
|
while (neuron.neuron)
|
|
neuron = neuron.neuron;
|
|
ids[neuron.ID] = i;
|
|
|
|
var copy = {
|
|
trace: {
|
|
elegibility: {},
|
|
extended: {}
|
|
},
|
|
state: neuron.state,
|
|
old: neuron.old,
|
|
activation: neuron.activation,
|
|
bias: neuron.bias,
|
|
layer: list[i].layer
|
|
};
|
|
|
|
copy.squash = neuron.squash == Neuron.squash.LOGISTIC ? "LOGISTIC" :
|
|
neuron.squash == Neuron.squash.TANH ? "TANH" :
|
|
neuron.squash == Neuron.squash.IDENTITY ? "IDENTITY" :
|
|
neuron.squash == Neuron.squash.HLIM ? "HLIM" :
|
|
null;
|
|
|
|
neurons.push(copy);
|
|
}
|
|
|
|
// get connections
|
|
for (var i in list) {
|
|
var neuron = list[i].neuron;
|
|
while (neuron.neuron)
|
|
neuron = neuron.neuron;
|
|
|
|
for (var j in neuron.connections.projected) {
|
|
var connection = neuron.connections.projected[j];
|
|
connections.push({
|
|
from: ids[connection.from.ID],
|
|
to: ids[connection.to.ID],
|
|
weight: connection.weight,
|
|
gater: connection.gater ? ids[connection.gater.ID] : null,
|
|
});
|
|
}
|
|
if (neuron.selfconnected())
|
|
connections.push({
|
|
from: ids[neuron.ID],
|
|
to: ids[neuron.ID],
|
|
weight: neuron.selfconnection.weight,
|
|
gater: neuron.selfconnection.gater ? ids[neuron.selfconnection.gater.ID] : null,
|
|
});
|
|
}
|
|
|
|
return {
|
|
neurons: neurons,
|
|
connections: connections
|
|
}
|
|
},
|
|
|
|
// export the topology into dot language which can be visualized as graphs using dot
|
|
/* example: ... console.log(net.toDotLang());
|
|
$ node example.js > example.dot
|
|
$ dot example.dot -Tpng > out.png
|
|
*/
|
|
toDot: function(edgeConnection) {
|
|
if (! typeof edgeConnection)
|
|
edgeConnection = false;
|
|
var code = "digraph nn {\n rankdir = BT\n";
|
|
var layers = [this.layers.input].concat(this.layers.hidden, this.layers.output);
|
|
for (var layer in layers) {
|
|
for (var to in layers[layer].connectedTo) { // projections
|
|
var connection = layers[layer].connectedTo[to];
|
|
var layerTo = connection.to;
|
|
var size = connection.size;
|
|
var layerID = layers.indexOf(layers[layer]);
|
|
var layerToID = layers.indexOf(layerTo);
|
|
/* http://stackoverflow.com/questions/26845540/connect-edges-with-graph-dot
|
|
* DOT does not support edge-to-edge connections
|
|
* This workaround produces somewhat weird graphs ...
|
|
*/
|
|
if ( edgeConnection) {
|
|
if (connection.gatedfrom.length) {
|
|
var fakeNode = "fake" + layerID + "_" + layerToID;
|
|
code += " " + fakeNode +
|
|
" [label = \"\", shape = point, width = 0.01, height = 0.01]\n";
|
|
code += " " + layerID + " -> " + fakeNode + " [label = " + size + ", arrowhead = none]\n";
|
|
code += " " + fakeNode + " -> " + layerToID + "\n";
|
|
} else
|
|
code += " " + layerID + " -> " + layerToID + " [label = " + size + "]\n";
|
|
for (var from in connection.gatedfrom) { // gatings
|
|
var layerfrom = connection.gatedfrom[from].layer;
|
|
var layerfromID = layers.indexOf(layerfrom);
|
|
code += " " + layerfromID + " -> " + fakeNode + " [color = blue]\n";
|
|
}
|
|
} else {
|
|
code += " " + layerID + " -> " + layerToID + " [label = " + size + "]\n";
|
|
for (var from in connection.gatedfrom) { // gatings
|
|
var layerfrom = connection.gatedfrom[from].layer;
|
|
var layerfromID = layers.indexOf(layerfrom);
|
|
code += " " + layerfromID + " -> " + layerToID + " [color = blue]\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
code += "}\n";
|
|
return {
|
|
code: code,
|
|
link: "https://chart.googleapis.com/chart?chl=" + escape(code.replace("/ /g", "+")) + "&cht=gv"
|
|
}
|
|
},
|
|
|
|
// returns a function that works as the activation of the network and can be used without depending on the library
|
|
standalone: function() {
|
|
if (!this.optimized)
|
|
this.optimize();
|
|
|
|
var data = this.optimized.data;
|
|
|
|
// build activation function
|
|
var activation = "function (input) {\n";
|
|
|
|
// build inputs
|
|
for (var i in data.inputs)
|
|
activation += "F[" + data.inputs[i] + "] = input[" + i + "];\n";
|
|
|
|
// build network activation
|
|
for (var neuron in data.activate) { // shouldn't this be layer?
|
|
for (var sentence in data.activate[neuron])
|
|
activation += data.activate[neuron][sentence].join('') + "\n";
|
|
}
|
|
|
|
// build outputs
|
|
activation += "var output = [];\n";
|
|
for (var i in data.outputs)
|
|
activation += "output[" + i + "] = F[" + data.outputs[i] + "];\n";
|
|
activation += "return output;\n}";
|
|
|
|
// reference all the positions in memory
|
|
var memory = activation.match(/F\[(\d+)\]/g);
|
|
var dimension = 0;
|
|
var ids = {};
|
|
for (var address in memory) {
|
|
var tmp = memory[address].match(/\d+/)[0];
|
|
if (!(tmp in ids)) {
|
|
ids[tmp] = dimension++;
|
|
}
|
|
}
|
|
var hardcode = "F = {\n";
|
|
for (var i in ids)
|
|
hardcode += ids[i] + ": " + this.optimized.memory[i] + ",\n";
|
|
hardcode = hardcode.substring(0, hardcode.length - 2) + "\n};\n";
|
|
hardcode = "var run = " + activation.replace(/F\[(\d+)]/g, function(
|
|
index) {
|
|
return 'F[' + ids[index.match(/\d+/)[0]] + ']'
|
|
}).replace("{\n", "{\n" + hardcode + "") + ";\n";
|
|
hardcode += "return run";
|
|
|
|
// return standalone function
|
|
return new Function(hardcode)();
|
|
},
|
|
|
|
|
|
// Return a HTML5 WebWorker specialized on training the network stored in `memory`.
|
|
// Train based on the given dataSet and options.
|
|
// The worker returns the updated `memory` when done.
|
|
worker: function(memory, set, options) {
|
|
|
|
// Copy the options and set defaults (options might be different for each worker)
|
|
var workerOptions = {};
|
|
if(options) workerOptions = options;
|
|
workerOptions.rate = options.rate || .2;
|
|
workerOptions.iterations = options.iterations || 100000;
|
|
workerOptions.error = options.error || .005;
|
|
workerOptions.cost = options.cost || null;
|
|
workerOptions.crossValidate = options.crossValidate || null;
|
|
|
|
// Cost function might be different for each worker
|
|
costFunction = "var cost = " + (options && options.cost || this.cost || Trainer.cost.MSE) + ";\n";
|
|
var workerFunction = Network.getWorkerSharedFunctions();
|
|
workerFunction = workerFunction.replace(/var cost = options && options\.cost \|\| this\.cost \|\| Trainer\.cost\.MSE;/g, costFunction);
|
|
|
|
// Set what we do when training is finished
|
|
workerFunction = workerFunction.replace('return results;',
|
|
'postMessage({action: "done", message: results, memoryBuffer: F}, [F.buffer]);');
|
|
|
|
// Replace log with postmessage
|
|
workerFunction = workerFunction.replace("console.log('iterations', iterations, 'error', error, 'rate', currentRate)",
|
|
"postMessage({action: 'log', message: {\n" +
|
|
"iterations: iterations,\n" +
|
|
"error: error,\n" +
|
|
"rate: currentRate\n" +
|
|
"}\n" +
|
|
"})");
|
|
|
|
// Replace schedule with postmessage
|
|
workerFunction = workerFunction.replace("abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate })",
|
|
"postMessage({action: 'schedule', message: {\n" +
|
|
"iterations: iterations,\n" +
|
|
"error: error,\n" +
|
|
"rate: currentRate\n" +
|
|
"}\n" +
|
|
"})");
|
|
|
|
if (!this.optimized)
|
|
this.optimize();
|
|
|
|
var hardcode = "var inputs = " + this.optimized.data.inputs.length + ";\n";
|
|
hardcode += "var outputs = " + this.optimized.data.outputs.length + ";\n";
|
|
hardcode += "var F = new Float64Array([" + this.optimized.memory.toString() + "]);\n";
|
|
hardcode += "var activate = " + this.optimized.activate.toString() + ";\n";
|
|
hardcode += "var propagate = " + this.optimized.propagate.toString() + ";\n";
|
|
hardcode +=
|
|
"onmessage = function(e) {\n" +
|
|
"if (e.data.action == 'startTraining') {\n" +
|
|
"train(" + JSON.stringify(set) + "," + JSON.stringify(workerOptions) + ");\n" +
|
|
"}\n" +
|
|
"}";
|
|
|
|
var workerSourceCode = workerFunction + '\n' + hardcode;
|
|
var blob = new Blob([workerSourceCode]);
|
|
var blobURL = window.URL.createObjectURL(blob);
|
|
|
|
return new Worker(blobURL);
|
|
},
|
|
|
|
// returns a copy of the network
|
|
clone: function() {
|
|
return Network.fromJSON(this.toJSON());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates a static String to store the source code of the functions
|
|
* that are identical for all the workers (train, _trainSet, test)
|
|
*
|
|
* @return {String} Source code that can train a network inside a worker.
|
|
* @static
|
|
*/
|
|
Network.getWorkerSharedFunctions = function() {
|
|
// If we already computed the source code for the shared functions
|
|
if(typeof Network._SHARED_WORKER_FUNCTIONS !== 'undefined')
|
|
return Network._SHARED_WORKER_FUNCTIONS;
|
|
|
|
// Otherwise compute and return the source code
|
|
// We compute them by simply copying the source code of the train, _trainSet and test functions
|
|
// using the .toString() method
|
|
|
|
// Load and name the train function
|
|
var train_f = Trainer.prototype.train.toString();
|
|
train_f = train_f.replace('function (set', 'function train(set') + '\n';
|
|
|
|
// Load and name the _trainSet function
|
|
var _trainSet_f = Trainer.prototype._trainSet.toString().replace(/this.network./g, '');
|
|
_trainSet_f = _trainSet_f.replace('function (set', 'function _trainSet(set') + '\n';
|
|
_trainSet_f = _trainSet_f.replace('this.crossValidate', 'crossValidate');
|
|
_trainSet_f = _trainSet_f.replace('crossValidate = true', 'crossValidate = { }');
|
|
|
|
// Load and name the test function
|
|
var test_f = Trainer.prototype.test.toString().replace(/this.network./g, '');
|
|
test_f = test_f.replace('function (set', 'function test(set') + '\n';
|
|
|
|
return Network._SHARED_WORKER_FUNCTIONS = train_f + _trainSet_f + test_f;
|
|
};
|
|
|
|
// rebuild a network that has been stored in a json using the method toJSON()
|
|
Network.fromJSON = function(json) {
|
|
|
|
var neurons = [];
|
|
|
|
var layers = {
|
|
input: new Layer(),
|
|
hidden: [],
|
|
output: new Layer()
|
|
};
|
|
|
|
for (var i in json.neurons) {
|
|
var config = json.neurons[i];
|
|
|
|
var neuron = new Neuron();
|
|
neuron.trace.elegibility = {};
|
|
neuron.trace.extended = {};
|
|
neuron.state = config.state;
|
|
neuron.old = config.old;
|
|
neuron.activation = config.activation;
|
|
neuron.bias = config.bias;
|
|
neuron.squash = config.squash in Neuron.squash ? Neuron.squash[config.squash] : Neuron.squash.LOGISTIC;
|
|
neurons.push(neuron);
|
|
|
|
if (config.layer == 'input')
|
|
layers.input.add(neuron);
|
|
else if (config.layer == 'output')
|
|
layers.output.add(neuron);
|
|
else {
|
|
if (typeof layers.hidden[config.layer] == 'undefined')
|
|
layers.hidden[config.layer] = new Layer();
|
|
layers.hidden[config.layer].add(neuron);
|
|
}
|
|
}
|
|
|
|
for (var i in json.connections) {
|
|
var config = json.connections[i];
|
|
var from = neurons[config.from];
|
|
var to = neurons[config.to];
|
|
var weight = config.weight;
|
|
var gater = neurons[config.gater];
|
|
|
|
var connection = from.project(to, weight);
|
|
if (gater)
|
|
gater.gate(connection);
|
|
}
|
|
|
|
return new Network(layers);
|
|
};
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module)))
|
|
|
|
/***/ },
|
|
/* 5 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
/* WEBPACK VAR INJECTION */(function(module) {// export
|
|
if (module) module.exports = Trainer;
|
|
|
|
/*******************************************************************************************
|
|
TRAINER
|
|
*******************************************************************************************/
|
|
|
|
function Trainer(network, options) {
|
|
options = options || {};
|
|
this.network = network;
|
|
this.rate = options.rate || .2;
|
|
this.iterations = options.iterations || 100000;
|
|
this.error = options.error || .005;
|
|
this.cost = options.cost || null;
|
|
this.crossValidate = options.crossValidate || null;
|
|
}
|
|
|
|
Trainer.prototype = {
|
|
|
|
// trains any given set to a network
|
|
train: function(set, options) {
|
|
|
|
var error = 1;
|
|
var iterations = bucketSize = 0;
|
|
var abort = false;
|
|
var currentRate;
|
|
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
|
var crossValidate = false, testSet, trainSet;
|
|
|
|
var start = Date.now();
|
|
|
|
if (options) {
|
|
if (options.shuffle) {
|
|
//+ Jonas Raoni Soares Silva
|
|
//@ http://jsfromhell.com/array/shuffle [v1.0]
|
|
function shuffle(o) { //v1.0
|
|
for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
|
|
return o;
|
|
};
|
|
}
|
|
if (options.iterations)
|
|
this.iterations = options.iterations;
|
|
if (options.error)
|
|
this.error = options.error;
|
|
if (options.rate)
|
|
this.rate = options.rate;
|
|
if (options.cost)
|
|
this.cost = options.cost;
|
|
if (options.schedule)
|
|
this.schedule = options.schedule;
|
|
if (options.customLog){
|
|
// for backward compatibility with code that used customLog
|
|
console.log('Deprecated: use schedule instead of customLog')
|
|
this.schedule = options.customLog;
|
|
}
|
|
if (this.crossValidate || options.crossValidate) {
|
|
if(!this.crossValidate) this.crossValidate = {};
|
|
crossValidate = true;
|
|
if (options.crossValidate.testSize)
|
|
this.crossValidate.testSize = options.crossValidate.testSize;
|
|
if (options.crossValidate.testError)
|
|
this.crossValidate.testError = options.crossValidate.testError;
|
|
}
|
|
}
|
|
|
|
currentRate = this.rate;
|
|
if(Array.isArray(this.rate)) {
|
|
var bucketSize = Math.floor(this.iterations / this.rate.length);
|
|
}
|
|
|
|
if(crossValidate) {
|
|
var numTrain = Math.ceil((1 - this.crossValidate.testSize) * set.length);
|
|
trainSet = set.slice(0, numTrain);
|
|
testSet = set.slice(numTrain);
|
|
}
|
|
|
|
var lastError = 0;
|
|
while ((!abort && iterations < this.iterations && error > this.error)) {
|
|
if (crossValidate && error <= this.crossValidate.testError) {
|
|
break;
|
|
}
|
|
|
|
var currentSetSize = set.length;
|
|
error = 0;
|
|
iterations++;
|
|
|
|
if(bucketSize > 0) {
|
|
var currentBucket = Math.floor(iterations / bucketSize);
|
|
currentRate = this.rate[currentBucket] || currentRate;
|
|
}
|
|
|
|
if(typeof this.rate === 'function') {
|
|
currentRate = this.rate(iterations, lastError);
|
|
}
|
|
|
|
if (crossValidate) {
|
|
this._trainSet(trainSet, currentRate, cost);
|
|
error += this.test(testSet).error;
|
|
currentSetSize = 1;
|
|
} else {
|
|
error += this._trainSet(set, currentRate, cost);
|
|
currentSetSize = set.length;
|
|
}
|
|
|
|
// check error
|
|
error /= currentSetSize;
|
|
lastError = error;
|
|
|
|
if (options) {
|
|
if (this.schedule && this.schedule.every && iterations %
|
|
this.schedule.every == 0)
|
|
abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate });
|
|
else if (options.log && iterations % options.log == 0) {
|
|
console.log('iterations', iterations, 'error', error, 'rate', currentRate);
|
|
};
|
|
if (options.shuffle)
|
|
shuffle(set);
|
|
}
|
|
}
|
|
|
|
var results = {
|
|
error: error,
|
|
iterations: iterations,
|
|
time: Date.now() - start
|
|
};
|
|
|
|
return results;
|
|
},
|
|
|
|
// trains any given set to a network, using a WebWorker (only for the browser). Returns a Promise of the results.
|
|
trainAsync: function(set, options) {
|
|
var train = this.workerTrain.bind(this);
|
|
return new Promise(function(resolve, reject) {
|
|
try {
|
|
train(set, resolve, options, true)
|
|
} catch(e) {
|
|
reject(e)
|
|
}
|
|
})
|
|
},
|
|
|
|
// preforms one training epoch and returns the error (private function used in this.train)
|
|
_trainSet: function(set, currentRate, costFunction) {
|
|
var errorSum = 0;
|
|
for (var train in set) {
|
|
var input = set[train].input;
|
|
var target = set[train].output;
|
|
|
|
var output = this.network.activate(input);
|
|
this.network.propagate(currentRate, target);
|
|
|
|
errorSum += costFunction(target, output);
|
|
}
|
|
return errorSum;
|
|
},
|
|
|
|
// tests a set and returns the error and elapsed time
|
|
test: function(set, options) {
|
|
|
|
var error = 0;
|
|
var input, output, target;
|
|
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
|
|
|
var start = Date.now();
|
|
|
|
for (var test in set) {
|
|
input = set[test].input;
|
|
target = set[test].output;
|
|
output = this.network.activate(input);
|
|
error += cost(target, output);
|
|
}
|
|
|
|
error /= set.length;
|
|
|
|
var results = {
|
|
error: error,
|
|
time: Date.now() - start
|
|
};
|
|
|
|
return results;
|
|
},
|
|
|
|
// trains any given set to a network using a WebWorker [deprecated: use trainAsync instead]
|
|
workerTrain: function(set, callback, options, suppressWarning) {
|
|
|
|
if (!suppressWarning) {
|
|
console.warn('Deprecated: do not use `workerTrain`, use `trainAsync` instead.')
|
|
}
|
|
var that = this;
|
|
|
|
if (!this.network.optimized)
|
|
this.network.optimize();
|
|
|
|
// Create a new worker
|
|
var worker = this.network.worker(this.network.optimized.memory, set, options);
|
|
|
|
// train the worker
|
|
worker.onmessage = function(e) {
|
|
switch(e.data.action) {
|
|
case 'done':
|
|
var iterations = e.data.message.iterations;
|
|
var error = e.data.message.error;
|
|
var time = e.data.message.time;
|
|
|
|
that.network.optimized.ownership(e.data.memoryBuffer);
|
|
|
|
// Done callback
|
|
callback({
|
|
error: error,
|
|
iterations: iterations,
|
|
time: time
|
|
});
|
|
|
|
// Delete the worker and all its associated memory
|
|
worker.terminate();
|
|
break;
|
|
|
|
case 'log':
|
|
console.log(e.data.message);
|
|
|
|
case 'schedule':
|
|
if (options && options.schedule && typeof options.schedule.do === 'function') {
|
|
var scheduled = options.schedule.do
|
|
scheduled(e.data.message)
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
// Start the worker
|
|
worker.postMessage({action: 'startTraining'});
|
|
},
|
|
|
|
// trains an XOR to the network
|
|
XOR: function(options) {
|
|
|
|
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
|
throw new Error("Incompatible network (2 inputs, 1 output)");
|
|
|
|
var defaults = {
|
|
iterations: 100000,
|
|
log: false,
|
|
shuffle: true,
|
|
cost: Trainer.cost.MSE
|
|
};
|
|
|
|
if (options)
|
|
for (var i in options)
|
|
defaults[i] = options[i];
|
|
|
|
return this.train([{
|
|
input: [0, 0],
|
|
output: [0]
|
|
}, {
|
|
input: [1, 0],
|
|
output: [1]
|
|
}, {
|
|
input: [0, 1],
|
|
output: [1]
|
|
}, {
|
|
input: [1, 1],
|
|
output: [0]
|
|
}], defaults);
|
|
},
|
|
|
|
// trains the network to pass a Distracted Sequence Recall test
|
|
DSR: function(options) {
|
|
options = options || {};
|
|
|
|
var targets = options.targets || [2, 4, 7, 8];
|
|
var distractors = options.distractors || [3, 5, 6, 9];
|
|
var prompts = options.prompts || [0, 1];
|
|
var length = options.length || 24;
|
|
var criterion = options.success || 0.95;
|
|
var iterations = options.iterations || 100000;
|
|
var rate = options.rate || .1;
|
|
var log = options.log || 0;
|
|
var schedule = options.schedule || {};
|
|
var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
|
|
|
var trial, correct, i, j, success;
|
|
trial = correct = i = j = success = 0;
|
|
var error = 1,
|
|
symbols = targets.length + distractors.length + prompts.length;
|
|
|
|
var noRepeat = function(range, avoid) {
|
|
var number = Math.random() * range | 0;
|
|
var used = false;
|
|
for (var i in avoid)
|
|
if (number == avoid[i])
|
|
used = true;
|
|
return used ? noRepeat(range, avoid) : number;
|
|
};
|
|
|
|
var equal = function(prediction, output) {
|
|
for (var i in prediction)
|
|
if (Math.round(prediction[i]) != output[i])
|
|
return false;
|
|
return true;
|
|
};
|
|
|
|
var start = Date.now();
|
|
|
|
while (trial < iterations && (success < criterion || trial % 1000 != 0)) {
|
|
// generate sequence
|
|
var sequence = [],
|
|
sequenceLength = length - prompts.length;
|
|
for (i = 0; i < sequenceLength; i++) {
|
|
var any = Math.random() * distractors.length | 0;
|
|
sequence.push(distractors[any]);
|
|
}
|
|
var indexes = [],
|
|
positions = [];
|
|
for (i = 0; i < prompts.length; i++) {
|
|
indexes.push(Math.random() * targets.length | 0);
|
|
positions.push(noRepeat(sequenceLength, positions));
|
|
}
|
|
positions = positions.sort();
|
|
for (i = 0; i < prompts.length; i++) {
|
|
sequence[positions[i]] = targets[indexes[i]];
|
|
sequence.push(prompts[i]);
|
|
}
|
|
|
|
//train sequence
|
|
var distractorsCorrect;
|
|
var targetsCorrect = distractorsCorrect = 0;
|
|
error = 0;
|
|
for (i = 0; i < length; i++) {
|
|
// generate input from sequence
|
|
var input = [];
|
|
for (j = 0; j < symbols; j++)
|
|
input[j] = 0;
|
|
input[sequence[i]] = 1;
|
|
|
|
// generate target output
|
|
var output = [];
|
|
for (j = 0; j < targets.length; j++)
|
|
output[j] = 0;
|
|
|
|
if (i >= sequenceLength) {
|
|
var index = i - sequenceLength;
|
|
output[indexes[index]] = 1;
|
|
}
|
|
|
|
// check result
|
|
var prediction = this.network.activate(input);
|
|
|
|
if (equal(prediction, output))
|
|
if (i < sequenceLength)
|
|
distractorsCorrect++;
|
|
else
|
|
targetsCorrect++;
|
|
else {
|
|
this.network.propagate(rate, output);
|
|
}
|
|
|
|
error += cost(output, prediction);
|
|
|
|
if (distractorsCorrect + targetsCorrect == length)
|
|
correct++;
|
|
}
|
|
|
|
// calculate error
|
|
if (trial % 1000 == 0)
|
|
correct = 0;
|
|
trial++;
|
|
var divideError = trial % 1000;
|
|
divideError = divideError == 0 ? 1000 : divideError;
|
|
success = correct / divideError;
|
|
error /= length;
|
|
|
|
// log
|
|
if (log && trial % log == 0)
|
|
console.log("iterations:", trial, " success:", success, " correct:",
|
|
correct, " time:", Date.now() - start, " error:", error);
|
|
if (schedule.do && schedule.every && trial % schedule.every == 0)
|
|
schedule.do({
|
|
iterations: trial,
|
|
success: success,
|
|
error: error,
|
|
time: Date.now() - start,
|
|
correct: correct
|
|
});
|
|
}
|
|
|
|
return {
|
|
iterations: trial,
|
|
success: success,
|
|
error: error,
|
|
time: Date.now() - start
|
|
}
|
|
},
|
|
|
|
// train the network to learn an Embeded Reber Grammar
|
|
ERG: function(options) {
|
|
|
|
options = options || {};
|
|
var iterations = options.iterations || 150000;
|
|
var criterion = options.error || .05;
|
|
var rate = options.rate || .1;
|
|
var log = options.log || 500;
|
|
var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
|
|
|
// gramar node
|
|
var Node = function() {
|
|
this.paths = [];
|
|
};
|
|
Node.prototype = {
|
|
connect: function(node, value) {
|
|
this.paths.push({
|
|
node: node,
|
|
value: value
|
|
});
|
|
return this;
|
|
},
|
|
any: function() {
|
|
if (this.paths.length == 0)
|
|
return false;
|
|
var index = Math.random() * this.paths.length | 0;
|
|
return this.paths[index];
|
|
},
|
|
test: function(value) {
|
|
for (var i in this.paths)
|
|
if (this.paths[i].value == value)
|
|
return this.paths[i];
|
|
return false;
|
|
}
|
|
};
|
|
|
|
var reberGrammar = function() {
|
|
|
|
// build a reber grammar
|
|
var output = new Node();
|
|
var n1 = (new Node()).connect(output, "E");
|
|
var n2 = (new Node()).connect(n1, "S");
|
|
var n3 = (new Node()).connect(n1, "V").connect(n2, "P");
|
|
var n4 = (new Node()).connect(n2, "X");
|
|
n4.connect(n4, "S");
|
|
var n5 = (new Node()).connect(n3, "V");
|
|
n5.connect(n5, "T");
|
|
n2.connect(n5, "X");
|
|
var n6 = (new Node()).connect(n4, "T").connect(n5, "P");
|
|
var input = (new Node()).connect(n6, "B");
|
|
|
|
return {
|
|
input: input,
|
|
output: output
|
|
}
|
|
};
|
|
|
|
// build an embeded reber grammar
|
|
var embededReberGrammar = function() {
|
|
var reber1 = reberGrammar();
|
|
var reber2 = reberGrammar();
|
|
|
|
var output = new Node();
|
|
var n1 = (new Node).connect(output, "E");
|
|
reber1.output.connect(n1, "T");
|
|
reber2.output.connect(n1, "P");
|
|
var n2 = (new Node).connect(reber1.input, "P").connect(reber2.input,
|
|
"T");
|
|
var input = (new Node).connect(n2, "B");
|
|
|
|
return {
|
|
input: input,
|
|
output: output
|
|
}
|
|
|
|
};
|
|
|
|
// generate an ERG sequence
|
|
var generate = function() {
|
|
var node = embededReberGrammar().input;
|
|
var next = node.any();
|
|
var str = "";
|
|
while (next) {
|
|
str += next.value;
|
|
next = next.node.any();
|
|
}
|
|
return str;
|
|
};
|
|
|
|
// test if a string matches an embeded reber grammar
|
|
var test = function(str) {
|
|
var node = embededReberGrammar().input;
|
|
var i = 0;
|
|
var ch = str.charAt(i);
|
|
while (i < str.length) {
|
|
var next = node.test(ch);
|
|
if (!next)
|
|
return false;
|
|
node = next.node;
|
|
ch = str.charAt(++i);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// helper to check if the output and the target vectors match
|
|
var different = function(array1, array2) {
|
|
var max1 = 0;
|
|
var i1 = -1;
|
|
var max2 = 0;
|
|
var i2 = -1;
|
|
for (var i in array1) {
|
|
if (array1[i] > max1) {
|
|
max1 = array1[i];
|
|
i1 = i;
|
|
}
|
|
if (array2[i] > max2) {
|
|
max2 = array2[i];
|
|
i2 = i;
|
|
}
|
|
}
|
|
|
|
return i1 != i2;
|
|
};
|
|
|
|
var iteration = 0;
|
|
var error = 1;
|
|
var table = {
|
|
"B": 0,
|
|
"P": 1,
|
|
"T": 2,
|
|
"X": 3,
|
|
"S": 4,
|
|
"E": 5
|
|
};
|
|
|
|
var start = Date.now();
|
|
while (iteration < iterations && error > criterion) {
|
|
var i = 0;
|
|
error = 0;
|
|
|
|
// ERG sequence to learn
|
|
var sequence = generate();
|
|
|
|
// input
|
|
var read = sequence.charAt(i);
|
|
// target
|
|
var predict = sequence.charAt(i + 1);
|
|
|
|
// train
|
|
while (i < sequence.length - 1) {
|
|
var input = [];
|
|
var target = [];
|
|
for (var j = 0; j < 6; j++) {
|
|
input[j] = 0;
|
|
target[j] = 0;
|
|
}
|
|
input[table[read]] = 1;
|
|
target[table[predict]] = 1;
|
|
|
|
var output = this.network.activate(input);
|
|
|
|
if (different(output, target))
|
|
this.network.propagate(rate, target);
|
|
|
|
read = sequence.charAt(++i);
|
|
predict = sequence.charAt(i + 1);
|
|
|
|
error += cost(target, output);
|
|
}
|
|
error /= sequence.length;
|
|
iteration++;
|
|
if (iteration % log == 0) {
|
|
console.log("iterations:", iteration, " time:", Date.now() - start,
|
|
" error:", error);
|
|
}
|
|
}
|
|
|
|
return {
|
|
iterations: iteration,
|
|
error: error,
|
|
time: Date.now() - start,
|
|
test: test,
|
|
generate: generate
|
|
}
|
|
},
|
|
|
|
timingTask: function(options){
|
|
|
|
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
|
throw new Error("Invalid Network: must have 2 inputs and one output");
|
|
|
|
if (typeof options == 'undefined')
|
|
options = {};
|
|
|
|
// helper
|
|
function getSamples (trainingSize, testSize){
|
|
|
|
// sample size
|
|
var size = trainingSize + testSize;
|
|
|
|
// generate samples
|
|
var t = 0;
|
|
var set = [];
|
|
for (var i = 0; i < size; i++) {
|
|
set.push({ input: [0,0], output: [0] });
|
|
}
|
|
while(t < size - 20) {
|
|
var n = Math.round(Math.random() * 20);
|
|
set[t].input[0] = 1;
|
|
for (var j = t; j <= t + n; j++){
|
|
set[j].input[1] = n / 20;
|
|
set[j].output[0] = 0.5;
|
|
}
|
|
t += n;
|
|
n = Math.round(Math.random() * 20);
|
|
for (var k = t+1; k <= (t + n) && k < size; k++)
|
|
set[k].input[1] = set[t].input[1];
|
|
t += n;
|
|
}
|
|
|
|
// separate samples between train and test sets
|
|
var trainingSet = []; var testSet = [];
|
|
for (var l = 0; l < size; l++)
|
|
(l < trainingSize ? trainingSet : testSet).push(set[l]);
|
|
|
|
// return samples
|
|
return {
|
|
train: trainingSet,
|
|
test: testSet
|
|
}
|
|
}
|
|
|
|
var iterations = options.iterations || 200;
|
|
var error = options.error || .005;
|
|
var rate = options.rate || [.03, .02];
|
|
var log = options.log === false ? false : options.log || 10;
|
|
var cost = options.cost || this.cost || Trainer.cost.MSE;
|
|
var trainingSamples = options.trainSamples || 7000;
|
|
var testSamples = options.trainSamples || 1000;
|
|
|
|
// samples for training and testing
|
|
var samples = getSamples(trainingSamples, testSamples);
|
|
|
|
// train
|
|
var result = this.train(samples.train, {
|
|
rate: rate,
|
|
log: log,
|
|
iterations: iterations,
|
|
error: error,
|
|
cost: cost
|
|
});
|
|
|
|
return {
|
|
train: result,
|
|
test: this.test(samples.test)
|
|
}
|
|
}
|
|
};
|
|
|
|
// Built-in cost functions
|
|
Trainer.cost = {
|
|
// Eq. 9
|
|
CROSS_ENTROPY: function(target, output)
|
|
{
|
|
var crossentropy = 0;
|
|
for (var i in output)
|
|
crossentropy -= (target[i] * Math.log(output[i]+1e-15)) + ((1-target[i]) * Math.log((1+1e-15)-output[i])); // +1e-15 is a tiny push away to avoid Math.log(0)
|
|
return crossentropy;
|
|
},
|
|
MSE: function(target, output)
|
|
{
|
|
var mse = 0;
|
|
for (var i in output)
|
|
mse += Math.pow(target[i] - output[i], 2);
|
|
return mse / output.length;
|
|
},
|
|
BINARY: function(target, output){
|
|
var misses = 0;
|
|
for (var i in output)
|
|
misses += Math.round(target[i] * 2) != Math.round(output[i] * 2);
|
|
return misses;
|
|
}
|
|
}
|
|
|
|
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module)))
|
|
|
|
/***/ },
|
|
/* 6 */
|
|
/***/ function(module, exports, __webpack_require__) {
|
|
|
|
/* WEBPACK VAR INJECTION */(function(module) {// import
|
|
var Layer = __webpack_require__(3)
|
|
, Network = __webpack_require__(4)
|
|
, Trainer = __webpack_require__(5)
|
|
|
|
/*******************************************************************************************
|
|
ARCHITECT
|
|
*******************************************************************************************/
|
|
|
|
// Collection of useful built-in architectures
|
|
var Architect = {
|
|
|
|
// Multilayer Perceptron
|
|
Perceptron: function Perceptron() {
|
|
|
|
var args = Array.prototype.slice.call(arguments); // convert arguments to Array
|
|
if (args.length < 3)
|
|
throw new Error("not enough layers (minimum 3) !!");
|
|
|
|
var inputs = args.shift(); // first argument
|
|
var outputs = args.pop(); // last argument
|
|
var layers = args; // all the arguments in the middle
|
|
|
|
var input = new Layer(inputs);
|
|
var hidden = [];
|
|
var output = new Layer(outputs);
|
|
|
|
var previous = input;
|
|
|
|
// generate hidden layers
|
|
for (var level in layers) {
|
|
var size = layers[level];
|
|
var layer = new Layer(size);
|
|
hidden.push(layer);
|
|
previous.project(layer);
|
|
previous = layer;
|
|
}
|
|
previous.project(output);
|
|
|
|
// set layers of the neural network
|
|
this.set({
|
|
input: input,
|
|
hidden: hidden,
|
|
output: output
|
|
});
|
|
|
|
// trainer for the network
|
|
this.trainer = new Trainer(this);
|
|
},
|
|
|
|
// Multilayer Long Short-Term Memory
|
|
LSTM: function LSTM() {
|
|
|
|
var args = Array.prototype.slice.call(arguments); // convert arguments to array
|
|
if (args.length < 3)
|
|
throw new Error("not enough layers (minimum 3) !!");
|
|
|
|
var last = args.pop();
|
|
var option = {
|
|
peepholes: Layer.connectionType.ALL_TO_ALL,
|
|
hiddenToHidden: false,
|
|
outputToHidden: false,
|
|
outputToGates: false,
|
|
inputToOutput: true,
|
|
};
|
|
if (typeof last != 'number') {
|
|
var outputs = args.pop();
|
|
if (last.hasOwnProperty('peepholes'))
|
|
option.peepholes = last.peepholes;
|
|
if (last.hasOwnProperty('hiddenToHidden'))
|
|
option.hiddenToHidden = last.hiddenToHidden;
|
|
if (last.hasOwnProperty('outputToHidden'))
|
|
option.outputToHidden = last.outputToHidden;
|
|
if (last.hasOwnProperty('outputToGates'))
|
|
option.outputToGates = last.outputToGates;
|
|
if (last.hasOwnProperty('inputToOutput'))
|
|
option.inputToOutput = last.inputToOutput;
|
|
} else
|
|
var outputs = last;
|
|
|
|
var inputs = args.shift();
|
|
var layers = args;
|
|
|
|
var inputLayer = new Layer(inputs);
|
|
var hiddenLayers = [];
|
|
var outputLayer = new Layer(outputs);
|
|
|
|
var previous = null;
|
|
|
|
// generate layers
|
|
for (var layer in layers) {
|
|
// generate memory blocks (memory cell and respective gates)
|
|
var size = layers[layer];
|
|
|
|
var inputGate = new Layer(size).set({
|
|
bias: 1
|
|
});
|
|
var forgetGate = new Layer(size).set({
|
|
bias: 1
|
|
});
|
|
var memoryCell = new Layer(size);
|
|
var outputGate = new Layer(size).set({
|
|
bias: 1
|
|
});
|
|
|
|
hiddenLayers.push(inputGate);
|
|
hiddenLayers.push(forgetGate);
|
|
hiddenLayers.push(memoryCell);
|
|
hiddenLayers.push(outputGate);
|
|
|
|
// connections from input layer
|
|
var input = inputLayer.project(memoryCell);
|
|
inputLayer.project(inputGate);
|
|
inputLayer.project(forgetGate);
|
|
inputLayer.project(outputGate);
|
|
|
|
// connections from previous memory-block layer to this one
|
|
if (previous != null) {
|
|
var cell = previous.project(memoryCell);
|
|
previous.project(inputGate);
|
|
previous.project(forgetGate);
|
|
previous.project(outputGate);
|
|
}
|
|
|
|
// connections from memory cell
|
|
var output = memoryCell.project(outputLayer);
|
|
|
|
// self-connection
|
|
var self = memoryCell.project(memoryCell);
|
|
|
|
// hidden to hidden recurrent connection
|
|
if (option.hiddenToHidden)
|
|
memoryCell.project(memoryCell, Layer.connectionType.ALL_TO_ELSE);
|
|
|
|
// out to hidden recurrent connection
|
|
if (option.outputToHidden)
|
|
outputLayer.project(memoryCell);
|
|
|
|
// out to gates recurrent connection
|
|
if (option.outputToGates) {
|
|
outputLayer.project(inputGate);
|
|
outputLayer.project(outputGate);
|
|
outputLayer.project(forgetGate);
|
|
}
|
|
|
|
// peepholes
|
|
memoryCell.project(inputGate, option.peepholes);
|
|
memoryCell.project(forgetGate, option.peepholes);
|
|
memoryCell.project(outputGate, option.peepholes);
|
|
|
|
// gates
|
|
inputGate.gate(input, Layer.gateType.INPUT);
|
|
forgetGate.gate(self, Layer.gateType.ONE_TO_ONE);
|
|
outputGate.gate(output, Layer.gateType.OUTPUT);
|
|
if (previous != null)
|
|
inputGate.gate(cell, Layer.gateType.INPUT);
|
|
|
|
previous = memoryCell;
|
|
}
|
|
|
|
// input to output direct connection
|
|
if (option.inputToOutput)
|
|
inputLayer.project(outputLayer);
|
|
|
|
// set the layers of the neural network
|
|
this.set({
|
|
input: inputLayer,
|
|
hidden: hiddenLayers,
|
|
output: outputLayer
|
|
});
|
|
|
|
// trainer
|
|
this.trainer = new Trainer(this);
|
|
},
|
|
|
|
// Liquid State Machine
|
|
Liquid: function Liquid(inputs, hidden, outputs, connections, gates) {
|
|
|
|
// create layers
|
|
var inputLayer = new Layer(inputs);
|
|
var hiddenLayer = new Layer(hidden);
|
|
var outputLayer = new Layer(outputs);
|
|
|
|
// make connections and gates randomly among the neurons
|
|
var neurons = hiddenLayer.neurons();
|
|
var connectionList = [];
|
|
|
|
for (var i = 0; i < connections; i++) {
|
|
// connect two random neurons
|
|
var from = Math.random() * neurons.length | 0;
|
|
var to = Math.random() * neurons.length | 0;
|
|
var connection = neurons[from].project(neurons[to]);
|
|
connectionList.push(connection);
|
|
}
|
|
|
|
for (var j = 0; j < gates; j++) {
|
|
// pick a random gater neuron
|
|
var gater = Math.random() * neurons.length | 0;
|
|
// pick a random connection to gate
|
|
var connection = Math.random() * connectionList.length | 0;
|
|
// let the gater gate the connection
|
|
neurons[gater].gate(connectionList[connection]);
|
|
}
|
|
|
|
// connect the layers
|
|
inputLayer.project(hiddenLayer);
|
|
hiddenLayer.project(outputLayer);
|
|
|
|
// set the layers of the network
|
|
this.set({
|
|
input: inputLayer,
|
|
hidden: [hiddenLayer],
|
|
output: outputLayer
|
|
});
|
|
|
|
// trainer
|
|
this.trainer = new Trainer(this);
|
|
},
|
|
|
|
Hopfield: function Hopfield(size) {
|
|
|
|
var inputLayer = new Layer(size);
|
|
var outputLayer = new Layer(size);
|
|
|
|
inputLayer.project(outputLayer, Layer.connectionType.ALL_TO_ALL);
|
|
|
|
this.set({
|
|
input: inputLayer,
|
|
hidden: [],
|
|
output: outputLayer
|
|
});
|
|
|
|
var trainer = new Trainer(this);
|
|
|
|
var proto = Architect.Hopfield.prototype;
|
|
|
|
proto.learn = proto.learn || function(patterns)
|
|
{
|
|
var set = [];
|
|
for (var p in patterns)
|
|
set.push({
|
|
input: patterns[p],
|
|
output: patterns[p]
|
|
});
|
|
|
|
return trainer.train(set, {
|
|
iterations: 500000,
|
|
error: .00005,
|
|
rate: 1
|
|
});
|
|
};
|
|
|
|
proto.feed = proto.feed || function(pattern)
|
|
{
|
|
var output = this.activate(pattern);
|
|
|
|
var pattern = [];
|
|
for (var i in output)
|
|
pattern[i] = output[i] > .5 ? 1 : 0;
|
|
|
|
return pattern;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extend prototype chain (so every architectures is an instance of Network)
|
|
for (var architecture in Architect) {
|
|
Architect[architecture].prototype = new Network();
|
|
Architect[architecture].prototype.constructor = Architect[architecture];
|
|
}
|
|
|
|
// export
|
|
if (module) module.exports = Architect;
|
|
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module)))
|
|
|
|
/***/ }
|
|
/******/ ]); |