Skip to content
Snippets Groups Projects
Commit a8c42238 authored by Matias Mennecart's avatar Matias Mennecart
Browse files

Premiere version du rapport d'analyse K-NN

parent 480a1555
Branches
Tags
No related merge requests found
# 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
...@@ -7,16 +7,12 @@ import fr.univlille.sae.classification.model.DataType; ...@@ -7,16 +7,12 @@ import fr.univlille.sae.classification.model.DataType;
import fr.univlille.sae.classification.model.LoadableData; import fr.univlille.sae.classification.model.LoadableData;
import javax.xml.crypto.Data;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
public class MethodKNN { 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; public static String path = System.getProperty("user.dir") + File.separator + "res" + File.separator;
...@@ -24,9 +20,7 @@ public class MethodKNN { ...@@ -24,9 +20,7 @@ public class MethodKNN {
public static double[] minData; public static double[] minData;
public static double[] maxData; public static double[] maxData;
public MethodKNN(ClassificationModel model) { private MethodKNN() {
updateModel(model.getDatas());
} }
...@@ -37,13 +31,18 @@ public class MethodKNN { ...@@ -37,13 +31,18 @@ public class MethodKNN {
*/ */
public static void updateModel(List<LoadableData> datas) { public static void updateModel(List<LoadableData> datas) {
if(datas.isEmpty()) return; if(datas.isEmpty()) return;
minData = new double[datas.get(0).getAttributes().length];
maxData = new double[datas.get(0).getAttributes().length]; int numAttributes = datas.get(0).getAttributes().length;
amplitude = new double[datas.get(0).getAttributes().length]; minData = new double[numAttributes];
maxData = new double[numAttributes];
amplitude = new double[numAttributes];
for(LoadableData l :datas) { for(LoadableData l :datas) {
for(int i = 0; i<l.getAttributes().length; i++) { double[] attributes = l.getAttributes();
if(l.getAttributes()[i] < minData[i]) minData[i] = l.getAttributes()[i]; for(int i = 0; i<numAttributes; i++) {
if(l.getAttributes()[i] > maxData[i]) maxData[i] = l.getAttributes()[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 { ...@@ -82,45 +81,49 @@ public class MethodKNN {
// On recupere les K voisions de data. // On recupere les K voisions de data.
List<LoadableData> kVoisins = MethodKNN.kVoisins(datas, data, k, distance); 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 // 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<>(); Map<String, Integer> classOfNeighbours = new HashMap<>();
String currentClass = kVoisins.get(0).getClassification();
for(LoadableData voisin : kVoisins) { for(LoadableData voisin : kVoisins) {
int newValue = ((classOfNeighbours.get(voisin.getClassification()) == null) ? 0 : classOfNeighbours.get(voisin.getClassification()) )+ 1; int newValue = ((classOfNeighbours.get(voisin.getClassification()) == null) ? 0 : classOfNeighbours.get(voisin.getClassification()) )+ 1;
classOfNeighbours.put(voisin.getClassification(), newValue); classOfNeighbours.put(voisin.getClassification(), newValue);
// 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();
} }
// 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;
}
} }
// System.out.println("Estimate class = " + currentClass);
return currentClass; return currentClass;
} }
public static int bestK(List<LoadableData> datas, Distance distance) { 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())); int maxK = (int) (Math.sqrt(datas.size()));
System.out.println("Max k: " + maxK); 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. // 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) { for(int i =1; i<maxK; i = i +2) {
results.put(i, robustesse(datas, i, distance, 0.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 betK;
return Collections.max(results.entrySet(), Map.Entry.comparingByValue()).getKey();
} }
...@@ -131,7 +134,7 @@ public class MethodKNN { ...@@ -131,7 +134,7 @@ public class MethodKNN {
double taux = 0; double taux = 0;
for(int i = 0; i<(int)1/testPart; i++) { for(int i = 0; i<1/testPart; i++) {
int totalFind = 0; int totalFind = 0;
int totalTry = 0; int totalTry = 0;
...@@ -148,12 +151,8 @@ public class MethodKNN { ...@@ -148,12 +151,8 @@ public class MethodKNN {
// On estime la classe chaque donnée de test, et on verifie si l'algo a bon // On estime la classe chaque donnée de test, et on verifie si l'algo a bon
for(LoadableData l : testData) { for(LoadableData l : testData) {
totalTry++; totalTry++;
System.out.println(l);
String baseClass = l.getClassification(); 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++; if(baseClass.equals(MethodKNN.estimateClass(trainingData,l, k, distance))) totalFind++;
} }
...@@ -180,16 +179,18 @@ public class MethodKNN { ...@@ -180,16 +179,18 @@ public class MethodKNN {
System.out.println(); System.out.println();
List<LoadableData> datas = ClassificationModel.getClassificationModel().getDatas(); 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++) { for(int i = 0; i<1; i++) {
System.out.println("Search best k"); System.out.println("Search best k");
// On cherche le meilleure K // On cherche le meilleure K
int bestK = MethodKNN.bestK(datas, new DistanceEuclidienneNormalisee()); int bestK = MethodKNN.bestK(datas, new DistanceManhattanNormalisee());
System.out.println(bestK); System.out.println(bestK);
// Puis on clacul la robustesse avec le K trouvé // 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));
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment