/// <summary> /// Test a scaling operation of the high point, and replace it if it is an improvement /// </summary> /// <param name="scaleFactor"></param> /// <param name="errorProfile"></param> /// <param name="vertices"></param> /// <param name="errorValues"></param> /// <param name="objectiveFunction"></param> /// <returns></returns> static double TryToScaleSimplex(double scaleFactor, ref ErrorProfile errorProfile, Vector <double>[] vertices, double[] errorValues, IObjectiveFunction objectiveFunction) { // find the centroid through which we will reflect Vector <double> centroid = ComputeCentroid(vertices, errorProfile); // define the vector from the centroid to the high point Vector <double> centroidToHighPoint = vertices[errorProfile.HighestIndex].Subtract(centroid); // scale and position the vector to determine the new trial point Vector <double> newPoint = centroidToHighPoint.Multiply(scaleFactor).Add(centroid); // evaluate the new point objectiveFunction.EvaluateAt(newPoint); double newErrorValue = objectiveFunction.Value; // if it's better, replace the old high point if (newErrorValue < errorValues[errorProfile.HighestIndex]) { vertices[errorProfile.HighestIndex] = newPoint; errorValues[errorProfile.HighestIndex] = newErrorValue; } return(newErrorValue); }
/// <summary> /// Evaluate the objective function at each vertex to create a corresponding /// list of error values for each vertex /// </summary> /// <param name="vertices"></param> /// <param name="objectiveFunction"></param> /// <returns></returns> static double[] InitializeErrorValues(Vector <double>[] vertices, IObjectiveFunction objectiveFunction) { double[] errorValues = new double[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { objectiveFunction.EvaluateAt(vertices[i]); errorValues[i] = objectiveFunction.Value; } return(errorValues); }
/// <summary> /// Contract the simplex uniformly around the lowest point /// </summary> /// <param name="errorProfile"></param> /// <param name="vertices"></param> /// <param name="errorValues"></param> /// <param name="objectiveFunction"></param> static void ShrinkSimplex(ErrorProfile errorProfile, Vector <double>[] vertices, double[] errorValues, IObjectiveFunction objectiveFunction) { Vector <double> lowestVertex = vertices[errorProfile.LowestIndex]; for (int i = 0; i < vertices.Length; i++) { if (i != errorProfile.LowestIndex) { vertices[i] = (vertices[i].Add(lowestVertex)).Multiply(0.5); objectiveFunction.EvaluateAt(vertices[i]); errorValues[i] = objectiveFunction.Value; } } }
/// <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); ValidateGradientAndObjective(objective); // Check that we're not already done ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); if (currentExitCondition != ExitCondition.None) { return(new MinimizationResult(objective, 0, currentExitCondition)); } // Set up line search algorithm var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-10), 1000); // First step var inversePseudoHessian = CreateMatrix.DenseIdentity <double>(initialGuess.Count); var lineSearchDirection = -objective.Gradient; var stepSize = 100 * GradientTolerance / (lineSearchDirection * lineSearchDirection); var previousPoint = objective; LineSearchResult lineSearchResult; try { lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize); } catch (OptimizationException e) { throw new InnerOptimizationException("Line search failed.", e); } catch (ArgumentException e) { throw new InnerOptimizationException("Line search failed.", e); } var candidate = lineSearchResult.FunctionInfoAtMinimum; ValidateGradientAndObjective(candidate); var gradient = candidate.Gradient; var step = candidate.Point - initialGuess; // Subsequent steps Matrix <double> I = CreateMatrix.DiagonalIdentity <double>(initialGuess.Count); int iterations; 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 == ExitCondition.None) { throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations)); } return(new MinimizationWithLineSearchResult(candidate, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch)); }
public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector <double> initialGuess) { if (!objective.IsGradientSupported) { throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for Newton minimization."); } if (!objective.IsHessianSupported) { throw new IncompatibleObjectiveException("Hessian not supported in objective function, but required for Newton minimization."); } // Check that we're not already done objective.EvaluateAt(initialGuess); ValidateGradient(objective); if (ExitCriteriaSatisfied(objective.Gradient)) { return(new MinimizationResult(objective, 0, ExitCondition.AbsoluteGradient)); } // Set up line search algorithm var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, 1e-4, maxIterations: 1000); // Subsequent steps int iterations = 0; int totalLineSearchSteps = 0; int iterationsWithNontrivialLineSearch = 0; bool tmpLineSearch = false; while (!ExitCriteriaSatisfied(objective.Gradient) && iterations < MaximumIterations) { ValidateHessian(objective); var searchDirection = objective.Hessian.LU().Solve(-objective.Gradient); if (searchDirection * objective.Gradient >= 0) { searchDirection = -objective.Gradient; tmpLineSearch = true; } if (UseLineSearch || tmpLineSearch) { LineSearchResult result; 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; objective = result.FunctionInfoAtMinimum; } else { objective.EvaluateAt(objective.Point + searchDirection); } ValidateGradient(objective); tmpLineSearch = false; iterations += 1; } if (iterations == MaximumIterations) { throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations)); } return(new MinimizationWithLineSearchResult(objective, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch)); }
/// <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."); } // Check that dimensions match if (lowerBound.Count != upperBound.Count || lowerBound.Count != initialGuess.Count) { throw new ArgumentException("Dimensions of bounds and/or initial guess do not match."); } // Check that initial guess is feasible for (int ii = 0; ii < initialGuess.Count; ++ii) { if (initialGuess[ii] < lowerBound[ii] || initialGuess[ii] > upperBound[ii]) { throw new ArgumentException("Initial guess is not in the feasible region"); } } objective.EvaluateAt(initialGuess); ValidateGradientAndObjective(objective); // Check that we're not already done ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); if (currentExitCondition != ExitCondition.None) { return(new MinimizationResult(objective, 0, currentExitCondition)); } // Set up line search algorithm var lineSearcher = new StrongWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-5), maxIterations: 1000); // Declare state variables Vector <double> reducedSolution1, reducedGradient, reducedInitialPoint, reducedCauchyPoint, solution1; Matrix <double> reducedHessian; List <int> reducedMap; // First step var pseudoHessian = CreateMatrix.DiagonalIdentity <double>(initialGuess.Count); // Determine active set var gradientProjectionResult = QuadraticGradientProjectionSearch.Search(objective.Point, objective.Gradient, pseudoHessian, lowerBound, upperBound); var cauchyPoint = gradientProjectionResult.CauchyPoint; var fixedCount = gradientProjectionResult.FixedCount; var isFixed = gradientProjectionResult.IsFixed; var 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); CreateReducedData(objective.Point, cauchyPoint, isFixed, lowerBound, upperBound, objective.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; } var directionFromCauchy = solution1 - cauchyPoint; var maxStepFromCauchyPoint = FindMaxStep(cauchyPoint, directionFromCauchy, lowerBound, upperBound); var solution2 = cauchyPoint + Math.Min(maxStepFromCauchyPoint, 1.0) * directionFromCauchy; var lineSearchDirection = solution2 - objective.Point; var maxLineSearchStep = FindMaxStep(objective.Point, lineSearchDirection, lowerBound, upperBound); var estStepSize = -objective.Gradient * lineSearchDirection / (lineSearchDirection * pseudoHessian * lineSearchDirection); var startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep); // Line search LineSearchResult lineSearchResult; try { lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, startingStepSize, upperBound: maxLineSearchStep); } catch (Exception e) { throw new InnerOptimizationException("Line search failed.", e); } var previousPoint = objective.Fork(); var candidatePoint = lineSearchResult.FunctionInfoAtMinimum; var gradient = candidatePoint.Gradient; var step = candidatePoint.Point - initialGuess; // Subsequent steps int totalLineSearchSteps = lineSearchResult.Iterations; int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1; int iterations = DoBfgsUpdate(ref currentExitCondition, lineSearcher, ref pseudoHessian, ref lineSearchDirection, ref previousPoint, ref lineSearchResult, ref candidatePoint, ref step, ref totalLineSearchSteps, ref iterationsWithNontrivialLineSearch); if (iterations == MaximumIterations && currentExitCondition == ExitCondition.None) { throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations)); } return(new MinimizationWithLineSearchResult(candidatePoint, iterations, currentExitCondition, totalLineSearchSteps, iterationsWithNontrivialLineSearch)); }
public static MinimizationResult Minimum(IObjectiveFunction objective, Vector <double> initialGuess, double gradientTolerance = 1e-8, int maxIterations = 1000) { if (!objective.IsGradientSupported) { throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for ConjugateGradient minimization."); } objective.EvaluateAt(initialGuess); var gradient = objective.Gradient; ValidateGradient(objective); // Check that we're not already done if (gradient.Norm(2.0) < gradientTolerance) { return(new MinimizationResult(objective, 0, ExitCondition.AbsoluteGradient)); } // Set up line search algorithm var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.1, 1e-4, 1000); // First step var steepestDirection = -gradient; var searchDirection = steepestDirection; double initialStepSize = 100 * gradientTolerance / (gradient * gradient); LineSearchResult result; try { result = lineSearcher.FindConformingStep(objective, searchDirection, initialStepSize); } catch (Exception e) { throw new InnerOptimizationException("Line search failed.", e); } objective = result.FunctionInfoAtMinimum; ValidateGradient(objective); double stepSize = result.FinalStep; // Subsequent steps int iterations = 1; int totalLineSearchSteps = result.Iterations; int iterationsWithNontrivialLineSearch = result.Iterations > 0 ? 0 : 1; int steepestDescentResets = 0; while (objective.Gradient.Norm(2.0) >= gradientTolerance && iterations < maxIterations) { var previousSteepestDirection = steepestDirection; steepestDirection = -objective.Gradient; var searchDirectionAdjuster = Math.Max(0, steepestDirection * (steepestDirection - previousSteepestDirection) / (previousSteepestDirection * previousSteepestDirection)); searchDirection = steepestDirection + searchDirectionAdjuster * searchDirection; if (searchDirection * objective.Gradient >= 0) { searchDirection = steepestDirection; steepestDescentResets += 1; } try { result = lineSearcher.FindConformingStep(objective, searchDirection, stepSize); } catch (Exception e) { throw new InnerOptimizationException("Line search failed.", e); } iterationsWithNontrivialLineSearch += result.Iterations == 0 ? 1 : 0; totalLineSearchSteps += result.Iterations; stepSize = result.FinalStep; objective = result.FunctionInfoAtMinimum; iterations += 1; } //if (iterations == maxIterations) //{ // throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", maxIterations)); //} return(new MinimizationWithLineSearchResult(objective, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch)); }
/// <summary></summary> /// <param name="startingPoint">The objective function being optimized, evaluated at the starting point of the search</param> /// <param name="searchDirection">Search direction</param> /// <param name="initialStep">Initial size of the step in the search direction</param> /// <param name="upperBound">The upper bound</param> public LineSearchResult FindConformingStep(IObjectiveFunctionEvaluation startingPoint, Vector <double> searchDirection, double initialStep, double upperBound) { ValidateInputArguments(startingPoint, searchDirection, initialStep, upperBound); double lowerBound = 0.0; double step = initialStep; double initialValue = startingPoint.Value; Vector <double> initialGradient = startingPoint.Gradient; double initialDd = searchDirection * initialGradient; IObjectiveFunction objective = startingPoint.CreateNew(); int ii; ExitCondition reasonForExit = ExitCondition.None; for (ii = 0; ii < MaximumIterations; ++ii) { objective.EvaluateAt(startingPoint.Point + searchDirection * step); ValidateGradient(objective); ValidateValue(objective); double stepDd = searchDirection * objective.Gradient; if (objective.Value > initialValue + C1 * step * initialDd) { upperBound = step; step = 0.5 * (lowerBound + upperBound); } else if (WolfeCondition(stepDd, initialDd)) { lowerBound = step; step = double.IsPositiveInfinity(upperBound) ? 2 * lowerBound : 0.5 * (lowerBound + upperBound); } else { reasonForExit = WolfeExitCondition; break; } if (!double.IsInfinity(upperBound)) { double maxRelChange = 0.0; for (int jj = 0; jj < objective.Point.Count; ++jj) { double tmp = Math.Abs(searchDirection[jj] * (upperBound - lowerBound)) / Math.Max(Math.Abs(objective.Point[jj]), 1.0); maxRelChange = Math.Max(maxRelChange, tmp); } if (maxRelChange < ParameterTolerance) { reasonForExit = ExitCondition.LackOfProgress; break; } } } if (ii == MaximumIterations && Double.IsPositiveInfinity(upperBound)) { throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached. Function appears to be unbounded in search direction.", MaximumIterations)); } if (ii == MaximumIterations) { throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations)); } return(new LineSearchResult(objective, ii, step, reasonForExit)); }
/// <summary> /// Finds the minimum of the objective function with an initial perturbation /// </summary> /// <param name="objectiveFunction">The objective function, no gradient or hessian needed</param> /// <param name="initialGuess">The initial guess</param> /// <param name="initalPertubation">The initial perturbation</param> /// <returns>The minimum point</returns> public static MinimizationResult Minimum(IObjectiveFunction objectiveFunction, Vector <double> initialGuess, Vector <double> initalPertubation, double convergenceTolerance = 1e-8, int maximumIterations = 1000) { // confirm that we are in a position to commence if (objectiveFunction == null) { throw new ArgumentNullException(nameof(objectiveFunction), "ObjectiveFunction must be set to a valid ObjectiveFunctionDelegate"); } if (initialGuess == null) { throw new ArgumentNullException(nameof(initialGuess), "initialGuess must be initialized"); } if (initalPertubation == null) { throw new ArgumentNullException(nameof(initalPertubation), "initalPertubation must be initialized, if unknown use overloaded version of FindMinimum()"); } SimplexConstant[] simplexConstants = SimplexConstant.CreateSimplexConstantsFromVectors(initialGuess, initalPertubation); // create the initial simplex int numDimensions = simplexConstants.Length; int numVertices = numDimensions + 1; Vector <double>[] vertices = InitializeVertices(simplexConstants); int evaluationCount = 0; ExitCondition exitCondition; ErrorProfile errorProfile; double[] errorValues = InitializeErrorValues(vertices, objectiveFunction); int numTimesHasConverged = 0; // iterate until we converge, or complete our permitted number of iterations while (true) { errorProfile = EvaluateSimplex(errorValues); // see if the range in point heights is small enough to exit // to handle the case when the function is symmetrical and extra iteration is performed if (HasConverged(convergenceTolerance, errorProfile, errorValues)) { numTimesHasConverged++; } else { numTimesHasConverged = 0; } if (numTimesHasConverged == 2) { exitCondition = ExitCondition.Converged; break; } // attempt a reflection of the simplex double reflectionPointValue = TryToScaleSimplex(-1.0, ref errorProfile, vertices, errorValues, objectiveFunction); ++evaluationCount; if (reflectionPointValue <= errorValues[errorProfile.LowestIndex]) { // it's better than the best point, so attempt an expansion of the simplex TryToScaleSimplex(2.0, ref errorProfile, vertices, errorValues, objectiveFunction); ++evaluationCount; } else if (reflectionPointValue >= errorValues[errorProfile.NextHighestIndex]) { // it would be worse than the second best point, so attempt a contraction to look // for an intermediate point double currentWorst = errorValues[errorProfile.HighestIndex]; double contractionPointValue = TryToScaleSimplex(0.5, ref errorProfile, vertices, errorValues, objectiveFunction); ++evaluationCount; if (contractionPointValue >= currentWorst) { // that would be even worse, so let's try to contract uniformly towards the low point; // don't bother to update the error profile, we'll do it at the start of the // next iteration ShrinkSimplex(errorProfile, vertices, errorValues, objectiveFunction); evaluationCount += numVertices; // that required one function evaluation for each vertex; keep track } } // check to see if we have exceeded our alloted number of evaluations if (evaluationCount >= maximumIterations) { throw new MaximumIterationsException(FormattableString.Invariant($"Maximum iterations ({maximumIterations}) reached.")); } } objectiveFunction.EvaluateAt(vertices[errorProfile.LowestIndex]); var regressionResult = new MinimizationResult(objectiveFunction, evaluationCount, exitCondition); return(regressionResult); }
/// <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 L-BFGS minimization."); } objective.EvaluateAt(initialGuess); ValidateGradientAndObjective(objective); // Check that we're not already done ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); if (currentExitCondition != ExitCondition.None) { return(new MinimizationResult(objective, 0, currentExitCondition)); } // Set up line search algorithm var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-10), 1000); // First step var lineSearchDirection = -objective.Gradient; var stepSize = (100 * GradientTolerance) / (lineSearchDirection * lineSearchDirection); var previousPoint = objective; LineSearchResult lineSearchResult; try { lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize); } catch (OptimizationException e) { throw new InnerOptimizationException("Line search failed.", e); } catch (ArgumentException e) { throw new InnerOptimizationException("Line search failed.", e); } var candidate = lineSearchResult.FunctionInfoAtMinimum; ValidateGradientAndObjective(candidate); var gradient = candidate.Gradient; var step = candidate.Point - initialGuess; var yk = candidate.Gradient - previousPoint.Gradient; var ykhistory = new List <Vector <double> >() { yk }; var skhistory = new List <Vector <double> >() { step }; var rhokhistory = new List <double>() { 1.0 / yk.DotProduct(step) }; // Subsequent steps int iterations = 1; int totalLineSearchSteps = lineSearchResult.Iterations; int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1; previousPoint = candidate; while ((iterations++ < MaximumIterations) && (previousPoint.Gradient.Norm(2) >= GradientTolerance)) { lineSearchDirection = -ApplyLbfgsUpdate(previousPoint, ykhistory, skhistory, rhokhistory); var directionalDerivative = previousPoint.Gradient.DotProduct(lineSearchDirection); if (directionalDerivative > 0) { throw new InnerOptimizationException("Direction is not a descent direction."); } try { lineSearchResult = lineSearcher.FindConformingStep(previousPoint, lineSearchDirection, 1.0); } catch (OptimizationException e) { throw new InnerOptimizationException("Line search failed.", e); } catch (ArgumentException e) { throw new InnerOptimizationException("Line search failed.", e); } iterationsWithNontrivialLineSearch += lineSearchResult.Iterations > 0 ? 1 : 0; totalLineSearchSteps += lineSearchResult.Iterations; candidate = lineSearchResult.FunctionInfoAtMinimum; currentExitCondition = ExitCriteriaSatisfied(candidate, previousPoint, iterations); if (currentExitCondition != ExitCondition.None) { break; } step = candidate.Point - previousPoint.Point; yk = candidate.Gradient - previousPoint.Gradient; ykhistory.Add(yk); skhistory.Add(step); rhokhistory.Add(1.0 / yk.DotProduct(step)); previousPoint = candidate; if (ykhistory.Count > Memory) { ykhistory.RemoveAt(0); skhistory.RemoveAt(0); rhokhistory.RemoveAt(0); } } if ((iterations == MaximumIterations) && (currentExitCondition == ExitCondition.None)) { throw new MaximumIterationsException(String.Format("Maximum iterations ({0}) reached.", MaximumIterations)); } return(new MinimizationWithLineSearchResult(candidate, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch)); }