Skip to content
This repository was archived by the owner on Nov 23, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions backtracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@
package optimize

const (
defaultBacktrackingDecrease = 0.5
defaultBacktrackingFuncConst = 1e-4
minimumBacktrackingStepSize = 1e-20
defaultBacktrackingContraction = 0.5
defaultBacktrackingDecrease = 1e-4
minimumBacktrackingStepSize = 1e-20
)

// Backtracking is a Linesearcher that uses backtracking to find a point that
// satisfies the Armijo condition with the given function constant FuncConst. If
// the Armijo condition has not been met, the step size is decreased by a
// factor of Decrease.
// satisfies the Armijo condition with the given decrease factor. If the Armijo
// condition has not been met, the step size is decreased by ContractionFactor.
//
// The Armijo condition only requires the gradient at the beginning of each
// major iteration (not at successive step locations), and so Backtracking may
// be a good linesearch for functions with expensive gradients. Backtracking is
// not appropriate for optimizers that require the Wolfe conditions to be met,
// such as BFGS.
//
// Both FuncConst and Decrease must be between zero and one, and Backtracking will
// panic otherwise. If either FuncConst or Decrease are zero, it will be set to a
// reasonable default.
// Both DecreaseFactor and ContractionFactor must be between zero and one, and
// Backtracking will panic otherwise. If either DecreaseFactor or
// ContractionFactor are zero, it will be set to a reasonable default.
type Backtracking struct {
FuncConst float64 // Necessary function descrease for Armijo condition.
Decrease float64 // Step size multiplier at each iteration (stepSize *= Decrease).
DecreaseFactor float64 // Constant factor in the sufficient decrease (Armijo) condition.
ContractionFactor float64 // Step size multiplier at each iteration (step *= ContractionFactor).

stepSize float64
initF float64
Expand All @@ -43,17 +42,17 @@ func (b *Backtracking) Init(f, g float64, step float64) Operation {
panic("backtracking: initial derivative is non-negative")
}

if b.Decrease == 0 {
b.Decrease = defaultBacktrackingDecrease
if b.ContractionFactor == 0 {
b.ContractionFactor = defaultBacktrackingContraction
}
if b.FuncConst == 0 {
b.FuncConst = defaultBacktrackingFuncConst
if b.DecreaseFactor == 0 {
b.DecreaseFactor = defaultBacktrackingDecrease
}
if b.Decrease <= 0 || b.Decrease >= 1 {
panic("backtracking: Decrease must be between 0 and 1")
if b.ContractionFactor <= 0 || b.ContractionFactor >= 1 {
panic("backtracking: ContractionFactor must be between 0 and 1")
}
if b.FuncConst <= 0 || b.FuncConst >= 1 {
panic("backtracking: FuncConst must be between 0 and 1")
if b.DecreaseFactor <= 0 || b.DecreaseFactor >= 1 {
panic("backtracking: DecreaseFactor must be between 0 and 1")
}

b.stepSize = step
Expand All @@ -69,11 +68,11 @@ func (b *Backtracking) Iterate(f, _ float64) (Operation, float64, error) {
panic("backtracking: Init has not been called")
}

if ArmijoConditionMet(f, b.initF, b.initG, b.stepSize, b.FuncConst) {
if ArmijoConditionMet(f, b.initF, b.initG, b.stepSize, b.DecreaseFactor) {
b.lastOp = MajorIteration
return b.lastOp, b.stepSize, nil
}
b.stepSize *= b.Decrease
b.stepSize *= b.ContractionFactor
if b.stepSize < minimumBacktrackingStepSize {
b.lastOp = NoOperation
return b.lastOp, b.stepSize, ErrLinesearcherFailure
Expand Down
21 changes: 12 additions & 9 deletions bisection.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ package optimize
import "math"

// Bisection is a Linesearcher that uses a bisection to find a point that
// satisfies the strong Wolfe conditions with the given gradient constant and
// function constant of zero. If GradConst is zero, it will be set to a reasonable
// value. Bisection will panic if GradConst is not between zero and one.
// satisfies the strong Wolfe conditions with the given curvature factor and
// sufficient decrease factor of zero.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curvature factor and no function decrease.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that saying "no function decrease" without "factor" could be misleading/incorrect because in fact there is no function increase. Finishing the sentence after "Wolfe conditions" should be enough clear, we talk about the factors because we want to stress that there is only the curvature factor. But even if we use the decrease factor of zero, there is no reason to prevent the users from setting it to a positive value. So unless you object, in another PR I'd like to add DecreaseFactor to Bisection and trim the sentence after "Wolfe conditions". I'll wait for your comment before merging this PR, @btracey

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, there should probably be a DecreaseFactor. The internal logic may need to change some though, so if you want to do that in a different PR that's fine.

type Bisection struct {
GradConst float64
// CurvatureFactor is the constant factor in the curvature condition.
// Smaller values result in a more exact line search.
// A set value must be in the interval (0, 1), otherwise Init will panic.
// If it is zero, it will be defaulted to 0.9.
CurvatureFactor float64

minStep float64
maxStep float64
Expand All @@ -35,11 +38,11 @@ func (b *Bisection) Init(f, g float64, step float64) Operation {
panic("bisection: initial derivative is non-negative")
}

if b.GradConst == 0 {
b.GradConst = 0.9
if b.CurvatureFactor == 0 {
b.CurvatureFactor = 0.9
}
if b.GradConst <= 0 || b.GradConst >= 1 {
panic("bisection: GradConst not between 0 and 1")
if b.CurvatureFactor <= 0 || b.CurvatureFactor >= 1 {
panic("bisection: CurvatureFactor not between 0 and 1")
}

b.minStep = 0
Expand Down Expand Up @@ -94,7 +97,7 @@ func (b *Bisection) Iterate(f, g float64) (Operation, float64, error) {
f = b.lastF
// The function value was lower. Check if this location is sufficient to
// converge the linesearch, otherwise iterate.
if StrongWolfeConditionsMet(f, g, minF, b.initGrad, b.currStep, 0, b.GradConst) {
if StrongWolfeConditionsMet(f, g, minF, b.initGrad, b.currStep, 0, b.CurvatureFactor) {
b.lastOp = MajorIteration
return b.lastOp, b.currStep, nil
}
Expand Down
22 changes: 11 additions & 11 deletions linesearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ func (ls *LinesearchMethod) initNextLinesearch(loc *Location) (Operation, error)
// true, though this is not enforced:
// - initGrad < 0
// - step > 0
// - 0 < funcConst < 1
func ArmijoConditionMet(currObj, initObj, initGrad, step, funcConst float64) bool {
return currObj <= initObj+funcConst*step*initGrad
// - 0 < decrease < 1
func ArmijoConditionMet(currObj, initObj, initGrad, step, decrease float64) bool {
return currObj <= initObj+decrease*step*initGrad
}

// StrongWolfeConditionsMet returns true if the strong Wolfe conditions have been met.
Expand All @@ -195,12 +195,12 @@ func ArmijoConditionMet(currObj, initObj, initGrad, step, funcConst float64) boo
// enforced:
// - initGrad < 0
// - step > 0
// - 0 <= funcConst < gradConst < 1
func StrongWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, funcConst, gradConst float64) bool {
if currObj > initObj+funcConst*step*initGrad {
// - 0 <= decrease < curvature < 1
func StrongWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
if currObj > initObj+decrease*step*initGrad {
return false
}
return math.Abs(currGrad) < gradConst*math.Abs(initGrad)
return math.Abs(currGrad) < curvature*math.Abs(initGrad)
}

// WeakWolfeConditionsMet returns true if the weak Wolfe conditions have been met.
Expand All @@ -209,10 +209,10 @@ func StrongWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, funcCo
// conditions, the following should be true, though this is not enforced:
// - initGrad < 0
// - step > 0
// - 0 <= funcConst < gradConst < 1
func WeakWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, funcConst, gradConst float64) bool {
if currObj > initObj+funcConst*step*initGrad {
// - 0 <= decrease < curvature< 1
func WeakWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
if currObj > initObj+decrease*step*initGrad {
return false
}
return currGrad >= gradConst*initGrad
return currGrad >= curvature*initGrad
}
4 changes: 2 additions & 2 deletions linesearcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ func TestMoreThuente(t *testing.T) {
func TestBisection(t *testing.T) {
c := 0.1
ls := &Bisection{
GradConst: c,
CurvatureFactor: c,
}
testLinesearcher(t, ls, 0, c, true)
}

func TestBacktracking(t *testing.T) {
d := 0.001
ls := &Backtracking{
FuncConst: d,
DecreaseFactor: d,
}
testLinesearcher(t, ls, d, 0, false)
}
Expand Down
2 changes: 1 addition & 1 deletion unconstrained_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ func TestGradientDescent(t *testing.T) {
func TestGradientDescentBacktracking(t *testing.T) {
testLocal(t, gradientDescentTests, &GradientDescent{
Linesearcher: &Backtracking{
FuncConst: 0.1,
DecreaseFactor: 0.1,
},
})
}
Expand Down