Home Reference Source Repository

lib/NeuralNetwork/NeuralNetwork.js

import { Matrix, RandomGaussianGenerator, multiplyMatricies, arrayToMatrix } from 'stones';
import { sigmoidCurve, sigmoidDerivative } from '../curves';



/**
Class representing a Neural Network
*/
export class NeuralNetwork {

	/**
	Create a NeuralNetwork
	@constructor
	@param {Int} inputNodes - The number of input nodes
	@param {Int} hiddenNodes - The number of hidden nodes
	@param {Int} outputNodes - The number of output nodes
	@param {Int} learningRate - The learning rate for training
	*/
	constructor(inputNodes, hiddenNodes, outputNodes, learningRate) {
		if(!inputNodes || !hiddenNodes || !outputNodes || !learningRate) {
			throw new Error('NeuralNetwork: an argument is missing');
		}

		//General Properties
		this.inputNodes = inputNodes;
		this.hiddenNodes = hiddenNodes;
		this.outputNodes = outputNodes;
		this.learningRate = learningRate;

		//Matricies
		this.inputToHiddenMatrix = new Matrix(hiddenNodes, inputNodes);
		this.hiddenToOutputMatrix = new Matrix(outputNodes, hiddenNodes);

		//Utilities
		this.randGen = new RandomGaussianGenerator().next;


		//Randomize Matricies
		this.inputToHiddenMatrix = this.inputToHiddenMatrix.map(() => this.randGen());
		this.hiddenToOutputMatrix = this.hiddenToOutputMatrix.map(() => this.randGen());

	}

	/**
	Returns a copy of the NeuralNetwork
	@return {NeuralNetwork} A NeuralNetwork object
	*/
	copy() {
		let networkCopy = new NeuralNetwork(this.inputNodes, this.hiddenNodes, this.outputNodes, this.learningRate);
		networkCopy.inputToHiddenMatrix = this.inputToHiddenMatrix.copy();
		networkCopy.hiddenToOutputMatrix = this.hiddenToOutputMatrix.copy();

		return networkCopy;
	}


	/**
	Train the NeuralNetwork
	@param {array[number]} inputArr - A single dimensional array of number inputs
	@param {array[number]} targetArr - A single dimensional array of numbers representing expected outputs
	@return {object} An object containing properties representing errors between the Network's output and target outputs and errors in the hidden node layer
	*/
	train(inputArr, targetArr) {
		//Use Weights to make a guess
		let evalResults = this.eval(inputArr);
		
		//Calculate Error
		const targetMatrix = new Matrix(arrayToMatrix(targetArr,targetArr.length, 1));
		const errors = targetMatrix.copy().subtract(evalResults.outputMatrix);
		
		//Calculate Hidden Errors
		const hiddenOutputMatrix_transposed = this.hiddenToOutputMatrix.copy().transpose();
		const hiddenErrors = new Matrix(multiplyMatricies(hiddenOutputMatrix_transposed.matrix, errors.matrix));
		
		//Calculate Gradients
		const gradient = evalResults.outputMatrix.map(sigmoidDerivative).hadamardProduct(errors).multiply(this.learningRate);
		const hiddenGradient = evalResults.hiddenOutputMatrix.map(sigmoidDerivative).hadamardProduct(hiddenErrors).multiply(this.learningRate);
		
		//Calculate Change in Outputs
		const hiddenToOutputChange = gradient.copy().multiply(evalResults.hiddenOutputMatrix.copy().transpose());
		const inputToHiddenChange = hiddenGradient.copy().multiply(evalResults.inputMatrix.copy().transpose());
		
		//Update weights
		this.hiddenToOutputMatrix.add(hiddenToOutputChange);
		this.inputToHiddenMatrix.add(inputToHiddenChange);


		return {
			errors,
			hiddenErrors,
		};
	}

	/**
	Evaluate inputs based on the current state of the NeuralNetwork
	@param {array[number]} inputArr - A single dimensional array of number inputs
	@return {object} An object containing matricies representing the input, hidden node outputs, and final outputs
	*/
	eval(inputArr) {
		let inputMatrix = new Matrix(arrayToMatrix(inputArr,inputArr.length, 1));

		let hiddenOutputMatrix = new Matrix(multiplyMatricies(this.inputToHiddenMatrix.matrix, inputMatrix.matrix));
		hiddenOutputMatrix = hiddenOutputMatrix.map(sigmoidCurve);

		return {
			inputMatrix,
			hiddenOutputMatrix,
			outputMatrix : new Matrix(multiplyMatricies(this.hiddenToOutputMatrix.matrix, hiddenOutputMatrix.matrix)).map(sigmoidCurve),
		};

	}
}