/* * Artificial Intelligence for Humans * Volume 3: Deep Learning and Neural Networks * Java Version * http://www.aifh.org * http://www.jeffheaton.com * * Code repository: * https://github.com/jeffheaton/aifh * * Copyright 2014-2015 by Jeff Heaton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package com.heatonresearch.aifh.ann.train; import com.heatonresearch.aifh.AIFH; import com.heatonresearch.aifh.ann.BasicNetwork; import com.heatonresearch.aifh.ann.Layer; import com.heatonresearch.aifh.ann.activation.ActivationFunction; import com.heatonresearch.aifh.ann.train.error.ErrorFunction; import com.heatonresearch.aifh.error.ErrorCalculation; /** * A utility class used to help calculate the gradient of the error function for neural networks. */ public class GradientCalc { /** * The network to train. */ private final BasicNetwork network; /** * The actual values from the neural network. */ private final double[] actual; /** * The deltas for each layer. */ private final double[] layerDelta; /** * The output from each layer. */ private final double[] layerOutput; /** * The sums. */ private final double[] layerSums; /** * The gradients. */ private final double[] gradients; /** * The weights and thresholds. */ private final double[] weights; /** * The owner of the gradient calculation. */ private final GradientCalcOwner owner; /** * The error function to use. */ private final ErrorFunction errorFunction; /** * Construct the gradient calculation class. * @param theNetwork The network to use. * @param ef The error function to use. * @param theOwner The owner (usually a trainer). */ public GradientCalc(final BasicNetwork theNetwork, ErrorFunction ef, GradientCalcOwner theOwner) { this.network = theNetwork; this.errorFunction = ef; this.layerDelta = new double[this.network.getLayerOutput().length]; this.gradients = new double[this.network.getWeights().length]; this.actual = new double[this.network.getOutputCount()]; this.weights = this.network.getWeights(); this.layerOutput = this.network.getLayerOutput(); this.layerSums = this.network.getLayerSums(); this.owner = theOwner; } /** * @return The network being processed. */ public BasicNetwork getNetwork() { return this.network; } /** * @return The weights for this network. */ public double[] getWeights() { return this.weights; } /** * Process one training set element. * @param errorCalc The error calculation. * @param input The network input. * @param ideal The ideal values. */ public void process(ErrorCalculation errorCalc, double[] input, double[] ideal) { this.network.compute(input, this.actual); errorCalc.updateError(this.actual, ideal, 1.0); // Calculate error for the output layer. int outputLayerIndex = this.network.getLayers().size() - 1; ActivationFunction outputActivation = this.network.getLayers().get(outputLayerIndex).getActivation(); this.errorFunction.calculateError( outputActivation, this.layerSums, this.layerOutput, ideal, this.actual, this.layerDelta, 0, 1.0); // Apply regularization, if requested. if (this.owner.getL1() > AIFH.DEFAULT_PRECISION || this.owner.getL1() > AIFH.DEFAULT_PRECISION) { double[] lp = new double[2]; calculateRegularizationPenalty(lp); for (int i = 0; i < this.actual.length; i++) { double p = (lp[0] * this.owner.getL1()) + (lp[1] * this.owner.getL2()); this.layerDelta[i] += p; } } // Propagate backwards (chain rule from calculus). for (int i = this.network.getLayers().size() - 1; i > 0; i--) { Layer layer = this.network.getLayers().get(i); layer.computeGradient(this); } } /** * Reset all gradients to zero. */ public void reset() { for (int i = 0; i < this.gradients.length; i++) { this.gradients[i] = 0; } } /** * @return the gradients */ public double[] getGradients() { return this.gradients; } /** * Apply a regularization penalty, such as that from L1/L2 regularization. * @param l The penalty. */ public void calculateRegularizationPenalty(double[] l) { for (int i = 0; i < this.network.getLayers().size() - 1; i++) { layerRegularizationPenalty(i, l); } } /** * Apply a regularization penalty, such as that from L1/L2 regularization. * @param fromLayer The from layer. * @param l The penalty. */ public void layerRegularizationPenalty(final int fromLayer, final double[] l) { final int fromCount = this.network.getLayerTotalNeuronCount(fromLayer); final int toCount = this.network.getLayers().get(fromLayer + 1).getCount(); for (int fromNeuron = 0; fromNeuron < fromCount; fromNeuron++) { for (int toNeuron = 0; toNeuron < toCount; toNeuron++) { double w = this.network.getWeight(fromLayer, fromNeuron, toNeuron); l[0] += Math.abs(w); l[1] += w * w; } } } /** * @return The layer deltas used to calculate the gradient. */ public double[] getLayerDelta() { return this.layerDelta; } }