%matplotlib inline
import numpy as np
from scipy.optimize import minimize, minimize_scalar, line_search
import time
nf = 0
ngrad = 0
def f(x):
global nf
nf += 1
return (x[0]**2 + x[1] - 11)**2 + (x[0] + x[1]**2 - 7)**2
def grad(x):
global ngrad
ngrad += 1
partial_x0 = 4 * x[0] * (x[0]**2 + x[1] - 11) + 2*(x[0] + x[1]**2 - 7)
partial_x1 = 2 * (x[0]**2 + x[1] - 11) + 4 * x[1] * (x[0] + x[1]**2 - 7)
return np.array([partial_x0, partial_x1])
import matplotlib.pyplot as plt
def f2(x,y):
return f(np.array([x,y]))
x = np.linspace(-6.0, 6.0, 100)
y = np.linspace(-6.0, 6.0, 100)
X,Y = np.meshgrid(x,y)
Z = np.vectorize(f2)(X,Y)
fig, ax = plt.subplots()
CS = ax.contour(X, Y, Z, levels=np.array([0,0.5,1,10,50, 100, 150]))
ax.clabel(CS, fontsize=9, inline=1)
ax.set_title('Curfas de nivel da função f')
plt.show()
def report(t0,res):
t1=time.time()-t0
print(" x = [",round(res.x[0],1),',',round(res.x[1],1),"]")
print(' f(x) = ',round(f(res.x),3))
print(' execution time = ',round(t1*1000.0,1), 'ms' )
print(' n calls function = ',nf)
print(' n calls grad = ',ngrad)
def reset():
global nf
global ngrad
nf = 0
ngrad = 0
O minimize para o CG funciona tanto com e sem o gradiente. Vamos ver as 2 versões
reset()
t0=time.time()
res = minimize(f,[4, 4], method="CG")
report(t0,res)
reset()
t0=time.time()
res = minimize(f,[4, 4], method="CG", jac = grad)
report(t0,res)
Mesma solução, mas ele gasta menos tempo usando o gradiente. Isso faz sentido pois o CG usa o gradiente, e portando se voce não esta passando a função de gradiente, ele vai aproxima-la usando diferenças entre chamadas do f
O BFGS tabem pode receber ou não o gradiente. Vamos ver as 2 versões
reset()
t0=time.time()
res = minimize(f,[4, 4], method="L-BFGS-B")
report(t0,res)
reset()
t0=time.time()
res = minimize(f,[4, 4], method="L-BFGS-B", jac = grad)
report(t0,res)
reset()
x0 = [[-4,-4], [-4,1],[4, -1]]
t0=time.time()
res = minimize(f, [0,0], method="Nelder-Mead", options={'initial_simplex': x0})
report(t0,res)
O Nelder Mead do minimize
precisa receber o ponto inial mas ele nao usara esse ponto de passarmos o simplix (nesse caso triangulo) inicial.
import pybobyqa
reset()
t0=time.time()
res = pybobyqa.solve(f,[4, 4])
report(t0,res)
Há várias versões do GD com line search. O que alguns alunos não fizeram foi fazer o loop externo ao line search. Há um loop exteno tipo o GD controlado pela tolerancia (e o numero maximo de iterações).
class Resposta:
pass
def line1(f,grad,x0):
niter = 0
x = x0
while niter<10000:
pk = -grad(x)
res = line_search(f,grad,x,pk)
xnew = x + res[0]*pk
# usando a tolerancia do exrcicio passado
tol = np.linalg.norm(x-xnew)/np.linalg.norm(xnew)
if tol < 1e-5:
break
x = xnew
niter += 1
res = Resposta()
res.x = x
res.niter = niter
return res
reset()
t0=time.time()
res = line1(f,grad, np.array([4, 4]))
report(t0,res)
print("numero de iterações =",res.niter)
Pode-se tambem não usar a tolerancia. O line search vai gerar uma exception se ele nao achar o minimo e podemos usar isso para interromper o loop exteno
def line2(f,grad,x0):
niter = 0
x = x0
while niter<10000:
pk = -grad(x)
try:
res = line_search(f,grad,x,pk)
xnew = x + res[0]*pk
except:
break
x = xnew
niter += 1
res = Resposta()
res.x = x
res.niter = niter
return res
reset()
t0=time.time()
res = line2(f,grad, np.array([4, 4]))
report(t0,res)
print("numero de iterações =",res.niter)
Usando o minimize-scalar
. Neste caso eu tenho que criar uma função auxiliar que usa o x e o pk como "variavies globais". Da para fazer algo parecido usando o argumento args=() da função minimize_scalar
.
def line3(f,grad,x0):
def faux(alpha):
return f(x+alpha*pk)
niter = 0
x = x0
while niter<10000:
pk = -grad(x)
res = minimize_scalar(faux, method='brent')
xnew = x + res.x*pk
tol = np.linalg.norm(x-xnew)/np.linalg.norm(xnew)
if tol < 1e-5:
break
x = xnew
niter += 1
res = Resposta()
res.x = x
res.niter = niter
return res
reset()
t0=time.time()
res = line3(f,grad, np.array([4, 4]))
report(t0,res)
print("numero de iterações =",res.niter)