Commit 5abb2e15 authored by Guillaume Garrigos's avatar Guillaume Garrigos
Browse files

upload tp4

parent 98e0dee6
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<p hidden>Here are some Latex definitions</p> \n",
"\n",
"$\\newcommand{\\R}{\\mathbb{R}}$\n",
"$\\newcommand{\\RR}{\\mathbb{R}}$\n",
"$\\newcommand{\\N}{\\mathcal{N}}$\n",
"$\\newcommand{\\E}{\\mathbb{E}}$\n",
"$\\newcommand{\\Mm}{\\mathcal{M}}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<font color='red'><b>IMPORTANT: </b></font> Avant de commencer le TP, veuillez exécuter la cellule suivante afin de maintenir une session mybinder active pendant 90 minutes. (cliquer sur la cellule puis Ctrl+Entrée)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import mybinder\n",
"mybinder.start_session() # N'exécuter qu'une seule fois, si vous êtes sur mybinder.org"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# TP 4 : Défloutage d'une image via la minimisation d'une fonction quadratique"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"L'objectif final de ce TP est de résoudre le problème suivant: étant donné l'image ci-dessous, qui est clairement floue, peut-on en extraire, ou disons reconstruire, une image plus nette?\n",
"\n",
"| |\n",
"| --- |\n",
"| ![](images/comete_blur.png) |\n",
"\n",
"Ce TP va consister en 3 parties :\n",
"\n",
"1. Une partie *modélisation* où nous allons essayer de comprendre comment formuler notre problème mathématiquement. Nous allons voir qu'on peut se ramener à la minimisation d'une fonction quadratique. \n",
"2. Une partie *optimisation* où nous allons résoudre une version simplifiée du problème, à l'aide de l'algorithme du gradient.\n",
"3. Une partie *application* où nous allons pouvoir résoudre notre problème."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Voici quelques commandes python dont vous pourrez avoir besoin:\n",
"\n",
"| | Ici `np`=`numpy` et `la`=`numpy.linalg` |\n",
"|-|-|\n",
"|`np.random.randn(m,n)`| Génère une matrice aléatoire dans $\\Mm_{m,n}(\\RR)$ |\n",
"| `np.zeros((m,n))` | Génère une matrice nulle dans $\\Mm_{m,n}(\\RR)$ |\n",
"| `np.ones((m,n))` | Génère une matrice remplie de $1$ dans $\\Mm_{m,n}(\\RR)$ |\n",
"| `A.T` | Transposée de la matrice `A` |\n",
"| `A@B` | Produit entre deux matrices ou matrice/vecteur |\n",
"| `A*B` | Multiplie coefficient par coefficient le contenu de ces deux matrices/vecteurs |\n",
"| `np.sum(A)` | Renvoie la somme de tous les coefficients de `A` |\n",
"| `la.norm(A,2)` | Plus grande valeur singulière de `A` |\n",
"| `la.norm(A,-2)` | Plus petite valeur singulière de `A` |\n",
"| `la.norm(x)` | Norme Euclidienne du vecteur `x` |\n",
"| `print(f\"Hello {x}\")` | Affiche à l'écran `Hello 18` si `x=18` |\n",
"| `liste=[1,2,3]` | Une liste contenant les éléments 1,2,3 |\n",
"| `liste=[]` | Une liste vide |\n",
"| `liste.append(18)` | Ajoute `18` à la fin de la `liste` |\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# I. Modélisation d'un problème de traitement d'image\n",
"\n",
"## I.1. Une image = un vecteur\n",
"\n",
"Commençons déjà par acquérir cette image floue:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.linalg as la\n",
"import matplotlib.pyplot as plt\n",
"plt.set_cmap('gray') # Fixe la colormap à 'gray' pour afficher les images"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y = np.load('data/photo.npy')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pour un ordinateur, une image n'est rien d'autre qu'un *tableau* (array), où chaque pixel se voit attribué une valeur. Vous pouvez utiliser la fonction `type()` et l'attribut `.shape` pour déterminer le type et la dimension de l'objet `y` que l'on vient de récupérer. Combien de pixels à notre image?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pour visualiser notre image (qui est en fait un simple tableau), on dispose de la fonction `plt.imshow(y)`. Cette fonction va lire les coefficients de notre tableau, et afficher des pixels selon la règle suivante : \n",
"\n",
"- Le coefficient 0 correspond à un pixel noir\n",
"- Le coefficient 1 correspond à un pixel blanc\n",
"- Tout coefficient entre 0 et 1 correspond à un pixel gris, plus ou moins clair/foncé, selon sa valeur.\n",
"\n",
"Testez!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En résumé, nous avons donc une image, qui se présente comme un tableau carré de taille $128 \\times 128$, ce que l'on peut donc modéliser mathématiquement comme une matrice dans $\\mathcal{M}_{128}(\\mathbb{R})$, ou un vecteur de $\\mathbb{R}^{128\\times 128}$. Par la suite on préfèrera la vision 'vecteur' que 'matrice'."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## I.2. Flouter est une application linéaire\n",
"\n",
"Intéressons-nous maintenant à la notion de \"floutage\". Notre image `y` est clairement floue, mais qu'est-ce que cela veut dire?\n",
"Pour répondre à cette question, il faut commencer par comprendre comment flouter une image."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from image import flou"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nous avons maintenant à notre disposition une fonction `flou`, qui prend en entrée une image à $128\\times 128$ pixels, et la floute. Il s'agit donc d'une fonction que l'on va noter $\\Phi : \\mathbb{R}^{128\\times 128} \\longrightarrow \\mathbb{R}^{128\\times 128}$.\n",
"\n",
"\n",
"Il est maintenant temps de la tester, et d'étudier ses propriétés."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**1)** Appliquer la fonction de floutage $\\Phi$ à `y`, et afficher le résultat obtenu. Vous afficherez également ce qui se passe lorsque l'on applique 10 fois cette fonction à `y` (autrement dit, on veut visualiser $\\Phi^{10}(y)$)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**2)** Nous allons maintenant vérifier que l'application de floutage $\\Phi$ est ... *linéaire*. Ou plus exactement, nous allons nous en convaincre.\n",
"\n",
"Pour cela, vérifiez que la relation $\\Phi(X+Y) = \\Phi (X) + \\Phi (Y)$ est vérifiée pour des vecteurs aléatoires $X,Y$ dans $\\mathbb{R}^{128 \\times 128}$."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3)** Vérifions que l'application de floutage $\\Phi$ est non seulement linéaire, mais est en plus *symétrique*: c'est-à-dire que $\\Phi^*=\\Phi$. \n",
"\n",
"Sauf que ... ici $\\Phi$ n'est pas une matrice! C'est une application linéaire $\\Phi : \\mathbb{R}^{128 \\times 128} \\longrightarrow \\mathbb{R}^{128 \\times 128}$, et tout ce que l'on peut faire c'est l'évaluer avec la fonction Python `flou`. \n",
"\n",
"**Du coup il faut calculer la matrice associée à cette aplication linéaire?**\n",
"\n",
"On *pourrait* essayer de calculer sa matrice dans la base canonique. Mais cela donnerait une matrice carrée dont chaque côté serait de taille $128 \\times 128 = 16384$, c'est à dire une matrice avec $16384^2 \\simeq 2\\times 10^8$ coefficients. Sachant que un nombre = un octet, on parle donc ici d'une matrice qui pèse $100$ Mo. Non merci. \n",
"\n",
"Heureusement, on dispose d'une astuce, qui est : la **propriété de l'adjoint**! Pour rappel, si $u : \\mathbb{R}^N \\longrightarrow \\mathbb{R}^N$ est une application linéaire, alors son application adjointe $u^* : \\mathbb{R}^N \\longrightarrow \\mathbb{R}^N$ est l'unique fonction vérifiant la propriété de l'adjoint :\n",
"\n",
"\\begin{equation*}\n",
" (\\forall X,Y \\in \\mathbb{R}^N) \\quad \\langle u(X),Y \\rangle = \\langle X, u^*(Y) \\rangle.\n",
"\\end{equation*}\n",
"\n",
"Puisque l'adjoint est l'application linéaire correspondant à la transposée, il nous suffit donc de vérifier que $\\Phi^*=\\Phi$, c'est-à-dire:\n",
"\n",
"\\begin{equation*}\n",
" (\\forall X,Y \\in \\mathbb{R}^{128\\times 128}) \\quad \\langle \\Phi(X),Y \\rangle = \\langle X, \\Phi(Y) \\rangle.\n",
"\\end{equation*}\n",
"\n",
"A vous de vérifier ça, en prenant encore une fois des vecteurs aléatoires."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<details>\n",
"<summary>Cliquez <b>ici</b> si vous ne trouvez pas comment calculer le produit scalaire.</summary>\n",
"Si on fait abstraction de la fonction de floutage, vous devez calculer un produit scalaire $\\langle X,Y \\rangle $ entre deux tableaux $X$ et $Y$. Pour cela il vous faut 1) multiplier terme à terme les coefficients de ces tableaux 2) faire la somme de toutes ces multiplications. \n",
" \n",
"Vous avez en début d'énoncé les commandes qui vous permettent de faire ces deux opérations.\n",
"</details>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## I.3 Modélisation du problème \n",
"\n",
"Nous avons donc sous la main une fonction de floutage $\\Phi$ qui s'avère être linéaire, et qui a la propriété de flouter des images.\n",
"\n",
"D'autre part, nous disposons d'une image $y$ qui a visiblement été floutée. Nous pouvons donc raisonablement faire *l'hypothèse* que cette image $y$ a été obtenue à partir d'une certaine image $x$, que l'on ne connait pas, via le processus\n",
"\n",
"$$ \\Phi(x) = y$$\n",
"\n",
"Nous devons donc résoudre un système linéaire! On dispose de $\\Phi$, de $y$, il nous suffit donc de trouver un $x$ qui satisfait à cette équation.\n",
"\n",
"**Sauf que**\n",
"\n",
"Sauf que comme on l'a déjà vu plus haut, on n'a accès qu'à l'application linaire `flou`, pas sa matrice. Donc hors de question de penser à des choses comme \"calculer l'inverse de `flou`\" ou appliquer le pivot de Gauss au système.\n",
"\n",
"**Super ... et donc?**\n",
"\n",
"Nous disposons encore d'un outil : l'optimisation! \n",
"\n",
"En effet, résoudre $\\Phi x = y$ est équivalent à trouver $x \\in \\mathbb{R}^{128\\times 128}$ tel que $\\Phi x-y = 0$.\n",
"Or un vecteur est nul si et seulement si sa *norme* s'annule, donc c'est équivalent à trouver $x$ tel que $\\Vert \\Phi x - y \\Vert =0$.\n",
"Et puisque notre norme euclidienne est positive, on voit que c'est encore équivalent à\n",
"\n",
"\\begin{equation*}\n",
" \\underset{x \\in \\mathbb{R}^{128\\times 128}}{\\text{minimiser}} \\ \\frac{1}{2} \\Vert \\Phi(x) - y \\Vert^2.\n",
"\\end{equation*}\n",
"\n",
"Comme on va le voir à la fin de ce TP, cette formulation du problème est plus avantageuse puisqu'elle s'écrit sous forme *fonctionnelle*, ce qui correspond exactement à ce que l'on a sous la main : une fonction `flou`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## II. Algorithme du gradient pour une fonction quadratique\n",
"\n",
"Dans cette section nous allons voir comment minimiser une fonction quadratique de la forme \n",
"\\begin{equation}\n",
"f(x) = \\frac{1}{2}\\Vert Ax - b \\Vert^2.\n",
"\\tag{1}\n",
"\\end{equation}\n",
"où $A$ est une matrice de $\\R^{N\\times N}$, $b\\in \\R^N$. Pour cela nous allons utiliser l'**Algorithme du Gradient à Pas Fixe (GPF)**. On rappelle que pour une fonction à gradient $L$-Lipschitzien, cet algorithm s'écrit:\n",
"\n",
"| | | |\n",
"|-|-|-|\n",
"|(GPF)| On choisit $x_0$ un vecteur de $\\R^N$ et $\\rho \\in ]0,2/L[$ un pas fixe. |\n",
"| | Pour $k\\geq 0$ : $x_{k+1} = x_k - \\rho \\nabla f(x_k)$. |"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**1)** Définir une matrice $A \\in \\Mm_{50,100}(\\RR)$, et un vecteur $b \\in \\RR^{50}$, tous deux alétoires. \n",
"\n",
"Ici, et durant toute la suite du TP, les *vecteurs* devront être considérés comme des matrices sous forme de *colonne*."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**2)** Rappel du cours/TD: Le gradient de la fonction $f$ en $x$ vaut $\\nabla f(x) = A^*(Ax-b)$ et $\\nabla^2 f(x) \\equiv A^*A$. Calculer $L$, la constante de Lipschitz du gradient de $f$."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3.1)** Définir une fonction `algo_gradient` qui:\n",
"- prend en arguments une matrice `A`, un vecteur `b`, un point initial `x0`, un pas `rho`, et un nombre d'itérations maximal `itermax`\n",
"- applique l'algorithme du gradient à pas constant à $f$, en partant de `x0`, pendant `itermax` itérations\n",
"- renvoie le dernier itéré de la suite"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def algo_gradient(A, b, x0, rho, itermax):\n",
" # à completer\n",
" \n",
" \n",
" return x"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3.2)** Vérifier que votre fonction marche bien, en la testant avec $\\rho=1/L$, `nmax`$=10^3$ et un point initial de votre choix dans $\\RR^{100}$. Vous vérifierez également que la solution `x` ainsi obtenue satisfait $\\nabla f(x) \\simeq 0$."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3.3)** Vu que l'algorithme a l'air de marcher très bien, on n'a peut être pas besoin de le faire tourner pour toutes les `itermax` itérations: peut être que l'on peut s'arrêter avant. \n",
"\n",
"Modifier la fonction `algo_gradient` afin que:\n",
"- il prenne en nouvel argument: un niveau de tolérance `tol`\n",
"- il s'arrête dès lors que $\\Vert \\nabla f(x) \\Vert <$ `tol`\n",
"- avant de renvoyer le dernier itéré, il affiche le nombre d'itérations qui ont été effectuées avec `print`\n",
"\n",
"Pour cela, vous pourrez utiliser l'instruction `break` qui, utilisée à l'intérieur d'une boucle, permet de sortir directement de la boucle (voir le petit exemple ci-dessous pour comprendre comment `break` fonctionne en python)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exemple de focntionnement pour BREAK\n",
"for boucle_externe in [1,2,3]:\n",
" print(\"Boucle externe numéro \"+str(boucle_externe))\n",
" for lettre in \"abcde\":\n",
" print(lettre)\n",
" if lettre == \"c\":\n",
" break\n",
" print(\"La boucle interne s'est arrêtée à : \"+lettre)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Nouvelle version de algo_gradient\n",
"def algo_gradient(A, b, x0, rho, itermax, tol):\n",
" # à completer\n",
" \n",
" \n",
" \n",
" \n",
" return x"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3.4)** Tester la nouvelle version de l'algorithme avec les mêmes paramètres qu'à la question **3.2)**, et une tolérance de $10^{-10}$. De combien d'itérations a-t-on besoin? Etait-il nécessaire d'aller jusqu'à 1000 itérations?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**4)** On souhaite maintenant que l'algorithme aille le **plus vite** possible. Pour cela, on va se servir du cours, qui indique que l'algorithme du gradient à pas fixe converge le plus vite lorsque $\\rho = \\frac{2}{\\mu+L}$, où $\\mu$ est la plus petite valeur singlulière de $A^*A$.\n",
"\n",
"**4.1)** Calculer `mu` de deux façons différentes: $\\sigma_{min}(A^*A)$ et $\\sigma_{min}(A)^2$. Que constatez-vous? Lequel donne un résultat satisfaisant? A votre avis, pourquoi?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**4.2)** Utiliser l'algorithme comme à la question **3.4)** en remplaçant le pas $1/L$ par $\\frac{2}{\\mu+L}$. Observer le nombre d'itérations qu'il faut maintenant."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**4.3)** A votre avis est-ce que ce problème est bien conditionné?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# III. Résolution du problème de défloutage\n",
"\n",
"Revenons à notre problème de défloutage. Comme on l'a vu, il nous suffit de minimiser la fonction quadratique $\\Vert \\Phi(x) - y \\Vert^2$, où $\\Phi$= `flou` et $y$ est notre image floutée."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.linalg as la\n",
"import matplotlib.pyplot as plt\n",
"plt.set_cmap('gray') # Fixe la colormap à 'gray' pour afficher les images\n",
"y = np.load('data/photo.npy')\n",
"from image import flou"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**1)** Récupérer le code de la fonction `algo_gradient` afin d'écrire une fonction `défloutage` qui:\n",
"\n",
"- prend en arguments une image `y`, un point initial `x0`, un pas `rho`, un nombre d'itérations maximal `itermax`, un niveau de tolérance `tol`\n",
"- applique l'algorithme du gradient à pas constant à $f$, en partant de `x0`, pendant `itermax` itérations\n",
"- s'arrête dès lors que $\\Vert \\nabla f(x) \\Vert <$ `tol`\n",
"- renvoie le dernier itéré de la suite\n",
"- avant de renvoyer le dernier itéré, elle affiche le nombre d'itérations qui ont été effectuées avec `print`\n",
"\n",
"Le travail ici consiste essentiellement à remplacer la matrice $A$ par l'application linéaire de floutage (qui est symétrique, rappelons-le)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def defloutage(y, x0, rho, itermax, tol):\n",
" # a completer\n",
" \n",
" return x "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**2)** Tester cette fonction avec `itermax=2000`, une tolérance de $10^{-4}$, et un point initial de votre choix. On ne connait pas la constante de Lipschitz de $\\nabla f$, donc il va falloir choisir un pas au doigt mouillé (mais pas trop grand!): par exemple `rho = 0.01`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# pour afficher deux images x et y\n",
"_ = plt.figure(dpi=100)\n",
"_ = plt.subplot(1,2,1)\n",
"plt.imshow(y) # l'image floutée\n",
"_ = plt.subplot(1,2,2)\n",
"plt.imshow(x) # l'image obtenue par l'algorithme"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**3)** Cette histoire de pas nous embête un peu... \n",
"\n",
"Idéalement on voudrait prendre quelque chose comme $\\frac{1}{L}$, où $L$ est la norme de la Hessienne de $f$, qui est ici $\\Phi \\circ \\Phi$. Autrement dit, $L = \\Vert \\Phi \\circ \\Phi \\Vert = \\Vert \\Phi \\Vert^2$.\n",