NeuralNetwork.js

import Matrix from '@astuanax/funmatrix'
import sigmoid from './util/sigmoid'

/**
 * @class NeuralNetwork
 * @param {Number} inputNodes
 * @param {Nmber} hiddenNodes
 * @param {Number} outputNodes
 */
class NeuralNetwork {
  constructor (...values) {
    [this.inputNodes, this.hiddenNodes, this.outputNodes] = values

    const f = e => Math.random() * 2 - 1
    this.weightsIh = Matrix.random(f, this.hiddenNodes, this.inputNodes)
    this.weightsHo = Matrix.random(f, this.outputNodes, this.hiddenNodes)

    this.biasH = Matrix.random(f, this.hiddenNodes, 1)
    this.biasO = Matrix.random(f, this.outputNodes, 1)

    this.setActivationFunction()
    this.setLearningRate()

    // console.log('Init')
    // console.log('weightsIh', this.weightsIh.__value)
    // console.log('weightsHo', this.weightsIh.__value)
    // console.log('biasH', this.biasH.__value)
    // console.log('biasO', this.biasO.__value)

    this.type = 'NeuralNetwork'
  }
}
/**
 * @memberOf NeuralNetwork
 * @function NeuralNetwork#setActivationFunction
 * @param {Function} func
 */
NeuralNetwork.prototype.setActivationFunction = function (func = sigmoid) {
  this.activationFunction = func
}

/**
 * @memberOf NeuralNetwork
 * @function NeuralNetwork#setLearningRate
 * @param {Number} learningRate
 */
NeuralNetwork.prototype.setLearningRate = function (learningRate = 0.1) {
  this.learningRate = learningRate
}

/**
 * @memberOf NeuralNetwork
 * @function NeuralNetwork#predict
 * @param {Array} inputArray
 * @returns {Array}
 */
NeuralNetwork.prototype.predict = function (inputArray) {
  // Generating the Hidden Outputs
  let inputs = Matrix.fromArray(inputArray)
  let hidden = Matrix.dot(this.weightsIh, inputs)
  hidden = Matrix.of(hidden).add(this.biasH)
  // activation function!
  hidden = hidden.map(row => row.map(this.activationFunction.func))

  // Generating the output's output!
  let output = Matrix.dot(this.weightsHo, hidden)
  output = Matrix.of(output).add(this.biasO)
  output = output.map(row => row.map(this.activationFunction.func))
  // Sending back to the caller!
  return output.toArray()
}

/**
 * @memberOf NeuralNetwork
 * @function NeuralNetwork#train
 * @param {Array} inputArray
 * @param {Array} targetArray
 */
NeuralNetwork.prototype.train = function (inputArray, targetArray) {
  const dotAddMap = (weight, input, bias) => Matrix.dot(weight, input).add(bias).map(row => row.map(this.activationFunction.func))
  const mapMulMul = (layer, error) => layer.map(row => row.map(this.activationFunction.dfunc)).multiply(error).multiply(this.learningRate)

  // Generating the Hidden Outputs
  let inputs = Matrix.fromArray(inputArray)

  let hidden = dotAddMap(this.weightsIh, inputs, this.biasH)
  // Matrix.dot(this.weightsIh, inputs)
  // .add(this.biasH)
  // .map(row => row.map(this.activationFunction.func))

  // Generating the output's output!
  let outputs = dotAddMap(this.weightsHo, hidden, this.biasO)
  // Matrix.dot(this.weightsHo, hidden)
  // .add(this.biasO)
  // .map(row => row.map(this.activationFunction.func))

  // Calculate the error
  // ERROR = TARGETS - OUTPUTS
  let outputErrors = Matrix.fromArray(targetArray).subtract(outputs)

  // let gradient = outputs * (1 - outputs);
  // Calculate gradient
  let gradients = mapMulMul(outputs, outputErrors)
  // outputs.map(row => row.map(this.activationFunction.dfunc))
  // .multiply(outputErrors)
  // .multiply(this.learningRate)

  // Calculate deltas
  let hiddenT = Matrix.transpose(hidden)
  let weightHoDeltas = Matrix.dot(gradients, hiddenT)

  // Adjust the weights by deltas
  this.weightsHo = this.weightsHo.add(weightHoDeltas)

  // Adjust the bias by its deltas (which is just the gradients)
  this.biasO = this.biasO.add(gradients)

  // Calculate the hidden layer errors
  let whoT = Matrix.transpose(this.weightsHo)
  let hiddenErrors = Matrix.dot(whoT, outputErrors)

  // Calculate hidden gradient
  let hiddenGradient = mapMulMul(hidden, hiddenErrors)
  // hidden.map(row => row.map(this.activationFunction.dfunc))
  // .multiply(hiddenErrors)
  // .multiply(this.learningRate)

  // Calculate input->hidden deltas
  let inputsT = Matrix.transpose(inputs)
  let weightIhDeltas = Matrix.dot(hiddenGradient, inputsT)

  this.weightsIh = this.weightsIh.add(weightIhDeltas)
  // Adjust the bias by its deltas (which is just the gradients)
  this.biasH = this.biasH.add(hiddenGradient)
}

export default NeuralNetwork