/*
* Copyright (c) 2012-2014, 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.UtilEjml;
import org.ejml.alg.dense.linsol.LinearSolverSafe;
import org.ejml.alg.dense.mult.MatrixMultProduct;
import org.ejml.alg.dense.mult.VectorVectorMult;
import org.ejml.data.DenseMatrix64F;
import org.ejml.factory.LinearSolverFactory;
import org.ejml.interfaces.linsol.LinearSolver;
import org.ejml.ops.CommonOps;
/**
*
* Implementation of Levenberg's algorithm which explicitly computes the J(x)'J(x) matrix and iteratively
* adjusts the dampening parameter. If a candidate sample point decreases the score then it is accepted
* ad the dampening parameter adjusted according to [1]. Because it immediately accepts any decrease
* it will tend to require more function and gradient calculations and less linear solutions. Explicitly
* computing J(x)'J(x) improves the linear solver's speed at the cost of some numerical precision.
*
*
*
* The step 'x' is computed using the following formula:
* (J(k)'*J(k) + μ*I)x = -g = -J'*f
* where J is the Jacobian, μ is the damping coefficient, I is an identify matrix, g is the gradient,
* f is the functions output.
*
*
*
* Unlike some implementations, the option for a scaling matrix is not provided. Scaling can be done inside
* the function itself and would add even more complexity to the code. The dampening parameter is updated
* using the equation below from [1]:
* damp = damp * max( 1/3 , 1 - (2*ratio-1)^3 )
* where ratio is the actual reduction over the predicted reduction.
*
*
*
* [1] K. Madsen and H. B. Nielsen and O. Tingleff, "Methods for Non-Linear Least Squares Problems (2nd ed.)"
* Informatics and Mathematical Modelling, Technical University of Denmark
*
*
* @author Peter Abeles
*/
// After some minor modifications it was compared against Matlab code in [1] and produced identical results
// in each step. Stopping conditions and initialization is a bit different.
public class LevenbergDampened extends LevenbergDenseBase {
// solver used to compute (A + mu*diag(A))d = g
protected LinearSolver solver;
/**
* Specifies termination condition and dampening parameter
*
* @param initialDampParam Initial value of the dampening parameter. Tune.. try 1e-3;
*/
public LevenbergDampened(double initialDampParam) {
super(initialDampParam);
}
@Override
protected void computeJacobian( DenseMatrix64F residuals , DenseMatrix64F gradient) {
// calculate the Jacobian values at the current sample point
function.computeJacobian(jacobianVals.data);
// compute helper matrices
// B = J'*J; g = J'*r
// Take advantage of symmetry when computing B and only compute the upper triangular
// portion used by cholesky decomposition
MatrixMultProduct.inner_reorder_upper(jacobianVals, B);
CommonOps.multTransA(jacobianVals, residuals, gradient);
// extract diagonal elements from B
CommonOps.extractDiag(B, Bdiag);
}
@Override
protected boolean computeStep(double lambda, DenseMatrix64F gradientNegative , DenseMatrix64F step) {
// add dampening parameter
for( int i = 0; i < N; i++ ) {
int index = B.getIndex(i,i);
B.data[index] = Bdiag.data[i] + lambda;
}
// compute the change in step.
if( solver.setA(B) ) {
if( solver.quality() > UtilEjml.EPS ) {
// solve for change in x
solver.solve(gradientNegative, step);
return true;
}
}
return false;
}
/**
* Specifies function being optimized.
*
* @param function Computes residuals and Jacobian.
*/
@Override
public void setFunction( CoupledJacobian function ) {
super.setFunction(function);
solver = LinearSolverFactory.symmPosDef(N);
if( solver.modifiesB() )
this.solver = new LinearSolverSafe(solver);
}
/**
* compute the change predicted by the model
*
* m_k(0) - m_k(p_k) = -g_k'*p - 0.5*p'*B*p
* (J'*J+mu*I)*p = -J'*r = -g
*
* @return predicted reduction
*/
@Override
protected double predictedReduction( DenseMatrix64F param, DenseMatrix64F gradientNegative , double mu ) {
double p_dot_p = VectorVectorMult.innerProd(param,param);
double p_dot_g = VectorVectorMult.innerProd(param,gradientNegative);
return 0.5*(mu*p_dot_p + p_dot_g);
}
}
Ads help maintain this website.