diff --git a/plotComplexite.py b/plotComplexite.py new file mode 100644 index 0000000..0fb8a59 --- /dev/null +++ b/plotComplexite.py @@ -0,0 +1,85 @@ +import os +import sys +parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +sys.path.append(parent_dir_name + "/3nar/code") + +from nnnar import * +from knn import * + +import matplotlib.pyplot as plt +import numpy as np +from time import time + + +# initialisation du modèle +comp = False +if (sys.argv[1] == "comp"): + model1 = Knn() + model2 = Nnnar(1, 0, 1, 50) + comp = True +if (sys.argv[1] == "knn"): + model = Knn() +else: + model = Nnnar(1, 0, 1, 50) + +maxVal = 20_000 +nbTest = 100 +nbPts = 10 + +# Création des données d'entrainement +train = [] +test = [] +for i in range(maxVal): + x = np.random.rand() + y = np.random.rand() + train.append([x,y]) +for i in range(nbTest): + x = np.random.rand() + test.append(x) +train = np.array(train) +test = np.array(test) + +def testModel(model, train ,test): + model.reset() + t = time() + # Entrainement du modèle + for i in range(len(train)): + model.addPoint(np.array([train[i,0]]), np.array([train[i,1]])) + # Test du modèles + for i in range(len(test)): + model.getValueOfPoint(np.array([test[i]]), 5)[0] + return time() - t + +if comp: + res1 = [] + res2 = [] + idxs = [] + for i in range(1,nbPts): + idx = round((i*maxVal)/nbPts) + print(idx) + idxs.append(idx) + res1.append(testModel(model1, train[:idx], test)) + res2.append(testModel(model2, train[:idx], test)) + + plt.xlabel('Number of training points') + plt.ylabel('Time (s)') + plt.xticks(range(len(idxs)), idxs) + + plt.plot(res1,label='KNN') + plt.plot(res2,label='3NAR') + plt.legend() + plt.show() +else: + res = [] + idxs = [] + for i in range(1,nbPts): + idx = round((i*maxVal)/nbPts) + print(idx) + idxs.append(idx) + res.append(testModel(model, train[:round((i*maxVal)/nbPts)], test)) + + plt.xlabel('Number of training points') + plt.ylabel('Time (s)') + plt.xticks(range(len(idxs)), idxs) + plt.plot(res) + plt.show() \ No newline at end of file diff --git a/plotComplexite2.py b/plotComplexite2.py new file mode 100644 index 0000000..5c3ae1e --- /dev/null +++ b/plotComplexite2.py @@ -0,0 +1,71 @@ +import os +import sys +parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +sys.path.append(parent_dir_name + "/3nar/code") + +from nnnar import * +from knn import * + +import matplotlib.pyplot as plt +import numpy as np +from time import time + + +# initialisation du modèle +comp = False +if (sys.argv[1] == "comp"): + model1 = Knn() + model2 = Nnnar(1, 0, 1, 100) + comp = True +if (sys.argv[1] == "knn"): + model = Knn() +else: + model = Nnnar(1, 0, 1, 100) + +maxVal = 1_000 +nbTest = 1_000 +nbPts = 10 + +# Création des données d'entrainement +train = [] +test = [] +for i in range(maxVal): + x = np.random.rand() + y = np.random.rand() + train.append([x,y]) +for i in range(nbTest): + x = np.random.rand() + test.append(x) +train = np.array(train) +test = np.array(test) + +def testModel(model, train ,test): + model.reset() + t = time() + # Entrainement du modèle + for i in range(len(train)): + model.addPoint(np.array([train[i,0]]), np.array([train[i,1]])) + # Test du modèles + for i in range(len(test)): + model.getValueOfPoint(np.array([test[i]]), 5)[0] + return time() - t + +if comp: + res1 = [] + res2 = [] + idxs = [] + for i in range(1,nbPts): + idx = round((i*nbTest)/nbPts) + print(idx) + idxs.append(idx) + res1.append(testModel(model1, train, test[:idx])) + res2.append(testModel(model2, train, test[:idx])) + + plt.xlabel('Number of points infered') + plt.ylabel('Time (s)') + plt.xticks(range(len(idxs)), idxs) + + plt.plot(res1,label='KNN') + plt.plot(res2,label='3NAR') + plt.legend() + plt.show() \ No newline at end of file diff --git a/plotComplexite3.py b/plotComplexite3.py new file mode 100644 index 0000000..de4cbef --- /dev/null +++ b/plotComplexite3.py @@ -0,0 +1,61 @@ +import os +import sys +parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +sys.path.append(parent_dir_name + "/3nar/code") + +from nnnar import * + +import matplotlib.pyplot as plt +import numpy as np +from time import time + + + +maxVal = 1_000 +nbTest = 1000 +nbPts = 10 +nbModel = 10 +nbMaxSubDiv = 100 + +# Création des données d'entrainement +train = [] +test = [] +for i in range(maxVal): + x = np.random.rand() + y = np.random.rand() + train.append([x,y]) +for i in range(nbTest): + x = np.random.rand() + test.append(x) +train = np.array(train) +test = np.array(test) + +def testModel(model, train ,test): + model.reset() + t = time() + # Entrainement du modèle + for i in range(len(train)): + model.addPoint(np.array([train[i,0]]), np.array([train[i,1]])) + # Test du modèles + for i in range(len(test)): + model.getValueOfPoint(np.array([test[i]]), 5)[0] + return time() - t + + +for i in range(1,nbModel): + nbSub = round(i*nbMaxSubDiv/nbModel) + print(nbSub) + model = Nnnar(1,0,1,nbSub) + res = [] + idxs = [] + for i in range(1,nbPts): + idx = round((i*nbTest)/nbPts) + idxs.append(idx) + res.append(testModel(model, train, test[:idx])) + plt.plot(res,label='NNNAR('+str(nbSub)+')') + +plt.xlabel('Number of points infered') +plt.ylabel('Time (s)') +plt.xticks(range(len(idxs)), idxs) +plt.legend() +plt.show() \ No newline at end of file diff --git a/rapport.md b/rapport.md index abd8ef3..d0e04b2 100644 --- a/rapport.md +++ b/rapport.md @@ -13,8 +13,8 @@ Problématique

- Le but de ce projet était de trouver un algorithme généraliste (qui puisse être uttilisé dans différents cas d'uttilisations), rapide (qui n'a pas une grande complexité ou qui trouve des solutions dans un temp résonable) et précis (qui trouve des solutions proche de la réalité dans une majorité des cas).

- Pour ce faire j'avais plusieurs outil à ma disposition, mais celui sur laquel je me suis penché est l'algorithme Knn (n plus proche voisin). C'est un algorithme assez simple qui n'as pas réelement besoin d'entrainement contrairement à un réseau de neurone mais qui à cause de sa complexité algorithmique devient inutilisable dans sa forme naïve pour de large volume de données. J'ai donc du trouver un moyen de modifier l'implémentation de Knn pour réduire sa complexité tout en gardant si possible l'aspect généraliste et précis de Knn. + Le but de ce projet était de trouver un algorithme généraliste (qui puisse être utilisé dans différents cas d'utilisations), rapide (qui n'a pas une grande complexité ou qui trouve des solutions dans un temps raisonnable) et précis (qui trouve des solutions proches de la réalité dans une majorité des cas).

+ Pour ce faire, j'avais plusieurs outils à ma disposition, mais celui sur lequel je me suis penché est l'algorithme Knn (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 Knn pour réduire sa complexité tout en gardant si possible l'aspect généraliste et précis de Knn.

@@ -26,66 +26,85 @@ Point commun entre Knn et 3nar :

- Ces deux algorithme sont cappables d'inférer une ou plusieures valeurs à partir d'une ou plusieurs valeurs en entrer grâce à des exemples fournit au préalable. ils sont également cappable 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).

- Outre leurs capacités, ils fonctionnent de la même façon. La phase d'entrainement consiste simplement à enregistrer en meimoir 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. + 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). +

+ 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.

Problème de Knn :

- Le problème de l'implémentation naïve de Knn est que pour trouver les n points les plus proches d'un nouveau point A, on calcule la distance de chaque point en mémoir avec A puis on trouve les n points avec la plus petite distance. Ce qui veut dire que plus le nombre de point d'exemple en meimoir 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 en O(n) ?? + Le problème de l'implémentation naïve de Knn 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

+

Solution de 3nar :

- Pour trouver les n points les plus proches de A sans avoir à calculer les distances avec tout les autres points, 3nar échange de la RAM contre de la puissance de calcule

- A l'initialisation nous allons crée un espace orthonormé avec m dimention (le nombre de coordonées des exemples). Et nous allons remplire cet espace avec un certain nombre de sous espace à déterminer en fonction des cas d'uttilisations (ces espaces sont tous de tailles égaux et sont eux aussi orthonormé). Ensuite, lors de la phase d'entrainement, il suffit d'ajouter les points dans les différents sous éspaces en fonction de leurs coordonnées. Pour trouver le sous espace si vos sous éspace sont dans une liste vous pouvez calculer l'index avec cette formule:
- Soit t la taille d'un sous espace et nb le nombre de sous espace dans une dimension: + Pour trouver les n points les plus proches de A sans avoir à calculer les distances avec tous les autres points, 3nar profite de la phase d'ajout des points pour enregistrer des informations +

+ À 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:
+ Soit t, la taille d'un sous-espace et nb, le nombre de sous-espace dans une dimension:
- Dans le cas de ce programme, les sous éspaces sont stocké dans un tenseur et les coordonées du sous espace d'un point est donnée par la division euclidienne de toutes les coordonées du point par t.

- Une fois toutes les données ajouté dans le modèle, il est temp de lui demander la valeur de nouveau point. Pour cela l'algo vas trouver dans quel sous espace le point serait si il existait dans sa meimoir. Puis il verrifie si il a assez de point 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 faible. Si non on trouve les sous espace à proximiter et on recommence jusqu'a avoir assez de point. Cela permet de grandement réduire le nombre de calcul de distance entre point pour trouver les n points les plus proches.
- Concernant la distance, elle est calculé en fonction des coordonées du point que l'on veut deviner en fonction du centre de son sous espace. Cette distance doit être la plus grande possible (pour capté le plus de point) tout en ne sortant pas en aucun point du sous espace (pour être sûr de réelement trouver les points les plus proche). Cette distance peut être calculé de la façon suivante:
+ 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. +

+ 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. +

+ 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:
- dist = t - max(ABx,ABy) + t * nbSousEspaceAutour
- A noté que c'est un exemple en 2d, le même calcule peut être généralisé pour un nombre m de dimmension.
- Dans le cas de ce programme, je n'ai pas uttilisé cette distance à la place j'ai uttilisé cette formule qui me garentis de ne jamais sortir de l'espace et qui est plus simple à calculer: + dist = t/2 - max(ABx,ABy) + t * nbSousEspaceAutour
+ À noter que c'est un exemple en 2d, le même calcule peut-être généralisé pour un nombre m de dimension.
+ 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:
- dist = t * nbSousEspaceAutour
- Enfin, dernière optimisation de l'algorithme, pour éviter de recalculer en boucle les différents sous espace autour d'un sous espace (ce qui est couteux à faire dinnamiquement pour m dimention), on enregistre en meimoir 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 espace autour seront au même distance et dans les même directions). + dist = t * nbSousEspaceAutour +

+ Je vais maintenant vous expliquer l'algorithme qui me permet de trouver les sous-espaces autour d'un sous-espace. On appelle une fonction findCoordAround qui vas appeler getCoordDifférente pour tous les nombres entre 0 et la distance des sous-espaces autour. getCoordDifférente quant à elle, va appeler getCompletVariationOfCoord pour tout tableau de coordonner avec pour valeurs que des valeurs unique entre -la distance autour et +la distance autour. getCompletVariationOfCoord va appeler getPermutationOfCoord 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 getPermutationOfCoord vas ajouter dans une liste de sous-espaces toutes les permutations des coordonners passé en entrée. +

+ 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). +

+ Ainsi, toutes ses optimisations permettent de grandement réduire la complexité de 3nar par rapport à Knn 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:

+ + +

+ Cependant, il y a encore un dernier point intéressant à discuter avec 3nar 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: +

+

- Cas d'uttilisation + Cas d'utilisations

- Comme nous l'avons vue dans la première partie, 3nar est utilisable dans plein de context. Ce ne sera pas souvent le meilleur algo mais dans la plupart des cas il rendra un résultat satisfaisant en un temp satifaisant. Vous pouvez retrouver 4 cas d'uttilisations aussi appelé demo dans ce projet que vous pouvez lancer au choix avec Knn ou 3nar. Pour lancer les demos placer vous dans le répertoir et appeler les script python de demo en ajoutant a votre commande le nom de l'algo (knn ou nnnar).

+ Comme nous l'avons vue dans la première partie, 3nar 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 Knn ou 3nar. 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).

demo1.py

- Dans la demo 1, nous utillisons le csv ./data/maison.csv. Nous calculons une valeurs en sortie en fonction de 4 valeur en entrer. Nous calculons le prix des maisons en fonction du nombre de m², du nombre de chambre, du nombre de salle de bain et de l'étage. Sur ces données et avec 80% du dataset en entrainement et 20% en test, on arrive au alentour de 90% de precisions. + Dans la demo 1, nous utilisons le csv ./data/maison.csv. 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.

demo2.py

- Dans cet exemple, nous générons aléatoirement une fonction polynomiale du second degré avec des paramètres étant des réels compris entre -5 et 5. Puis après avoir fournit à 3nar quelques exemples (100 dans notre cas), nous uttilisons l'algorithme pour prédire de nouvelles valeurs choisit aléatoirement. + Dans cet exemple, nous générons aléatoirement une fonction polynomiale du second degré avec des paramètres étant des réels compris entre -5 et 5. Puis après avoir fourni à 3nar quelques exemples (100 dans notre cas), nous utlisons l'algorithme pour prédire de nouvelles valeurs choisit aléatoirement.

demo3.py

- Cette demo est plus visuel est présente un cas d'uttilisation ou nous devons calculer plusieurs valeurs en fonction de plusieurs valeurs. Dans cette demo, nous prenons une image (./data/img.jpg) puis de manière aléatoire un viens remplacer des pixels par des pixels blanc. Puis nous venons retrouver algorithmiquement les pixels qui ont été changé (on pourrait aussi faire cette étap avec 3nar mais ça serait plus long), puis enfin, on vient calculer la valeur de ces pixels avec 3nar. Dans cette exemple, on commence à avoir pas mal de donnée et on voit l'aventage de 3nar par rapport à Knn. + 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 (./data/img.jpg) 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 3nar mais ça serait plus long), puis enfin, on vient calculer la valeur de ces pixels avec 3nar. Dans cette exemple, on commence à avoir pas mal de donnée et on voit l'avantage de 3nar par rapport à Knn.

demo4.py

- Dans cette exemple, on fait la classification des espèces d'Iris à partir de la taillet de de la largeur de la tige et des pétalles. En quelque second 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 au alentour de 95% de précision. +

+

+ À 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.

diff --git a/rapport/comp3narParam.png b/rapport/comp3narParam.png new file mode 100644 index 0000000..1f2cdbc Binary files /dev/null and b/rapport/comp3narParam.png differ diff --git a/rapport/compKnn3narPointInfered.png b/rapport/compKnn3narPointInfered.png new file mode 100644 index 0000000..7b4aeab Binary files /dev/null and b/rapport/compKnn3narPointInfered.png differ diff --git a/rapport/compKnn3narTrainPoint.png b/rapport/compKnn3narTrainPoint.png new file mode 100644 index 0000000..e8c34ac Binary files /dev/null and b/rapport/compKnn3narTrainPoint.png differ diff --git a/rapport/knn.png b/rapport/knn.png new file mode 100644 index 0000000..d4bd8c2 Binary files /dev/null and b/rapport/knn.png differ