-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathoptimizers.py
140 lines (114 loc) · 5.51 KB
/
optimizers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Author: Evgeny Semyonov <[email protected]>
# Repository: https://github.com/lightforever/Levenberg_Manquardt
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
from abc import ABCMeta, abstractmethod
import numpy as np
def is_pos_def(x):
return np.all(np.linalg.eigvals(x) > 0)
class Optimizer:
def __init__(self, function, initialPoint, gradient=None, jacobi=None, hesse=None,
interval=None, epsilon=1e-7, function_array=None, metaclass=ABCMeta):
self.function_array = function_array
self.epsilon = epsilon
self.interval = interval
self.function = function
self.gradient = gradient
self.hesse = hesse
self.jacobi = jacobi
self.name = self.__class__.__name__.replace('Optimizer', '')
self.x = initialPoint
self.y = self.function(initialPoint)
"This method will return the next point in the optimization process"
@abstractmethod
def next_point(self):
pass
"""
Moving to the next point.
Saves in Optimizer class next coordinates
"""
def move_next(self, nextX):
nextY = self.function(nextX)
self.y = nextY
self.x = nextX
return self.x, self.y
class SteepestDescentOptimizer(Optimizer):
def __init__(self, function, initialPoint, gradient=None, jacobi=None, hesse=None,
interval=None, function_array=None, learningRate=0.05):
super().__init__(function, initialPoint, gradient, jacobi, hesse, interval, function_array=function_array)
self.learningRate = learningRate
def next_point(self):
nextX = self.x - self.learningRate * self.gradient(self.x)
return self.move_next(nextX)
class NewtonOptimizer(Optimizer):
def __init__(self, function, initialPoint, gradient=None, jacobi=None, hesse=None,
interval=None, function_array=None, learningRate=0.05):
super().__init__(function, initialPoint, gradient, jacobi, hesse, interval, function_array=function_array)
self.learningRate = learningRate
def next_point(self):
hesse = self.hesse(self.x)
# if Hessian matrix if positive - Ok, otherwise we are going in wrong direction, changing to gradient descent
if is_pos_def(hesse):
hesseInverse = np.linalg.inv(hesse)
nextX = self.x - self.learningRate * np.dot(hesseInverse, self.gradient(self.x))
else:
nextX = self.x - self.learningRate * self.gradient(self.x)
return self.move_next(nextX)
class NewtonGaussOptimizer(Optimizer):
def __init__(self, function, initialPoint, gradient=None, jacobi=None, hesse=None,
interval=None, function_array=None, learningRate=1):
super().__init__(function, initialPoint, gradient, jacobi, hesse, interval, function_array=function_array)
self.learningRate = learningRate
def next_point(self):
# Solve (J_t * J)d_ng = -J*f
jacobi = self.jacobi(self.x)
jacobisLeft = np.dot(jacobi.T, jacobi)
jacobiLeftInverse = np.linalg.inv(jacobisLeft)
jjj = np.dot(jacobiLeftInverse, jacobi.T) # (J_t * J)^-1 * J_t
nextX = self.x - self.learningRate * np.dot(jjj, self.function_array(self.x)).reshape((-1))
return self.move_next(nextX)
class LevenbergMarquardtOptimizer(Optimizer):
def __init__(self, function, initialPoint, gradient=None, jacobi=None, hesse=None,
interval=None, function_array=None, learningRate=1):
self.learningRate = learningRate
functionNew = lambda x: np.array([function(x)])
super().__init__(functionNew, initialPoint, gradient, jacobi, hesse, interval, function_array=function_array)
self.v = 2
self.alpha = 1e-3
self.m = self.alpha * np.max(self.getA(jacobi(initialPoint)))
def getA(self, jacobi):
return np.dot(jacobi.T, jacobi)
def getF(self, d):
function = self.function_array(d)
return 0.5 * np.dot(function.T, function)
def next_point(self):
if self.y==0: # finished. Y can't be less than zero
return self.x, self.y
jacobi = self.jacobi(self.x)
A = self.getA(jacobi)
g = np.dot(jacobi.T, self.function_array(self.x)).reshape((-1, 1))
leftPartInverse = np.linalg.inv(A + self.m * np.eye(A.shape[0], A.shape[1]))
d_lm = - np.dot(leftPartInverse, g) # moving direction
x_new = self.x + self.learningRate * d_lm.reshape((-1)) # line search
grain_numerator = (self.getF(self.x) - self.getF(x_new))
gain_divisor = 0.5* np.dot(d_lm.T, self.m*d_lm-g) + 1e-10
gain = grain_numerator / gain_divisor
if gain > 0: # it's a good function approximation.
self.move_next(x_new) # ok, step acceptable
self.m = self.m * max(1 / 3, 1 - (2 * gain - 1) ** 3)
self.v = 2
else:
self.m *= self.v
self.v *= 2
return self.x, self.y
def getOptimizers(function, initialPoint, gradient, jacobi, hesse, interval, function_array):
return [optimizer(function, initialPoint, gradient=gradient, jacobi=jacobi, hesse=hesse,
interval=interval, function_array=function_array)
for optimizer in [
SteepestDescentOptimizer,
NewtonOptimizer,
NewtonGaussOptimizer,
LevenbergMarquardtOptimizer
]]