Browse Source

Merged Bfgs optimizations and line search algorithms

pull/489/head
Erik Ovegard 10 years ago
parent
commit
660a9eda32
  1. 1
      src/Numerics/Numerics.csproj
  2. 302
      src/Numerics/Optimization/BfgsBMinimizer.cs
  3. 191
      src/Numerics/Optimization/BfgsMinimizer.cs
  4. 158
      src/Numerics/Optimization/BfgsMinimizerBase.cs
  5. 14
      src/Numerics/Optimization/BfgsSolver.cs
  6. 32
      src/Numerics/Optimization/LineSearch/LineSearchResult.cs
  7. 35
      src/Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs
  8. 32
      src/Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs
  9. 35
      src/Numerics/Optimization/LineSearch/WolfeLineSearch.cs
  10. 111
      src/Numerics/Optimization/LineSearch/WolfeRule.cs
  11. 23
      src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs
  12. 22
      src/UnitTests/OptimizationTests/BfgsTest.cs
  13. 86
      src/UnitTests/OptimizationTests/TestBfgsBMinimizer.cs
  14. 12
      src/UnitTests/OptimizationTests/TestBfgsMinimizer.cs

1
src/Numerics/Numerics.csproj

@ -117,6 +117,7 @@
<Compile Include="OdeSolvers\RungeKutta.cs" />
<Compile Include="Optimization\BaseEvaluation.cs" />
<Compile Include="Optimization\BaseObjectiveFunction.cs" />
<Compile Include="Optimization\BfgsMinimizerBase.cs" />
<Compile Include="Optimization\LineSearch\WolfeLineSearch.cs" />
<Compile Include="Optimization\NelderMeadSimplex.cs" />
<Compile Include="Optimization\ObjectiveFunctions\LazyObjectiveFunctionBase.cs" />

302
src/Numerics/Optimization/BfgsBMinimizer.cs

@ -1,4 +1,34 @@
using System;
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;
@ -10,23 +40,25 @@ namespace MathNet.Numerics.Optimization
/// Broyden–Fletcher–Goldfarb–Shanno Bounded (BFGS-B) algorithm is an iterative method for solving box-constrained nonlinear optimization problems
/// http://www.ece.northwestern.edu/~nocedal/PSfiles/limited.ps.gz
/// </summary>
public class BfgsBMinimizer
public class BfgsBMinimizer : BfgsMinimizerBase
{
public double GradientTolerance { get; set; }
public double ParameterTolerance { get; set; }
public int MaximumIterations { get; set; }
public double FunctionProgressTolerance { get; set; }
public BfgsBMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations = 1000)
: base(gradientTolerance,parameterTolerance,functionProgressTolerance,maximumIterations)
{
GradientTolerance = gradientTolerance;
ParameterTolerance = parameterTolerance;
MaximumIterations = maximumIterations;
FunctionProgressTolerance = functionProgressTolerance;
}
/// <summary>
/// Find the minimum of the objective function given lower and upper bounds
/// </summary>
/// <param name="objective">The objective function, must support a gradient</param>
/// <param name="lowerBound">The lower bound</param>
/// <param name="upperBound">The upper bound</param>
/// <param name="initialGuess">The initial guess</param>
/// <returns>The MinimizationResult which contains the minimum and the ExitCondition</returns>
public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector<double> lowerBound, Vector<double> upperBound, Vector<double> initialGuess)
{
_lowerBound = lowerBound;
_upperBound = upperBound;
if (!objective.IsGradientSupported)
throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for BFGS minimization.");
@ -40,9 +72,10 @@ namespace MathNet.Numerics.Optimization
throw new ArgumentException("Initial guess is not in the feasible region");
objective.EvaluateAt(initialGuess);
ValidateGradientAndObjective(objective);
// Check that we're not already done
MinimizationResult.ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, lowerBound, upperBound, 0);
MinimizationResult.ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0);
if (currentExitCondition != MinimizationResult.ExitCondition.None)
return new MinimizationResult(objective, 0, currentExitCondition);
@ -59,9 +92,9 @@ namespace MathNet.Numerics.Optimization
// Determine active set
var gradientProjectionResult = QuadraticGradientProjectionSearch.Search(objective.Point, objective.Gradient, pseudoHessian, lowerBound, upperBound);
var cauchyPoint = gradientProjectionResult.Item1;
var fixedCount = gradientProjectionResult.Item2;
var isFixed = gradientProjectionResult.Item3;
var cauchyPoint = gradientProjectionResult.CauchyPoint;
var fixedCount = gradientProjectionResult.FixedCount;
var isFixed = gradientProjectionResult.IsFixed;
var freeCount = lowerBound.Count - fixedCount;
if (freeCount > 0)
@ -96,10 +129,10 @@ namespace MathNet.Numerics.Optimization
var startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep);
// Line search
LineSearchResult result;
LineSearchResult lineSearchResult;
try
{
result = lineSearcher.FindConformingStep(objective, lineSearchDirection, startingStepSize, upperBound: maxLineSearchStep);
lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, startingStepSize, upperBound: maxLineSearchStep);
}
catch (Exception e)
{
@ -107,112 +140,94 @@ namespace MathNet.Numerics.Optimization
}
var previousPoint = objective.Fork();
var candidatePoint = result.FunctionInfoAtMinimum;
var candidatePoint = lineSearchResult.FunctionInfoAtMinimum;
var gradient = candidatePoint.Gradient;
var step = candidatePoint.Point - initialGuess;
// Subsequent steps
int iterations;
int totalLineSearchSteps = result.Iterations;
int iterationsWithNontrivialLineSearch = result.Iterations > 0 ? 0 : 1;
for (iterations = 1; iterations < MaximumIterations; ++iterations)
{
// Do BFGS update
var y = candidatePoint.Gradient - previousPoint.Gradient;
double sy = step*y;
if (sy > 0.0) // only do update if it will create a positive definite matrix
{
double sts = step*step;
//inverse_pseudo_hessian = inverse_pseudo_hessian + ((sy + y * inverse_pseudo_hessian * y) / Math.Pow(sy, 2.0)) * step.OuterProduct(step) - ((inverse_pseudo_hessian * y.ToColumnMatrix()) * step.ToRowMatrix() + step.ToColumnMatrix() * (y.ToRowMatrix() * inverse_pseudo_hessian)) * (1.0 / sy);
var Hs = pseudoHessian*step;
var sHs = step*pseudoHessian*step;
pseudoHessian = pseudoHessian + y.OuterProduct(y)*(1.0/sy) - Hs.OuterProduct(Hs)*(1.0/sHs);
}
else
{
//pseudo_hessian = LinearAlgebra.Double.DiagonalMatrix.Identity(initial_guess.Count);
}
int totalLineSearchSteps = lineSearchResult.Iterations;
int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1;
// Determine active set
gradientProjectionResult = QuadraticGradientProjectionSearch.Search(candidatePoint.Point, candidatePoint.Gradient, pseudoHessian, lowerBound, upperBound);
cauchyPoint = gradientProjectionResult.Item1;
fixedCount = gradientProjectionResult.Item2;
isFixed = gradientProjectionResult.Item3;
freeCount = lowerBound.Count - fixedCount;
if (freeCount > 0)
{
reducedGradient = new DenseVector(freeCount);
reducedHessian = new DenseMatrix(freeCount, freeCount);
reducedMap = new List<int>(freeCount);
reducedInitialPoint = new DenseVector(freeCount);
reducedCauchyPoint = new DenseVector(freeCount);
int iterations = DoBfgsUpdate(ref currentExitCondition, lineSearcher, ref pseudoHessian, ref lineSearchDirection, ref previousPoint, ref lineSearchResult, ref candidatePoint, ref step, ref totalLineSearchSteps, ref iterationsWithNontrivialLineSearch);
CreateReducedData(candidatePoint.Point, cauchyPoint, isFixed, lowerBound, upperBound, candidatePoint.Gradient, pseudoHessian, reducedInitialPoint, reducedCauchyPoint, reducedGradient, reducedHessian, reducedMap);
// Determine search direction and maximum step size
reducedSolution1 = reducedInitialPoint + reducedHessian.Cholesky().Solve(-reducedGradient);
solution1 = ReducedToFull(reducedMap, reducedSolution1, cauchyPoint);
}
else
{
solution1 = cauchyPoint;
}
if (iterations == MaximumIterations && currentExitCondition == MinimizationResult.ExitCondition.None)
throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations));
directionFromCauchy = solution1 - cauchyPoint;
maxStepFromCauchyPoint = FindMaxStep(cauchyPoint, directionFromCauchy, lowerBound, upperBound);
//var cauchy_eval = objective.Evaluate(cauchy_point);
return new MinimizationWithLineSearchResult(candidatePoint, iterations, currentExitCondition, totalLineSearchSteps, iterationsWithNontrivialLineSearch);
}
solution2 = cauchyPoint + Math.Min(maxStepFromCauchyPoint, 1.0)*directionFromCauchy;
protected override Vector<double> CalculateSearchDirection(ref Matrix<double> pseudoHessian,
out double maxLineSearchStep,
out double startingStepSize,
IObjectiveFunction previousPoint,
IObjectiveFunction candidatePoint,
Vector<double> step)
{
Vector<double> lineSearchDirection;
var y = candidatePoint.Gradient - previousPoint.Gradient;
lineSearchDirection = solution2 - candidatePoint.Point;
maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, lowerBound, upperBound);
double sy = step * y;
if (sy > 0.0) // only do update if it will create a positive definite matrix
{
double sts = step * step;
var Hs = pseudoHessian * step;
var sHs = step * pseudoHessian * step;
pseudoHessian = pseudoHessian + y.OuterProduct(y) * (1.0 / sy) - Hs.OuterProduct(Hs) * (1.0 / sHs);
}
else
{
//pseudo_hessian = LinearAlgebra.Double.DiagonalMatrix.Identity(initial_guess.Count);
}
//line_search_direction = solution1 - candidate_point.Point;
//max_line_search_step = FindMaxStep(candidate_point.Point, line_search_direction, lower_bound, upper_bound);
// Determine active set
var gradientProjectionResult = QuadraticGradientProjectionSearch.Search(candidatePoint.Point, candidatePoint.Gradient, pseudoHessian, _lowerBound, _upperBound);
var cauchyPoint = gradientProjectionResult.CauchyPoint;
var fixedCount = gradientProjectionResult.FixedCount;
var isFixed = gradientProjectionResult.IsFixed;
var freeCount = _lowerBound.Count - fixedCount;
Vector<double> solution1;
if (freeCount > 0)
{
var reducedGradient = new DenseVector(freeCount);
var reducedHessian = new DenseMatrix(freeCount, freeCount);
var reducedMap = new List<int>(freeCount);
var reducedInitialPoint = new DenseVector(freeCount);
var reducedCauchyPoint = new DenseVector(freeCount);
if (maxLineSearchStep == 0.0)
{
lineSearchDirection = cauchyPoint - candidatePoint.Point;
maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, lowerBound, upperBound);
}
CreateReducedData(candidatePoint.Point, cauchyPoint, isFixed, _lowerBound, _upperBound, candidatePoint.Gradient, pseudoHessian, reducedInitialPoint, reducedCauchyPoint, reducedGradient, reducedHessian, reducedMap);
estStepSize = -candidatePoint.Gradient*lineSearchDirection/(lineSearchDirection*pseudoHessian*lineSearchDirection);
// Determine search direction and maximum step size
Vector<double> reducedSolution1 = reducedInitialPoint + reducedHessian.Cholesky().Solve(-reducedGradient);
startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep);
solution1 = ReducedToFull(reducedMap, reducedSolution1, cauchyPoint);
}
else
{
solution1 = cauchyPoint;
}
// Line search
try
{
result = lineSearcher.FindConformingStep(candidatePoint, lineSearchDirection, startingStepSize, upperBound: maxLineSearchStep);
//result = line_searcher.FindConformingStep(objective, cauchy_eval, direction_from_cauchy, Math.Min(1.0, max_step_from_cauchy_point), upper_bound: max_step_from_cauchy_point);
}
catch (Exception e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
var directionFromCauchy = solution1 - cauchyPoint;
var maxStepFromCauchyPoint = FindMaxStep(cauchyPoint, directionFromCauchy, _lowerBound, _upperBound);
iterationsWithNontrivialLineSearch += result.Iterations > 0 ? 1 : 0;
totalLineSearchSteps += result.Iterations;
var solution2 = cauchyPoint + Math.Min(maxStepFromCauchyPoint, 1.0) * directionFromCauchy;
step = result.FunctionInfoAtMinimum.Point - candidatePoint.Point;
previousPoint = candidatePoint;
candidatePoint = result.FunctionInfoAtMinimum;
lineSearchDirection = solution2 - candidatePoint.Point;
maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, _lowerBound, _upperBound);
currentExitCondition = ExitCriteriaSatisfied(candidatePoint, previousPoint, lowerBound, upperBound, iterations);
if (currentExitCondition != MinimizationResult.ExitCondition.None)
break;
if (maxLineSearchStep == 0.0)
{
lineSearchDirection = cauchyPoint - candidatePoint.Point;
maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, _lowerBound, _upperBound);
}
if (iterations == MaximumIterations && currentExitCondition == MinimizationResult.ExitCondition.None)
throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations));
double estStepSize = -candidatePoint.Gradient * lineSearchDirection / (lineSearchDirection * pseudoHessian * lineSearchDirection);
return new MinimizationWithLineSearchResult(candidatePoint, iterations, currentExitCondition, totalLineSearchSteps, iterationsWithNontrivialLineSearch);
startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep);
return lineSearchDirection;
}
static Vector<double> ReducedToFull(List<int> reducedMap, Vector<double> reducedVector, Vector<double> fullVector)
private static Vector<double> ReducedToFull(List<int> reducedMap, Vector<double> reducedVector, Vector<double> fullVector)
{
var output = fullVector.Clone();
for (int ii = 0; ii < reducedMap.Count; ++ii)
@ -220,7 +235,10 @@ namespace MathNet.Numerics.Optimization
return output;
}
static double FindMaxStep(Vector<double> startingPoint, Vector<double> searchDirection, Vector<double> lowerBound, Vector<double> upperBound)
private Vector<double> _lowerBound;
private Vector<double> _upperBound;
private static double FindMaxStep(Vector<double> startingPoint, Vector<double> searchDirection, Vector<double> lowerBound, Vector<double> upperBound)
{
double maxStep = Double.PositiveInfinity;
for (int ii = 0; ii < startingPoint.Count; ++ii)
@ -239,7 +257,7 @@ namespace MathNet.Numerics.Optimization
return maxStep;
}
static void CreateReducedData(Vector<double> initialPoint, Vector<double> cauchyPoint, List<bool> isFixed, Vector<double> lowerBound, Vector<double> upperBound, Vector<double> gradient, Matrix<double> pseudoHessian, Vector<double> reducedInitialPoint, Vector<double> reducedCauchyPoint, Vector<double> reducedGradient, Matrix<double> reducedHessian, List<int> reducedMap)
private static void CreateReducedData(Vector<double> initialPoint, Vector<double> cauchyPoint, List<bool> isFixed, Vector<double> lowerBound, Vector<double> upperBound, Vector<double> gradient, Matrix<double> pseudoHessian, Vector<double> reducedInitialPoint, Vector<double> reducedCauchyPoint, Vector<double> reducedGradient, Matrix<double> reducedHessian, List<int> reducedMap)
{
int ll = 0;
for (int ii = 0; ii < lowerBound.Count; ++ii)
@ -267,71 +285,21 @@ namespace MathNet.Numerics.Optimization
}
}
const double VerySmall = 1e-15;
MinimizationResult.ExitCondition ExitCriteriaSatisfied(IObjectiveFunction candidatePoint, IObjectiveFunction lastPoint, Vector<double> lowerBound, Vector<double> upperBound, int iterations)
{
Vector<double> relGrad = new DenseVector(candidatePoint.Point.Count);
double relativeGradient = 0.0;
double normalizer = Math.Max(Math.Abs(candidatePoint.Value), 1.0);
for (int ii = 0; ii < relGrad.Count; ++ii)
{
double projectedGradient;
bool atLowerBound = candidatePoint.Point[ii] - lowerBound[ii] < VerySmall;
bool atUpperBound = upperBound[ii] - candidatePoint.Point[ii] < VerySmall;
if (atLowerBound && atUpperBound)
projectedGradient = 0.0;
else if (atLowerBound)
projectedGradient = Math.Min(candidatePoint.Gradient[ii], 0.0);
else if (atUpperBound)
projectedGradient = Math.Max(candidatePoint.Gradient[ii], 0.0);
else
projectedGradient = candidatePoint.Gradient[ii];
double tmp = projectedGradient*Math.Max(Math.Abs(candidatePoint.Point[ii]), 1.0)/normalizer;
relativeGradient = Math.Max(relativeGradient, Math.Abs(tmp));
}
if (relativeGradient < GradientTolerance)
{
return MinimizationResult.ExitCondition.RelativeGradient;
}
if (lastPoint != null)
{
double mostProgress = 0.0;
for (int ii = 0; ii < candidatePoint.Point.Count; ++ii)
{
var tmp = Math.Abs(candidatePoint.Point[ii] - lastPoint.Point[ii])/Math.Max(Math.Abs(lastPoint.Point[ii]), 1.0);
mostProgress = Math.Max(mostProgress, tmp);
}
if (mostProgress < ParameterTolerance)
{
return MinimizationResult.ExitCondition.LackOfProgress;
}
double functionChange = candidatePoint.Value - lastPoint.Value;
if (iterations > 500 && functionChange < 0 && Math.Abs(functionChange) < FunctionProgressTolerance)
return MinimizationResult.ExitCondition.LackOfProgress;
}
return MinimizationResult.ExitCondition.None;
}
void ValidateGradient(IObjectiveFunction eval)
protected override double GetProjectedGradient(IObjectiveFunction candidatePoint, int ii)
{
foreach (var x in eval.Gradient)
{
if (Double.IsNaN(x) || Double.IsInfinity(x))
throw new EvaluationException("Non-finite gradient returned.", eval);
}
}
void ValidateObjective(IObjectiveFunction eval)
{
if (Double.IsNaN(eval.Value) || Double.IsInfinity(eval.Value))
throw new EvaluationException("Non-finite objective function returned.", eval);
double projectedGradient;
bool atLowerBound = candidatePoint.Point[ii] - _lowerBound[ii] < VerySmall;
bool atUpperBound = _upperBound[ii] - candidatePoint.Point[ii] < VerySmall;
if (atLowerBound && atUpperBound)
projectedGradient = 0.0;
else if (atLowerBound)
projectedGradient = Math.Min(candidatePoint.Gradient[ii], 0.0);
else if (atUpperBound)
projectedGradient = Math.Max(candidatePoint.Gradient[ii], 0.0);
else
projectedGradient = base.GetProjectedGradient(candidatePoint, ii);
return projectedGradient;
}
}
}

191
src/Numerics/Optimization/BfgsMinimizer.cs

@ -1,4 +1,34 @@
using System;
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using System;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Optimization.LineSearch;
@ -7,31 +37,36 @@ namespace MathNet.Numerics.Optimization
/// <summary>
/// Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm is an iterative method for solving unconstrained nonlinear optimization problems
/// </summary>
public class BfgsMinimizer
public class BfgsMinimizer : BfgsMinimizerBase
{
public double GradientTolerance { get; set; }
public double ParameterTolerance { get; set; }
public int MaximumIterations { get; set; }
public BfgsMinimizer(double gradientTolerance, double parameterTolerance, int maximumIterations=1000)
/// <summary>
/// Creates BFGS minimizer
/// </summary>
/// <param name="gradientTolerance">The gradient tolerance</param>
/// <param name="parameterTolerance">The parameter tolerance</param>
/// <param name="functionProgressTolerance">The funciton progress tolerance</param>
/// <param name="maximumIterations">The maximum number of iterations</param>
public BfgsMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations=1000)
:base(gradientTolerance,parameterTolerance,functionProgressTolerance,maximumIterations)
{
GradientTolerance = gradientTolerance;
ParameterTolerance = parameterTolerance;
MaximumIterations = maximumIterations;
}
/// <summary>
/// Find the minimum of the objective function given lower and upper bounds
/// </summary>
/// <param name="objective">The objective function, must support a gradient</param>
/// <param name="initialGuess">The initial guess</param>
/// <returns>The MinimizationResult which contains the minimum and the ExitCondition</returns>
public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector<double> initialGuess)
{
if (!objective.IsGradientSupported)
throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for BFGS minimization.");
objective.EvaluateAt(initialGuess);
ValidateGradient(objective);
var initial = objective.Fork();
ValidateGradientAndObjective(objective);
// Check that we're not already done
MinimizationResult.ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null);
MinimizationResult.ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0);
if (currentExitCondition != MinimizationResult.ExitCondition.None)
return new MinimizationResult(objective, 0, currentExitCondition);
@ -40,122 +75,68 @@ namespace MathNet.Numerics.Optimization
// First step
var inversePseudoHessian = CreateMatrix.DenseIdentity<double>(initialGuess.Count);
var searchDirection = -objective.Gradient;
var stepSize = 100 * GradientTolerance / (searchDirection * searchDirection);
var lineSearchDirection = -objective.Gradient;
var stepSize = 100 * GradientTolerance / (lineSearchDirection * lineSearchDirection);
var previousPoint = objective.Point;
var previousGradient = objective.Gradient;
var previousPoint = objective;
LineSearchResult result;
LineSearchResult lineSearchResult;
try
{
result = lineSearcher.FindConformingStep(objective, searchDirection, stepSize);
lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize);
}
catch (Exception e)
catch (OptimizationException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
catch (ArgumentException e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
objective = result.FunctionInfoAtMinimum;
ValidateGradient(objective);
var candidate = lineSearchResult.FunctionInfoAtMinimum;
ValidateGradientAndObjective(candidate);
var gradient = objective.Gradient;
var step = objective.Point - initialGuess;
stepSize = result.FinalStep;
var gradient = candidate.Gradient;
var step = candidate.Point - initialGuess;
// Subsequent steps
Matrix<double> I = CreateMatrix.DiagonalIdentity<double>(initialGuess.Count);
int iterations;
int totalLineSearchSteps = result.Iterations;
int iterationsWithNontrivialLineSearch = result.Iterations > 0 ? 0 : 1;
for (iterations = 1; iterations < MaximumIterations; ++iterations)
{
var y = objective.Gradient - previousGradient;
double sy = step * y;
inversePseudoHessian = inversePseudoHessian + ((sy + y * inversePseudoHessian * y) / Math.Pow(sy, 2.0)) * step.OuterProduct(step) - ( (inversePseudoHessian * y.ToColumnMatrix())*step.ToRowMatrix() + step.ToColumnMatrix()*(y.ToRowMatrix() * inversePseudoHessian)) * (1.0 / sy);
searchDirection = -inversePseudoHessian * objective.Gradient;
if (searchDirection * objective.Gradient >= 0.0)
{
searchDirection = -objective.Gradient;
inversePseudoHessian = CreateMatrix.DenseIdentity<double>(initialGuess.Count);
}
previousGradient = objective.Gradient;
previousPoint = objective.Point;
try
{
result = lineSearcher.FindConformingStep(objective, searchDirection, 1.0);
}
catch (Exception e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
iterationsWithNontrivialLineSearch += result.Iterations > 0 ? 1 : 0;
totalLineSearchSteps += result.Iterations;
stepSize = result.FinalStep;
step = result.FunctionInfoAtMinimum.Point - previousPoint;
objective = result.FunctionInfoAtMinimum;
currentExitCondition = ExitCriteriaSatisfied(objective, previousPoint);
if (currentExitCondition != MinimizationResult.ExitCondition.None)
break;
}
int totalLineSearchSteps = lineSearchResult.Iterations;
int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1;
iterations = DoBfgsUpdate(ref currentExitCondition, lineSearcher, ref inversePseudoHessian, ref lineSearchDirection, ref previousPoint, ref lineSearchResult, ref candidate, ref step, ref totalLineSearchSteps, ref iterationsWithNontrivialLineSearch);
if (iterations == MaximumIterations && currentExitCondition == MinimizationResult.ExitCondition.None)
throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations));
return new MinimizationWithLineSearchResult(objective, iterations, MinimizationResult.ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch);
return new MinimizationWithLineSearchResult(candidate, iterations, MinimizationResult.ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch);
}
private MinimizationResult.ExitCondition ExitCriteriaSatisfied(IObjectiveFunction candidatePoint, Vector<double> lastPoint)
protected override Vector<double> CalculateSearchDirection(ref Matrix<double> inversePseudoHessian,
out double maxLineSearchStep,
out double startingStepSize,
IObjectiveFunction previousPoint,
IObjectiveFunction candidate,
Vector<double> step)
{
Vector<double> relGrad = new LinearAlgebra.Double.DenseVector(candidatePoint.Point.Count);
double relativeGradient = 0.0;
double normalizer = Math.Max(Math.Abs(candidatePoint.Value), 1.0);
for (int ii = 0; ii < relGrad.Count; ++ii)
{
double tmp = candidatePoint.Gradient[ii]*Math.Max(Math.Abs(candidatePoint.Point[ii]), 1.0) / normalizer;
relativeGradient = Math.Max(relativeGradient, Math.Abs(tmp));
}
if (relativeGradient < GradientTolerance)
{
return MinimizationResult.ExitCondition.RelativeGradient;
}
if (lastPoint != null)
{
double mostProgress = 0.0;
for (int ii = 0; ii < candidatePoint.Point.Count; ++ii)
{
var tmp = Math.Abs(candidatePoint.Point[ii] - lastPoint[ii])/Math.Max(Math.Abs(lastPoint[ii]), 1.0);
mostProgress = Math.Max(mostProgress, tmp);
}
if ( mostProgress < ParameterTolerance )
{
return MinimizationResult.ExitCondition.LackOfProgress;
}
}
return MinimizationResult.ExitCondition.None;
}
startingStepSize = 1.0;
maxLineSearchStep = double.PositiveInfinity;
private void ValidateGradient(IObjectiveFunction objective)
{
foreach (var x in objective.Gradient)
Vector<double> lineSearchDirection;
var y = candidate.Gradient - previousPoint.Gradient;
double sy = step * y;
inversePseudoHessian = inversePseudoHessian + ((sy + y * inversePseudoHessian * y) / Math.Pow(sy, 2.0)) * step.OuterProduct(step) - ((inversePseudoHessian * y.ToColumnMatrix()) * step.ToRowMatrix() + step.ToColumnMatrix() * (y.ToRowMatrix() * inversePseudoHessian)) * (1.0 / sy);
lineSearchDirection = -inversePseudoHessian * candidate.Gradient;
if (lineSearchDirection * candidate.Gradient >= 0.0)
{
if (Double.IsNaN(x) || Double.IsInfinity(x))
throw new EvaluationException("Non-finite gradient returned.", objective);
lineSearchDirection = -candidate.Gradient;
inversePseudoHessian = CreateMatrix.DenseIdentity<double>(candidate.Point.Count);
}
}
private void ValidateObjective(IObjectiveFunction objective)
{
if (Double.IsNaN(objective.Value) || Double.IsInfinity(objective.Value))
throw new EvaluationException("Non-finite objective function returned.", objective);
return lineSearchDirection;
}
}
}

158
src/Numerics/Optimization/BfgsMinimizerBase.cs

@ -0,0 +1,158 @@
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.Optimization.LineSearch;
using System;
namespace MathNet.Numerics.Optimization
{
public abstract class BfgsMinimizerBase
{
public double GradientTolerance { get; set; }
public double ParameterTolerance { get; set; }
public double FunctionProgressTolerance { get; set; }
public int MaximumIterations { get; set; }
protected const double VerySmall = 1e-15;
/// <summary>
/// Creates a base class for BFGS minimization
/// </summary>
/// <param name="gradientTolerance">The gradient tolerance</param>
/// <param name="parameterTolerance">The parameter tolerance</param>
/// <param name="functionProgressTolerance">The funciton progress tolerance</param>
/// <param name="maximumIterations">The maximum number of iterations</param>
public BfgsMinimizerBase(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations)
{
GradientTolerance = gradientTolerance;
ParameterTolerance = parameterTolerance;
FunctionProgressTolerance = functionProgressTolerance;
MaximumIterations = maximumIterations;
}
protected MinimizationResult.ExitCondition ExitCriteriaSatisfied(IObjectiveFunction candidatePoint, IObjectiveFunction lastPoint, int iterations)
{
Vector<double> relGrad = new DenseVector(candidatePoint.Point.Count);
double relativeGradient = 0.0;
double normalizer = Math.Max(Math.Abs(candidatePoint.Value), 1.0);
for (int ii = 0; ii < relGrad.Count; ++ii)
{
double projectedGradient = GetProjectedGradient(candidatePoint, ii);
double tmp = projectedGradient *
Math.Max(Math.Abs(candidatePoint.Point[ii]), 1.0) / normalizer;
relativeGradient = Math.Max(relativeGradient, Math.Abs(tmp));
}
if (relativeGradient < GradientTolerance)
{
return MinimizationResult.ExitCondition.RelativeGradient;
}
if (lastPoint != null)
{
double mostProgress = 0.0;
for (int ii = 0; ii < candidatePoint.Point.Count; ++ii)
{
var tmp = Math.Abs(candidatePoint.Point[ii] - lastPoint.Point[ii]) /
Math.Max(Math.Abs(lastPoint.Point[ii]), 1.0);
mostProgress = Math.Max(mostProgress, tmp);
}
if (mostProgress < ParameterTolerance)
{
return MinimizationResult.ExitCondition.LackOfProgress;
}
double functionChange = candidatePoint.Value - lastPoint.Value;
if (iterations > 500 && functionChange < 0 && Math.Abs(functionChange) < FunctionProgressTolerance)
return MinimizationResult.ExitCondition.LackOfProgress;
}
return MinimizationResult.ExitCondition.None;
}
protected virtual double GetProjectedGradient(IObjectiveFunction candidatePoint, int ii)
{
return candidatePoint.Gradient[ii];
}
protected void ValidateGradientAndObjective(IObjectiveFunction eval)
{
foreach (var x in eval.Gradient)
{
if (Double.IsNaN(x) || Double.IsInfinity(x))
throw new EvaluationException("Non-finite gradient returned.", eval);
}
if (Double.IsNaN(eval.Value) || Double.IsInfinity(eval.Value))
throw new EvaluationException("Non-finite objective function returned.", eval);
}
protected int DoBfgsUpdate(ref MinimizationResult.ExitCondition currentExitCondition, WolfeLineSearch lineSearcher, ref Matrix<double> inversePseudoHessian, ref Vector<double> lineSearchDirection, ref IObjectiveFunction previousPoint, ref LineSearchResult lineSearchResult, ref IObjectiveFunction candidate, ref Vector<double> step, ref int totalLineSearchSteps, ref int iterationsWithNontrivialLineSearch)
{
int iterations;
for (iterations = 1; iterations < MaximumIterations; ++iterations)
{
double startingStepSize;
double maxLineSearchStep;
lineSearchDirection = CalculateSearchDirection(ref inversePseudoHessian, out maxLineSearchStep, out startingStepSize, previousPoint, candidate, step);
try
{
lineSearchResult = lineSearcher.FindConformingStep(candidate, lineSearchDirection, startingStepSize, maxLineSearchStep);
}
catch (Exception e)
{
throw new InnerOptimizationException("Line search failed.", e);
}
iterationsWithNontrivialLineSearch += lineSearchResult.Iterations > 0 ? 1 : 0;
totalLineSearchSteps += lineSearchResult.Iterations;
step = lineSearchResult.FunctionInfoAtMinimum.Point - candidate.Point;
previousPoint = candidate;
candidate = lineSearchResult.FunctionInfoAtMinimum;
currentExitCondition = ExitCriteriaSatisfied(candidate, previousPoint, iterations);
if (currentExitCondition != MinimizationResult.ExitCondition.None)
break;
}
return iterations;
}
protected abstract Vector<double> CalculateSearchDirection(ref Matrix<double> inversePseudoHessian,
out double maxLineSearchStep,
out double startingStepSize,
IObjectiveFunction previousPoint,
IObjectiveFunction candidate,
Vector<double> step);
}
}

14
src/Numerics/Optimization/BfgsSolver.cs

@ -28,7 +28,6 @@
// OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
using System;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;
@ -56,6 +55,9 @@ namespace MathNet.Numerics.Optimization
/// <returns>The minimum found</returns>
public static Vector<double> Solve(Vector initialGuess, Func<Vector<double>, double> functionValue, Func<Vector<double>, Vector<double>> functionGradient)
{
var objectiveFunction = ObjectiveFunction.Gradient(functionValue, functionGradient);
objectiveFunction.EvaluateAt(initialGuess);
int dim = initialGuess.Count;
int iter = 0;
// H represents the approximation of the inverse hessian matrix
@ -65,18 +67,20 @@ namespace MathNet.Numerics.Optimization
Vector<double> x = initialGuess;
Vector<double> x_old = x;
Vector<double> grad;
WolfeLineSearch wolfeLineSearch = new WeakWolfeLineSearch(1e-4, 0.9, 1e-5, 200);
do
{
// search along the direction of the gradient
grad = functionGradient(x);
grad = objectiveFunction.Gradient;
Vector<double> p = -1 * H * grad;
double rate = WolfeRule.LineSearch(x, p, functionValue, functionGradient);
var lineSearchResult = wolfeLineSearch.FindConformingStep(objectiveFunction, p, 1.0);
double rate = lineSearchResult.FinalStep;
x = x + rate * p;
Vector<double> grad_old = grad;
// update the gradient
grad = functionGradient(x);
objectiveFunction.EvaluateAt(x);
grad = objectiveFunction.Gradient;// functionGradient(x);
Vector<double> s = x - x_old;
Vector<double> y = grad - grad_old;

32
src/Numerics/Optimization/LineSearch/LineSearchResult.cs

@ -1,4 +1,34 @@
namespace MathNet.Numerics.Optimization.LineSearch
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
namespace MathNet.Numerics.Optimization.LineSearch
{
public class LineSearchResult : MinimizationResult
{

35
src/Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs

@ -1,5 +1,34 @@
using System;
using MathNet.Numerics.LinearAlgebra;
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using System;
namespace MathNet.Numerics.Optimization.LineSearch
{
@ -8,7 +37,7 @@ namespace MathNet.Numerics.Optimization.LineSearch
public StrongWolfeLineSearch(double c1, double c2, double parameterTolerance, int maxIterations = 10)
: base(c1, c2, parameterTolerance, maxIterations)
{
// Validation in base class
// Argument validation in base class
}
protected override MinimizationResult.ExitCondition WolfeExitCondition { get { return MinimizationResult.ExitCondition.StrongWolfeCriteria; } }

32
src/Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs

@ -1,4 +1,34 @@
using System;
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using System;
using MathNet.Numerics.LinearAlgebra;
namespace MathNet.Numerics.Optimization.LineSearch

35
src/Numerics/Optimization/LineSearch/WolfeLineSearch.cs

@ -1,8 +1,35 @@
using MathNet.Numerics.LinearAlgebra;
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using MathNet.Numerics.LinearAlgebra;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MathNet.Numerics.Optimization.LineSearch
{

111
src/Numerics/Optimization/LineSearch/WolfeRule.cs

@ -1,111 +0,0 @@
// <copyright file="WolfeRule.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2015 Math.NET
//
// 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.
// </copyright>
using System;
using MathNet.Numerics.LinearAlgebra;
namespace MathNet.Numerics.Optimization.LineSearch
{
/// <summary>
/// Performs an inexact line search. This is used as a part of quasi-Newton optimization methods to figure
/// out how far to move along a certain gradient.
/// See http://en.wikipedia.org/wiki/Wolfe_conditions
/// Inspired by implementation: https://github.com/PatWie/CppNumericalSolvers/blob/master/src/linesearch/WolfeRule.h
/// </summary>
internal static class WolfeRule
{
/// <summary>
/// Searches along a line to satisfy the Wolfe conditions (inexact search for minimum)
/// </summary>
/// <param name="x0">Starting point of search</param>
/// <param name="z">Search direction</param>
/// <param name="functionValue">Evaluates the function being minimized</param>
/// <param name="functionGradient">Evaluates the gradient of the function</param>
/// <param name="alphaInit">Initial value for the coefficient of z (distance to travel in z direction)</param>
/// <returns></returns>
public static double LineSearch(
Vector<double> x0,
Vector<double> z,
Func<Vector<double>, double> functionValue,
Func<Vector<double>, Vector<double>> functionGradient,
float alphaInit = 1)
{
Vector<double> x = x0;
// evaluate phi(0)
double phi0 = functionValue(x0);
// evaluate phi'(0)
Vector<double> grad = functionGradient(x);
double phi0_dash = z * grad;
double alpha = alphaInit;
bool decrease_direction = true;
// 200 guesses max
for (int iter = 0; iter < 200; ++iter) {
// new guess for phi(alpha)
Vector<double> x_candidate = x + alpha * z;
double phi = functionValue(x_candidate);
// decrease condition invalid --> shrink interval
if (phi > phi0 + 0.0001 * alpha * phi0_dash)
{
alpha *= 0.5;
decrease_direction = false;
}
else
{
// valid decrease --> test strong wolfe condition
Vector<double> grad2 = functionGradient(x_candidate);
double phi_dash = z * grad2;
// curvature condition invalid ?
if ((phi_dash < 0.9 * phi0_dash) || !decrease_direction) {
// increase interval
alpha *= 4.0;
}
else {
// both condition are valid --> we are happy
x = x_candidate;
grad = grad2;
phi0 = phi;
return alpha;
}
}
}
return alpha;
}
}
}

23
src/Numerics/Optimization/QuadraticGradientProjectionSearch.cs

@ -6,7 +6,7 @@ namespace MathNet.Numerics.Optimization
{
public static class QuadraticGradientProjectionSearch
{
public static Tuple<Vector<double>,int,List<bool>> Search(Vector<double> x0, Vector<double> gradient, Matrix<double> hessian, Vector<double> lowerBound, Vector<double> upperBound)
public static GradientProjectionResult Search(Vector<double> x0, Vector<double> gradient, Matrix<double> hessian, Vector<double> lowerBound, Vector<double> upperBound)
{
List<bool> isFixed = new List<bool>(x0.Count);
List<double> breakpoint = new List<double>(x0.Count);
@ -46,7 +46,7 @@ namespace MathNet.Numerics.Optimization
var maxS = orderedBreakpoint[0];
if (sMin < maxS)
return Tuple.Create(x + sMin * d, 0,isFixed);
return new GradientProjectionResult(x + sMin * d, 0,isFixed);
// while minimum of the last quadratic piece observed is beyond the interval searched
while (true)
@ -66,7 +66,7 @@ namespace MathNet.Numerics.Optimization
}
if (Double.IsPositiveInfinity(orderedBreakpoint[jj + 1]))
return Tuple.Create(x, fixedCount, isFixed);
return new GradientProjectionResult(x, fixedCount, isFixed);
f1 = gradient * d + (x - x0) * hessian * d;
f2 = d * hessian * d;
@ -74,13 +74,26 @@ namespace MathNet.Numerics.Optimization
sMin = -f1 / f2;
if (sMin < maxS)
return Tuple.Create(x + sMin * d, fixedCount, isFixed);
return new GradientProjectionResult(x + sMin * d, fixedCount, isFixed);
else if (jj + 1 >= orderedBreakpoint.Count - 1)
{
isFixed[isFixed.Count - 1] = true;
return Tuple.Create(x + maxS * d, lowerBound.Count, isFixed);
return new GradientProjectionResult(x + maxS * d, lowerBound.Count, isFixed);
}
}
}
public struct GradientProjectionResult
{
public GradientProjectionResult(Vector<double> cauchyPoint, int fixedCount, List<bool> isFixed)
{
CauchyPoint = cauchyPoint;
FixedCount = fixedCount;
IsFixed = isFixed;
}
public Vector<double> CauchyPoint { get; }
public int FixedCount { get; }
public List<bool> IsFixed { get; }
}
}
}

22
src/UnitTests/OptimizationTests/BfgsTest.cs

@ -4,7 +4,7 @@
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2015 Math.NET
// Copyright (c) 2009-2016 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
@ -28,7 +28,6 @@
// OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.Optimization;
using NUnit.Framework;
@ -50,23 +49,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
private static void CheckRosenbrock(double a, double b, double expectedMin)
{
var x = BfgsSolver.Solve(new DenseVector(new[] { a, b }), Rosenbrock, RosenbrockGradient);
Numerics.Precision.AlmostEqual(expectedMin, Rosenbrock(x), Precision);
}
private static double Rosenbrock(Vector<double> x)
{
double t1 = (1 - x[0]);
double t2 = (x[1] - x[0] * x[0]);
return t1 * t1 + 100 * t2 * t2;
}
private static Vector<double> RosenbrockGradient(Vector<double> x)
{
var grad = new DenseVector(2);
grad[0] = -2 * (1 - x[0]) + 200 * (x[1] - x[0] * x[0]) * (-2 * x[0]);
grad[1] = 200 * (x[1] - x[0] * x[0]);
return grad;
var x = BfgsSolver.Solve(new DenseVector(new[] { a, b }), RosenbrockFunction.Value, RosenbrockFunction.Gradient);
Numerics.Precision.AlmostEqual(expectedMin, RosenbrockFunction.Value(x), Precision);
}
}
}

86
src/UnitTests/OptimizationTests/TestBfgsBMinimizer.cs

@ -1,3 +1,33 @@
// <copyright file="BfgsTest.cs" company="Math.NET">
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2016 Math.NET
//
// 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.
// </copyright>
using System;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.Optimization;
@ -19,8 +49,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.That(Math.Abs(result.MinimizingPoint[0] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - RosenbrockFunction.Minimum[1]), Is.LessThan(1e-3));
}
[Test]
@ -36,8 +66,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.That(Math.Abs(result.MinimizingPoint[0] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - RosenbrockFunction.Minimum[1]), Is.LessThan(1e-3));
}
[Test]
@ -52,8 +82,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
var result = solver.FindMinimum (obj, lowerBound, upperBound, initialGuess);
Assert.That(Math.Abs(result.MinimizingPoint[0] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - RosenbrockFunction.Minimum[1]), Is.LessThan(1e-3));
}
[Test]
@ -67,8 +97,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.That(Math.Abs(result.MinimizingPoint[0] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - RosenbrockFunction.Minimum[1]), Is.LessThan(1e-3));
}
[Test]
@ -82,9 +112,43 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.That(Math.Abs(result.MinimizingPoint[0] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - 1.0), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
Assert.That(Math.Abs(result.MinimizingPoint[1] - RosenbrockFunction.Minimum[1]), Is.LessThan(1e-3));
}
}
[Test]
public void FindMinimum_Rosenbrock_MinimumGreateerOrEqualToLowerBoundary()
{
var obj = ObjectiveFunction.Gradient(RosenbrockFunction.Value, RosenbrockFunction.Gradient);
var solver = new BfgsBMinimizer(1e-5, 1e-5, 1e-5, maximumIterations: 1000);
var lowerBound = new DenseVector(new[] { 2, 2.0 });
var upperBound = new DenseVector(new[] { 5.0, 5.0 });
var initialGuess = new DenseVector(new[] { 2.5, 2.5 });
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.GreaterOrEqual(result.MinimizingPoint[0],lowerBound[0]);
Assert.GreaterOrEqual(result.MinimizingPoint[1], lowerBound[1]);
}
[Test]
public void FindMinimum_Rosenbrock_MinimumLesserOrEqualToUpperBoundary()
{
var obj = ObjectiveFunction.Gradient(RosenbrockFunction.Value, RosenbrockFunction.Gradient);
var solver = new BfgsBMinimizer(1e-5, 1e-5, 1e-5, maximumIterations: 1000);
var lowerBound = new DenseVector(new[] { -2.0, -2.0 });
var upperBound = new DenseVector(new[] { 0.5, 0.5 });
var initialGuess = new DenseVector(new[] { -0.9, -0.5 });
var result = solver.FindMinimum(obj, lowerBound, upperBound, initialGuess);
Assert.LessOrEqual(result.MinimizingPoint[0],upperBound[0]);
Assert.LessOrEqual(result.MinimizingPoint[1],upperBound[1]);
}
}
}

12
src/UnitTests/OptimizationTests/TestBfgsMinimizer.cs

@ -12,7 +12,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_Rosenbrock_Easy()
{
var obj = ObjectiveFunction.Gradient(RosenbrockFunction.Value, RosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { 1.2, 1.2 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
@ -23,7 +23,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_Rosenbrock_Hard()
{
var obj = ObjectiveFunction.Gradient(RosenbrockFunction.Value, RosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { -1.2, 1.0 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
@ -34,7 +34,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_Rosenbrock_Overton()
{
var obj = ObjectiveFunction.Gradient(RosenbrockFunction.Value, RosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { -0.9, -0.5 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - RosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
@ -45,7 +45,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_BigRosenbrock_Easy()
{
var obj = ObjectiveFunction.Gradient(BigRosenbrockFunction.Value, BigRosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-10, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-10, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { 1.2*100.0, 1.2*100.0 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - BigRosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
@ -56,7 +56,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_BigRosenbrock_Hard()
{
var obj = ObjectiveFunction.Gradient(BigRosenbrockFunction.Value, BigRosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { -1.2*100.0, 1.0*100.0 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - BigRosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));
@ -67,7 +67,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void FindMinimum_BigRosenbrock_Overton()
{
var obj = ObjectiveFunction.Gradient(BigRosenbrockFunction.Value, BigRosenbrockFunction.Gradient);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1000);
var solver = new BfgsMinimizer(1e-5, 1e-5, 1e-5, 1000);
var result = solver.FindMinimum(obj, new DenseVector(new[] { -0.9*100.0, -0.5*100.0 }));
Assert.That(Math.Abs(result.MinimizingPoint[0] - BigRosenbrockFunction.Minimum[0]), Is.LessThan(1e-3));

Loading…
Cancel
Save