/*
* 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.ann.BasicNetwork;
import com.heatonresearch.aifh.ann.train.error.CrossEntropyErrorFunction;
import com.heatonresearch.aifh.error.ErrorCalculation;
import com.heatonresearch.aifh.error.ErrorCalculationMSE;
import com.heatonresearch.aifh.general.data.BasicData;
import com.heatonresearch.aifh.learning.LearningMethod;
import java.util.List;
/**
* One problem with the backpropagation algorithm is that the magnitude of the
* partial derivative is usually too large or too small. Further, the learning
* rate is a single value for the entire neural network. The resilient
* propagation learning algorithm uses a special update value(similar to the
* learning rate) for every neuron connection. Further these update values are
* automatically determined, unlike the learning rate of the backpropagation
* algorithm.
*
* There are a total of three parameters that must be provided to the resilient
* training algorithm. Defaults are provided for each, and in nearly all cases,
* these defaults are acceptable. This makes the resilient propagation algorithm
* one of the easiest and most efficient training algorithms available.
*
* It is also important to note that RPROP does not work well with online training.
* You should always use a batch size bigger than one. Typically the larger the better.
* By default a batch size of zero is used, zero means to include the entire training
* set in the batch.
*
* The optional parameters are:
*
* zeroTolerance - How close to zero can a number be to be considered zero. The
* default is 0.00000000000000001.
*
* initialUpdate - What are the initial update values for each matrix value. The
* default is 0.1.
*
* maxStep - What is the largest amount that the update values can step. The
* default is 50.xw
*
*/
public class ResilientPropagation implements GradientCalcOwner, LearningMethod {
/**
* The network to train.
*/
private final BasicNetwork network;
/**
* The training data.
*/
private final List<BasicData> training;
/**
* The gradients.
*/
private final GradientCalc gradients;
/**
* The weight delta from the last training iteration.
*/
private final double[] lastDelta;
/**
* The gradients from the last training iteration.
*/
private final double[] lastGradients;
/**
* The error calculation method to use.
*/
private final ErrorCalculation errorCalc = new ErrorCalculationMSE();
/**
* The current error.
*/
private double currentError = 1.0;
/**
* The L1 regularization.
*/
private double l1;
/**
* The L2 regularization.
*/
private double l2;
/**
* The current update values.
*/
private final double[] updateValues;
/**
* The POSITIVE ETA value. This is specified by the resilient propagation
* algorithm. This is the percentage by which the deltas are increased by if
* the partial derivative is greater than zero.
*/
public static final double POSITIVE_ETA = 1.2;
/**
* The NEGATIVE ETA value. This is specified by the resilient propagation
* algorithm. This is the percentage by which the deltas are increased by if
* the partial derivative is less than zero.
*/
public static final double NEGATIVE_ETA = 0.5;
/**
* The minimum delta value for a weight matrix value.
*/
public static final double DELTA_MIN = 1e-6;
/**
* The starting update for a delta.
*/
public static final double DEFAULT_INITIAL_UPDATE = 0.1;
/**
* The maximum amount a delta can reach.
*/
public static final double DEFAULT_MAX_STEP = 50;
public ResilientPropagation(BasicNetwork theNetwork, List<BasicData> theTraining) {
this.network = theNetwork;
this.training = theTraining;
this.gradients = new GradientCalc(this.network, new CrossEntropyErrorFunction(), this);
this.lastDelta = new double[theNetwork.getWeights().length];
this.updateValues = new double[theNetwork.getWeights().length];
this.lastGradients = new double[theNetwork.getWeights().length];
for (int i = 0; i < this.updateValues.length; i++) {
this.updateValues[i] = ResilientPropagation.DEFAULT_INITIAL_UPDATE;
}
}
/**
* {@inheritDoc}
*/
@Override
public void iteration() {
this.gradients.reset();
this.errorCalc.clear();
// Calculate gradients for entire training set, RPROP does not do online.
for (BasicData element : this.training) {
this.gradients.process(this.errorCalc, element.getInput(), element.getIdeal());
}
this.currentError = this.errorCalc.calculate();
// Apply the gradients according to the RPROP algorithm.
for(int i=0;i<this.gradients.getGradients().length;i++) {
double delta = calculateWeightDelta(this.gradients.getGradients(), this.lastGradients, i);
this.lastGradients[i] = this.gradients.getGradients()[i];
this.lastDelta[i] = delta;
this.network.getWeights()[i]+=delta;
}
}
/**
* Calculate the change in weights.
* @param gradients The gradients.
* @param lastGradient The last graidents.
* @param index The weight currently being updated.
* @return The weight change.
*/
public double calculateWeightDelta(final double[] gradients,
final double[] lastGradient, final int index) {
// multiply the current and previous gradient, and take the
// sign. We want to see if the gradient has changed its sign.
final int change = (int)Math.signum(gradients[index] * lastGradient[index]);
double weightChange = 0;
// if the gradient has retained its sign, then we increase the
// delta so that it will converge faster
if (change > 0) {
double delta = this.updateValues[index]
* ResilientPropagation.POSITIVE_ETA;
delta = Math.min(delta, ResilientPropagation.DEFAULT_MAX_STEP);
weightChange = -Math.signum(gradients[index]) * delta;
this.updateValues[index] = delta;
lastGradient[index] = gradients[index];
} else if (change < 0) {
// if change<0, then the sign has changed, and the last
// delta was too big
double delta = this.updateValues[index]
* ResilientPropagation.NEGATIVE_ETA;
delta = Math.max(delta, ResilientPropagation.DELTA_MIN);
this.updateValues[index] = delta;
weightChange = -this.lastDelta[index];
// set the previous gradent to zero so that there will be no
// adjustment the next iteration
lastGradient[index] = 0;
} else if (change == 0) {
// if change==0 then there is no change to the delta
final double delta = this.updateValues[index];
weightChange = -Math.signum(gradients[index]) * delta;
lastGradient[index] = gradients[index];
}
// apply the weight change, if any
return weightChange;
}
/**
* @return The error from the last training iteration.
*/
@Override
public double getLastError() {
return this.currentError;
}
/**
* @return True, if we are done learning. Not all learning algorithms know when they are done, in this case
* false is always returned.
*/
@Override
public boolean done() {
return false;
}
/**
* @return A string that indicates the status of training.
*/
@Override
public String getStatus() {
return "";
}
/**
* Should be called after the last iteration to make sure training completes any final tasks.
*/
@Override
public void finishTraining() {
}
/**
* @return How much to apply l1 regularization penalty, 0 (default) for none.
*/
@Override
public double getL1() {
return this.l1;
}
/**
* @return How much to apply l2 regularization penalty, 0 (default) for none.
*/
@Override
public double getL2() {
return this.l2;
}
/**
* Set the L1 regularization multiplier.
* @param theL1 The L1 regularization multiplier.
*/
public void setL1(double theL1) {
this.l1 = theL1;
}
/**
* Set the L2 regularization multiplier.
* @param theL2 The L2 regularization multiplier.
*/
public void setL2(double theL2) {
this.l2 = theL2;
}
}