Skip to content

Commit 1cc49d2

Browse files
author
Pavelrst
committed
implementation started
1 parent 235a3e3 commit 1cc49d2

9 files changed

+448
-0
lines changed
552 KB
Binary file not shown.

Diff for: Optimizer_utils.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import matplotlib.pyplot as plt
2+
from functions_utils import *
3+
from armijo_utils import Armijo_method
4+
from newton_utils import NewtonMethod
5+
from penalty_method import AugmentedLagrangian
6+
7+
8+
class Gradient_descent():
9+
def __init__(self, method_type='steepest_descent', threshold=0.00001, step_size_estimator=Armijo_method(),
10+
max_steps=100000, verbose=True):
11+
self.threshold = threshold
12+
self.max_steps = max_steps
13+
self.verbose = verbose
14+
self.f_val_list = []
15+
self.step_sizes_list = []
16+
self.step_size_estimator = step_size_estimator
17+
self.method_type = method_type
18+
19+
def optimize(self, func, start_point):
20+
self.f_val_list.append(func.val(start_point))
21+
22+
x = start_point
23+
24+
for step in range(self.max_steps):
25+
print("step:", step)
26+
prev_x = x
27+
if self.method_type == 'steepest_descent':
28+
x = self.optimizer_step(x, func)
29+
elif self.method_type == 'newton_method':
30+
x = self.optimizer_step_newton(x, func)
31+
else:
32+
print("Direction method not selected")
33+
break
34+
self.f_val_list.append(func.val(x))
35+
36+
# if self.verbose:
37+
# print("f(x)=", func.val(x), " current point= ~", np.round(x, 5))
38+
39+
# print("norm=",np.linalg.norm(func.grad(x)))
40+
if np.linalg.norm(func.grad(x)) < self.threshold:
41+
print("Optimizer reached accuracy threshold after", step, "iterations!")
42+
break
43+
return x
44+
45+
def optimizer_step(self, x, func):
46+
step_size = self.step_size_estimator.calc_step_size(x, func, direction=func.grad(x))
47+
x = x - step_size * func.grad(x)
48+
# self.step_size_estimator.armijo_plot()
49+
self.step_sizes_list.append(step_size)
50+
return x
51+
52+
def optimizer_step_newton(self, x, func):
53+
newton = NewtonMethod()
54+
d = newton.direction(x, func)
55+
step_size = self.step_size_estimator.calc_step_size(x, func, direction=d)
56+
x = x - step_size * d
57+
self.step_sizes_list.append(step_size)
58+
return x
59+
60+
def plot_step_sizes(self):
61+
iterations_list = range(len(self.step_sizes_list))
62+
63+
a, = plt.plot(iterations_list, self.step_sizes_list, label='step size')
64+
plt.legend(handles=[a])
65+
plt.ylabel('step size')
66+
plt.xlabel('iterations')
67+
plt.show()
68+
69+
def get_convergence(self, val_optimal):
70+
'''
71+
gets converg rates list
72+
:param f_list: list of values of f during gradient descent algo
73+
:param val_optimal: the global minimum value of the function
74+
'''
75+
converg_list = []
76+
iterations_list = []
77+
for idx, val in enumerate(self.f_val_list):
78+
converg_list.append(val - val_optimal)
79+
iterations_list.append(idx)
80+
81+
return iterations_list, converg_list
82+
83+
def plot_convergence(self, val_optimal, f_name='plot title', marker=None, save = True):
84+
'''
85+
plots the convergence rate
86+
:param f_list: list of values of f during gradient descent algo
87+
:param val_optimal: the global minimum value of the function
88+
'''
89+
converg_list = []
90+
iterations_list = []
91+
for idx, val in enumerate(self.f_val_list):
92+
#converg_list.append(abs(val - val_optimal))
93+
converg_list.append(val)
94+
iterations_list.append(idx)
95+
96+
plt.plot(iterations_list, converg_list)
97+
plt.ylabel('f(x)-f* / log')
98+
plt.xlabel('iterations')
99+
#plt.yscale('log')
100+
label = f_name + ' - ' + self.method_type + ' convergence rate'
101+
plt.title(label)
102+
if marker != None:
103+
x, y = marker
104+
plt.plot(x, y, 'ro')
105+
plt.gcf()
106+
name = label + '_fig.JPEG'
107+
plt.savefig(name, bbox_inches='tight')
108+
plt.show()
109+

Diff for: armijo_utils.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
5+
class Armijo_method():
6+
def __init__(self, initial_alpha=1, sigma=0.25, beta=0.5):
7+
self.initial_alpha = initial_alpha
8+
self.sigma = sigma
9+
self.beta = beta
10+
11+
def calc_step_size(self, x, func, direction):
12+
'''
13+
Denotre Phi(alpha) as function of step size: Phi(alpha)=f(x+alpha*d)-f(x)
14+
while d is direction. Deriviate Phi by alpha and we get:
15+
Phi'(alpha) = f'(x+alpha*d)*d so,
16+
Phi'(0) = f'(x)*d i.e. directional derivative, in our case - gradient
17+
Denote: c = Phi'(0) = f_grad(x), so
18+
alpha*c is a tangent line to Phi(alpha) at alpha=0.
19+
Now lets make a new line: sigma*alpha*c
20+
Finally let's state to Armijo condition:
21+
f(x+alpha*d)-f(x) <= sigma*alpha*c
22+
Also we assume search direction d is gradient direction.
23+
:param x: the point from which we want make a step
24+
:param alpha: Initial step size
25+
:param sigma: Factor for a slope of tangent
26+
:param beta: Step size decrement factor
27+
:return: step size which is fulfilling Armijo condition.
28+
'''
29+
30+
self.armijo = ArmijoPhiFunc(x, func.val, func.grad,
31+
direction=direction, sigma=0.25, beta=0.5)
32+
33+
alpha = self.initial_alpha
34+
# f(x+alpha*d)-f(x) <= sigma*alpha*c
35+
while self.armijo.phi_val(alpha) > self.armijo.elevated_tangent_val(alpha):
36+
alpha = alpha * self.beta
37+
return alpha
38+
39+
def armijo_plot(self):
40+
alpas = np.linspace(0, 1, num=1000)
41+
phi_list = []
42+
tangent_list = []
43+
elevated_tangent_list = []
44+
for idx, alpha in enumerate(alpas):
45+
phi_list.append(self.armijo.phi_val(alpha))
46+
tangent_list.append(self.armijo.tanget_val(alpha))
47+
elevated_tangent_list.append(self.armijo.elevated_tangent_val(alpha))
48+
49+
a, = plt.plot(alpas, phi_list, label='phi')
50+
b, = plt.plot(alpas, tangent_list, label='tangent')
51+
c, = plt.plot(alpas, elevated_tangent_list, label='elevated_tangent')
52+
plt.legend(handles=[a, b, c])
53+
plt.ylabel('Phi(alpha)')
54+
plt.xlabel('alpha')
55+
plt.grid()
56+
plt.show()
57+
58+
59+
class ArmijoPhiFunc:
60+
def __init__(self, x, func_val, func_grad, direction, sigma=0.25, beta=0.5):
61+
self.f = func_val
62+
self.g = func_grad
63+
self.x = x
64+
self.direction = direction
65+
self.sigma = sigma
66+
self.beta = beta
67+
68+
def phi_val(self, alpha):
69+
# The direction is opposite to gradient.
70+
alpha_d = -alpha*self.direction
71+
val = self.f(self.x + alpha_d) - self.f(self.x)
72+
return val
73+
74+
def tanget_val(self, alpha):
75+
'''
76+
:param alpha: given step size.
77+
:return: value of tangent to phi at given alpha.
78+
'''
79+
c = -np.matmul(self.g(self.x), self.direction)
80+
#c = -np.matmul(self.direction, self.direction)
81+
return alpha * c
82+
83+
def elevated_tangent_val(self, alpha):
84+
'''
85+
:param alpha: given step size.
86+
:return: value of tangent to phi at given alpha.
87+
'''
88+
c = -np.matmul(np.transpose(self.g(self.x)), self.direction)
89+
#c = np.matmul(np.transpose(self.g(self.x)), self.direction)
90+
return self.sigma * alpha * c
91+
92+
93+

Diff for: functions_utils.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from scipy.optimize import rosen
2+
import numpy as np
3+
from numdiff_utils import numdiff
4+
from scipy.io import loadmat
5+
6+
class Quadratic_general:
7+
def __init__(self, Q, d, e):
8+
'''
9+
xT Q x + dT x + e
10+
'''
11+
self.Q = Q
12+
self.d = d
13+
self.e = e
14+
15+
def val(self, x):
16+
xT = np.transpose(x)
17+
xT_Q_x = np.matmul(np.matmul(xT,self.Q),x)
18+
dT_x = np.matmul(np.transpose(self.d),x)
19+
return (1/2 * xT_Q_x + dT_x + self.e)[0]
20+
21+
def grad(self, x):
22+
Q_QT = self.Q+np.transpose(self.Q)
23+
return (1/2 * np.matmul(Q_QT,x) + self.d)
24+
25+
def hess(self, x):
26+
return 1/2 * self.Q+np.transpose(self.Q)
27+
28+

Diff for: main.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from functions_utils import Quadratic_general
2+
from Optimizer_utils import Gradient_descent
3+
from penalty_method import AugmentedLagrangian
4+
import numpy as np
5+
6+
def main():
7+
# opt_grad = Gradient_descent(method_type='steepest_descent', max_steps=50, verbose=False)
8+
objective, constraints_list, start_point, optimal, name = create_problem()
9+
f = AugmentedLagrangian(objective, constraints_list, start_point, optimal, name)
10+
11+
#opt_grad.optimize(f, f.starting_point())
12+
#opt_grad.plot_convergence(f.optimal, f.name)
13+
14+
opt_newton = Gradient_descent(method_type='newton_method', max_steps=50, verbose=True)
15+
16+
x = f.starting_point()
17+
for i in range(5):
18+
print("x=", x)
19+
x = opt_newton.optimize(f, x)
20+
f.update_mu(x)
21+
f.update_p()
22+
23+
opt_newton.plot_convergence(f.optimal, f.name)
24+
# opt_newton.optimize(f, f.starting_point())
25+
# opt_newton.plot_convergence(f.optimal(), f.name)
26+
27+
def create_problem():
28+
start_point = np.array([[1],
29+
[1]])
30+
optimal = 37+2/3
31+
name = 'Task 2'
32+
# objective
33+
Q = np.array([[2, 0],
34+
[0, 1]])
35+
b = np.array([[-20],
36+
[-2]])
37+
e = 51
38+
objective = Quadratic_general(Q, b, e)
39+
40+
# Constarints
41+
constraints_list = []
42+
43+
# 1
44+
Q_zeros = np.array([[0, 0],
45+
[0, 0]])
46+
b = np.array([[0.5],
47+
[1]])
48+
e = -1
49+
constraints_list.append(Quadratic_general(Q_zeros, b, e))
50+
51+
# 2
52+
b = np.array([[1],
53+
[-1]])
54+
e = 0
55+
constraints_list.append(Quadratic_general(Q_zeros, b, e))
56+
57+
# 3
58+
b = np.array([[-1],
59+
[-1]])
60+
e = 0
61+
constraints_list.append(Quadratic_general(Q_zeros, b, e))
62+
63+
return objective, constraints_list, start_point, optimal, name
64+
65+
if __name__ == "__main__":
66+
main()
67+

Diff for: newton_solver.py

Whitespace-only changes.

Diff for: newton_utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import numpy as np
2+
3+
class NewtonMethod:
4+
def direction(self, x, func):
5+
try:
6+
H_inv = np.linalg.inv(func.hess(x))
7+
except:
8+
H_inv = np.linalg.pinv(func.hess(x))
9+
return np.matmul(H_inv, func.grad(x))
10+

Diff for: numdiff_utils.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import numpy as np
2+
3+
def numdiff(myfunc, x, par, nargout=1):
4+
'''
5+
computes gradient and hessian of myfunc numerically
6+
7+
:param myfunc: pointer to either of f1 or f2
8+
:param x: Input vector R^(mx1)
9+
:param par: a dictionary including keys:
10+
'epsilon' : The incerement of x
11+
'f_par' : parameters dictionary given to function
12+
'gradient' : gradient function of f
13+
:param nargout: Like nargout of matlab, can be 1 or 2
14+
:return: [gnum, Hnum]
15+
gnum : Numerical estimation of function gradient
16+
Hnum : Numerical estimation of function Hessian
17+
'''
18+
assert callable(myfunc)
19+
assert isinstance(x, np.ndarray)
20+
assert isinstance(par, dict)
21+
assert 'epsilon' in par.keys()
22+
assert isinstance(nargout, int)
23+
assert nargout in range(1, 3)
24+
25+
epsilon_tot = par['epsilon']
26+
assert isinstance(epsilon_tot, float)
27+
max_abs_val_of_x = max(x.min(), x.max(), key=abs)
28+
if max_abs_val_of_x != 0:
29+
epsilon = pow(epsilon_tot, 1 / 3) * max_abs_val_of_x
30+
else:
31+
epsilon = epsilon_tot**2
32+
33+
34+
# standard_base = np.array(((1, 0, 0),
35+
# (0, 1, 0),
36+
# (0, 0, 1)))
37+
38+
standard_base = np.identity(len(x))
39+
40+
grad = []
41+
for i in range(0, len(x)):
42+
right_g_i = myfunc(x+epsilon*standard_base[i])
43+
left_g_i = myfunc(x-epsilon*standard_base[i])
44+
g_i = (right_g_i - left_g_i)/(2*epsilon)
45+
grad.append(g_i)
46+
grad = np.array(grad)
47+
48+
if nargout == 1:
49+
return grad
50+
51+
hess = []
52+
analytic_grad = par['gradient']
53+
assert callable(analytic_grad)
54+
for i in range(0, len(x)):
55+
right_sample = analytic_grad(x+epsilon*standard_base[i], par['f_par'])
56+
left_sample = analytic_grad(x-epsilon*standard_base[i], par['f_par'])
57+
h_i = (right_sample-left_sample)/(2*epsilon)
58+
hess.append(h_i)
59+
hess = np.array(hess)
60+
61+
return grad, hess

0 commit comments

Comments
 (0)