UNPKG

encog

Version:

Encog is a NodeJs ES6 framework based on the Encog Machine Learning Framework by Jeff Heaton, plus some the of basic data manipulation helpers.

200 lines (159 loc) 5.84 kB
const BasicTraining = require(PATHS.TRAINING + 'basic'); const ErrorCalculation = require(PATHS.ERROR_CALCULATION + 'errorCalculation'); const ArrayUtils = require(PATHS.PREPROCESSING + 'array'); const Adam = require(PATHS.SGD + 'update/adam'); const CrossEntropyErrorFunction = require(PATHS.ERROR_FUNCTIONS + 'crossEntropy'); const EncogLog = require(PATHS.UTILS + 'encogLog'); class StochasticGradientDescent extends BasicTraining { constructor(network, input, output, updateRule = new Adam()) { super(); this.input = input; this.output = output; this.method = network; this.flat = network.getFlat(); this.layerDelta = ArrayUtils.newFloatArray(this.flat.getLayerOutput().length); this.gradients = ArrayUtils.newFloatArray(this.flat.getWeights().length); this.errorCalculation = new ErrorCalculation(); this.learningRate = 0.001; this.momentum = 0.9; this.updateRule = updateRule; this.errorFunction = new CrossEntropyErrorFunction(); this.iterationCount = 0; } process(input, output) { let i; let p; this.errorCalculation = new ErrorCalculation(); const actual = this.flat.compute(input); this.errorCalculation.updateError(actual, output); // Calculate error for the output layer. this.errorFunction.calculateError( this.flat.getActivationFunctions()[0], this.flat.getLayerSums(), this.flat.getLayerOutput(), output, actual, this.layerDelta, 0); // Apply regularization, if requested. if (this.l1 > PATHS.CONSTANTS.DEFAULT_DOUBLE_EQUAL || this.l2 > PATHS.CONSTANTS.DEFAULT_DOUBLE_EQUAL) { const lp = new ArrayUtils.newFloatArray(2); this.calculateRegularizationPenalty(lp); for (i = 0; i < actual.length; i++) { p = (lp[0] * this.l1) + (lp[1] * this.l2); this.layerDelta[i] += p; } } // Propagate backwards (chain rule from calculus). for (i = this.flat.getBeginTraining(); i < this.flat.getEndTraining(); i++) { this.processLevel(i); } } update() { if (this.getIteration() === 0) { this.updateRule.init(this); } this.updateRule.update(this.gradients, this.flat.getWeights()); this.setError(this.errorCalculation.calculate()); this.postIteration(); ArrayUtils.fill(this.gradients, 0); this.errorCalculation.reset(); } resetError() { this.errorCalculation.reset(); } /** * @param currentLevel {Number} * */ processLevel(currentLevel) { const fromLayerIndex = this.flat.getLayerIndex()[currentLevel + 1]; const toLayerIndex = this.flat.getLayerIndex()[currentLevel]; const fromLayerSize = this.flat.getLayerCounts()[currentLevel + 1]; const toLayerSize = this.flat.getLayerFeedCounts()[currentLevel]; const index = this.flat.getWeightIndex()[currentLevel]; const activation = this.flat.getActivationFunctions()[currentLevel]; // handle weights const weights = this.flat.getWeights(); const layerOutput = this.flat.getLayerOutput(); const layerSums = this.flat.getLayerSums(); let yi = fromLayerIndex; for (let y = 0; y < fromLayerSize; y++) { const output = layerOutput[yi]; let sum = 0; let wi = index + y; const loopEnd = toLayerIndex + toLayerSize; for (let xi = toLayerIndex; xi < loopEnd; xi++, wi += fromLayerSize) { this.gradients[wi] += output * this.layerDelta[xi]; sum += weights[wi] * this.layerDelta[xi]; } this.layerDelta[yi] = sum * (activation.derivativeFunction(layerSums[yi], layerOutput[yi])); yi++; } } iteration() { for (let i = 0; i < this.input.length; i++) { this.process(this.input[i], this.output[i]); } if (this.getIteration() === 0) { this.updateRule.init(this); } this.preIteration(); this.update(); this.postIteration(); EncogLog.info(`Training iteration #${this.getIteration()} done, error: ${this.error}`); EncogLog.print(); } getLearningRate() { return this.learningRate; } setLearningRate(rate) { this.learningRate = rate; } getMomentum() { return this.momentum; } setMomentum(m) { this.momentum = m; } getL1() { return l1; } setL1(l1) { this.l1 = l1; } getL2() { return l2; } setL2(l2) { this.l2 = l2; } /** * @param l {Array} * */ calculateRegularizationPenalty(l) { for (let i = 0; i < this.flat.getLayerCounts().length - 1; i++) { this.layerRegularizationPenalty(i, l); } } /** * @param fromLayer {Number} * @param l {Array} * */ layerRegularizationPenalty(fromLayer, l) { const fromCount = this.flat.getLayerTotalNeuronCount(fromLayer); const toCount = this.flat.getLayerNeuronCount(fromLayer + 1); for (let fromNeuron = 0; fromNeuron < fromCount; fromNeuron++) { for (let toNeuron = 0; toNeuron < toCount; toNeuron++) { let w = this.flat.getWeight(fromLayer, fromNeuron, toNeuron); l[0] += Math.abs(w); l[1] += w * w; } } } getFlat() { return this.flat; } getUpdateRule() { return this.updateRule; } setUpdateRule(updateRule) { this.updateRule = updateRule; } } module.exports = StochasticGradientDescent;