package agents.anac.y2015.Phoenix.GP;/* This file is part of the jgpml Project.
* http://github.com/renzodenardi/jgpml
*
* Copyright (c) 2011 Renzo De Nardi and Hugo Gravato-Marques
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import agents.Jama.CholeskyDecomposition;
import agents.Jama.Matrix;
/**
* Main class of the package, contains the objects that constitutes a Gaussian
* Process as well as the algorithm to train the Hyperparameters and to do
* predictions.
*/
public class GaussianProcess {
/**
* hyperparameters
*/
public Matrix logtheta;
/**
* input data points
*/
public Matrix X;
/**
* Cholesky decomposition of the input
*/
public Matrix L;
/**
* partial factor
*/
public Matrix alpha;
/**
* covariance function
*/
CovarianceFunction covFunction;
/**
* Creates a new GP object.
*
* @param covFunction
* - the covariance function
*/
public GaussianProcess(CovarianceFunction covFunction) {
this.covFunction = covFunction;
}
/**
* Trains the GP Hyperparameters maximizing the marginal likelihood. By
* default the minimisation algorithm performs 100 iterations.
*
* @param X
* - the input data points
* @param y
* - the target data points
* @param logtheta0
* - the initial hyperparameters of the covariance function
*/
public void train(Matrix X, Matrix y, Matrix logtheta0) {
train(X, y, logtheta0, -100);
}
/**
* Trains the GP Hyperparameters maximizing the marginal likelihood. By
* default the algorithm performs 100 iterations.
*
* @param X
* - the input data points
* @param y
* - the target data points
* @param logtheta0
* - the initial hyperparameters of the covariance function
* @param iterations
* - number of iterations performed by the minimization algorithm
*/
public void train(Matrix X, Matrix y, Matrix logtheta0, int iterations) {
// System.out.println("X Dimension:"+X.getRowDimension()+"x"+X.getColumnDimension());
// System.out.println("y Dimension:"+y.getRowDimension()+"x"+y.getColumnDimension());
// System.out.println("training started...");
this.X = X;
logtheta = minimize(logtheta0, iterations, X, y);
}
/**
* Computes minus the log likelihood and its partial derivatives with
* respect to the hyperparameters; this mode is used to fit the
* hyperparameters.
*
* @param logtheta
* column Matrix
of hyperparameters
* @param y
* output dataset
* @param df0
* returned partial derivatives with respect to the
* hyperparameters
* @return lml minus log marginal likelihood
*/
public double negativeLogLikelihood(Matrix logtheta, Matrix x, Matrix y,
Matrix df0) {
int n = x.getRowDimension();
Matrix K = covFunction.compute(logtheta, x); // compute training set
// covariance matrix
CholeskyDecomposition cd = K.chol();
if (!cd.isSPD()) {
throw new RuntimeException(
"The covariance Matrix is not SDP, check your covariance function (maybe you mess the noise term..)");
} else {
L = cd.getL(); // cholesky factorization of the covariance
// alpha = L'\(L\y);
alpha = bSubstitutionWithTranspose(L, fSubstitution(L, y));
// double[][] yarr = y.getArray();
// double[][] alphaarr = alpha.getArray();
// double lml =0;
// for(int i=0; i -1; k--) {
x[k][i] = b[k][i];
for (int j = n; j > k; j--) {
x[k][i] -= l[k][j] * x[j][i];
}
x[k][i] /= l[k][k];
}
}
return new Matrix(x);
}
private static Matrix bSubstitutionWithTranspose(Matrix L, Matrix B) {
final double[][] l = L.getArray();
final double[][] b = B.getArray();
final double[][] x = new double[B.getRowDimension()][B
.getColumnDimension()];
final int n = x.length - 1;
for (int i = 0; i < B.getColumnDimension(); i++) {
for (int k = n; k > -1; k--) {
x[k][i] = b[k][i];
for (int j = n; j > k; j--) {
x[k][i] -= l[j][k] * x[j][i];
}
x[k][i] /= l[k][k];
}
}
return new Matrix(x);
}
private final static double INT = 0.1; // don't reevaluate within 0.1 of the
// limit of the current bracket
private final static double EXT = 3.0; // extrapolate maximum 3 times the
// current step-size
private final static int MAX = 20; // max 20 function evaluations per line
// search
private final static double RATIO = 10; // maximum allowed slope ratio
private final static double SIG = 0.1, RHO = SIG / 2; // SIG and RHO are the
// constants
// controlling the
// Wolfe-
// Powell conditions. SIG is the maximum allowed absolute ratio between
// previous and new slopes (derivatives in the search direction), thus
// setting
// SIG to low (positive) values forces higher precision in the
// line-searches.
// RHO is the minimum allowed fraction of the expected (from the slope at
// the
// initial point in the linesearch). Constants must satisfy 0 < RHO < SIG <
// 1.
// Tuning of SIG (depending on the nature of the function to be optimized)
// may
// speed up the minimization; it is probably not worth playing much with
// RHO.
private Matrix minimize(Matrix params, int length, Matrix in, Matrix out) {
double A, B;
double x1, x2, x3, x4;
double f0, f1, f2, f3, f4;
double d0, d1, d2, d3, d4;
Matrix df0, df3;
Matrix fX;
double red = 1.0;
int i = 0;
int ls_failed = 0;
int sizeX = params.getRowDimension();
df0 = new Matrix(sizeX, 1);
f0 = negativeLogLikelihood(params, in, out, df0);
// f0 = f.evaluate(params,cf, in, out, df0);
fX = new Matrix(new double[] { f0 }, 1);
i = (length < 0) ? i + 1 : i;
Matrix s = df0.times(-1);
// initial search direction (steepest) and slope
d0 = s.times(-1).transpose().times(s).get(0, 0);
x3 = red / (1 - d0); // initial step is red/(|s|+1)
final int nCycles = Math.abs(length);
int success;
double M;
while (i < nCycles) {
// System.out.println("-");
i = (length > 0) ? i + 1 : i; // count iterations?!
// make a copy of current values
double F0 = f0;
Matrix X0 = params.copy();
Matrix dF0 = df0.copy();
M = (length > 0) ? MAX : Math.min(MAX, -length - i);
while (true) { // keep extrapolating as long as necessary
x2 = 0;
f2 = f0;
d2 = d0;
f3 = f0;
df3 = df0.copy();
success = 0;
while (success == 0 && M > 0) {
// try
M = M - 1;
i = (length < 0) ? i + 1 : i; // count iterations?!
Matrix m1 = params.plus(s.times(x3));
// f3 = f.evaluate(m1,cf, in, out, df3);
f3 = negativeLogLikelihood(m1, in, out, df3);
if (Double.isNaN(f3) || Double.isInfinite(f3)
|| hasInvalidNumbers(df3.getRowPackedCopy())) {
x3 = (x2 + x3) / 2; // catch any error which occured in
// f
} else {
success = 1;
}
}
if (f3 < F0) { // keep best values
X0 = s.times(x3).plus(params);
F0 = f3;
dF0 = df3;
}
d3 = df3.transpose().times(s).get(0, 0); // new slope
if (d3 > SIG * d0 || f3 > f0 + x3 * RHO * d0 || M == 0) { // are
// we
// done
// extrapolating?
break;
}
x1 = x2;
f1 = f2;
d1 = d2; // move point 2 to point 1
x2 = x3;
f2 = f3;
d2 = d3; // move point 3 to point 2
A = 6 * (f1 - f2) + 3 * (d2 + d1) * (x2 - x1); // make cubic
// extrapolation
B = 3 * (f2 - f1) - (2 * d1 + d2) * (x2 - x1);
x3 = x1 - d1 * (x2 - x1) * (x2 - x1)
/ (B + Math.sqrt(B * B - A * d1 * (x2 - x1))); // num.
// error
// possible,
// ok!
if (Double.isNaN(x3) || Double.isInfinite(x3) || x3 < 0) // num
// prob
// |
// wrong
// sign?
x3 = x2 * EXT; // extrapolate maximum amount
else if (x3 > x2 * EXT) // new point beyond extrapolation limit?
x3 = x2 * EXT; // extrapolate maximum amount
else if (x3 < x2 + INT * (x2 - x1)) // new point too close to
// previous point?
x3 = x2 + INT * (x2 - x1);
}
f4 = 0;
x4 = 0;
d4 = 0;
while ((Math.abs(d3) > -SIG * d0 || f3 > f0 + x3 * RHO * d0)
&& M > 0) { // keep interpolating
if (d3 > 0 || f3 > f0 + x3 * RHO * d0) { // choose subinterval
x4 = x3;
f4 = f3;
d4 = d3; // move point 3 to point 4
} else {
x2 = x3;
f2 = f3;
d2 = d3; // move point 3 to point 2
}
if (f4 > f0) {
x3 = x2 - (0.5 * d2 * (x4 - x2) * (x4 - x2))
/ (f4 - f2 - d2 * (x4 - x2)); // quadratic
// interpolation
} else {
A = 6 * (f2 - f4) / (x4 - x2) + 3 * (d4 + d2); // cubic
// interpolation
B = 3 * (f4 - f2) - (2 * d2 + d4) * (x4 - x2);
x3 = x2
+ (Math.sqrt(B * B - A * d2 * (x4 - x2) * (x4 - x2)) - B)
/ A; // num. error possible, ok!
}
if (Double.isNaN(x3) || Double.isInfinite(x3)) {
x3 = (x2 + x4) / 2; // if we had a numerical problem then
// bisect
}
x3 = Math.max(Math.min(x3, x4 - INT * (x4 - x2)), x2 + INT
* (x4 - x2)); // don't accept too close
Matrix m1 = s.times(x3).plus(params);
// f3 = f.evaluate(m1,cf, in, out, df3);
f3 = negativeLogLikelihood(m1, in, out, df3);
if (f3 < F0) {
X0 = m1.copy();
F0 = f3;
dF0 = df3.copy(); // keep best values
}
M = M - 1;
i = (length < 0) ? i + 1 : i; // count iterations?!
d3 = df3.transpose().times(s).get(0, 0); // new slope
} // end interpolation
if (Math.abs(d3) < -SIG * d0 && f3 < f0 + x3 * RHO * d0) { // if
// line
// search
// succeeded
params = s.times(x3).plus(params);
f0 = f3;
double[] elem = fX.getColumnPackedCopy();
double[] newfX = new double[elem.length + 1];
System.arraycopy(elem, 0, newfX, 0, elem.length);
newfX[elem.length - 1] = f0;
fX = new Matrix(newfX, newfX.length); // update variables
// System.out.println("Function evaluation "+i+" Value "+f0);
double tmp1 = df3.transpose().times(df3)
.minus(df0.transpose().times(df3)).get(0, 0);
double tmp2 = df0.transpose().times(df0).get(0, 0);
s = s.times(tmp1 / tmp2).minus(df3);
df0 = df3; // swap derivatives
d3 = d0;
d0 = df0.transpose().times(s).get(0, 0);
if (d0 > 0) { // new slope must be negative
s = df0.times(-1); // otherwise use steepest direction
d0 = s.times(-1).transpose().times(s).get(0, 0);
}
x3 = x3 * Math.min(RATIO, d3 / (d0 - Double.MIN_VALUE)); // slope
// ratio
// but
// max
// RATIO
ls_failed = 0; // this line search did not fail
} else {
params = X0;
f0 = F0;
df0 = dF0; // restore best point so far
if (ls_failed == 1 || i > Math.abs(length)) { // line search
// failed twice
// in a row
break; // or we ran out of time, so we give up
}
s = df0.times(-1);
d0 = s.times(-1).transpose().times(s).get(0, 0); // try steepest
x3 = 1 / (1 - d0);
ls_failed = 1; // this line search failed
}
}
return params;
}
private static boolean hasInvalidNumbers(double[] array) {
for (double a : array) {
if (Double.isInfinite(a) || Double.isNaN(a)) {
return true;
}
}
return false;
}
/**
* A simple test
*
* @param args
*/
public static void main(String[] args) {
CovarianceFunction covFunc = new CovSum(6, new CovLINone(),
new CovNoise());
GaussianProcess gp = new GaussianProcess(covFunc);
double[][] logtheta0 = new double[][] { { 0.1 }, { Math.log(0.1) } };
/*
* double[][] logtheta0 = new double[][]{ {0.1}, {0.2}, {0.3}, {0.4},
* {0.5}, {0.6}, {0.7}, {Math.log(0.1)}};
*/
Matrix params0 = new Matrix(logtheta0);
Matrix[] data = CSVtoMatrix
.load("/Users/MaxLam/Desktop/Researches/ANAC2015/DragonAgent/DragonAgentBeta/src/armdata.csv",
6, 1);
Matrix X = data[0];
Matrix Y = data[1];
gp.train(X, Y, params0, -20);
// int size = 100;
// Matrix Xtrain = new Matrix(size, 1);
// Matrix Ytrain = new Matrix(size, 1);
//
// Matrix Xtest = new Matrix(size, 1);
// Matrix Ytest = new Matrix(size, 1);
// half of the sinusoid uses points very close to each other and the
// other half uses
// more sparse data
// double inc = 2 * Math.PI / 1000;
//
// double[][] data = new double[1000][2];
//
// Random random = new Random();
//
// for(int i=0; i<1000; i++){
// data[i][0] = i*inc;
// data[i][1] = Math.sin(i*+inc);
// }
//
//
// // NEED TO FILL Xtrain, Ytrain and Xtest, Ytest
//
//
// gp.train(Xtrain,Ytest,params0);
//
// // SimpleRealTimePlotter plot = new
// SimpleRealTimePlotter("","","",false,false,true,200);
//
// final Matrix[] out = gp.predict(Xtest);
//
// for(int i=0; i