diff --git a/Code/BayesNaif.py b/Code/BayesNaif.py index 4995281..e3a00aa 100644 --- a/Code/BayesNaif.py +++ b/Code/BayesNaif.py @@ -1,159 +1,162 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -* -""" -Vous allez definir une classe pour chaque algorithme que vous allez développer, -votre classe doit contenit au moins les 3 methodes definies ici bas, - * train : pour entrainer le modèle sur l'ensemble d'entrainement - * predict : pour prédire la classe d'un exemple donné - * test : pour tester sur l'ensemble de test -vous pouvez rajouter d'autres méthodes qui peuvent vous etre utiles, mais moi -je vais avoir besoin de tester les méthodes test, predict et test de votre code. -""" - -import numpy as np -import math -import metrics - - -# le nom de votre classe -# BayesNaif pour le modèle bayesien naif -# Knn pour le modèle des k plus proches voisins - -def gaussian_pdf(x, param): - """ - Fonction de densité de probabilité de la distribution gaussienne - """ - mu, sigma = param - return math.exp(-(float(x)-float(mu))**2/(2*float(sigma)**2)) / (float(sigma)*math.sqrt(2*math.pi)) - -class BayesNaif: #nom de la class à changer - - - - - def __init__(self, gaussian, **kwargs): - """ - c'est un Initializer. - Vous pouvez passer d'autre paramètres au besoin, - c'est à vous d'utiliser vos propres notations - """ - - self.gaussian=gaussian - - - def train(self, train, train_labels):#TODO A ENLEVER #vous pouvez rajouter d'autres attribus au besoin - """ - c'est la méthode qui va entrainer votre modèle, - train est une matrice de type Numpy et de taille nxm, avec - n : le nombre d'exemple d'entrainement dans le dataset - m : le mobre d'attribus (le nombre de caractéristiques) - - train_labels : est une matrice numpy de taille nx1 - - vous pouvez rajouter d'autres arguments, il suffit juste de - les expliquer en commentaire - - - - ------------ - Après avoir fait l'entrainement, faites maintenant le test sur - les données d'entrainement - IMPORTANT : - Vous devez afficher ici avec la commande print() de python, - - la matrice de confision (confusion matrix) - - l'accuracy - - la précision (precision) - - le rappel (recall) - - Bien entendu ces tests doivent etre faits sur les données d'entrainement - nous allons faire d'autres tests sur les données de test dans la méthode test() - """ - - # Distribution a priori des étiquettes P(C) - - n,m = train.shape - - self.unique_labels = np.unique(train_labels) - - self.labels_prob = {} - for i in self.unique_labels: - self.labels_prob.update({i:sum(train_labels == i) / n}) - - # Distribution des probabilités conditionnelles P(F1,F2,...|C) - - self.features_cond = {} - self.unique_values_cond = {} - - # on itère sur les labels, les colonnes et les valeurs uniques des colonnes (si discret) - - for l in self.unique_labels: - subset = train[np.where(train_labels==self.unique_labels[l])[0]] - n_subset, m_subset = subset.shape - for c in range(m_subset): - current_column = subset[:,c] - if (self.gaussian): - self.features_cond.update({(l,c):(np.mean(current_column),np.std(current_column))}) - else: - unique_values = np.unique(current_column) - self.unique_values_cond.update({(l,c):unique_values}) - for v in unique_values: - self.features_cond.update({(l,c,v):sum(current_column==v)/n_subset}) - - - - def predict(self, exemple, label): - """ - Prédire la classe d'un exemple donné en entrée - exemple est de taille 1xm - - si la valeur retournée est la meme que la veleur dans label - alors l'exemple est bien classifié, si non c'est une missclassification - - """ - - nb_unique_labels = len(self.unique_labels) - - # P(X|C) - p_xc = {} - for l in range(nb_unique_labels): - if (self.gaussian): - p_xc.update({l:np.product([gaussian_pdf(exemple[c],self.features_cond.get((self.unique_labels[l],c))) for c in range(len(exemple))])}) - else: - p_xc.update({l:np.product([self.features_cond.get((self.unique_labels[l],c,exemple[c])) for c in range(len(exemple))])}) - - prob = [self.labels_prob.get(i)*p_xc.get(i) for i in self.unique_labels] - prob = prob/sum(prob) - - return (self.unique_labels[np.where(prob==max(prob))[0]].tolist()[0],label) - - def test(self, test, test_labels, verbose=True): - """ - c'est la méthode qui va tester votre modèle sur les données de test - l'argument test est une matrice de type Numpy et de taille nxm, avec - n : le nombre d'exemple de test dans le dataset - m : le mobre d'attribus (le nombre de caractéristiques) - - test_labels : est une matrice numpy de taille nx1 - - vous pouvez rajouter d'autres arguments, il suffit juste de - les expliquer en commentaire - - Faites le test sur les données de test, et afficher : - - la matrice de confision (confusion matrix) - - l'accuracy - - la précision (precision) - - le rappel (recall) - - Bien entendu ces tests doivent etre faits sur les données de test seulement - - """ - prediction_test = [self.predict(test[i],test_labels[i])[0] for i in range(len(test_labels))] - cm = metrics.confusion_matrix(test_labels,prediction_test) - accuracy, precision, recall = metrics.prediction_metrics(cm,test_labels,prediction_test) - if (verbose): - metrics.print_prediction_metrics(cm,accuracy,precision,recall) - - return cm,accuracy,precision,recall - - # Vous pouvez rajouter d'autres méthodes et fonctions, - # il suffit juste de les commenter. +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +""" +Vous allez definir une classe pour chaque algorithme que vous allez développer, +votre classe doit contenit au moins les 3 methodes definies ici bas, + * train : pour entrainer le modèle sur l'ensemble d'entrainement + * predict : pour prédire la classe d'un exemple donné + * test : pour tester sur l'ensemble de test +vous pouvez rajouter d'autres méthodes qui peuvent vous etre utiles, mais moi +je vais avoir besoin de tester les méthodes test, predict et test de votre code. +""" + +import numpy as np +import math +import metrics +import time + + +# le nom de votre classe +# BayesNaif pour le modèle bayesien naif +# Knn pour le modèle des k plus proches voisins + +def gaussian_pdf(x, param): + """ + Fonction de densité de probabilité de la distribution gaussienne + """ + mu, sigma = param + return math.exp(-(float(x)-float(mu))**2/(2*float(sigma)**2)) / (float(sigma)*math.sqrt(2*math.pi)) + +class BayesNaif: + + def __init__(self, gaussian, **kwargs): + """ + c'est un Initializer. + Vous pouvez passer d'autre paramètres au besoin, + c'est à vous d'utiliser vos propres notations + gaussian: variable booléenne qui définit si on utilise la variante gaussienne de l'algorithme Bayes Naif + """ + self.gaussian=gaussian + + def train(self, train, train_labels, verbose=True): + """ + c'est la méthode qui va entrainer votre modèle, + train est une matrice de type Numpy et de taille nxm, avec + n : le nombre d'exemple d'entrainement dans le dataset + m : le mobre d'attribus (le nombre de caractéristiques) + + train_labels : est une matrice numpy de taille nx1 + + vous pouvez rajouter d'autres arguments, il suffit juste de + les expliquer en commentaire + + + + ------------ + Après avoir fait l'entrainement, faites maintenant le test sur + les données d'entrainement + IMPORTANT : + Vous devez afficher ici avec la commande print() de python, + - la matrice de confision (confusion matrix) + - l'accuracy + - la précision (precision) + - le rappel (recall) + + Bien entendu ces tests doivent etre faits sur les données d'entrainement + nous allons faire d'autres tests sur les données de test dans la méthode test() + """ + start_time = time.time() + # Distribution a priori des classes de l'étiquette P(C) + + n,m = train.shape + + self.unique_labels = np.unique(train_labels) + + self.labels_prob = {} + for c in self.unique_labels: + self.labels_prob.update({c:sum(train_labels == c) / n}) + + # Distribution des probabilités conditionnelles P(F1,F2,...|C) + + self.features_cond = {} + self.unique_values_cond = {} + + # on itère sur les labels, les colonnes et les valeurs uniques des colonnes (si discret) + + for c in self.unique_labels: + subset = train[np.where(train_labels==self.unique_labels[c])[0]] + n_subset, m_subset = subset.shape + for f in range(m_subset): + current_column = subset[:,f] + if (self.gaussian): + self.features_cond.update({(c,f):(np.mean(current_column),np.std(current_column))}) + else: + unique_values = np.unique(current_column) + self.unique_values_cond.update({(c,f):unique_values}) + for v in unique_values: + self.features_cond.update({(c,f,v):sum(current_column==v)/n_subset}) + cm,accuracy,precision,recall,compute_time = self.test(train, train_labels, verbose=False) + compute_time = time.time() - start_time + if (verbose): + metrics.print_prediction_metrics(cm,accuracy,precision,recall,compute_time) + return cm,accuracy,precision,recall,compute_time + + + def predict(self, exemple, label): + """ + Prédire la classe d'un exemple donné en entrée + exemple est de taille 1xm + + si la valeur retournée est la meme que la veleur dans label + alors l'exemple est bien classifié, si non c'est une missclassification + + """ + + nb_unique_labels = len(self.unique_labels) + + # P(X|C) + p_xc = {} + for l in range(nb_unique_labels): + if (self.gaussian): + p_xc.update({l:np.product([gaussian_pdf(exemple[f],self.features_cond.get((self.unique_labels[l],f))) or 1 for f in range(len(exemple))])}) + else: + p_xc.update({l:np.product([self.features_cond.get((self.unique_labels[l],f,exemple[f])) or 1 for f in range(len(exemple))])}) + + prob = [self.labels_prob.get(i)*p_xc.get(i) for i in self.unique_labels] + prob = prob/sum(prob) + + return (self.unique_labels[np.where(prob==max(prob))[0]].tolist()[0],label) + + def test(self, test, test_labels, verbose=True): + """ + c'est la méthode qui va tester votre modèle sur les données de test + l'argument test est une matrice de type Numpy et de taille nxm, avec + n : le nombre d'exemple de test dans le dataset + m : le mobre d'attribus (le nombre de caractéristiques) + + test_labels : est une matrice numpy de taille nx1 + + vous pouvez rajouter d'autres arguments, il suffit juste de + les expliquer en commentaire + + Faites le test sur les données de test, et afficher : + - la matrice de confision (confusion matrix) + - l'accuracy + - la précision (precision) + - le rappel (recall) + + Bien entendu ces tests doivent etre faits sur les données de test seulement + + """ + start_time = time.time() + prediction_test = [self.predict(test[i],test_labels[i])[0] for i in range(len(test_labels))] + cm = metrics.confusion_matrix(test_labels,prediction_test) + accuracy, precision, recall = metrics.prediction_metrics(cm,test_labels,prediction_test) + compute_time = time.time() - start_time + if (verbose): + metrics.print_prediction_metrics(cm,accuracy,precision,recall,compute_time) + + return cm,accuracy,precision,recall,compute_time + + # Vous pouvez rajouter d'autres méthodes et fonctions, + # il suffit juste de les commenter. diff --git a/Code/Knn.py b/Code/Knn.py index 165f2d1..0b9b610 100644 --- a/Code/Knn.py +++ b/Code/Knn.py @@ -12,6 +12,7 @@ je vais avoir besoin de tester les méthodes test, predict et test de votre code import numpy as np import metrics +import time def minkowski_distance(x,y,p_value): return pow(sum(pow(abs(a-b),p_value) for a,b in zip(x, y)),1/p_value) @@ -26,13 +27,14 @@ def mode(a): class Knn: #nom de la class à changer - def __init__(self, k=5, **kwargs): + def __init__(self, k=5, m=2, **kwargs): """ c'est un Initializer. Vous pouvez passer d'autre paramètres au besoin, c'est à vous d'utiliser vos propres notations """ self.k=k + self.minkowski_p=m def set_best_k(self, train, train_labels, nb_split, k_potentiel): ## Création des échantillons de validation croisée @@ -50,7 +52,7 @@ class Knn: #nom de la class à changer for i in range(nb_split): self.k=k_potentiel[k_index] self.train(train[train_index[i]], train_labels[train_index[i]], verbose=False) - cm,accuracy,precision,recall = self.test(train[validation_index[i]], train_labels[validation_index[i]], verbose=False) + cm,accuracy,precision,recall,compute_time = self.test(train[validation_index[i]], train_labels[validation_index[i]], verbose=False) accuracy_cv[k_index][i] = accuracy ## Calcul de la moyenne @@ -62,7 +64,7 @@ class Knn: #nom de la class à changer ## Assignation self.k=best_k - print("Le meilleur k est: "+str(best_k)) + print("Le meilleur k est: "+str(best_k)+"\nCalculé en "+str(compute_time)+"s") ## Retourner la valeur return best_k @@ -97,6 +99,8 @@ class Knn: #nom de la class à changer nous allons faire d'autres tests sur les données de test dans la méthode test() """ + start_time = time.time() + # on fait seulement utiliser les données du jeu d'entrainement comme paramètre d'un modèle Knn self.train_set=train self.train_labels=train_labels @@ -104,8 +108,6 @@ class Knn: #nom de la class à changer n,m = train.shape nn=np.empty((n,self.k,2)) - self.minkowski_p=m - # On trouve les k plus proches voisins et leur distance pour chacunes des observations du training set # On enlève la valeur testée de la liste des points pour lesquels on mesure la distance car on sait qu'elle vaut 0. # On veut tester sur les autres points seulement @@ -130,10 +132,11 @@ class Knn: #nom de la class à changer # on construit la matrice de confusion cm = metrics.confusion_matrix(train_labels,nn_mode_label) accuracy, precision, recall = metrics.prediction_metrics(cm,train_labels,nn_mode_label) + compute_time = time.time() - start_time if (verbose): - metrics.print_prediction_metrics(cm,accuracy,precision,recall) + metrics.print_prediction_metrics(cm,accuracy,precision,recall,compute_time) - return cm,accuracy,precision,recall + return cm,accuracy,precision,recall,compute_time def predict(self, exemple, label, verbose=True): @@ -187,7 +190,7 @@ class Knn: #nom de la class à changer Bien entendu ces tests doivent etre faits sur les données de test seulement """ - + start_time = time.time() n,m = test.shape n_ex,m_ex = self.train_set.shape nn=np.empty((n,self.k,2)) @@ -209,10 +212,11 @@ class Knn: #nom de la class à changer # on construit la matrice de confusion cm = metrics.confusion_matrix(test_labels,nn_mode_label) accuracy, precision, recall = metrics.prediction_metrics(cm,test_labels,nn_mode_label) + compute_time = time.time() - start_time if (verbose): - metrics.print_prediction_metrics(cm,accuracy,precision,recall) + metrics.print_prediction_metrics(cm,accuracy,precision,recall,compute_time) - return cm,accuracy,precision,recall + return cm,accuracy,precision,recall,compute_time diff --git a/Code/Rapport-Partiel.ipynb b/Code/Rapport-Partiel.ipynb index 855d8ef..2a57cde 100644 --- a/Code/Rapport-Partiel.ipynb +++ b/Code/Rapport-Partiel.ipynb @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -39,9 +39,9 @@ "import math\n", "# Librairies développées par l'étudiant\n", "import load_datasets as ld\n", + "import metrics\n", "import BayesNaif\n", - "import Knn\n", - "import metrics" + "import Knn" ] }, { @@ -50,16 +50,31 @@ "source": [ "## Jeux de données\n", "\n", - "Ce projet utilise 3 jeux de données: [Iris](https://archive.ics.uci.edu/ml/datasets/Iris), [MONKS](https://archive.ics.uci.edu/ml/datasets/MONK's+Problems) et [Congressional Voting Records Dataset](https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records)" + "Ce projet utilise 3 jeux de données: [Iris](https://archive.ics.uci.edu/ml/datasets/Iris), [MONKS](https://archive.ics.uci.edu/ml/datasets/MONK's+Problems) et [Congressional Voting Records Dataset](https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records). Pour les jeux qui ne sont pas déjà divisés en entrainement et test, on utilisera un ratio de 70% pour l'entrainement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train1, train_labels1, test1, test_labels1 = ld.load_iris_dataset(train_ratio = 0.7)\n", + "train2, train_labels2, test2, test_labels2 = ld.load_monks_dataset(1)\n", + "train3, train_labels3, test3, test_labels3 = ld.load_monks_dataset(2)\n", + "train4, train_labels4, test4, test_labels4 = ld.load_monks_dataset(3)\n", + "train5, train_labels5, test5, test_labels5 = ld.load_congressional_dataset(0.7)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "toc-hr-collapsed": false + }, "source": [ "# Algorithme des k plus proches voisins (kNN)\n", "\n", - "Dans cette section, on présente les résultats obtenus pour l'algorithme des k plus proches voisins. Ce n'est pas un algorithme d'apprentissage à proprement parler, car il n'y a aucun modèle d'entrainé. On prédit la classe d'un nouvel élément en recherchant les k plus proches voisins selon une métrique choisie. Dans notre cas, nous utiliserons la distance de Minkowski, qui est une généralisation de la distance euclidienne et de la distance de Manhattan à l'aide d'un paramètre p. \n", + "Dans cette section, on présente les résultats obtenus pour l'algorithme des k plus proches voisins. Ce n'est pas un algorithme d'apprentissage à proprement parler, car il n'y a aucun modèle d'entrainé. On prédit la classe d'un nouvel élément en recherchant les $k$ plus proches voisins selon une métrique choisie. Dans notre cas, nous utiliserons la distance de Minkowski, qui est une généralisation de la distance euclidienne et de la distance de Manhattan à l'aide d'un paramètre $p$. Pour nos besoins, on le fixe à $p=2$, soit la distance euclidienne.\n", "\n", "$${\\displaystyle D\\left(X,Y\\right)=\\left(\\sum _{i=1}^{N}|o_{i}-s_{i}|^{p}\\right)^{1/p}}$$\n", "\n", @@ -67,13 +82,20 @@ "\n", "## Pseudocode\n", "\n", + "- Initialisation\n", + "\n", + "```\n", + "k = 5\n", + "minkowski_p = 2\n", + "```\n", + "\n", "- Entrainement\n", "\n", "```\n", "Copier le jeu d'entrainement et les étiquettes d'entrainement dans la mémoire du modèle\n", "```\n", "\n", - "- Prédiction Knn\n", + "- Prédiction\n", "```\n", "Pour une observation x_i:\n", " Entrainement <- Vrai si on fait le premier entrainement\n", @@ -138,15 +160,6 @@ "On utilise 70% des données pour le jeu d'entrainement et 30% des données pour le jeu de test" ] }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "train1, train_labels1, test1, test_labels1 = ld.load_iris_dataset(train_ratio = 0.7)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -156,25 +169,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/francois/gitea/ift7025-projet/Code/metrics.py:40: RuntimeWarning: invalid value encountered in double_scalars\n", - " precision.append(cm[label_num,label_num] / sum(cm[:,label_num]))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le meilleur k est: 9\n" - ] - } - ], + "outputs": [], "source": [ "findbest_Knn = Knn.Knn()\n", "meilleur_k = findbest_Knn.set_best_k(train1, train_labels1, nb_split=5, k_potentiel=range(2,12))" @@ -189,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -205,29 +202,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[35. 0. 0.]\n", - " [ 0. 32. 1.]\n", - " [ 0. 3. 34.]]\n", - "\n", - "Accuracy:\n", - "0.9619047619047619\n", - "\n", - "Precision:\n", - "[1.0, 0.9142857142857143, 0.9714285714285714]\n", - "\n", - "Recall\n", - "[1.0, 0.9696969696969697, 0.918918918918919]\n" - ] - } - ], + "outputs": [], "source": [ "training_iris_knn = myKnn.train(train1, train_labels1)" ] @@ -241,29 +218,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[15. 0. 0.]\n", - " [ 0. 16. 1.]\n", - " [ 0. 1. 12.]]\n", - "\n", - "Accuracy:\n", - "0.9555555555555556\n", - "\n", - "Precision:\n", - "[1.0, 0.9411764705882353, 0.9230769230769231]\n", - "\n", - "Recall\n", - "[1.0, 0.9411764705882353, 0.9230769230769231]\n" - ] - } - ], + "outputs": [], "source": [ "test_iris_knn = myKnn.test(test1, test_labels1)" ] @@ -275,20 +232,11 @@ "## Monks Dataset" ] }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "train2, train_labels2, test2, test_labels2 = ld.load_monks_dataset(1)\n", - "train3, train_labels3, test3, test_labels3 = ld.load_monks_dataset(2)\n", - "train4, train_labels4, test4, test_labels4 = ld.load_monks_dataset(3)" - ] - }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "toc-hr-collapsed": false + }, "source": [ "### Premier sous-ensemble" ] @@ -302,17 +250,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le meilleur k est: 4\n" - ] - } - ], + "outputs": [], "source": [ "findbest_Knn = Knn.Knn()\n", "meilleur_k = findbest_Knn.set_best_k(train2, train_labels2, nb_split=5, k_potentiel=range(2,12))" @@ -320,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -336,28 +276,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[32. 30.]\n", - " [16. 46.]]\n", - "\n", - "Accuracy:\n", - "0.6290322580645161\n", - "\n", - "Precision:\n", - "[0.6666666666666666, 0.6052631578947368]\n", - "\n", - "Recall\n", - "[0.5161290322580645, 0.7419354838709677]\n" - ] - } - ], + "outputs": [], "source": [ "training_monks1_knn = myKnn2.train(train2, train_labels2)" ] @@ -371,28 +292,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[129. 87.]\n", - " [ 38. 178.]]\n", - "\n", - "Accuracy:\n", - "0.7106481481481481\n", - "\n", - "Precision:\n", - "[0.7724550898203593, 0.6716981132075471]\n", - "\n", - "Recall\n", - "[0.5972222222222222, 0.8240740740740741]\n" - ] - } - ], + "outputs": [], "source": [ "test_monks1_knn = myKnn2.test(test2, test_labels2)" ] @@ -413,17 +315,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le meilleur k est: 5\n" - ] - } - ], + "outputs": [], "source": [ "findbest_Knn = Knn.Knn()\n", "meilleur_k = findbest_Knn.set_best_k(train3, train_labels3, nb_split=5, k_potentiel=range(2,12))" @@ -431,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -447,28 +341,9 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[104. 1.]\n", - " [ 63. 1.]]\n", - "\n", - "Accuracy:\n", - "0.621301775147929\n", - "\n", - "Precision:\n", - "[0.6227544910179641, 0.5]\n", - "\n", - "Recall\n", - "[0.9904761904761905, 0.015625]\n" - ] - } - ], + "outputs": [], "source": [ "training_monks2_knn = myKnn3.train(train3, train_labels3)" ] @@ -482,28 +357,9 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[286. 4.]\n", - " [133. 9.]]\n", - "\n", - "Accuracy:\n", - "0.6828703703703703\n", - "\n", - "Precision:\n", - "[0.6825775656324582, 0.6923076923076923]\n", - "\n", - "Recall\n", - "[0.9862068965517241, 0.06338028169014084]\n" - ] - } - ], + "outputs": [], "source": [ "test_monks2_knn = myKnn3.test(test3, test_labels3)" ] @@ -524,17 +380,9 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le meilleur k est: 5\n" - ] - } - ], + "outputs": [], "source": [ "findbest_Knn = Knn.Knn()\n", "meilleur_k = findbest_Knn.set_best_k(train4, train_labels4, nb_split=5, k_potentiel=range(2,12))" @@ -542,7 +390,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -558,28 +406,9 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[27. 35.]\n", - " [ 1. 59.]]\n", - "\n", - "Accuracy:\n", - "0.7049180327868853\n", - "\n", - "Precision:\n", - "[0.9642857142857143, 0.6276595744680851]\n", - "\n", - "Recall\n", - "[0.43548387096774194, 0.9833333333333333]\n" - ] - } - ], + "outputs": [], "source": [ "training_monks3_knn = myKnn4.train(train4, train_labels4)" ] @@ -593,28 +422,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[ 99. 105.]\n", - " [ 8. 220.]]\n", - "\n", - "Accuracy:\n", - "0.7384259259259259\n", - "\n", - "Precision:\n", - "[0.9252336448598131, 0.676923076923077]\n", - "\n", - "Recall\n", - "[0.4852941176470588, 0.9649122807017544]\n" - ] - } - ], + "outputs": [], "source": [ "test_monks3_knn = myKnn4.test(test4, test_labels4)" ] @@ -628,12 +438,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "train5, train_labels5, test5, test_labels5 = ld.load_congressional_dataset(0.7)" - ] + "source": [] }, { "cell_type": "markdown", @@ -644,17 +452,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le meilleur k est: 9\n" - ] - } - ], + "outputs": [], "source": [ "findbest_Knn = Knn.Knn()\n", "meilleur_k = findbest_Knn.set_best_k(train5, train_labels5, nb_split=5, k_potentiel=range(2,12))" @@ -662,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -678,28 +478,9 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[ 76. 33.]\n", - " [ 15. 180.]]\n", - "\n", - "Accuracy:\n", - "0.8421052631578947\n", - "\n", - "Precision:\n", - "[0.8351648351648352, 0.8450704225352113]\n", - "\n", - "Recall\n", - "[0.6972477064220184, 0.9230769230769231]\n" - ] - } - ], + "outputs": [], "source": [ "training_congres_knn = myKnn5.train(train5, train_labels5)" ] @@ -713,28 +494,9 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Matrice de confusion:\n", - "[[42. 17.]\n", - " [ 6. 66.]]\n", - "\n", - "Accuracy:\n", - "0.8244274809160306\n", - "\n", - "Precision:\n", - "[0.875, 0.7951807228915663]\n", - "\n", - "Recall\n", - "[0.711864406779661, 0.9166666666666666]\n" - ] - } - ], + "outputs": [], "source": [ "test_congres_knn = myKnn5.test(test5, test_labels5)" ] @@ -743,7 +505,447 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Algorithme Naive Bayes" + "# Algorithme Bayes Naïf\n", + "\n", + "Cet algorithme est basé sur la règle de Bayes:\n", + "\n", + "$$\n", + "P(C|X) = \\frac{P(X|C)P(C)}{P(X)}\n", + "$$\n", + "\n", + "Dans notre cas, le dénominateur est une constante et ne nous intéresse pas. Nous le représentons par la lettre $\\alpha$\n", + "\n", + "En posant l'hypothèse de l'indépendance conditionnelle entre les caractéristiques $X_1,\\ldots,X_m$, on peut réécrire la règle comme suit:\n", + "\n", + "$$\n", + "P(C|X) = \\alpha P(X) \\prod_{f=1}^m P(X_f|C)\n", + "$$\n", + "\n", + "Dans le cas de variables descriptives continues, la probabilité $P(X_f|C)$ prend plutôt la forme d'une distribution de probabilités. On utilisera alors la fonction de densité de probabilités de la loi gaussienne, avec la moyenne et l'écart-type estimés à partir des données.\n", + "\n", + "## Pseudocode\n", + "\n", + "### Initialisation\n", + "\n", + "Il n'y a aucun paramètre à initialiser\n", + "\n", + "### Entrainement\n", + "\n", + "L'entrainement se fait en deux parties. On calcule d'abord les probabilités à priori des classes de l'étiquette $\\mathbf{P}(C)$\n", + "\n", + "```\n", + "T = copie jeu d'entrainement\n", + "L = copie des étiquettes d'entrainement\n", + "Pour c dans unique(L):\n", + " Compteur=0\n", + " Pour l dans L:\n", + " Si l=c:\n", + " Compteur+=1\n", + " P(C=c) = Compteur/N\n", + "```\n", + "\n", + "On calcule ensuite les probabilités conditionnelles des caractéristiques sachant la classe de l'étiquette $\\mathbf{P}(F_1,\\ldots,F_m|C)$\n", + "\n", + "```\n", + "F <- Liste des caractéristiques\n", + "Pour c dans unique(L):\n", + " TS <- filtrer(T si L=c)\n", + " NS <- taille(TS)\n", + " Pour f dans F:\n", + " Si Gaussien:\n", + " Parametres(C=c,F=f) = (moyenne(TS[f]), écart-type(TS[f]))\n", + " Sinon:\n", + " Pour v dans unique(TS[f]):\n", + " Compteur = 0\n", + " Pour l dans TS[f]:\n", + " Si l=v:\n", + " Compteur+=1\n", + " P(C=c,F=f,V=v) = Compteur/NS\n", + "```\n", + "\n", + "Pour la prédiction, on ne calcule pas le facteur de régularisation $\\alpha$ pour amener les probabilité à 1, car ce qui nous intéresse, c'est une relation d'ordre et celle-ci est inchangée si on la divise par un nombre positif.\n", + "\n", + "### Prédiction\n", + "\n", + "```\n", + "Pour c dans unique(L):\n", + " Produit=0\n", + " Pour f dans F:\n", + " Pour l dans TS[f]:\n", + " Si Gaussien:\n", + " Si Parametres(C=c,F=f) existe:\n", + " Produit *= Densité_Gaussien(l,Parametres(C=c,F=f))\n", + " Sinon\n", + " Si P(C=c,F=f,V=l) existe:\n", + " Produit *= P(C=c,F=f,V=l)\n", + " P(X|c) = Produit\n", + "Retourner c où P(X|c) = Max(P(X|C))\n", + "```\n", + "\n", + "### Test\n", + "\n", + "```\n", + "Pour chaque observation x_i, i=1,N:\n", + " S_i <- Prédiction(x_i)\n", + "M <- matrice de confusion avec les étiquettes d'entrainement et les étiquettes S\n", + "Calculer les métriques depuis M\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Iris Dataset\n", + "\n", + "Comme ce jeu de données présente des caractéristiques continues, on utilisera la variante gaussienne du Bayes Naif" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BN = BayesNaif.BayesNaif(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon d'entrainement" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_iris_nb = BN.train(train1, train_labels1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_iris_nb = BN.test(test1,test_labels1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Monks Dataset\n", + "\n", + "Comme ce jeu de données présente des caractéristiques discrètes, on n'utilise pas la variante gaussienne du Bayes Naif" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Premier sous-ensemble" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BN2 = BayesNaif.BayesNaif(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_monks1_nb = BN2.train(train2, train_labels2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_monks1_nb = BN2.test(test2,test_labels2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Second sous-ensemble" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BN3 = BayesNaif.BayesNaif(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_monks2_nb = BN3.train(train3, train_labels3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_monks2_nb = BN3.test(test3,test_labels3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Troisième sous-ensemble" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BN4 = BayesNaif.BayesNaif(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_monks3_nb = BN4.train(train4, train_labels4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_monks3_nb = BN4.test(test4,test_labels4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Congressional Voting Records Dataset\n", + "\n", + "Comme ce jeu de données présente des caractéristiques discrètes, on n'utilise pas la variante gaussienne du Bayes Naif" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BN5 = BayesNaif.BayesNaif(False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_congres_nb = BN5.train(train5, train_labels5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Mesures de performance sur l'échantillon de test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_congres_nb = BN5.test(test5, test_labels5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conclusion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tableau sommaire" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tous_resultats = [[\"Iris\",test_iris_knn,test_iris_nb],\n", + " [\"MONKS1\",test_monks1_knn,test_monks1_nb],\n", + " [\"MONKS2\",test_monks2_knn,test_monks2_nb],\n", + " [\"MONKS3\",test_monks3_knn,test_monks3_nb],\n", + " [\"Votes\",test_congres_knn,test_congres_nb]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exactitude\n", + "\n", + "Le tableau ci-dessous présente l'exactitude pour les modèles KNN et NB ainsi que le meilleur modèle dans ce cas. On remarque que pour le jeu de données Iris, le résultat obtenu est le même pour les deux modèles. Pour les jeux MONKS, dans deux des trois cas, le modèle KNN est meilleur, mais les deux résultats sont faibles. Dans le troisième cas, le modèle NB est largement plus exact, une situation similaire se reproduit pour les données sur les votes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(np.array([(i[0],\n", + " i[1][1],\n", + " i[2][1],\n", + " \"KNN\" if i[1][1]>i[2][1] else \"NB\") for i in tous_resultats]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Précision\n", + "\n", + "On obtient les mêmes constats que précédemment au niveau de la précision" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(np.array([(i[0],\n", + " i[1][2],\n", + " i[2][2],\n", + " \"KNN\" if i[1][2]>i[2][2] else \"NB\") for i in tous_resultats]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Rappel\n", + "\n", + "Pour le rappel, on obtient des résultats différents. Pour une même exactitude, le modele KNN a un meilleur rappel pour la seconde classe. Pour le jeu de données MONKS1, l'ordre des valeurs du rappel est inversé. Ceci nous montre qu'il est important de sélectionner la mesure la plus importante pour le problème que nous voulons résoudre, car il est possible d'obtenir un conclusion erronée en utilisant une autre." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(np.array([(i[0],\n", + " i[1][3],\n", + " i[2][3],\n", + " \"KNN\" if i[1][3]>i[2][3] else \"NB\") for i in tous_resultats]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Temps d'exécution\n", + "\n", + "Dans tous les cas, le temps de calcul du Naive Bayes est signicativement plus court." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(np.array([(i[0],\n", + " i[1][4],\n", + " i[2][4],\n", + " \"KNN\" if i[1][4]