TP de maths de novembre-décembre 2021 (M. Orestis)
\ No newline at end of file
Rédigé par l'élève Philippe Montandon
## Introduction
Ce travail pratique a été réalisé dans le cadre du cours du professeur **Orestis Malaspinas**, en classe de Mathématiques en technologie de l'information, lors de notre 3ème semestre.
Le but de ce TP était de réaliser, en binôme (sauf moi car tous les groupes étaient déjà pleins), un programme permettant de réaliser une régression linéaire à l'aide de la méthode de la descente de gradient, puis de représenter graphiquement les résultats obtenus.
### But
Nous partons d'un nuage de points aléatoires, et le but de notre programme va être de trouver la droite qui passe *au mieux* par ces points. Notre algorithme va effectuer d'innombrables calculs afin que la droite s'approche le plus possible de la droite optimale.
Mais n'existe-t-il pas une méthode plus simple et moins compliquée ?
Sur papier, oui. La méthode analytique par exemple, qui permet de trouver rapidement la droite optimale, et sans approximations... **MAIS** cela n'est possible que pour de tous petits nuages de points. Dès que le nuage devient quelquepeu conséquent, le calcul est long et fastidieux et le temps de résolution augmente de façon exponentielle. Il faudrait des millions, voire des milliards d'années pour calculer la droite optimale d'un nuage avec des milliers de points.
La méthode que notre professeur nous a demandé d'implémenter est celle de *la descente de gradient*.
Celle-ci est toujours approximative mais peut trouver des résultats extrêmement proches et bien plus rapidement que la méthode analytique.
Si je devais résumer en deux mots tout en vulgarisant un peu : nous calculons un *a* et un *b* au hasard avec notre algorithme. Si celui-ci réalise qu'il est très loin du *a* et *b* optimal, il effectue un grand pas avant de réitérer. La beauté de cet algorithme est que plus il sera proche de la solution optimale et plus il va faire de petits pas pour s'en approcher le plus possible.
### Petits détails
Le langage de programmation imposé pour la partie calculs-algorithmie était le **C**. Pour la partie graphique, rien n'était imposé tant que l'on arrivait à afficher nos résultats sur un graphe mais j'ai pour ma part choisi d'utiliser un affichage graphique codé en **python** et avec la librairie **matplotlib** car simple d'utilisation.
La durée accordée était d'environ 2 mois.
## Structure de ce rapport
### Théorie
* Cheminement logique entre les formules essentielles à ce TP
### Résultats
* Réponses aux questions posées
* Résultats obtenus après implémentation
* Validation de notre modèle de régression
## Théorie
Par où commencer ?
Commençons en douceur en suivant la trame narrative proposée par l'énoncé.
### La solution analytique
Si vous vous rappelez de mon introduction, j'énonçais la qualité et le défaut de cette méthode.
* Simple et rapide pour un tout petit nuage de point.
* Longue et fastidieuse pour dès que le nuage devient un peu grand.
L'équation ci-dessous est la fonction de coût (erreur quadratique):
\begin{equation}
E(a,b)=\sum_{j=1}^N(a\cdot x_j + b - y_j)^2
\end{equation}
Il faut résoudre le système ci-dessous:
\begin{equation}
\vec\nabla E(a, b)=\vec 0
\end{equation}
Puis:
\begin{equation}
\begin{cases}\frac{\partial E}{\partial a} = \sum_i^n (a \cdot x_{i} + b - y_{i}) \cdot x_{i}=0\\\frac{\partial E}{\partial b} = \sum_i^n (a \cdot x_{i} + b - y_{i})=0\end{cases}
\end{equation}
Pour arriver à:
\begin{equation}
\vec\nabla E(a, b)=\begin{cases} a \cdot \sum_i^N x_{i}^{2}+ b \cdot \sum_i^N x_{i}-\sum_i^N x_{i}y_{i}=0\\a\cdot\sum_i^N x_{i}+b\cdot N - \sum_i^N y_{i}=0 \end{cases}
\end{equation}
Ensuite:
\begin{equation}
\vec\nabla E(a, b)=\begin{cases} a \cdot \sum_i^N x_{i}^{2}+ b \cdot \sum_i^N x_{i}=\sum_i^N x_{i}y_{i}\\a\cdot\sum_i^N x_{i}+b\cdot N = \sum_i^N y_{i} \end{cases}
\end{equation}
Nous trouvons *b* dans la seconde ligne et nous simplifions car somme/N = moyenne
\begin{equation}
b=\frac{1}{N} \sum_i^N y_{i}- a \frac{1}{N}\sum_i^N x_{i}= \overline y- a \overline x
\end{equation}
Nous remplaçons *b* dans la première ligne pour trouver a:
\begin{equation}
(\overline y- a \overline x) \cdot \sum_i^N y_{i} + a \cdot \sum_i^N x_{i}^{2} =\sum_i^N x_{i}y_{i}
Nous pouvons désormais trouver le a et le b d'une droite passant par une (petite) multitude de points, mais comme vous avez pu le constaté, le calcul semble fastidieux.
### La solution numérique
Nous allons maintenant nous intéresser à la méthode de la descente de gradient afin de minimiser E(a,b).
Au départ, nous sommes dans le flou total quant à la bonne droite à trouver (et ne connaissons rien du *a* et du *b* à calculer).
Nous allons donc choisir un *a* et un *b* de manière aléatoire. Le but de cette méthode est de se rapprocher itérativement d'un résultat proche des valeurs optimales de *a* et de *b* (et donc de trouver la droite qui passe au mieux par le nuage de points).
Nous avons notre fonction de coût:
\begin{equation}
\vec\nabla E(a, b)=\vec 0
\end{equation}
Avec cette méthode, il est mathématiquement impossible de trouver exactement la réponse à cette équation. Nous cherchons donc à minimiser l'erreur pour que celle-ci tende vers 0.
Si on prend quelques pas de recul, on aperçoit l'ensemble de l'algorithme et on comprend que celui-ci n'est pas si compliqué que ça.
Le principe est simple:
1. Calculer la fonction de coût
2. Tant que le résultat de celle-ci est supérieur à la marge d'erreur qu'on autorise on calcule le gradient de *a* et de *b*
3. Ces 2 gradients nous permettent de calculer le nouveau *a* et *b* (descente de gradient)
4. Cela nous donne un nouveau *a* et *b* que l'on remet dans la fonction de coût
Nous allons maintenant tester notre algorithme de la descente de gradient.
Dans le cadre de ce TP nous avons créé une droite random de départ. Les points de notre nuage sont donc générés de manière aléatoire le long de cette droite (avec un random max que nous appelerons $\lambda$).
En testant mon code sur différentes valeurs *a* et *b* de départ, j'obtiens plus ou moins toujours la même marge d'erreur. Mes résultats semblent donc cohérents.
La console affiche, lors de multiple essais avec différents *a* et *b* de départ:
> Cost : 9.99938e-05
> Cost : 9.99977e-05
> Cost : 9.99608e-05
> Cost : 9.99481e-05
> Cost : 9.99938e-05
... ce qui est juste en-dessous de la marge d'erreur que j'ai autorisé au programme, donc aucun soucis de ce point de vue là.
L'erreur signifie la "distance" entre l'endroit actuel et l'endroit où la pente sur la fonction convexe est à son minimum (=0), mais on ne sait pas exactement où elle est et c'est pourquoi l'algorithme y va à tâton.
Il serait maintenant interressant de voir ce qu'il se passe si nous augmentons notre fameux $\lambda$.
> Nous remarquons que les 2 droites sont totalement différentes.
### Validation du modèle de régression
Je vous l'ai un peu spoilé avec la précédente figure (volontairement pour vous donner un avant-goût), mais il va maintenant falloir valider notre modèle de régression en effectuant une **validation croisée**.
La validation croisée nous permet de vérifier si le a et b que nous avons déterminés ont des valeurs qui continueraient à être correctes si on ajoutait de nouveaux points (aléatoirement) à notre modèle. Au lieu d'en ajouter, nous préférons diviser notre nuage de points en 3 parties : G1, G2 et G3.
* Nous entraînons le modèle sur G1 U G2 et le testons sur G3
* Nous entraînons le modèle sur G1 U G3 et le testons sur G2
* Nous entraînons le modèle sur G2 U G3 et le testons sur G1
Il va maintenant être intéressant de voir quelle est la valeur de notre erreur entre le modèle et les groupes de test.
Les résultats varient grandement suivant le $\lambda$, la marge d'erreur, le Learning Rate (valeur du "pas").
## Conclusion
Ce TP était vraiment passionnant car il est une véritable introduction au machine learning, sujet qui me passionne grandement.
J'espère de nouveau pouvoir faire un TP de la sorte, mais cette fois-ci avec un binôme car je me suis senti très seul. Tout le monde a fini par être pris.
### Points à améliorer
* Je pourrai grandement améliorer le code, l'épurer.
* Il y a des pointeurs inutiles
* La transition du C au python aurait plus être un peu plus fluide avec un meilleur Makefile
P.S.: pour lancer le programme, tapez **make** dans la console, puis **./opti**, puis **python3 displayer.py**.
Chaque fichier peut être commenté/décommenté pour avoir toutes les fonctionnalités.