#!/usr/bin/env python # coding: utf-8 # *** # ** Algorithmes d'optimisation -- L3 MINT et doubles licences 2017/2018 -- Université Paris-Sud ** # *** # # # TP 2: Descente de gradient avec recherche linéaire # # $\newcommand{\Rsp}{\mathbb{R}} # \newcommand{\nr}[1]{\|#1\|} # \newcommand{\abs}[1]{|#1|} # \newcommand{\eps}{\varepsilon} # \newcommand{\sca}[2]{\langle#1|#2\rangle} # \newcommand{\D}{\mathrm{D}} # \newcommand{\hdots}{\dots} # \newcommand{\cond}{\mathrm{cond}}$ # # Étant donné un couple $(x,d)$ le pas d'Armijo est défini de la manière suivante: # # $$ t^{arm}(x,d) = \max \left\{ t \mid t = \beta^k, k\in \mathbb{N}, \hbox{ tq } f(x + tv) \leq f(x) + \alpha t\sca{\nabla f(x)}{d} \right\}, $$ # # où $\beta \in ]0,1[$, $\alpha \in ]0,\frac{1}{2}[$. En pratique, on prendra souvent $\alpha = 0.3, \beta = 0.5$. Algorithmiquement, on procède de la manière suivante pour trouver $t = t^{arm}(x,d)$: # $$ # \left| # \begin{aligned} # &t \gets 1\\ # &m \gets \sca{\nabla f(x)}{d}\\ # &\textbf{while } f(x + t v) > f(x) + \alpha t m \\ # &\qquad t \gets \beta t # \end{aligned}\right. # $$ # # L'algorithme de descente de gradient avec backtracking d'Armijo s'écrit alors de la manière suivante: # # $$ # \begin{cases} # d^{(k)} = - \nabla f(x^{(k)} \\ # t^{(k)} = t(x^{(k)}, d^{(k)})\\ # x^{(k+1} = x^{k} + t^{(k)} d^{(k)} # \end{cases}$$ # # L'objectif de ce TP est 1) d'implémenter cet algorithme, 2) de le comparer à la descente de gradient à pas optimal et 3) de l'appliquer à des problèmes d'optimisation de dimension élevée, provenant du traitement du signal et d'images. En particulier, on verra qu'il n'est pas particulièrement difficile de résoudre un problème quadratique de dimension $\simeq 250000$ dès lors que la matrice SDP définissant le problème est bien conditionnée. # # III Implémentation de l'algorithme # # L'algorithme du gradient à pas optimal demande de savoir calculer de manière exacte le minimum de $f(x+td)$. Ceci est rarement faisable (sauf dans le cas où $f$ est quadratique). Il est donc utile d'avoir une autre méthode permettant de choisir le pas de manière automatique: on appelle ce problème la "recherche linéaire" car on travaille sur la (demi-)droite $\{x + td \mid t\geq 0\}$. Il existe de nombreuses approches pour la recherche linéaire, nous présentons l'algorithme de "backtracking" d'Armijo, qui est simple à programmer et à analyser mathématiquement. # # **QIII.1**) Écrire une fonction `backtrack` prenant en entrée $f:\Rsp^N\to\Rsp$, $x,d\in\Rsp^N$ et $m = \sca{\nabla f(x)}{d}$, et retournant le pas $t$. # # Tester la fonction `backtrack` dans le cas $N=1$, $f(x) = x^{2} + e^{-x}$, $x_0 = 1$ et en arrêtant l'algorithme dès que $\nr{d^{(k)}} \leq 10^{-8}$. Vous devriez trouver $x^* = -0.3517...$. Comparer le nombre d'itérations pour $\alpha=0.3$ et $\beta$ variant entre $0.1$ et $0.9$. # In[1]: # on importe les modules numpy et pyplot import numpy as np import matplotlib.pyplot as plt # les deux commandes suivante paramètrent l'affichage des figures get_ipython().run_line_magic('matplotlib', 'inline') plt.rcParams['figure.figsize'] = [9.,6.] # les paramètres alpha et beta comportent une valeur par défaut, c'est-à-dire que # backtrack(f,x,d,m) est équivalent à backtrack(f,x,d,m,0.3,0.5) def backtrack(f,x,d,m,alpha=0.3,beta=0.5): t = 1 while f(x+t*d) > f(x) + alpha*t*m: t = beta*t return t # test de la fonction backtracking f = lambda x: x**2 + np.exp(x) g = lambda x: 2*x + np.exp(x) for i in range(1,10): x = 1. k = 0 while(True): k = k+1 d = - g(x) if np.linalg.norm(d) < 1e-8: break beta = float(i)/10 t = backtrack(f,x,d,-d*d, 0.3, beta) x = x + t*d print('nombre d\'itérations pour beta = %g: %d' % (beta, k)) # **QIII.2**) Écrire une fonction `gradient_backtracking` prenant en argument la fonction $f$, $g = \nabla f$ (c'est-à-dire une deuxième fonction telle que $g(x) = \nabla f(x)$), $x_0$ et un critère d'arrêt `err`, et qui effectue l'algorithme de descente de gradient avec backtracking en arrêtant la boucle dès que $\nr{d^{(k)}} \leq$ `err`. Cette fonction retournera le point $x$ trouvé, un vecteur contenant $f(x^{(k)})$ et un second vecteur contenant $\nr{d^{(k)}}$. # # Tester la fonction avec les $f$ et $g$ donnés ci-dessous (on notera la notation **lambda** permettant de définir une fonction en une seule ligne): # In[2]: # la descente de gradient prend en argument: # f = la fonction à évaluer # g = le gradient de f # x0 = le point de départ def gradient_backtracking(f,g,x0,err=1e-6): x = x0 E = [] F = [] k = 0 # nombre d'itérations while(True): k = k+1 if k > 1e6: # maximum de 10^6 itérations print('erreur: nombre maximum d\'itérations atteint') break # calcul de d, t, d = -g(x) F.append(f(x)) E.append(np.linalg.norm(d)) if np.linalg.norm(d) <= err: break t = backtrack(f,x,d,-np.linalg.norm(d)**2) x = x + t*d return x,np.array(E),np.array(F) # on rappelle la fonction gradient_optimal du TP précédent, # permettant de minimiser f(x) = .5* + , def gradient_optimal(Q,b,x0,err=1e-6): x = x0 niter=0 E = [] F = [] k = 0 # nombre d'itérations while (True): k = k+1 if k > 1e6: # maximum de 10^6 itérations print('erreur: nombre maximum d\'itérations atteint') break # calculer la direction de descente d = -(np.dot(Q,x)+b) E.append(np.linalg.norm(d)) F.append(.5*np.dot(x,np.dot(Q,x)) + np.dot(b,x)) # vérifier le critère d'arrêt, et quitter la boucle (avec break) s'il est vérifié if np.linalg.norm(d) <= err: break # calculer le pas de descente et mettre à jour x t = np.linalg.norm(d)**2/np.dot(np.dot(Q,d), d) x = x + t*d E = np.array(E) F = np.array(F) return x,E,F # on teste avec la fonction de la partie I. Ci-dessous, on utilise la # notation lambda pour définir des fonctions sur une seule ligne, ce qui # est parfois utile. K = 10. f = lambda x: .5*(K*x[0]**2 + x[1]**2) g = lambda x: np.array([K*x[0], x[1]]) # Appliquer l'algorithme du gradient avec backtracking # puis comparer le nombre d'itérations avec le gradient à pas optimal x,E,F = gradient_backtracking(f,g,np.array([1.,K])) print('nombre d\'itérations gradient avec backtracking', len(E)) Q = np.array([[K, 0],[0,1]]) b = np.zeros(2) x,E,F = gradient_optimal(Q,b,np.array([1.,K])) print('nombre d\'itérations gradient à pas optimal:', len(E)) # # IV Application: débruitage de signal # # On propose une application: on se donne un signal $1D$, encodé sous la forme d'un vecteur $y_1,\hdots,y_{N}$, par exemple $y_i = sin(2 i \pi/N)$. On suppose que notre signal a été modifié par l'addition d'une (petite) composante aléatoire à chacune de ses entrées, de la manière suivante: # In[3]: N=100 y = np.sin(2*np.linspace(1.,N,N)*np.pi/N) p1, = plt.plot(y,label='signal orignal') y = y + .2*(np.random.rand(N)-.5) p2, = plt.plot(y,label='signal bruité') plt.legend() # On voit bien que la particularité du signal bruité est la présence de 'sauts' entre deux composantes successives $y_i$ et $y_{i+1}$. On va chercher à débruiter signal $y$ en résolvant le problème d'optimisation suivant # # $$ \inf_{x\in \Rsp^N} f(x) := \frac{1}{2} \nr{x - y}^2 + \frac{K}{2}\sum_{1\leq i\leq N} (x_{i+1} - x_i)^2, $$ # # où l'on a défini $x_{N+1} := x_1$. # Le minimiseur $x^*$ de ce problème offrira un compromis entre deux aspects. Le premier terme de $f$ demande à ce que $x^*$ soit près du signal donné en entrée, $y$. Le deuxième terme est grand lorsque les entrées de $x$ varient rapidement, et incite donc $x^*$ à varier lentement. Cela aura l'effet de gommer les sauts (le bruit) ajouté à $y$. On pourrait dire beaucoup de chose sur ce modèle (dans la direction des statistiques ou dans celles des équations aux dérivées partielles), mais ici on le considère comme un problème d'optimisation à résoudre numériquement. # # **QIV.1** Démontrer que le problème d'optimisation admet une solution. Montrer que la fonction $f$ est convexe et vérifie $D^2 f(x) \geq 1$. Calculer le gradient de $f$. # # **Réponse:** # Pour montrer l'existence d'un minimum on va montrer que la fonction $f$ est convexe et coercive. Pour la coercivité (c'est à dire que $f(x) \to +\infty$ quand $\|x \| \to +\infty$) # on remarque que $\frac{K}{2}\sum_{1\leq i\leq N} (x_{i+1} - x_i)^2\geq 0$ et donc, par inégalité triangulaire, $f(x)\geq \frac{1}{2} \nr{x - y}^2\geq \frac{1}{2} \nr{x}^2-\frac{1}{2} \nr{ y}^2 \to +\infty$ quand $\|x \| \to +\infty$. # # Pour la convexité : on sait que $x \mapsto \frac{1}{2} \nr{x - y}^2$ est convexe (on peut vérifier par exemple que sa Hessienne est la matrice $Id$). Comme la somme de fonctions convexes est convexe il suffit de verifier que pour i fixé, $ h_i : x \mapsto (x_{i+1} - x_i)^2$ est convexe. # Pour cela on considère un $0