Le but de ce projet était de trouver un algorithme <b>généraliste</b> (qui puisse être utilisé dans différents cas d'utilisations), <b>rapide</b> (qui n'a pas une grande complexité ou qui trouve des solutions dans un temps raisonnable) et <b>précis</b> (qui trouve des solutions proches de la réalité dans une majorité des cas).<br/></br>
Pour ce faire, j'avais plusieurs outils à ma disposition, mais celui sur lequel je me suis penché est l'algorithme <i>Knn</i> (n plus proche voisin). C'est un algorithme assez simple qui n'a pas réellement besoin d'entraînement contrairement à un réseau de neurones, mais qui à cause de sa complexité algorithmique devient inutilisable dans sa forme naïve pour de larges volumes de données. J'ai donc dû trouver un moyen de modifier l'implémentation de <i>Knn</i> pour réduire sa <b>complexité</b> tout en gardant si possible l'aspect <b>généraliste</b> et <b>précis</b> de <i>Knn</i>.
Le but de ce projet était de trouver un algorithme <b>généraliste</b> (qui puisse être utilisé dans différents cas d'utilisation), <b>rapide</b> (qui n'a pas une grande complexité ou qui trouve des solutions dans un temps raisonnable) et <b>précis</b> (qui trouve des solutions proches de la réalité dans une majorité des cas).<br/></br>
Pour ce faire, j'avais plusieurs outils à ma disposition, mais celui sur lequel je me suis penché est l'algorithme <i>Knn</i> (n plus proche voisin). C'est un algorithme assez simple qui n'a pas réellement besoin d'entraînement contrairement à un réseau de neurones, mais qui à cause de sa complexité algorithmique devient inutilisable dans sa forme naïve pour de larges volumes de données. J'ai donc dû trouver un moyen de modifier l'implémentation de <i>Knn</i> pour réduire sa <b>complexité</b>, tout en gardant si possible l'aspect <b>généraliste</b> et <b>précis</b> de <i>Knn</i>.
</p>
</section>
@ -26,48 +26,48 @@
Point commun entre <i>Knn</i> et <i>3nar</i> :
</h3>
<p>
Ces deux algorithmes sont capables d'inférer une ou plusieurs valeurs à partir d'une ou plusieurs valeurs en entrer grâce à des exemples fournit au préalable. Ils sont également capables de réaliser de la classification ou de la multi-classification (c'est-à-dire associer une ou plusieurs class à une ou un groupe de donnée en entrer).
Ces deux algorithmes sont capables d'inférer une ou plusieurs valeurs à partir d'une ou plusieurs valeurs en entrée grâce à des exemples fournis au préalable. Ils sont également capables de réaliser de la classification ou de la multi-classification (c'est-à-dire associer une ou plusieurs classes à une donnée ou un groupe de donnée en entrée).
</br></br>
Outre leurs capacités, ils fonctionnent de la même façon. La phase d'entraînement consiste simplement à enregistrer en mémoire les différentes données d'exemples ainsi que leurs valeurs associées (valeur, groupe de valeurs, class ou groupe de class). Ensuite pour inférer la/les valeur.s/class associé à de nouvels coordonnées, on trouve les n exemples les plus proches et on retourne la moyenne des valeurs des n point multiplié par un poid calculé en fonction de leur distance.
Outre leurs capacités, ils fonctionnent de la même façon. La phase d'entraînement consiste simplement à enregistrer en mémoire les différentes données d'exemples, ainsi que leurs valeurs associées (valeur, groupe de valeurs, classe ou groupe de classe). Ensuite, pour inférer la/les valeur.s/classe.s associée.s à de nouvelles coordonnées, on trouve les n exemples les plus proches et on retourne la moyenne des valeurs des n points multipliés par un poid calculé en fonction de leur distance.
</p>
<h3>
Problème de <i>Knn</i> :
</h3>
<p>
Le problème de l'implémentation naïve de <i>Knn</i> est que pour trouver les n points les plus proches d'un nouveau point A, on calcule la distance de chaque point en mémoire avec A puis on trouve les n points avec la plus petite distance. Ce qui veut dire que plus le nombre de points d'exemple en mémoire augmente plus on calcule de distance pour trouver la valeur d'un point. Ainsi, comme nous pouvons le voir sur le graphique ci-dessous la complexité de l'algorithme est linéaire
Le problème de l'implémentation naïve de <i>Knn</i> est que pour trouver les n points les plus proches d'un nouveau point A, on calcule la distance de chaque point en mémoire avec A, puis on trouve les n points avec la plus petite distance. Ce qui veut dire que plus le nombre de points d'exemple en mémoire augmente, plus on calcule de distance pour trouver la valeur d'un point. Ainsi, comme nous pouvons le voir sur le graphique ci-dessous, la complexité de l'algorithme est linéaire.
</p>
<imagesrc="./rapport/knn.png"/>
<h3>
Solution de <i>3nar</i> :
</h3>
<p>
Pour trouver les n points les plus proches de A sans avoir à calculer les distances avec tous les autres points, <i>3nar</i> profite de la phase d'ajout des points pour enregistrer des informations</b>
Pour trouver les n points les plus proches de A sans avoir à calculer les distances avec tous les autres points, <i>3nar</i> profite de la phase d'ajout des points pour enregistrer des informations</b>.
</br></br>
À l'initialisation, nous allons créer un espace orthonormé avec m dimension (le nombre de coordonner des exemples). Et nous allons remplir cet espace avec un certain nombre de sous-espace à déterminer en fonction des cas d'utilisations (ces espaces sont tous de tailles égaux et sont eux aussi orthonormé). Ensuite, lors de la phase d'entraînement, il suffit d'ajouter les points dans les différents sous-espaces en fonction de leurs coordonnées. Pour trouver le sous-espace si vos sous-espaces sont dans une liste, vous pouvez calculer l'index avec cette formule:<br/>
À l'initialisation, nous allons créer un espace orthonormé avec m dimension (le nombre de coordonnées des exemples). Nous allons ensuite remplir cet espace avec un certain nombre de sous-espaces à déterminer en fonction des cas d'utilisation (ces espaces sont tous de tailles égales et sont eux aussi orthonormés). Ensuite, lors de la phase d'entraînement, il suffit d'ajouter les points dans les différents sous-espaces en fonction de leurs coordonnées. Pour trouver le sous-espace si vos sous-espaces sont dans une liste, vous pouvez calculer l'index avec cette formule:<br/>
Soit t, la taille d'un sous-espace et nb, le nombre de sous-espace dans une dimension:
<imgsrc="./rapport/formuleIdx.png"/><br/>
</br><imgsrc="./rapport/formuleIdx.png"/><br/>
Dans le cas de ce programme, les sous-espaces sont stockés dans un tenseur et les coordonnées du sous-espace d'un point sont données par la division euclidienne de toutes les coordonnées du point par t.
</br></br>
Une fois toutes les données ajoutées dans le modèle, il est temps de lui demander la valeur de nouveau point. Pour cela, l'algorithme va trouver dans quel sous-espace le point serait, s'il existait dans sa mémoire. Puis il vérifie s'il a assez de points dans ce sous-espace dans une distance (dont on parlera plus tard). Si c'est le cas alors il calcule les distances avec ces points et il retourne les n plus faibles. Sinon on trouve les sous-espaces à proximité et on recommence jusqu'à avoir assez de points. Cela permet de grandement réduire le nombre de calcul de distance entre points pour trouver les n points les plus proches.
Une fois toutes les données ajoutées dans le modèle, il est temps de lui demander la valeur de nouveau point. Pour cela, l'algorithme va trouver dans quel sous-espace le point serait, s'il existait dans sa mémoire. Puis il vérifie s'il a assez de points dans ce sous-espace dans une distance (dont on parlera plus tard). Si c'est le cas, alors il calcule les distances avec ces points et il retourne les n plus faibles. Sinon on trouve les sous-espaces à proximité et on recommence jusqu'à avoir assez de points. Cela permet de grandement réduire le nombre de calcul de distance entre points pour trouver les n points les plus proches.
<br/></br>
Concernant la distance, elle est calculée en fonction des coordonnées du point que l'on veut deviner, de la taille du sous-espace et en fonction de son centre. Cette distance doit être la plus grande possible (pour capter le plus de point) tout en ne sortant en aucun point du sous-espace (pour être sûr de réellement trouver les points les plus proches). Cette distance peut être calculée de la façon suivante:</br>
<imagesrc="./rapport/rayon.png"/>
<imagesrc="./rapport/rayon.png"/></br>
<i>dist = t/2 - max(ABx,ABy) + t * nbSousEspaceAutour</i></br>
À noter que c'est un exemple en 2d, le même calcule peut-être généralisé pour un nombre m de dimension.<br/>
Dans le cas de ce programme, je n'ai pas utilisé cette distance à la place, j'ai utilisé cette formule qui me garantis de ne jamais sortir de l'espace et qui est plus simple à calculer:
<imagesrc="./rapport/rayon2.png"></br>
Dans le cas de ce programme, je n'ai pas utilisé cette distance. A la place, j'ai utilisé cette formule qui me garantit de ne jamais sortir de l'espace et qui est plus simple à calculer:
</br><imagesrc="./rapport/rayon2.png"></br>
<i>dist = t * nbSousEspaceAutour</i>
</br><br/>
Je vais maintenant vous expliquer l'algorithme qui me permet de trouver les sous-espaces autour d'un sous-espace. On appelle une fonction <i>findCoordAround</i> qui vas appeler <i>getCoordDifférente</i> pour tous les nombres entre 0 et la distance des sous-espaces autour. <i>getCoordDifférente</i> quant à elle, va appeler <i>getCompletVariationOfCoord</i> pour tout tableau de coordonner avec pour valeurs que des valeurs unique entre -la distance autour et +la distance autour. <i>getCompletVariationOfCoord</i> va appeler <i>getPermutationOfCoord</i> pour toutes les coordonnées qui possède les coordonners passé en paramètre puis comble les coordonners avec les valeurs ordonnées de ses coordonners. Enfin <i>getPermutationOfCoord</i> vas ajouter dans une liste de sous-espaces toutes les permutations des coordonners passé en entrée.
Il s'agira maintenant de présenter l'algorithme qui permet de trouver les sous-espaces autour d'un sous-espace. Pour trouver tous les sous-espaces à d de distance d'un sous-espace, il faut dans un premier temps trouver les sous-espaces autour de l'origine, puis appliquer une translation, qui transforme l'origine en notre sous-espace, aux sous-espaces que l'on trouve. Pour ce faire, je vais parcourir les différentes étapes de l'algorithme avec vous en prenant l'exemple d'un espace à 2 dimensions où l'on veut trouver tous les sous-espaces à 1 de distance. Dans une première boucle, on va mettre dans une variable le nombre de valeurs différentes que peut comporter nos coordonnées de sous-espace. Ici, c'est 1 et 2. Puis on va créer un radical de coordonnées (une première partie de coordonnées ordonnées qui contient n valeurs différentes entre -1 et 1, la distance maximum par rapport à zéro). Les radicaux crées vont être: <i>[-1],[0],[1],[-1,0],[-1,1],[0,1]</i>. Une fois qu'on a ces radicaux, nous allons compléter ceux-ci avec les valeurs des radicaux jusqu'à avoir des coordonnées complètes (en conservant seulement les valeurs des radicaux). Nous obtenons ici <i>[-1,-1],[0,0],[1,1],[-1,0],[-1,1],[0,1]</i>. Enfin, les sous-espaces vont être les permutations de coordonnées complètes que l'on vient de trouver. Dans notre cas : <i>[-1,-1],[0,0],[1,1],[-1,0],[0,-1],[-1,1],[1,-1],[0,1],[1,0]</i>. Comme vous pouvez le constater, résultent de l'algorithme les 9 coordonnées que nous souhaitions. Notons également que cet algorithme fonctionne peu importe le nombre de dimensions (positives et entières) et peu importe la distance (elle aussi positive et entière).
</br></br>
Enfin, dernière optimisation de l'algorithme, pour éviter de recalculer en boucle les différents sous-espaces autour d'un sous-espace (ce qui est coûteux à faire dynamiquement pour m dimension), on enregistre en mémoire le résultat de cette opération. Puis lorsque l'on veut recalculer ce résultat, on a juste à l'appliquer à notre cas (car le sous-espace ne sera pas le même, mais les sous-espaces autour seront aux mêmes distances et dans les mêmes directions).
Enfin, dernière optimisation de l'algorithme, pour éviter de recalculer en boucle les différents sous-espaces autour d'un sous-espace (ce qui est coûteux à faire dynamiquement pour m dimension), on enregistre en mémoire le résultat de cette opération. Puis, lorsque l'on veut recalculer ce résultat, nous pouvons simplement l'appliquer à notre cas (car les sous-espaces ne seront pas les mêmes, mais leurs sous-espaces autour seront aux mêmes distances et aux mêmes directions).
<br/><br>
Ainsi, toutes ses optimisations permettent de grandement réduire la complexité de <i>3nar</i> par rapport à <i>Knn</i> cependant comme vous pouvez le voir sur les graphiques suivants, la complexité reste linéaire que l'on fasse varier le nombre de points inféré ou le nombre de données d'entraînement:
Ainsi, toutes ces optimisations permettent de grandement réduire la complexité de <i>3nar</i> par rapport à <i>Knn</i>. Cependant, comme vous pouvez le voir sur les graphiques suivants, la complexité reste linéaire que l'on fasse varier le nombre de points inférés ou le nombre de données d'entraînement:
Cependant, il y a encore un dernier point intéressant à discuter avec <i>3nar</i> c'est l'impact d'un paramètre sur la complexité de l'algorithme. Plus on divise l'espace en sous-espace plus la complexité de l'algorithme se réduit. Vous pouvez voir ce phénomène grâce au graphique suivant:
Cependant, il y a encore un dernier point intéressant à discuter avec <i>3nar</i>, c'est l'impact d'un paramètre sur la complexité de l'algorithme. Plus on divise l'espace en sous-espace, plus la complexité de l'algorithme se réduit. Vous pouvez voir ce phénomène grâce au graphique suivant:
</p>
<imagesrc="./rapport/comp3narParam.png"/>
</section>
@ -77,13 +77,13 @@
Cas d'utilisations
</h2>
<p>
Comme nous l'avons vue dans la première partie, <i>3nar</i> est utilisable dans plein de contexte. Ce ne sera pas souvent le meilleur algorithme, mais dans la plupart des cas, il rendra un résultat satisfaisant en un temps satisfaisant. Vous pouvez retrouver 4 cas d'utilisations aussi appelées demo dans ce projet que vous pouvez lancer au choix avec <i>Knn</i> ou <i>3nar</i>. Pour lancer les demos placer vous dans le répertoire et appeler les script python de demo en ajoutant à votre commande le nom de l'algo (knn ou nnnar).<br/><br/>
Comme nous l'avons vu dans la première partie, <i>3nar</i> est utilisable dans pleins de contextes. Ce ne sera pas souvent le meilleur algorithme, mais dans la plupart des cas, il rendra un résultat satisfaisant en un temps lui aussi satisfaisant. Vous pouvez retrouver 4 cas d'utilisations, aussi appelées demo dans ce projet, que vous pouvez lancer au choix avec <i>Knn</i> ou <i>3nar</i>. Pour lancer les demos, placez vous dans le répertoire et appelez les scripts python de demo en ajoutant à votre commande le nom de l'algorithme (knn ou nnnar).<br/><br/>
</p>
<h3>
demo1.py
</h3>
<p>
Dans la demo 1, nous utilisons le csv <i>./data/maison.csv</i>. Nous calculons une valeur en sortie en fonction de 4 valeurs en entrer. Nous calculons le prix des maisons en fonction du nombre de m², du nombre de chambres, du nombre de salles de bain et de l'étage. Sur ces données et avec 80% du dataset en entraînement et 20% en test, on arrive au alentour de 90% de précisions.
Dans la demo 1, nous utilisons le csv <i>./data/maison.csv</i>. Nous calculons une valeur en sortie en fonction de 4 valeurs en entrée. Nous calculons le prix des maisons en fonction du nombre de m², du nombre de chambres, du nombre de salles de bain et de l'étage. Sur ces données et avec 80% du dataset en entraînement et 20% en test, on arrive aux alentours de 90% de précision.
</p>
<h3>
demo2.py
@ -95,16 +95,16 @@
demo3.py
</h3>
<p>
Cette demo est plus visuel est présente un cas d'utilisation ou nous devons calculer plusieurs valeurs en fonction de plusieurs valeurs. Dans cette demo, nous prenons une image (<i>./data/img.jpg</i>) puis de manière aléatoire on vient remplacer des pixels par des pixels blancs. Puis nous venons retrouver algorithmiquement les pixels qui ont été changés (on pourrait aussi faire cette étape avec <i>3nar</i> mais ça serait plus long), puis enfin, on vient calculer la valeur de ces pixels avec <i>3nar</i>. Dans cette exemple, on commence à avoir pas mal de donnée et on voit l'avantage de <i>3nar</i> par rapport à <i>Knn</i>.
Cette demo est plus visuelle et présente un cas d'utilisation ou nous devons calculer plusieurs valeurs en fonction de plusieurs valeurs. Dans cette demo, nous prenons une image (<i>./data/img.jpg</i>), puis de manière aléatoire, on vient remplacer des pixels par des pixels blancs. Ensuite, nous venons retrouver algorithmiquement les pixels qui ont été changés (on pourrait aussi faire cette étape avec <i>3nar</i> mais cela serait plus long).Enfin, on vient calculer la valeur de ces pixels avec <i>3nar</i>. Dans cet exemple, le nombre de données comence à être non négligeable, et on voit l'avantage de <i>3nar</i> par rapport à <i>Knn</i>.
</p>
<h3>
demo4.py
</h3>
<p>
Dans cet exemple, on fait la classification des espèces d'Iris à partir de la taille de la largeur de la tige et des pétales. En quelques secondes, on arrive à avoir au alentour de 95% de précision.
Dans cet exemple, on fait la classification des espèces d'Iris à partir de la taille de la largeur de la tige et des pétales. En quelques secondes, on arrive à avoir environ 95% de précision.
</p>
<p>
À noter que pour les démos, je n'ai pas de dataset d'évaluation seulement un dataset de test, car je n'ai pas d'entraînement à proprement parler à réaliser (comme de la backpropagation) donc le dataset de test renvoie forcément le même résultat que le dataset d'évaluation.
À noter que pour les démos, je n'ai pas de dataset d'évaluation, seulement un dataset de test, car je n'ai pas d'entraînement à proprement parler (comme de la backpropagation) donc le dataset de test renvoie forcément le même résultat que le dataset d'évaluation.