/*
* Copyright (c) 2012-2015, Peter Abeles. All Rights Reserved.
*
* This file is part of DDogleg (http://ddogleg.org).
*
* 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.
*/
package org.ddogleg.optimization.impl;
import org.ddogleg.optimization.functions.CoupledJacobian;
import org.ejml.data.DenseMatrix64F;
import org.ejml.ops.CommonOps;
import org.ejml.ops.NormOps;
import org.ejml.ops.SpecializedOps;
/**
*
* Trust region based optimization algorithms switch between the optimal Gauss Newton step and
* gradient descent. A circular region is defined around the current estimate for which the
* Taylor series expansion is trusted. If the Gauss Newton step falls within this "trust region"
* then it is used, otherwise the optimal quadratic solution is found subject to the trust
* region's constraint.
*
*
*
* There are several different ways for which to estimate the solution to the trust region problem.
* The Levenberg-Marquardt algorithm is actually a type of trust region algorithm, even though
* it was originally proposed before the trust region approach had been.
*
*
* @author Peter Abeles
*/
public class TrustRegionLeastSquares {
// algorithm for computing the step
private TrustRegionStep stepAlg;
// error function at x
private double fx;
// the previous error
private double fx_prev;
// Jacobian
private DenseMatrix64F J = new DenseMatrix64F(1,1);
// Sample point
private DenseMatrix64F x = new DenseMatrix64F(1,1);
// Candidate sample point
private DenseMatrix64F candidate = new DenseMatrix64F(1,1);
// Step from x to sample point
private DenseMatrix64F xdelta = new DenseMatrix64F(1,1);
// Residual error function
private DenseMatrix64F residuals = new DenseMatrix64F(1,1);
// Residual error for the candidate point
private DenseMatrix64F candidateResiduals = new DenseMatrix64F(1,1);
// Gradient of residuals
private DenseMatrix64F gradient = new DenseMatrix64F(1,1);
// size of the current trust region
private double regionRadius;
// maximum size of the trust region
private double maxRadius;
// tolerance for termination. magnitude of gradient. absolute
private double gtol;
// tolerance for termination, change in function value. relative
private double ftol;
// function being optimized
private CoupledJacobian function;
private int mode = 0;
private boolean updated;
public TrustRegionLeastSquares(double maxRadius, TrustRegionStep stepAlg) {
this.maxRadius = maxRadius;
this.stepAlg = stepAlg;
}
public void setFunction( CoupledJacobian function ) {
this.function = function;
int m = function.getM();
int n = function.getN();
x.reshape(n,1);
candidate.reshape(n,1);
xdelta.reshape(n,1);
J.reshape(m,n);
residuals.reshape(m,1);
candidateResiduals.reshape(m,1);
gradient.reshape(n, 1);
stepAlg.init(n,m);
}
/**
* Specify convergence tolerances
*
* @param gtol absolute convergence tolerance based on gradient norm. 0 {@code <=} gtol
*/
public void setConvergence( double ftol , double gtol ) {
if( ftol < 0 || ftol >= 1 )
throw new IllegalArgumentException("0 <= ftol < 1");
if( gtol < 0 )
throw new IllegalArgumentException("gtol < 0 ");
this.gtol = gtol;
this.ftol = ftol;
}
public void initialize(double[] initial) {
System.arraycopy(initial,0,x.data,0,x.numRows);
function.setInput(x.data);
function.computeFunctions(residuals.data);
fx = cost(residuals);
fx_prev = 0;
regionRadius = maxRadius;
mode = 0;
}
/**
* Performs a single iteration.
*
* @return true if it has converged and false if it has not.
*/
public boolean iterate() {
updated = false;
if( mode == 0 ) {
// compute the Jacobian and gradient
function.computeJacobian(J.data);
CommonOps.multTransA(J, residuals, gradient);
// check for convergence
double gnorm = CommonOps.elementMaxAbs(gradient);
if( gnorm <= gtol || Math.abs(fx-fx_prev) <= ftol*Math.max(fx,fx_prev) ) {
mode = 2;
return true;
}
//initialize step finding
stepAlg.setInputs(x,residuals,J,gradient,fx);
mode = 1;
fx_prev = fx;
} else if( mode == 1 ) {
if( findStep() ) {
mode = 0;
}
} else {
throw new RuntimeException("Has already converged");
}
return false;
}
/**
* Solves for the step within allowed region and accepts the step if it
* is a large enough improvement.
*
* @return true if it found a good step
*/
private boolean findStep() {
// solve for the step
stepAlg.computeStep(regionRadius,xdelta);
// evaluate the candidate point
CommonOps.add(x,xdelta,candidate);
function.setInput(candidate.data);
function.computeFunctions(candidateResiduals.data);
double fxp = cost(candidateResiduals);
// ratio of predicted reduction versus actual reduction
double actual = fx-fxp;
double predicted = stepAlg.predictedReduction();
// uncomment to sanity check predicted reduction
// checkPredicted(predicted,xdelta);
boolean acceptCandidate;
if( actual == 0 || predicted == 0 ) {
acceptCandidate = true;
} else {
double reductionRatio = actual/predicted;
// System.out.println(" ratio "+reductionRatio);
if( reductionRatio < 0.25 ) {
// if the model is a poor predictor reduce the size of the trust region
regionRadius = 0.5*regionRadius;
} else {
// only increase the size of the trust region if it is taking a step of maximum size
// otherwise just assume it's doing good enough job
if( reductionRatio > 0.75 ) {
double r = NormOps.normF(xdelta);
regionRadius = Math.max(regionRadius,3*r);
}
}
acceptCandidate = reductionRatio > 0;
}
if( acceptCandidate ) {
// make the candidate the current step
DenseMatrix64F temp = x;
x = candidate;
candidate = temp;
temp=candidateResiduals;
candidateResiduals = residuals;
residuals=temp;
fx = fxp;
updated = true;
}
return acceptCandidate;
}
// private void checkPredicted( double found , DenseMatrix64F step ) {
// SimpleMatrix J = SimpleMatrix.wrap(this.J);
// SimpleMatrix h = SimpleMatrix.wrap(step);
//
// SimpleMatrix r = SimpleMatrix.wrap(residuals);
//
// double z = SpecializedOps.elementSumSq(r.plus(J.mult(h)).getMatrix());
// double expected = fx-0.5*z;
//
// if( Math.abs(found-expected)/Math.max(found,expected) > 1e-3 ) {
// System.out.println("found "+found+" expected "+expected);
// throw new RuntimeException("Predicted reduction is unexpected");
// }
// }
/**
* Cost is equal to (1/2)*f(x)T*f(x)
*/
private double cost( DenseMatrix64F residuals ) {
return 0.5*SpecializedOps.elementSumSq(residuals);
}
public double[] getParameters() {
return x.data;
}
public double getError() {
return fx;
}
public boolean isConverged() {
return mode == 2;
}
public boolean isUpdated() {
return updated;
}
}
Ads help maintain this website.