oct files for BFGS, numeric derivatives
Michael Creel <[email protected]> Tue, 09 Mar 2004 16:32:23 +0100
| Newsgroups | gmane.comp.gnu.octave.sources |
|---|---|
| Organization | UAB |
| Message-ID | <[email protected]> |
The attached files are new versions with argument checking and better documentation strings. - these oct files minimize a function in about 55% of the time that comparable .m file algorithms use. - the BFGS algorithm is considerably faster, in my experience I will replace the .m files in octave forge with these files after some time for comments. It seems that camelcase filenames are not common in the octave world, so I'm a bit uncomfortable with the names I'm using. I would use bfgs, newton, gradient and hessian as file names, but there are already files in octave-forge with these or very similar names. Any suggestions? Regards, Michael
FiniteDifference.cc
(text/x-c++src, 935 B)
// ========================= FiniteDifference ==========================
// finite differences for numeric differentiation
// the formulae here are based on those in Ox 3.20 (Doornik)
// which references Rice
#include <oct.h>
DEFUN_DLD(FiniteDifference, args, ,"FiniteDifference, C++ version\n\
differences for NumGradient and NumHessian")
{
double x = args(0).double_value();
int order = args(1).int_value();
int test;
double eps, SQRT_EPS, DIFF_EPS, DIFF_EPS1, DIFF_EPS2, diff, d;
eps = 2.2204e-16; // eps is machine precision - this is for i686
SQRT_EPS = sqrt(eps);
DIFF_EPS = exp(log(eps)/2);
DIFF_EPS1 = exp(log(eps)/3);
DIFF_EPS2 = exp(log(eps)/4);
if (order == 0) diff = DIFF_EPS;
else if (order == 1) diff = DIFF_EPS1;
else diff = DIFF_EPS2;
test = (fabs(x) + SQRT_EPS) * SQRT_EPS > diff;
if (test)
{
d = (fabs(x) + SQRT_EPS) * SQRT_EPS;
}
else
{
d = diff;
}
return octave_value(d);
}
BFGSMin.cc
(text/x-c++src, 10.7 KB)
// ============================ BFGSMin.m =====================================
//
// Minimize a function of the form
//
// [obj_value, ...] = f(arg, otherargs)
//
// or
//
// [obj_value, ...] = f(arg)
//
// with respect to arg. The first return of f() must be the value of f().
// f() may have other returns, but they are not used here.
//
// Calling syntax:
//
// [theta, obj_value, iters, convergence] = BFGSMin(f, arg, otherargs, control)
// or
// [theta, obj_value, iters, convergence] = BFGSMin(f, arg, {1}, control)
//
// NOTE: the last argument (control) is optional
//
//
// Input arguments:
//
// REQUIRED
// f - (string) the function to minimize
// arg - the arg we min with respect to, a vector
// otherargs - a cell array of other arguments of func, or a placeholder, e.g., {1}
//
// OPTIONAL
// control - 3x1 vector
// * 1st elem. controls maximum iterations
// scalar > 0 - max. iters
// or -1 for infinity (default)
// * 2nd elem. is verbosity control
// 0 = no results printed (default)
// 1 = intermediate results
// 2 = only last iteration results
// * 3rd elem. specifies convergence criterion
// 1 = function, gradient, and parameter change
// are all tested (default)
// 0 = only function conv tested
//
// Outputs:
// theta - the minimizer
// obj_value - the minimized funtion value
// iters - number of iterations used
// convergence (1 = success, 0 = failure)
#include <oct.h>
#include <octave/parse.h>
#include <octave/Cell.h>
#include <octave/lo-mappers.h>
// define argument checks
static bool
any_bad_argument(const octave_value_list& args)
{
if (args.length() > 4)
{
error("\nBFGSMin: you have provided more than 4 arguments\n");
return true;
}
if (args.length() < 3)
{
error("\nBFGSMin: you have provided fewer than 3 arguments\n");
return true;
}
if (!args(0).is_string())
{
error("BFGSMin: first argument must be string holding objective function name");
return true;
}
if (!args(1).is_real_matrix())
{
error("BFGSMin: second argument must be vector of initial parameter values");
return true;
}
if (!args(2).is_cell())
{
error("BFGSMin: third argument must a cell of other arguments\n\
(or a placeholder such as {1})");
return true;
}
if (args.length() == 4)
{
if (!args(3).is_real_matrix())
{
error("BFGSMin: fourth argument, if supplied must a 3x1 vector of control options");
return true;
}
}
return false;
}
DEFUN_DLD(BFGSMin, args, , "Usage:\n\
[m, o, i, c] = BFGSMin(f, x, otherargs, control)\n\
[m, o, i, c] = BFGSMin(f, x, {1}, control)\n\
\n\
BFGS minimization of f() with respect to x\n\
f() should be of the form\n\
[o, possibly more returns...] = f(x, otherargs)\n\
where otherargs is a cell array\n\
\n\
or if there are no other arguments\n\
[o, possibly more returns...] = f(x)\n\
\n\
Inputs:\n\
f: string - the function to be minimized\n\
x: vector - starting values of arg w.r.t. which minimization is done\n\
otherargs: cell array - additional arguments to function\n\
(e.g., data)\n\
if there are none, use a place holder such as {1}\n\
control: (optional) 3x1 vector of switches\n\
control(1): max iters, use -1 for infinity\n\
control(2): verbosity\n\
0 (default): no screen output\n\
1: report at every iteration\n\
2: report at last iteration\n\
control(3): strict or weak convergence criterion\n\
1 (default): function, gradient and parameter\n\
convergence required\n\
any other value: only function convergence required\n\
Outputs:\n\
m: the minimizing value of x\n\
o: the value of f() at m\n\
i: number of iterations required\n\
c: convergence message\n\
c = 1: normal convergence\n\
c = 2: failure of algorithm\n\
c = -1: max iters exceeded\n\
\n\
Example:\n\
\n\
octave:1> function obj_value = quadratic(x)\n\
> obj_value = x'*x;\n\
> endfunction\n\
octave:2> x = rand(5,1)\n\
x =\n\
\n\
0.81472\n\
0.13548\n\
0.90579\n\
0.83501\n\
0.12699\n\
\n\
octave:3> BFGSMin(\"quadratic\", x, {1}, [10,2,1]);\n\
\n\
BFGSMin final results: Iteration 1\n\
Stepsize 0.0000000\n\
Objective function value 0.0000000000\n\
Function conv 1 Param conv 1 Gradient conv 1\n\
params gradient change\n\
-0.0000 -0.0000 0.0000\n\
0.0000 0.0000 -0.0000\n\
-0.0000 -0.0000 0.0000\n\
-0.0000 -0.0000 0.0000\n\
-0.0000 -0.0000 -0.0000\n\
")
{
int nargin = args.length();
if (nargin < 1)
{
error("BFGSMin: you must supply at least 2 arguments");
return octave_value_list();
}
// check the arguments
if (any_bad_argument(args)) return octave_value_list();
std::string f (args(0).string_value());
Matrix theta (args(1).matrix_value());
Cell otherargs (args(2).cell_value());
octave_value_list f_args(2,1);
octave_value_list g_args = args;
octave_value_list step_args(4,1);
octave_value_list f_return;
int criterion, max_iters, convergence, verbosity, iter;
int gradient_failed, i, j, conv1, conv2, conv3;
int k = theta.rows();
double func_tol, param_tol, gradient_tol, stepsize, obj_value;
double last_obj_value, denominator, test, tempscalar;
Matrix control, thetain, thetanew, H, g, d, g_new, p, q, temp, H1, H2;
f_args(0) = theta;
f_args(1) = otherargs;
step_args(0) = f; // the 4 elements are func, direction, theta, otherargs
step_args(3) = otherargs;
// tolerances
func_tol = 10*sqrt(2.22e-16);
param_tol = 1e-5;
gradient_tol = 1e-4;
// Default values for controls
max_iters = INT_MAX; // no limit on iterations
verbosity = 0; // by default don't report results to screen
criterion = 1; // strong convergence required
// use provided controls, if applicable
if (args.length() == 4)
{
control = args(3).matrix_value();
max_iters = (int) control(0);
if (max_iters == -1) max_iters = INT_MAX;
verbosity = (int) control(1);
criterion = (int) control(2);
}
// initialize things
convergence = -1; // if this doesn't change, it means that maxiters were exceeded
thetain = theta;
H = identity_matrix(k,k);
// Initial obj_value
f_return = feval(f, f_args);
last_obj_value = f_return(0).double_value();
// initial gradient
f_return = feval("NumGradient", args);
g = f_return(0).matrix_value();
g = g.transpose();
// check that gradient is ok
gradient_failed = 0; // test = 1 means gradient failed
for (i=0; i<k;i++)
{
gradient_failed = gradient_failed + xisnan(g(i));
}
if (gradient_failed)
{
error("BFGSMin: Initial gradient could not be calculated: exiting");
convergence = 2;
}
// MAIN LOOP STARTS HERE
for (iter = 0; iter < max_iters; iter++)
{
d = -H*g;
// Regular step
step_args(1) = d;
step_args(2) = theta;
f_return = feval("NewtonStep", step_args);
stepsize = f_return(0).double_value();
obj_value = f_return(1).double_value();
// Steepest descent if regular step fails
if (xisnan(stepsize))
{
d = -g; // try steepest descent
step_args(1) = d;
f_return = feval("NewtonStep", step_args);
stepsize = f_return(0).double_value();
obj_value = f_return(1).double_value();
warning("BFGSMin: Stepsize failure in BFGS direction, trying steepest descent direction");
// if that didn't work, were out of luck
if (xisnan(stepsize))
{
warning("BFGSMin: unable to find direction of improvement: exiting");
theta = thetain;
convergence = 2;
break;
}
}
p = stepsize*d;
// check normal convergence: all 3 must be satisfied
// function convergence
if (fabs(last_obj_value) > 1.0)
{
conv1 = (fabs(obj_value - last_obj_value)/fabs(last_obj_value)) < func_tol;
}
else
{
conv1 = fabs(obj_value - last_obj_value) < func_tol;
}
// parameter change convergence
temp = theta.transpose() * theta;
test = sqrt(temp(0,0));
if (test > 1)
{
temp = p.transpose() * p;
conv2 = (temp(0,0) / test) < param_tol ;
}
else
{
temp = p.transpose() * p;
conv2 = temp(0,0) < param_tol;
}
// gradient convergence
temp = g.transpose() * g;
conv3 = sqrt(temp(0,0)) < gradient_tol;
// Want intermediate results?
if (verbosity == 1)
{
printf("\nBFGSMin intermediate results: Iteration %d\n",iter);
printf("Stepsize %8.7f\n", stepsize);
printf("Objective function value %16.10f\n", last_obj_value);
printf("Function conv %d Param conv %d Gradient conv %d\n", conv1, conv2, conv3);
printf(" params gradient change\n");
for (j = 0; j<k; j++)
{
printf("%8.4f %8.4f %8.4f\n",theta(j),g(j),p(j));
}
printf("\n");
}
// Are we done?
if (criterion == 1)
{
if (conv1 && conv2 && conv3)
{
convergence = 1;
break;
}
}
else if (conv1)
{
convergence = 1;
break;
}
last_obj_value = obj_value;
thetanew = theta + p;
theta = thetanew;
// get gradient at new parameter value
g_args(1) = theta;
f_return = feval("NumGradient", g_args);
g_new = f_return(0).matrix_value();
g_new = g_new.transpose();
// Check that gradient is ok
gradient_failed = 0; // test = 1 means gradient failed
for (i=0; i<k;i++)
{
gradient_failed = gradient_failed + xisnan(g_new(i));
}
// Hessian update if gradient ok
if (!gradient_failed)
{
q = g_new-g;
g = g_new;
temp = q.transpose() * p;
denominator = temp(0,0);
if (denominator < 2.2e-16) // reset Hessian if necessary
{
// if we already reset Hessian then we're out of luck
if (H == identity_matrix(k,k))
{
convergence = 2;
theta = thetain;
warning("BFGSMin: unable to find direction of improvement: exiting");
break;
}
// Here's the Hessian re-set
H = identity_matrix(k,k);
}
else // normal update
{
temp = (1.0+(q.transpose() * H * q) / denominator) / denominator;
tempscalar = temp(0,0);
H1 = tempscalar * (p * p.transpose());
H2 = (p * q.transpose() * H + H*q*p.transpose());
H2 = H2 / denominator;
H = H + H1 - H2;
}
}
else // failed gradient - try Hessian reset
{
// if we already tried it, we're out of luck
if (H == identity_matrix(k,k))
{
convergence = 2;
theta = thetain;
warning("BFGSMin: failure of gradient: exiting");
break;
}
H = identity_matrix(k,k);
}
}
// // Want last iteration results?
if (verbosity == 2)
{
printf("\nBFGSMin final results: Iteration %d\n",iter);
printf("Stepsize %8.7f\n", stepsize);
printf("Objective function value %16.10f\n", last_obj_value);
printf("Function conv %d Param conv %d Gradient conv %d\n", conv1, conv2, conv3);
printf(" params gradient change\n");
for (j = 0; j<k; j++)
{
printf("%8.4f %8.4f %8.4f\n",theta(j),g(j),p(j));
}
printf("\n");
}
f_return(0) = theta;
f_return(1) = obj_value;
f_return(2) = iter;
f_return(3) = convergence;
return octave_value_list(f_return);
}
BisectionStep.cc
(text/x-c++src, 1.9 KB)
// ============================ BisectionStep ===================================== // // this is for use by BFGSMin // // Uses bisection to find a stepsize that leads to a decrease, then // continues until no further improvement // Michael Creel [email protected] // // // usage: // [a, obj_value] = BisectionStep(f, dx, x, otherargs) // inputs: // f: the objective function // dx: the direction // x: the parameter value // args: the other arguments of the function, in a cell array. #include <oct.h> #include <octave/parse.h> DEFUN_DLD(BisectionStep, args, , "BisectionStep.cc") { std::string func = args(0).string_value(); Matrix dx = args(1).matrix_value(); Matrix x = args(2).matrix_value(); octave_value otherargs = args(3); octave_value_list f_args(2,1); f_args(1) = otherargs; double obj_0, obj, a; octave_value_list f_return; octave_value_list stepobj(2,1); f_args(0) = x; Matrix x_in = x; // possibly function returns a cell array // obj. value will be in first position f_return = feval(func, f_args); obj_0 = f_return(0).double_value(); a = 1.0; // this first loop goes until an improvement is found while (a > 4e-16) // limit iterations { f_args(0) = x + a*dx; f_return = feval(func, f_args); obj = f_return(0).double_value(); // reduce stepsize if worse, or if function can't be evaluated if ((obj > obj_0) || isnan(obj)) { a = 0.5 * a; } else { obj_0 = obj; break; } } // now keep going until we no longer improve, or reach max trials while (a > 4e-16) { a = 0.5*a; f_args(0) = x + a*dx; f_return = feval(func, f_args); obj = f_return(0).double_value(); // if improved, record new best and try another step if ((obj < obj_0) & !isnan(obj)) { obj_0 = obj; } else { a = a / 0.5; // put it back to best found break; } } stepobj(0) = a; stepobj(1) = obj_0; return octave_value_list(stepobj); }
NewtonStep.cc
(text/x-c++src, 2.4 KB)
// ============================ NewtonStep ===================================== // // this is for use by BFGSMin // // Uses a Newton interation to attempt to find a good stepsize // falls back to BisectionStep if no improvement found // Michael Creel [email protected] // 13/01/2004 // // usage: // [a, obj_value] = NewtonStep(f, dx, x, otherargs) // inputs: // f: the objective function // dx: the direction // x: the parameter value // args: the other arguments of the function, in a cell array. #include <oct.h> #include <octave/parse.h> DEFUN_DLD(NewtonStep, args, , "NewtonStep.cc") { std::string func = args(0).string_value(); Matrix dx = args(1).matrix_value(); Matrix x = args(2).matrix_value(); octave_value otherargs = args(3); octave_value_list f_args(2,1); f_args(1) = otherargs; double obj, obj_0, obj_left, obj_right, delta, a, gradient, hessian; octave_value_list f_return; octave_value_list stepobj(2,1); f_args(0) = x; Matrix x_in = x; gradient = 1.0; // possibly function return cell array // obj. value will be in first position f_return = feval(func, f_args); obj = f_return(0).double_value(); obj_0 = obj; delta = 0.001; // experimentation show that this is a good choice Matrix x_right = x + delta*dx; Matrix x_left = x - delta*dx; // possibly function return cell array // obj. value will be in first position f_args(0) = x_right; f_return = feval(func, f_args); obj_right = f_return(0).double_value(); f_args(0) = x_left; f_return = feval(func, f_args); obj_left = f_return(0).double_value(); gradient = (obj_right - obj_left) / (2*delta); // take central difference hessian = (obj_right - 2*obj + obj_left) / pow(delta, 2.0); hessian = fabs(hessian); // ensures we're going in a decreasing direction if (hessian <= 2e-16) hessian = 1.0; // avoid div by zero a = - gradient / hessian; // hessian inverse gradient: the Newton step a = (a < 5.0)*a + 5.0*(a>=5.0); // Let's avoid extreme steps that might cause crashes // check that this is improvement f_args(0) = x + a*dx; f_return = feval(func, f_args); obj = f_return(0).double_value(); // if not, fall back to bisection if ((obj > obj_0) || isnan(obj)) { f_return = feval("BisectionStep", args); a = f_return(0).double_value(); obj = f_return(1).double_value(); } stepobj(0) = a; stepobj(1) = obj; return octave_value_list(stepobj); }
NumGradient.cc
(text/x-c++src, 3.8 KB)
// ============================= NumGradient =============================
// Central difference gradient of a function of the form
// f(theta, otherargs), where theta is a vector and otherargs is a cell array.
//
// The gradient is respect to the FIRST element of args.
//
// * Allows diff. of vector-valued function
// * Uses systematic finite difference
//
// See ExampleNumGradient.m
#include <oct.h>
#include <octave/parse.h>
#include <octave/Cell.h>
#include <octave/lo-mappers.h>
// define argument checks
static bool
any_bad_argument(const octave_value_list& args)
{
if (!args(0).is_string())
{
error("NumGradient: first argument must be string holding objective function name");
return true;
}
if (!args(1).is_real_matrix())
{
error("NumGradient: second argument must be vector of initial parameter values");
return true;
}
if (!args(2).is_cell())
{
error("NumGradient: third argument must a cell of other arguments\n\
(or a placeholder such as {1})");
return true;
}
return false;
}
DEFUN_DLD(NumGradient, args, ,
"Usage:\n\
gradient = NumGradient(f, x, otherargs)\n\
\n\
Numeric central difference gradient of a function f() with respect to x\n\
\n\
f() should be of the form\n\
[f_value, possibly more returns...] = f(x, otherargs)\n\
where otherargs is a cell array\n\
\n\
or if there are no other arguments\n\
[f_value, possibly more returns...] = f(x)\n\
\n\
f_value can be a scalar or column vector.\n\
\n\
gradient will be dimension nxk, where n=dim(f_value) and k=dim(x)\n\
\n\
Inputs:\n\
f: string - the function to be minimized\n\
x: vector - the value at which gradient is taken\n\
otherargs: cell array - additional arguments to function\n\
(e.g., data)\n\
if there are none, use a place holder such as {1}\n\
Outputs:\n\
gradient: nxk matrix\n\
\n\
Example:\n\
\n\
function a = myfunc(x)\n\
a = x'*x;\n\
endfunction\n\
\n\
x = rand(3,1)\n\
x =\n\
\n\
0.81472\n\
0.13548\n\
0.90579\n\
\n\
g = NumGradient(\"myfunc\", x, {1})\n\
g =\n\
\n\
1.62945 0.27095 1.81158\n\
")
{
int nargin = args.length();
if (nargin < 1)
{
error("NumGradient: you must supply 3 arguments\n\
even if the 3rd is just a placeholder such as {1}");
return octave_value_list();
}
// check the arguments
if (any_bad_argument(args)) return octave_value_list();
std::string f = args(0).string_value();
Matrix parameter = args(1).matrix_value();
octave_value otherargs = args(2);
octave_value_list f_args(2,1);
octave_value_list fdiff_args(2,1);
f_args(0) = parameter;
f_args(1) = otherargs;
Matrix obj_value, obj_left, obj_right;
octave_value_list f_return;
double p, d, delta, delta_right, delta_left;
int i, j;
// possibly function return cell array
// obj. value will be in first position
f_return = feval(f, f_args);
obj_value = f_return(0).matrix_value();
const int n = obj_value.rows();
const int k = parameter.rows();
Matrix derivative(n, k);
Matrix columnj;
for (j=0; j<k; j++) // get 1st derivative by central difference
{
p = parameter(j);
fdiff_args(0) = p;
fdiff_args(1) = 1;
f_return = feval("FiniteDifference", fdiff_args);
delta = f_return(0).double_value();
// right side
parameter(j) = d = p + delta;
delta_right = d - p;
f_args(0) = parameter;
f_return = feval(f, f_args);
obj_right = f_return(0).matrix_value();
// left size
d = p - delta;
parameter(j) = d;
delta_left = p - d;
f_args(0) = parameter;
f_return = feval(f, f_args);
obj_left = f_return(0).matrix_value();
parameter(j) = p; // restore original parameter
columnj = (obj_right - obj_left) / (delta_right + delta_left);
for (i=0; i<n; i++)
{
derivative(i, j) = columnj(i);
}
}
return octave_value(derivative);
}
NewtonMin.cc
(text/x-c++src, 11 KB)
// ============================ NewtonMin.m =====================================
//
// Minimize a function of the form
//
// [obj_value, ...] = f(arg, otherargs)
//
// or
//
// [obj_value, ...] = f(arg)
//
// with respect to arg. The first return of f() must be the value of f().
// f() may have other returns, but they are not used here.
//
// Calling syntax:
//
// [theta, obj_value, iters, convergence] = NewtonMin(f, arg, otherargs, control)
// or
// [theta, obj_value, iters, convergence] = NewtonMin(f, arg, {1}, control)
//
// NOTE: the last argument (control) is optional
//
//
// Input arguments:
//
// REQUIRED
// f - (string) the function to minimize
// arg - the arg we min with respect to, a vector
// otherargs - a cell array of other arguments of func, or a placeholder, e.g., {1}
//
// OPTIONAL
// control - 3x1 vector
// * 1st elem. controls maximum iterations
// scalar > 0 - max. iters
// or -1 for infinity (default)
// * 2nd elem. is verbosity control
// 0 = no results printed (default)
// 1 = intermediate results
// 2 = only last iteration results
// * 3rd elem. specifies convergence criterion
// 1 = function, gradient, and parameter change
// are all tested (default)
// 0 = only function conv tested
//
// Outputs:
// theta - the minimizer
// obj_value - the minimized funtion value
// iters - number of iterations used
// convergence (1 = success, 0 = failure)
#include <oct.h>
#include <octave/parse.h>
#include <octave/Cell.h>
#include <octave/lo-mappers.h>
// define argument checks
static bool
any_bad_argument(const octave_value_list& args)
{
if (args.length() > 4)
{
error("\nNewtonMin: you have provided more than 4 arguments\n");
return true;
}
if (args.length() < 3)
{
error("\nNewtonMin: you have provided fewer than 3 arguments\n");
return true;
}
if (!args(0).is_string())
{
error("NewtonMin: first argument must be string holding objective function name");
return true;
}
if (!args(1).is_real_matrix())
{
error("NewtonMin: second argument must be vector of initial parameter values");
return true;
}
if (!args(2).is_cell())
{
error("NewtonMin: third argument must a cell of other arguments\n\
(or a placeholder such as {1})");
return true;
}
if (args.length() == 4)
{
if (!args(3).is_real_matrix())
{
error("NewtonMin: fourth argument, if supplied must a 3x1 vector of control options");
return true;
}
}
return false;
}
DEFUN_DLD(NewtonMin, args, ,
"Usage:\n\
[m, o, i, c] = NewtonMin(f, x, otherargs, control)\n\
[m, o, i, c] = NewtonMin(f, x, {1}, control)\n\
\n\
Newton minimization of f() with respect to x\n\
f() should be of the form\n\
[o, possibly more returns...] = f(x, otherargs)\n\
where otherargs is a cell array\n\
\n\
or if there are no other arguments\n\
[o, possibly more returns...] = f(x)\n\
\n\
Inputs:\n\
f: string - the function to be minimized\n\
x: vector - starting values of arg w.r.t. which minimization is done\n\
otherargs: cell array - additional arguments to function\n\
(e.g., data)\n\
if there are none, use a place holder such as {1}\n\
control: (optional) 3x1 vector of switches\n\
control(1): max iters, use -1 for infinity\n\
control(2): verbosity\n\
0 (default): no screen output\n\
1: report at every iteration\n\
2: report at last iteration\n\
control(3): strict or weak convergence criterion\n\
1 (default): function, gradient and parameter\n\
convergence required\n\
any other value: only function convergence required\n\
Outputs:\n\
m: the minimizing value of x\n\
o: the value of f() at m\n\
i: number of iterations required\n\
c: convergence message\n\
c = 1: normal convergence\n\
c = 2: failure of algorithm\n\
c = -1: max iters exceeded\n\
\n\
Example:\n\
\n\
octave:1> function obj_value = quadratic(x)\n\
> obj_value = x'*x;\n\
> endfunction\n\
octave:2> x = rand(5,1)\n\
x =\n\
\n\
0.81472\n\
0.13548\n\
0.90579\n\
0.83501\n\
0.12699\n\
\n\
octave:3> NewtonMin(\"quadratic\", x, {1}, [10,2,1]);\n\
\n\
NewtonMin final results: Iteration 1\n\
Stepsize 0.0000000\n\
Objective function value 0.0000000000\n\
Function conv 1 Param conv 1 Gradient conv 1\n\
params gradient change\n\
-0.0000 -0.0000 0.0000\n\
0.0000 0.0000 -0.0000\n\
-0.0000 -0.0000 0.0000\n\
-0.0000 -0.0000 0.0000\n\
-0.0000 -0.0000 -0.0000\n\
")
{
int nargin = args.length();
if (nargin < 1)
{
error("NewtonMin: you must supply at least 2 arguments");
return octave_value_list();
}
// check the arguments
if (any_bad_argument(args)) return octave_value_list();
std::string f = args(0).string_value();
Matrix theta = args(1).matrix_value();
octave_value otherargs = args(2);
octave_value_list f_args(2,1);
octave_value_list g_args = args;
octave_value_list step_args(4,1);
octave_value_list f_return;
int criterion, max_iters, convergence, verbosity, iter;
int gradient_failed, i, j, conv1, conv2, conv3;
int k = theta.rows();
double func_tol, param_tol, gradient_tol, stepsize, obj_value;
double last_obj_value, test, tempscalar;
Matrix control, thetain, thetanew, H, g, d, g_new, p, q, temp;
f_args(0) = theta;
f_args(1) = otherargs;
step_args(0) = f; // the 4 elements are func, direction, theta, otherargs
step_args(3) = otherargs;
// Check number of args
if (args.length() > 4) error("\nNewtonMin: you have provided more than 4 arguments\n");
if (args.length() < 3) error("\nNewtonMin: you have provided fewer than 3 arguments\n");
// Type checking: not done yet, since I don't know how to do it
// // Check types of required arguments
// if !ischar(func) error("\nNewtonMin: first argument must be a string that gives name of objective function\n"); endif
// if !isvector(theta) error("\nNewtonMin: second argument must be a vector\n"); endif
// if !iscell(otherargs) error("\nNewtonMin: third argument must be a cell array\n\
// (even if it's just placeholder such as {1}\n"); endif
// if (!is_matrix_type(control)) error("\nNewtonMin: If 4 arguments passed, the 4th must be control vector\n"); endif
// if (control.rows() != 3) error("\nNewtonMin: control (4th argument) must be a 3x1 vector\n"); endif
// tolerances
func_tol = 10*sqrt(2.22e-16);
param_tol = 1e-5;
gradient_tol = 1e-4;
// Default values for controls
max_iters = INT_MAX; // no limit on iterations
verbosity = 0; // by default don't report results to screen
criterion = 1; // strong convergence required
// use provided controls, if applicable
if (args.length() == 4)
{
control = args(3).matrix_value();
max_iters = (int) control(0);
if (max_iters == -1) max_iters = INT_MAX;
verbosity = (int) control(1);
criterion = (int) control(2);
}
// initialize things
convergence = -1; // if this doesn't change, it means that maxiters were exceeded
thetain = theta;
H = identity_matrix(k,k);
// Initial obj_value
f_return = feval(f, f_args);
last_obj_value = f_return(0).double_value();
// initial gradient
f_return = feval("NumGradient", args);
g = f_return(0).matrix_value();
g = g.transpose();
// check that gradient is ok
gradient_failed = 0; // test = 1 means gradient failed
for (i=0; i<k;i++)
{
gradient_failed = gradient_failed + isnan(g(i));
}
if (gradient_failed)
{
error("NewtonMin: Initial gradient could not be calculated: exiting");
convergence = 2;
}
// MAIN LOOP STARTS HERE
for (iter = 0; iter < max_iters; iter++)
{
d = -H.inverse() * g;
// Regular step
step_args(1) = d;
step_args(2) = theta;
f_return = feval("NewtonStep", step_args);
stepsize = f_return(0).double_value();
obj_value = f_return(1).double_value();
// Steepest descent if regular step fails
if (isnan(stepsize))
{
d = -g; // try steepest descent
step_args(1) = d;
f_return = feval("NewtonStep", step_args);
stepsize = f_return(0).double_value();
obj_value = f_return(1).double_value();
warning("NewtonMin: Stepsize failure in Newton direction, trying steepest descent direction");
// if that didn't work, were out of luck
if (isnan(stepsize))
{
warning("NewtonMin: unable to find direction of improvement: exiting");
theta = thetain;
convergence = 2;
break;
}
}
p = stepsize*d;
// check normal convergence: all 3 must be satisfied
// function convergence
if (fabs(last_obj_value) > 1.0)
{
conv1 = (fabs(obj_value - last_obj_value)/fabs(last_obj_value)) < func_tol;
}
else
{
conv1 = fabs(obj_value - last_obj_value) < func_tol;
}
// parameter change convergence
temp = theta.transpose() * theta;
test = sqrt(temp(0,0));
if (test > 1)
{
temp = p.transpose() * p;
conv2 = (temp(0,0) / test) < param_tol ;
}
else
{
temp = p.transpose() * p;
conv2 = temp(0,0) < param_tol;
}
// gradient convergence
temp = g.transpose() * g;
conv3 = sqrt(temp(0,0)) < gradient_tol;
// Want intermediate results?
if (verbosity == 1)
{
printf("\nNewtonMin intermediate results: Iteration %d\n",iter);
printf("Stepsize %8.7f\n", stepsize);
printf("Objective function value %16.10f\n", last_obj_value);
printf("Function conv %d Param conv %d Gradient conv %d\n", conv1, conv2, conv3);
printf(" params gradient change\n");
for (j = 0; j<k; j++)
{
printf("%8.4f %8.4f %8.4f\n",theta(j),g(j),p(j));
}
printf("\n");
}
// Are we done?
if (criterion == 1)
{
if (conv1 && conv2 && conv3)
{
convergence = 1;
break;
}
}
else if (conv1)
{
convergence = 1;
break;
}
last_obj_value = obj_value;
thetanew = theta + p;
theta = thetanew;
// get gradient at new parameter value
g_args(1) = theta;
f_return = feval("NumGradient", g_args);
g_new = f_return(0).matrix_value();
g_new = g_new.transpose();
// Check that gradient is ok
gradient_failed = 0; // test = 1 means gradient failed
for (i=0; i<k;i++)
{
gradient_failed = gradient_failed + isnan(g_new(i));
}
// Hessian
if (!gradient_failed)
{
g = g_new;
f_return = feval("NumHessian", g_args);
H = f_return(0).matrix_value();
}
else // failed gradient - try Hessian reset
{
// if we already tried it, we're out of luck
if (H == identity_matrix(k,k))
{
convergence = 2;
theta = thetain;
warning("NewtonMin: failure of gradient: exiting");
break;
}
H = identity_matrix(k,k);
}
}
// // Want last iteration results?
if (verbosity == 2)
{
printf("\nNewtonMin final results: Iteration %d\n",iter);
printf("Stepsize %8.7f\n", stepsize);
printf("Objective function value %16.10f\n", last_obj_value);
printf("Function conv %d Param conv %d Gradient conv %d\n", conv1, conv2, conv3);
printf(" params gradient change\n");
for (j = 0; j<k; j++)
{
printf("%8.4f %8.4f %8.4f\n",theta(j),g(j),p(j));
}
printf("\n");
}
f_return(0) = theta;
f_return(1) = obj_value;
f_return(2) = iter;
f_return(3) = convergence;
return octave_value_list(f_return);
}
NumHessian.cc
(text/x-c++src, 4.5 KB)
// ============================= NumHessian =============================
// Numeric second derivative function of the form
// f(theta, otherargs), where theta is a vector and otherargs is a cell array.
//
// The derivative is respect to the FIRST element of args.
//
#include <oct.h>
#include <octave/parse.h>
#include <octave/Cell.h>
#include <octave/lo-mappers.h>
// define argument checks
static bool
any_bad_argument(const octave_value_list& args)
{
if (!args(0).is_string())
{
error("NumHessian: first argument must be string holding objective function name");
return true;
}
if (!args(1).is_real_matrix())
{
error("NumHessian: second argument must be vector of initial parameter values");
return true;
}
if (!args(2).is_cell())
{
error("NumHessian: third argument must a cell of other arguments\n\
(or a placeholder such as {1})");
return true;
}
return false;
}
DEFUN_DLD(NumHessian, args, ,
"Usage:\n\
hessian = NumHessian(f, x, otherargs)\n\
\n\
Numeric Hessian matrix of a function f() with respect to x\n\
\n\
f() should be of the form\n\
[f_value, possibly more returns...] = f(x, otherargs)\n\
where otherargs is a cell array\n\
\n\
or if there are no other arguments\n\
[f_value, possibly more returns...] = f(x)\n\
\n\
f_value must be real-valued\n\
\n\
hessian will be dimension kxk, where k=dim(x)\n\
\n\
Inputs:\n\
f: string - the function to be minimized\n\
x: vector - the value to at which gradient is taken\n\
otherargs: cell array - additional arguments to function\n\
(e.g., data)\n\
if there are none, use a place holder such as {1}\n\
Outputs:\n\
hessian: kxk matrix\n\
\n\
Example:\n\
\n\
function a = myfunc(x)\n\
a = x'*x;\n\
endfunction\n\
\n\
x = rand(3,1)\n\
x =\n\
\n\
0.81472\n\
0.13548\n\
0.90579\n\
\n\
h = NumHessian(\"myfunc\", x, {1})\n\
h =\n\
\n\
2.00000 0.00000 0.00000\n\
0.00000 2.00000 0.00000\n\
0.00000 0.00000 2.00000\n\
")
{
int nargin = args.length();
if (nargin < 1)
{
error("NumHessian: you must supply 3 arguments\n\
even if the 3rd is just a placeholder such as {1}");
return octave_value_list();
}
std::string f = args(0).string_value();
Matrix parameter = args(1).matrix_value();
octave_value otherargs = args(2);
octave_value_list f_args(2,1);
octave_value_list fdiff_args(2,1);
octave_value_list f_return;
const int k = parameter.rows();
Matrix derivative(k, k);
int i, j;
double di, hi, pi, dj, hj, pj, hia;
double hja, fpp, fmm, fmp, fpm, obj_value;
f_args(0) = parameter;
f_args(1) = otherargs;
f_return = feval(f, f_args);
obj_value = f_return(0).double_value();
for (i = 0; i<k;i++) // approximate 2nd deriv. by central difference
{
pi = parameter(i);
fdiff_args(0) = pi;
fdiff_args(1) = 2;
f_return = feval("FiniteDifference", fdiff_args);
hi = f_return(0).double_value();
for (j = 0; j < i; j++) // off-diagonal elements
{
pj = parameter(j);
fdiff_args(0) = pj;
fdiff_args(1) = 2;
f_return = feval("FiniteDifference", fdiff_args);
hj = f_return(0).double_value();
// +1 +1
parameter(i) = di = pi + hi;
parameter(j) = dj = pj + hj;
hia = di - pi;
hja = dj - pj;
f_args(0) = parameter;
f_return = feval(f, f_args);
fpp = f_return(0).double_value();
// -1 -1
parameter(i) = di = pi - hi;
parameter(j) = dj = pj - hj;
hia = hia + pi - di;
hja = hja + pj - dj;
f_args(0) = parameter;
f_return = feval(f, f_args);
fmm = f_return(0).double_value();
// +1 -1
parameter(i) = pi + hi;
parameter(j) = pj - hj;
f_args(0) = parameter;
f_return = feval(f, f_args);
fpm = f_return(0).double_value();
// -1 +1
parameter(i) = pi - hi;
parameter(j) = pj + hj;
f_args(0) = parameter;
f_return = feval(f, f_args);
fmp = f_return(0).double_value();
derivative(j,i) = ((fpp - fpm) + (fmm - fmp)) / (hia * hja);
derivative(i,j) = derivative(j,i);
parameter(j) = pj;
}
// diagonal elements
// +1 +1
parameter(i) = di = pi + 2 * hi;
f_args(0) = parameter;
f_return = feval(f, f_args);
fpp = f_return(0).double_value();
hia = (di - pi) / 2;
// -1 -1
parameter(i) = di = pi - 2 * hi;
f_args(0) = parameter;
f_return = feval(f, f_args);
fmm = f_return(0).double_value();
hia = hia + (pi - di) / 2;
derivative(i,i) = ((fpp - obj_value) + (fmm - obj_value)) / (hia * hia);
parameter(i) = pi;
}
return octave_value(derivative);
}
Makefile
(text/x-makefile, 186 B)
# Makefile optimization stuff .phony: all all: BisectionStep.oct NewtonStep.oct NumGradient.oct BFGSMin.oct NewtonMin.oct FiniteDifference.oct NumHessian.oct %.oct: %.cc mkoctfile $<
ExampleMin.m
(text/octave, 2.7 KB)
1;
# This shows how to call BFGSMin.m
# This is just used to form objective functions, not important
# Function value and gradient vector of the rosenbrock function
# The minimizer is at the vector (1,1,..,1),
# and the minimized value is 0.
function [obj_value, gradient] = rosenbrock(x);
dimension = length(x);
obj_value = sum(100*(x(2:dimension)-x(1:dimension-1).^2).^2 + (1-x(1:dimension-1)).^2);
if nargout > 1
gradient = zeros(dimension, 1);
gradient(1:dimension-1) = - 400*x(1:dimension-1).*(x(2:dimension)-x(1:dimension-1).^2) - 2*(1-x(1:dimension-1));
gradient(2:dimension) = gradient(2:dimension) + 200*(x(2:dimension)-x(1:dimension-1).^2);
endif
endfunction
# example obj. fn. - illustrates use of "otherargs", and more than 1 return
function [obj_value, junk] = objective(theta, otherargs)
location = otherargs{1};
x = theta - location + ones(rows(theta),1); # move minimizer to "location"
obj_value = rosenbrock(x);
junk = 1;
endfunction
# example obj. fn. - illustrates calling BFGSMin with 2 arguments
function obj_value = objective2(theta)
# location defined internally, since not passed
dim = rows(theta) - 1;
location = 5*(0:dim)/dim;
location = location';
x = theta - location + ones(rows(theta),1); # move minimizer to "location"
obj_value = rosenbrock(x);
endfunction
# control options and initial value
control = [1000;2;1]; # max 1000 iterations; only report results of last iteration; strong convergence required
dim = 10; # dimension of Rosenbrock function
theta = zeros(dim+1,1); # starting values
location = 5*(0:dim)/dim;
location = location';
# do the minimization
printf("If this was successful, the minimizer should be\n");
printf("a vector of even steps from 0 to 5\n\n");
printf("BFGSMin - 4 input args\n");
theta = theta - theta;
t=cputime();
[theta, obj_value, iterations, convergence] = BFGSMin("objective", theta, {location}, control);
t = cputime() - t;
printf("Elapsed time = %f\n",t);
printf("BFGSMin - 4 input args (dummy otherargs, control supplied)\n");
theta = theta - theta;
t=cputime();
[theta, obj_value, iterations, convergence] = BFGSMin("objective2", theta, {1}, control);
t = cputime() - t;
printf("Elapsed time = %f\n",t);
printf("BFGSMin - 3 input args (dummy otherargs, control not supplied)\n");
theta = theta - theta;
t=cputime();
[theta, obj_value, iterations, convergence] = BFGSMin("objective2", theta, {1});
t = cputime() - t;
printf("check that this last worked, since it doesn't print results\n");
theta
printf("Elapsed time = %f\n",t);
printf("NewtonMin - 4 input args\n");
theta = theta - theta;
t=cputime();
[theta, obj_value, iterations, convergence] = NewtonMin("objective", theta, {location}, control);
t = cputime() - t;
printf("Elapsed time = %f\n",t);