/*
* Artificial Intelligence for Humans
* Volume 1: Fundamental Algorithms
* Java Version
* http://www.aifh.org
* http://www.jeffheaton.com
*
* Code repository:
* https://github.com/jeffheaton/aifh
* Copyright 2013 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.learning;
import com.heatonresearch.aifh.learning.score.ScoreFunction;
/**
* The Nelder-Mead method is a commonly used parameter optimization method that
* can be used for machine learning. It typically provides a good error
* rate and is relatively fast.
* <p/>
* Nelder-Mead must build a simplex, which is an n*(n+1) matrix of weights. If
* you have a large number of weights, this matrix can quickly overflow memory.
* <p/>
* The biggest enhancement that is needed for this trainer is to make use of
* multi-threaded code to evaluate the speed evaluations when training on a
* multi-core.
* <p/>
* This implementation is based on the source code provided by John Burkardt
* (http://people.sc.fsu.edu/~jburkardt/)
* <p/>
* http://people.sc.fsu.edu/~jburkardt/c_src/asa047/asa047.c
*/
public class TrainNelderMead implements LearningMethod {
/**
* True if the network has converged, and no further training is needed.
*/
private boolean converged = false;
/**
* Used to calculate the centroid.
*/
public final static double CCOEFF = 0.5;
public final static double ECOEFF = 2.0;
public final static double EPS = 0.001;
public final static double RCOEFF = 1.0;
private double del;
private int jcount;
private final int nn;
private final double[] p;
private final double[] p2star;
private final double[] pbar;
private final double[] pstar;
private final double rq;
private final double[] y;
private final double[] start;
private final double[] trainedWeights;
private final double[] step;
private final int konvge;
private final MachineLearningAlgorithm algorithm;
private final ScoreFunction score;
private double lastError;
public TrainNelderMead(final MachineLearningAlgorithm theAlgorithm, final ScoreFunction theScore) {
this(theAlgorithm, theScore, 100);
}
public TrainNelderMead(final MachineLearningAlgorithm theAlgorithm, final ScoreFunction theScore, final double stepValue) {
this.algorithm = theAlgorithm;
this.score = theScore;
this.start = this.algorithm.getLongTermMemory().clone();
this.trainedWeights = this.algorithm.getLongTermMemory().clone();
final int n = this.start.length;
this.p = new double[n * (n + 1)];
this.pstar = new double[n];
this.p2star = new double[n];
this.pbar = new double[n];
this.y = new double[n + 1];
this.nn = n + 1;
this.del = 1.0;
this.rq = 0.000001 * n;
this.step = new double[this.start.length];
this.jcount = this.konvge = 500;
for (int i = 0; i < this.step.length; i++) {
this.step[i] = stepValue;
}
}
/**
* Calculate the error for the neural network with a given set of weights.
*
* @param weights The weights to use.
* @return The current error.
*/
public double fn(final double[] weights) {
System.arraycopy(weights, 0, this.algorithm.getLongTermMemory(), 0, weights.length);
return score.calculateScore(this.algorithm);
}
/**
* {@inheritDoc}
*/
@Override
public boolean done() {
return this.converged;
}
/**
* {@inheritDoc}
*/
@Override
public String getStatus() {
return "";
}
/**
* {@inheritDoc}
*/
@Override
public void iteration() {
if (this.converged) {
return;
}
final int n = this.start.length;
System.arraycopy(this.start, 0, this.p, n * n, n);
this.y[n] = fn(this.start);
for (int j = 0; j < n; j++) {
final double x = this.start[j];
this.start[j] = this.start[j] + this.step[j] * this.del;
System.arraycopy(this.start, 0, this.p, j * n, n);
this.y[j] = fn(this.start);
this.start[j] = x;
}
/*
* The simplex construction is complete.
*
* Find highest and lowest Y values. YNEWLO = Y(IHI) indicates the
* vertex of the simplex to be replaced.
*/
double ylo = this.y[0];
int ilo = 0;
for (int i = 1; i < this.nn; i++) {
if (this.y[i] < ylo) {
ylo = this.y[i];
ilo = i;
}
}
/*
* Inner loop.
*/
double ynewlo;
double z;
for (; ; ) {
/*
* if (kcount <= icount) { break; }
*/
ynewlo = this.y[0];
int ihi = 0;
for (int i = 1; i < this.nn; i++) {
if (ynewlo < this.y[i]) {
ynewlo = this.y[i];
ihi = i;
}
}
/*
* Calculate PBAR, the centroid of the simplex vertices excepting
* the vertex with Y value YNEWLO.
*/
for (int i = 0; i < n; i++) {
z = 0.0;
for (int j = 0; j < this.nn; j++) {
z = z + this.p[i + j * n];
}
z = z - this.p[i + ihi * n];
this.pbar[i] = z / n;
}
/*
* Reflection through the centroid.
*/
for (int i = 0; i < n; i++) {
this.pstar[i] = this.pbar[i] + RCOEFF
* (this.pbar[i] - this.p[i + ihi * n]);
}
final double ystar = fn(this.pstar);
/*
* Successful reflection, so extension.
*/
final double y2star;
if (ystar < ylo) {
for (int i = 0; i < n; i++) {
this.p2star[i] = this.pbar[i] + ECOEFF
* (this.pstar[i] - this.pbar[i]);
}
y2star = fn(this.p2star);
/*
* Check extension.
*/
if (ystar < y2star) {
System.arraycopy(this.pstar, 0, this.p, ihi * n, n);
this.y[ihi] = ystar;
}
/*
* Retain extension or contraction.
*/
else {
System.arraycopy(this.p2star, 0, this.p, ihi * n, n);
this.y[ihi] = y2star;
}
}
/*
* No extension.
*/
else {
int l = 0;
for (int i = 0; i < this.nn; i++) {
if (ystar < this.y[i]) {
l = l + 1;
}
}
if (1 < l) {
System.arraycopy(this.pstar, 0, this.p, ihi * n, n);
this.y[ihi] = ystar;
}
/*
* Contraction on the Y(IHI) side of the centroid.
*/
else if (l == 0) {
for (int i = 0; i < n; i++) {
this.p2star[i] = this.pbar[i] + CCOEFF
* (this.p[i + ihi * n] - this.pbar[i]);
}
y2star = fn(this.p2star);
/*
* Contract the whole simplex.
*/
if (this.y[ihi] < y2star) {
for (int j = 0; j < this.nn; j++) {
for (int i = 0; i < n; i++) {
this.p[i + j * n] = (this.p[i + j * n] + this.p[i
+ ilo * n]) * 0.5;
this.trainedWeights[i] = this.p[i + j * n];
}
this.y[j] = fn(this.trainedWeights);
}
ylo = this.y[0];
ilo = 0;
for (int i = 1; i < this.nn; i++) {
if (this.y[i] < ylo) {
ylo = this.y[i];
ilo = i;
}
}
continue;
}
/*
* Retain contraction.
*/
else {
System.arraycopy(this.p2star, 0, this.p, ihi * n, n);
this.y[ihi] = y2star;
}
}
/*
* Contraction on the reflection side of the centroid.
*/
else if (l == 1) {
for (int i = 0; i < n; i++) {
this.p2star[i] = this.pbar[i] + CCOEFF
* (this.pstar[i] - this.pbar[i]);
}
y2star = fn(this.p2star);
/*
* Retain reflection?
*/
if (y2star <= ystar) {
System.arraycopy(this.p2star, 0, this.p, ihi * n, n);
this.y[ihi] = y2star;
} else {
System.arraycopy(this.pstar, 0, this.p, ihi * n, n);
this.y[ihi] = ystar;
}
}
}
/*
* Check if YLO improved.
*/
if (this.y[ihi] < ylo) {
ylo = this.y[ihi];
ilo = ihi;
}
this.jcount = this.jcount - 1;
if (0 < this.jcount) {
continue;
}
/*
* Check to see if minimum reached.
*/
// if (icount <= kcount)
{
this.jcount = this.konvge;
z = 0.0;
for (int i = 0; i < this.nn; i++) {
z = z + this.y[i];
}
final double x = z / this.nn;
z = 0.0;
for (int i = 0; i < this.nn; i++) {
z = z + Math.pow(this.y[i] - x, 2);
}
if (z <= this.rq) {
break;
}
}
}
/*
* Factorial tests to check that YNEWLO is a local minimum.
*/
System.arraycopy(this.p, ilo * n, this.trainedWeights, 0, n);
ynewlo = this.y[ilo];
boolean fault = false;
for (int i = 0; i < n; i++) {
this.del = this.step[i] * EPS;
this.trainedWeights[i] += this.del;
z = fn(this.trainedWeights);
if (z < ynewlo) {
fault = true;
break;
}
this.trainedWeights[i] = this.trainedWeights[i] - this.del
- this.del;
z = fn(this.trainedWeights);
if (z < ynewlo) {
fault = true;
break;
}
this.trainedWeights[i] += this.del;
}
if (!fault) {
this.converged = true;
} else {
/*
* Restart the procedure.
*/
System.arraycopy(this.trainedWeights, 0, this.start, 0, n);
this.del = EPS;
}
this.lastError = ynewlo;
System.arraycopy(this.trainedWeights, 0, this.algorithm.getLongTermMemory(), 0, this.trainedWeights.length);
}
/**
* {@inheritDoc}
*/
@Override
public double getLastError() {
return this.lastError;
}
/**
* {@inheritDoc}
*/
@Override
public void finishTraining() {
}
}