neuralnetwork
Version:
Rudimentary Neural Network in Typescript
249 lines (188 loc) • 8.04 kB
text/typescript
/*global module, require*/
import {Layer} from './layers';
import {Connection} from './connections';
export function meanSquaredError(errors: Array<number>): number {
var mean = errors.reduce((prev: number, curr: number, index: number) => {
return prev + Math.pow(curr, 2);
}, 0);
return mean / errors.length;
}
export class Network {
layers: {[name: string]: Layer}; //Map<string, Layer>;
forwardConnections: {[name: string]: Array<Connection>}; //Map<string, Array<Connection>>;
backwardConnections: {[name: string]: Array<Connection>}; //Map<string, Array<Connection>>;
rootLayer: Layer | null;
outputLayer: Layer | null;
constructor() {
this.layers = {}; //new Map<string, Layer>();
this.forwardConnections = {}; //new Map<string, Array<Connection>>();
this.backwardConnections = {}; //new Map<string, Array<Connection>>();
this.rootLayer = null;
this.outputLayer = null;
}
setRootLayer(layer: Layer) {
this.rootLayer = layer;
this.layers[layer.name] = layer;
}
setOutputLayer(layer: Layer) {
this.outputLayer = layer;
this.layers[layer.name] = layer;
}
addLayer(layer: Layer) {
this.layers[layer.name] = layer;
}
addConnection(con: Connection) {
this.forwardConnections[con.inputLayer.name] = this.forwardConnections[con.inputLayer.name] || [];
this.forwardConnections[con.inputLayer.name].push(con);
this.backwardConnections[con.outputLayer.name] = this.backwardConnections[con.outputLayer.name] || [];
this.backwardConnections[con.outputLayer.name].push(con);
this.layers[con.inputLayer.name] = con.inputLayer;
this.layers[con.outputLayer.name] = con.outputLayer;
}
resetLayers() {
for (let prop in this.layers) {
if (this.layers.hasOwnProperty(prop)) {
this.layers[prop].resetBuffers();
}
}
}
forwardPropogate(input: Array<number>): Array<number> {
var self = this,
currentLayer: Layer = null,
connections: Array<Connection> = null,
openSet: Array<Layer> = [],
outputLayerReached = false;
// Located outside the for loop: see <http://jsperf.com/closure-vs-name-function-in-a-loop/2>. jslint also complains otherwise
function forwardEachConnectionIn(connections: Array<Connection>) {
connections.forEach(function (connection: Connection) {
connection.forward(); // If there is a connection from output layer to another, there is a final 'push'
openSet.push(connection.outputLayer);
});
}
this.rootLayer.inputBuffer = input;
// Initial condition for Breadth First exploration
openSet.push(this.rootLayer);
while (openSet.length > 0) {
currentLayer = openSet.shift();
currentLayer.forward();
connections = this.forwardConnections[currentLayer.name] || [];
forwardEachConnectionIn(connections);
if (currentLayer === this.outputLayer) {
outputLayerReached = true;
break;
}
}
return this.outputLayer.outputBuffer;
}
backwardPropogate(errorVector: Array<number>) {
var self = this,
currentLayer: Layer = null,
connections: Array<Connection> = null,
openSet: Array<Layer> = [];
function backwardEachConnectionIn(connections: Array<Connection>) {
connections.forEach(function (connection: Connection) {
connection.backward();
openSet.push(connection.inputLayer);
});
}
this.forEachConnection(function (connection: Connection) {
connection.resetDerivatives();
});
this.outputLayer.outputError = errorVector;
// Initial condition for Breadth First exploration
openSet.push(self.outputLayer);
while (openSet.length > 0) {
currentLayer = openSet.shift();
currentLayer.backward();
connections = self.backwardConnections[currentLayer.name] || [];
backwardEachConnectionIn(connections);
// Unless recurrent...
// TODO: Non 1 to 1 typescript conversion. inputLayer changed to rootLayer
if (currentLayer === this.rootLayer) {
break;
}
}
}
forEachConnection(callback: (el: Connection) => void) {
for (let i in this.forwardConnections) {
if (this.forwardConnections.hasOwnProperty(i)) {
this.forwardConnections[i].forEach(function (el: Connection, index: number) {
callback(el);
});
}
}
}
// TODO
exploreForward (layerCallback: Function, connectionsCallback: Function) {
'use strict';
var self = this,
currentOpenSet: Array<Layer> = [],
nextOpenSet: Array<Layer> = [],
currentLayer: Layer,
connections: Array<Connection>;
// TODO: Non 1-1 typescript conversion, inputLayer to rootLayer
currentOpenSet.push(this.rootLayer);
while (currentOpenSet.length > 0 || nextOpenSet.length > 0) {
// tick tock
if (currentOpenSet.length === 0) {
currentOpenSet = nextOpenSet;
nextOpenSet = [];
}
currentLayer = currentOpenSet.shift();
layerCallback(currentLayer);
connections = this.forwardConnections[currentLayer.name] || [];
nextOpenSet.push(connectionsCallback(connections));
// Unless recurrent...
// TODO: Non 1-1 typescript conversion, inputLayer to rootLayer
if (currentLayer === this.rootLayer) {
break;
}
}
}
train(input: Array<number>, targetVector: Array<number>, options?: any) {
if (input.length !== this.rootLayer.inputBuffer.length) {
throw new Error('Wrong input dimensions');
}
if (targetVector.length !== this.outputLayer.outputBuffer.length) {
throw new Error('Wrong output dimensions');
}
var self = this,
connections: Array<Connection>,
errorVector: Array<number> = [],
options = options || {},
step: number = options.step || 0.21,
momentum: number = options.momentum || 0.1,
dropout: number = options.dropout || 0.2;
this.forEachConnection(function (con: Connection) {
con.resetDerivatives();
// TODO: Make type friendly
if (!con._lastStep) {
con._lastStep = con.parameters.map(function (el, index) {
return 0;
});
}
});
this.resetLayers();
this.forwardPropogate(input);
errorVector = targetVector.map(function (target, index) {
return target - self.outputLayer.outputBuffer[index];
});
this.backwardPropogate(errorVector);
this.forEachConnection(function (connection) {
if (connection.parameters.length !== connection.derivatives.length) {
throw new Error('Wrong derivative dims');
}
connection.parameters = connection.parameters.map(function (el, index) {
var calculatedStep: number, newParam: number;
if (Math.random() < dropout) {
return el;
}
calculatedStep = step * connection.derivatives[index] + momentum * connection._lastStep[index];
newParam = el + calculatedStep;
connection._lastStep[index] = calculatedStep;
return newParam;
});
});
return meanSquaredError(errorVector);
}
}