Il s'agit dans ce TME de coder la descente de gradient pour une fonction générique. Nous utiliserons pour cela une classe abstraite OptimFunc, qui contiendra la définition de la fonction à calculer (f), le gradient de cette fonction (grad_f) et la fonction x_random pour générer aléatoirement un point initial.
In [28]:
import numpy as np
from numpy import random
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# fonction pour transformer un vecteur en array si ce n'est pas un array
#, a mettre en première ligne de l'appel de fonctions, comme dans le squelette OptimFunc
def to_array(x):
if len(x.shape)==1:
x=x.reshape(1,x.shape[0])
return x
#Classe abstraite d'une fonction à optimiser. self.dim contient la dimension attendue de l'entrée (pour la génération aléatoire d'un point).
class OptimFunc:
def __init__(self,dim=2):
self.dim=dim
def f(self,x): #fonction a calculer
x=to_array(x)
pass
def grad_f(self,x): #gradient de la fonction
x=to_array(x)
pass
def x_random(self,low=2,high=5):
return random.random(self.dim)*(high-low)+low
Voici le squelette de la classe pour l'optimisation par descente de gradient. Les paramètres sont les suivants:
eps : epsilon, le pas du gradient; la fonction get_eps() permet de faire un pas adaptatif
optiim_f : objet de type OptimFunc, la fonction à optimiser
max_iter : le nombre d'itération maximum, None s'il ne faut pas le prendre en compte
delta : la différence minimale à avoir entre deux pas pour continuer à itérer
En plus de ces paramètres, les variables suivantes sont utilisées :
i : le numéro de l'itération courante
x : le point optimal courant
log_x, log_f, log_grad : l'historique des points parcourus, de la valeur de la fonction en ces points, de la valeur du gradient en ces points.
Compléter ce qui manque. Utiliser en particuliler np.vstack((log_x,x)) pour empiler au fur et à mesure les résultats dans l'historique.
In [29]:
class GradientDescent:
def __init__(self,optim_f,eps=1e-4,max_iter=5000,delta=1e-6):
self.eps=eps
self.optim_f=optim_f
self.max_iter=max_iter
self.delta=delta
def reset(self):
self.i=0
self.x = self.optim_f.x_random()
self.log_x=np.array(self.x)
self.log_f=np.array(self.optim_f.f(self.x))
self.log_grad=np.array(self.optim_f.grad_f(self.x))
def optimize(self,reset=True):
if reset:
self.reset()
while not self.stop():
self.x = self.x - self.get_eps()*self.optim_f.grad_f(self.x)
self.log_x=np.vstack((self.log_x,self.x))
self.log_f=np.vstack((self.log_f,self.optim_f.f(self.x)))
self.log_grad=np.vstack((self.log_grad,self.optim_f.grad_f(self.x)))
self.i+=1
def stop(self):
return (self.i>2) and (self.max_iter and (self.i>self.max_iter) or (self.delta and np.abs(self.log_f[-1]-self.log_f[-2]))<self.delta)
def get_eps(self):
return self.eps
class Rosenbrock(OptimFunc):
def f(self,x): #fonction a calculer
x=to_array(x)
return 100*(x[:,1]-x[:,0]**2)**2 + (1-x[:,0])**2
def grad_f(self,x): #gradient de la fonction
x=to_array(x)
x1=x[:,0]
x2=x[:,1]
d1=200*(x2-x1**2)*(-2*x1)-2*(2-2*x1)
d2=200*(x2-x1**2)
return np.array([d1,d2]).T
class FF1(OptimFunc):
def f(self,x): #fonction a calculer
x=to_array(x)
return x*np.cos(x)
def grad_f(self,x): #gradient de la fonction
x=to_array(x)
return np.cos(x)-x*(np.sin(x))
class FF2(OptimFunc):
def f(self,x): #fonction a calculer
l=[]
for i in x:
if i==0:
l.append(0)
else:
l.append(-1*np.log(i) + (i)**2)
return np.asarray(l)
def grad_f(self,x): #gradient de la fonction
x=to_array(x)
return -1/x + 2*x
Tester votre implémentation sur 3 fonctions :
en 1d sur la fonction f(x)=x.cos(x)
en 1d sur la fonction -log(x)+x^2
en 2d sur la fonction Rosenbrock (ou banana), définie par : f(x_1,x_2)= 100*(x_2-x_1^2)^2 + (1-x_1)^2 .
Tracer les courbes d'évolution de f(x) en fonction du nombre d'itérations. Tracer (en 3d ou en 2d) la surface de la fonction, et la trajectoire de l'optimisation. Vous pouvez utilisez le code suivant pour la visualisation 3d.
In [5]:
#Crétion de la fonction Rosenbrock
rosen = Rosenbrock()
#Quadrillage pour evaluer la fonction f rosenbrock.
m=5
x=np.arange(-m,m,2*m/20.)
y=np.arange(-m,m,2*m/20.)
xx,yy=np.meshgrid(x,y)
grid=np.c_[xx.ravel(),yy.ravel()]
z=rosen.f(grid).reshape(xx.shape)
#Création du gradient et optimisation de celui ci
grd_rosen = GradientDescent(rosen)
grd_rosen.optimize()
#Création de la figure
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(xx, yy, z, rstride=1, cstride=1, cmap=cm.gist_rainbow, linewidth=0, antialiased=False)
fig.colorbar(surf)
ax.plot(grd_rosen.log_x[:,0], grd_rosen.log_x[:,1], grd_rosen.log_f.ravel(),color='black')
titre="Gradient_descent_Rosenbrock"
plt.title(titre)
savefig(titre)
plt.show()
In [8]:
#création de la fonction
ft = FF1(1)
#création de x et y
x = np.arange(-6,6,0.1)
f_x = ft.f(x).ravel()
#création du gradient et optimisation
grd = GradientDescent(ft,eps=1e-4,max_iter=200000,delta=1e-7)
grd.optimize()
plt.plot(x,f_x)
plt.plot(grd.log_x, grd.log_f, color='red')
titre="Gradient_descent_xcos(x)"
plt.title(titre)
savefig(titre)
plt.show()
In [6]:
plt.plot(x,f_x)
for i in range(30):
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(grd.log_x, grd.log_f, color='red')
titre="Cheminement_gradient"
plt.title(titre)
savefig(titre)
plt.show()
In [22]:
for i in range(30):
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(ft.f(grd.log_x))
titre="Gradient_descent_ordonnee_xlog(x)"
plt.title(titre)
savefig(titre)
plt.show()
In [17]:
for i in range(30):
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(grd.log_x)
titre="Gradient_descent_abscisse_xlog(x)"
plt.title(titre)
savefig(titre)
plt.show()
In [30]:
#création de la fonction
ft = FF2(1)
#création de x et y
x = np.arange(0.2,5,0.1)
f_x = ft.f(x).ravel()
#création du gradient et optimisation
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(x,f_x)
plt.plot(grd.log_x, grd.log_f, color='red')
titre="Gradient_descent_-log(x)+x^2"
plt.title(titre)
savefig(titre)
plt.show()
In [31]:
for i in range(30):
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(ft.f(grd.log_x))
titre="Gradient_descent_ordonnee_-log(x)+x^2"
plt.title(titre)
savefig(titre)
plt.show()
In [32]:
for i in range(30):
grd = GradientDescent(ft,eps=1e-4,max_iter=20000,delta=1e-7)
grd.optimize()
plt.plot(grd.log_x)
titre="Gradient_descent_abscisse_-log(x)+x^2"
plt.title(titre)
savefig(titre)
plt.show()
In [ ]: