From a8c422384636a653f371eb73c9b4914921e2597b Mon Sep 17 00:00:00 2001
From: Numbtus <matias.mennecart.etu@univ-lille.fr>
Date: Thu, 28 Nov 2024 21:53:05 +0100
Subject: [PATCH] Premiere version du rapport d'analyse K-NN

---
 DevEfficace/rapport.md                        | 129 ++++++++++++++++++
 .../sae/classification/knn/MethodKNN.java     |  77 +++++------
 2 files changed, 168 insertions(+), 38 deletions(-)
 create mode 100644 DevEfficace/rapport.md

diff --git a/DevEfficace/rapport.md b/DevEfficace/rapport.md
new file mode 100644
index 0000000..57e2c8b
--- /dev/null
+++ b/DevEfficace/rapport.md
@@ -0,0 +1,129 @@
+# Algorithme K-NN - Rapport R3.02 (EN COURS - NON RELU)
+
+### Groupe H-4
+
+-  [MENNECART Matias](mailto:matias.mennecart.etu@univ-lille.fr)
+-  [DEBUYSER Hugo](mailto:hugo.debuyser.etu@univ-lille.fr)
+-  [ANTOINE Maxence](mailto:maxence.antoine.etu@univ-lille.fr)
+-  [DEKEISER Matisse](mailto:matisse.dekeiser.etu@univ-lille.fr)
+-  [DESMONS Hugo](mailto:hugo.desmons.etu@univ-lille.fr)
+
+
+---
+
+## Implémentation de K-NN
+
+*Une description de votre implémentation de l’algorithme k-NN : classe implémentant l’algorithme, méthode(s) de cette classe implémentant le calcul de la distance, traitement de la normalisation, méthode(s) de cette classe implémentant la classification, méthode(s) évaluant la robustesse. N’hésitez
+pas à mettre en avant l’efficacité de ces méthodes (approprié pour un grand volume de données, normalisation
+efficace des distances).*
+
+Nous avons implémenté l'algorithme K-NN dans une classe MethodKNN. Nous avons choisis d'utiliser des methodes statiques pour faciliter l'utilisation de cette algorithme.
+Cette classe contient 5 methodes permettant d'implementer l'algorithme K-NN et ses differentes fonctionnalités.
+
+> public static void updateModel(List<LoadableData> datas)
+
+Cette premiere methode permet de mettre a jour les données de l'algorithme. Ainsi il faut passer en parametre les données sur lesquelles ont souhaite travailler.
+Cette methode va calculer les valeurs max et min ainsi que l'amplitude de chaque attribue des données passé en parametre.
+Cette methode necessite 2 parcours,  1 sur les données, puis un sur les min et les max de chaque attribue pour calculer l'amplitude. Il est donc conseillé de n'utiliser cette methode qu'une fois par jeu de données (toute facon le resultat serait le meme)
+C'est pour cela que cette methode n'est pas directement appelé dans les methodes ci-dessous mais qu'elle doit explicitement être appelé aupparavant.
+
+>  public static List<LoadableData> kVoisins(List<LoadableData> datas, LoadableData data, int k, Distance distance) 
+ 
+Cette methode a pour objectif de recupéré les k voisins les plus proches d'un donnée parmis un jeu de données et selon une distance.
+Elle prends donc en parametre le jeu de données, la données pour laquelle on souhaite obtenir les voisins, le nombre de voisins souhaités ainsi que la distance avec laquelle les calculs doivent etres effectués.
+Cette methode n'effectue qu'un seul parcours de boucle et calcul pour chacune des données sa distance avec la donnée passée en parametre. Les calculs de distance sont definis dans l'objet implémentant l'interface Distance passé en parametre.
+Si il s'agit d'une distance normalisée la normalisation s'effectue au moment de la recherche des voisins.
+
+>public static double robustesse(List<LoadableData> datas, int k, Distance distance, double testPart)
+
+
+Cette methode a pour objectif d'évaluer la robustesse de l'algorithme sur un jeu de donné avec un K donné et une distance donnée. Elle prends en parametre le jeu de données sur lequel effectué l'evalutation,
+le k a tester, la distance a utiliser ainsi que un pourcentage correspondant a la partie des données qui sera utilisé afin de tester les données. Ex avec testPart=0.2, 80% des données serviront de données de reférence et 20% seront utilisé pour tester la 
+validité de l'algorithme. Cette effectue une validation croisée (Voir #Validation Croisée)
+
+
+> public static int bestK(List<LoadableData> datas, Distance distance)
+
+Cette methode a pour objectif de rechercher le meilleur K possible pour un jeu de données et une distance données.
+Elle va tester la totalité des K impair compris entre 1 et racine caré du nombre de données. Cette valeur max permet d'eviter que le K choisit soit trop grand et
+fausse les résultats. Elle test donc la robustesse de chaque K et renvoie le K ayant la meilleure robustesse. Cette methode parcours le jeu de donnée Kmax*(1/testPart) fois, ou testPart correspond au pourcentage des données utilisé comme valeurs de test.
+
+
+---
+
+## Validation croisée
+
+
+#### Rappel de la methode de validation croisée
+
+La validation croisée est une méthode d'évaluation utilisée pour mesurer la performance d'un modèle en le testant sur des données qu'il n'a pas utilisées pour l'entraînement. Dans ce cas précis :
+
+Les données sont divisées en plusieurs parties égales (appelées folds ou partitions).
+À chaque itération, une des partitions est utilisée comme jeu de test, tandis que les autres servent pour l'entraînement.
+Les résultats des tests sont cumulés pour calculer un score global.
+Cette méthode permet de minimiser le biais d'évaluation en utilisant toutes les données tour à tour pour l'entraînement et le test.
+
+#### Implémentation de la validation croisée
+
+
+La méthode effectue une validation croisée en divisant les données en plusieurs partitions. À chaque itération :
+
+Une partie des données sert de jeu de test.
+Le reste des données sert de jeu d'entraînement.
+
+Voici les étapes principales :
+
+- Calcul du nombre d'itérations :
+Le nombre d'itérations est déterminé par
+1/testPart. Par exemple, si testPart = 0.1, la méthode effectue 10 itérations, si testPart = 0.2, la méthode effectue 5 iterations, etc.
+
+- Division des données :
+Pour chaque itération i, une sous-liste correspondant au pourcentage testPart (par exemple, 10 % des données) est extraite et utilisée comme jeu de test (testData).
+Le reste des données sert de jeu d'entraînement (trainingData).
+
+- Estimation des classes :
+Chaque élément du jeu de test est classé à l’aide de la fonction MethodKNN.estimateClass(trainingData, l, k, distance) où trainingData correspond au reste des données.
+Si la classe prédite correspond à la classe réelle (retournée par l.getClassification()), un compteur (totalFind) est incrémenté.
+
+- Calcul du taux de réussite :
+Après chaque itération, le taux de réussite est calculé totalFind/totalTry et ajouté à la variable taux.
+- Renvoie du taux total moyen de reussite: Enfin, on divise la variable taux par le nombre d'itération afin d'obtenir un taux de reussite moyen.
+---
+
+## Choix du meilleur K
+
+
+Pour obtenir le meilleur K, on appel la methode bestK(List<LoadableData> datas, Distance distance) decrite plus haut. On obtient un K optimal, puis on appel la methode robustesse(...) avec le k trouvé plutot comme parametre.
+
+En appliquant cette methode voici les resultats que nous avons obtenue avec:
+
+##### Iris
+
+
+
+| Distance \ K                    | 1     | 3   | 5 | 7 | 9 | 11    | K choisit |
+|---------------------------------|-------|-------|-------|-------|-------|-------|----|
+| Distance Euclidienne            | 0.96  | 0.966 | 0.96 | 0.98 | 0.98 | 0.98 | 5 |
+| Distance Euclidienne Normalisée | 0.946 | 0.946 | 0.96 |  0.96  | 0.96 | 0.96 | 7 |
+| Distance Manhattan | 0.953 | 0.946 | 0.946 | 0.96 | 0.96 | 0.953 | 7 |
+| Distance Manhattan Normalisée | 0.946 | 0.96 | 0.946 | 0.953 | 0.953 | 0.953 | 3 | 
+
+On obtient donc un taux de reussiste plutôt élevé. A chaque fois l'algorithme choisit le K avec le plus haut taux de reussite. En cas d'égalité, il choisit le plus petit K parmis les égalités.
+
+##### Pokemon
+
+| Distance \ K                    | 1     | 3   | 5 | 7 | 9 | 11 |  13 | 15 | 17 | 19 | 21  | K choisit |
+|---------------------------------|-------|-------|-------|-------|-------|-------|----|----|----|----|----|----|
+| Distance Euclidienne | 0.243 | 0.229 | 0.239 | 0.235 | 0.247 | 0.251 | 0.237 | 0.225 | 0.215 | 0.205 | 0.2 | 11 |
+| Distance Euclidienne Normalisée | 0.211 |  0.229 | 0.251 | 0.245 | 0.245 | 0.239 | 0.245 | 0.243 | 0.237 | 0.239 | 0.225 | 5 |
+| Distance Manhattan | 0.231 | 0.235 | 0.239 | 0.239 | 0.241 | 0.239 | 0.237 | 0.235 | 0.233 | 0.207 | 0.201 | 9 |
+| Distance Manhattan Normalisée | 0.178 | 0.188 | 0.2 | 0.215 | 0.205 | 0.203 | 0.194 | 0.190 | 0.184 | 0.180 | 0.190 | 7 |
+---
+
+ajoyter un commentaire sur les resultats
+
+## Efficacité
+
+Comme expliqué pour chaque methode dans la partie Implementation de l'algorithme, nous avons chercher a minimiser le nombre de parcours du fichier de données et plus generalement le nombre de boucle.
+L'algorithme necessite une List qui sera donnée en parametre, il est donc libre a la personne qui l'utilise de fournir l'implementation de List qu'il souhaite. De plus, pour le calcul des parametres du jeu de données (
+amplitude,valeur minimale, valeur maximal) nous avons utiliser un tableau de double afin de limiter les performances
\ No newline at end of file
diff --git a/src/main/java/fr/univlille/sae/classification/knn/MethodKNN.java b/src/main/java/fr/univlille/sae/classification/knn/MethodKNN.java
index 300c92d..686303c 100644
--- a/src/main/java/fr/univlille/sae/classification/knn/MethodKNN.java
+++ b/src/main/java/fr/univlille/sae/classification/knn/MethodKNN.java
@@ -7,16 +7,12 @@ import fr.univlille.sae.classification.model.DataType;
 import fr.univlille.sae.classification.model.LoadableData;
 
 
-import javax.xml.crypto.Data;
 import java.io.File;
 import java.util.*;
 
 public class MethodKNN {
 
-    // name,attack,base_egg_steps,capture_rate,defense,experience_growth,hp,sp_attack,sp_defense,type1,type2,speed,is_legendary
-    // Test,45,5800,240.0,50,800000,65,55,65,normal,flying,1.5,False
 
-    private static final Random random = new Random();
 
 
     public static String path = System.getProperty("user.dir") + File.separator + "res" + File.separator;
@@ -24,11 +20,9 @@ public class MethodKNN {
     public static double[] minData;
     public static double[] maxData;
 
-    public MethodKNN(ClassificationModel model) {
+   private MethodKNN() {
 
-        updateModel(model.getDatas());
-
-    }
+   }
 
 
     /**
@@ -37,13 +31,18 @@ public class MethodKNN {
      */
     public static void updateModel(List<LoadableData> datas) {
         if(datas.isEmpty()) return;
-        minData = new double[datas.get(0).getAttributes().length];
-        maxData = new double[datas.get(0).getAttributes().length];
-        amplitude = new double[datas.get(0).getAttributes().length];
+
+        int numAttributes = datas.get(0).getAttributes().length;
+        minData = new double[numAttributes];
+        maxData = new double[numAttributes];
+        amplitude = new double[numAttributes];
+
+
         for(LoadableData l :datas) {
-            for(int i = 0; i<l.getAttributes().length; i++) {
-                if(l.getAttributes()[i] < minData[i]) minData[i] = l.getAttributes()[i];
-                if(l.getAttributes()[i] > maxData[i]) maxData[i] = l.getAttributes()[i];
+            double[] attributes = l.getAttributes();
+            for(int i = 0; i<numAttributes; i++) {
+                if(attributes[i] < minData[i]) minData[i] = attributes[i];
+                if(attributes[i] > maxData[i]) maxData[i] = attributes[i];
             }
         }
 
@@ -82,45 +81,49 @@ public class MethodKNN {
 
         // On recupere les K voisions  de data.
         List<LoadableData> kVoisins = MethodKNN.kVoisins(datas, data, k, distance);
-        System.out.println("Neighbours: " + kVoisins);
-
-       // System.out.println("Neighbours found :  " + kVoisins);
 
         // On compte le nombre de représentation de chaque class parmis les voisins
+        // Et on récupere la plus présente
+
         Map<String, Integer> classOfNeighbours = new HashMap<>();
+        String currentClass = kVoisins.get(0).getClassification();
+
+
+
         for(LoadableData voisin : kVoisins) {
             int newValue =  ((classOfNeighbours.get(voisin.getClassification()) == null) ? 0 : classOfNeighbours.get(voisin.getClassification()) )+ 1;
             classOfNeighbours.put(voisin.getClassification(), newValue);
-        }
-        // On recupere la classe la plus repésenté parmis les voisins (au hasard si egalité entre 2)
-        String currentClass = kVoisins.get(0).getClassification();
-        for(String classification : classOfNeighbours.keySet()) {
-            if(classOfNeighbours.get(classification) > classOfNeighbours.get(currentClass)) {
-                currentClass = classification;
-            }else if (classOfNeighbours.get(classification).equals(classOfNeighbours.get(currentClass))) {
-                if(random.nextInt(2) == 1) currentClass = classification;
+            // si la classe est plus presente que la classe acutelemnt majoritaire, on change la classe majoritaire.
+            // Si il y'a egalité alors on garde la premiere trouvé
+            if(classOfNeighbours.get(voisin.getClassification()) > classOfNeighbours.get(currentClass)) {
+                currentClass = voisin.getClassification();
             }
+
         }
 
-      //  System.out.println("Estimate class = " + currentClass);
         return currentClass;
     }
 
 
     public static int bestK(List<LoadableData> datas, Distance distance) {
+        // On borne le K pour eviter de trouver un K trop grand
         int maxK = (int) (Math.sqrt(datas.size()));
         System.out.println("Max k: " + maxK);
 
-        Map<Integer, Double> results = new HashMap<>();
+        int betK = 1;
+
+        Map<Integer, Double> results = new LinkedHashMap<>();
         // Pour chaque valeur impaire possible de K, on calcul la robustesse (le taux de reussite) de l'algorithme.
         for(int i =1; i<maxK; i = i +2) {
             results.put(i, robustesse(datas, i, distance, 0.2));
+            // On modifie le meilleur k si le taux est superieur au K precedent
+            // Si egalité, on garde le premier trouvé
+            if(results.get(i) > results.get(betK)) betK = i;
         }
 
-        System.out.println(results);
+        System.out.println("Results: " + results);
 
-        // On return le K ayant le meilleur taux de reussite ( ou l'un des K si egalités).
-        return Collections.max(results.entrySet(), Map.Entry.comparingByValue()).getKey();
+        return betK;
 
     }
 
@@ -131,7 +134,7 @@ public class MethodKNN {
 
         double taux = 0;
 
-        for(int i = 0; i<(int)1/testPart; i++) {
+        for(int i = 0; i<1/testPart; i++) {
 
             int totalFind = 0;
             int totalTry = 0;
@@ -148,12 +151,8 @@ public class MethodKNN {
             // On estime la classe chaque donnée de test, et on verifie si l'algo a bon
             for(LoadableData l : testData) {
                 totalTry++;
-                System.out.println(l);
                 String baseClass = l.getClassification();
-                //  System.out.println("Base class : " + baseClass);
-                //  System.out.println("Base data: " + l);
                 if(baseClass.equals(MethodKNN.estimateClass(trainingData,l, k, distance))) totalFind++;
-
             }
 
 
@@ -180,22 +179,24 @@ public class MethodKNN {
         System.out.println();
 
         List<LoadableData> datas = ClassificationModel.getClassificationModel().getDatas();
+        // On mélange les données pour tester sur differentes variétes car le fichier de base est trié.
+        Collections.shuffle(datas);
 
         for(int i = 0; i<1; i++) {
             System.out.println("Search best k");
 
             // On cherche le meilleure K
-            int bestK = MethodKNN.bestK(datas, new DistanceEuclidienneNormalisee());
+            int bestK = MethodKNN.bestK(datas, new DistanceManhattanNormalisee());
             System.out.println(bestK);
 
             // Puis on clacul la robustesse avec le K trouvé
-            System.out.println(MethodKNN.robustesse( datas, bestK, new DistanceEuclidienneNormalisee(), 0.2));
+            System.out.println(MethodKNN.robustesse( datas, bestK, new DistanceManhattanNormalisee(), 0.2));
 
         }
 
 
 
-        }
+    }
 
 
 
-- 
GitLab