From 54badfdff4e8f7ef8aac41c60a7cf298f98e0c3d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Pelletier?= <francois@francoispelletier.org>
Date: Mon, 29 Apr 2019 21:35:45 -0400
Subject: [PATCH] ajout du cas discret

---
 Code/DecisionTree.py | 115 ++++++++++++++++++++++++++++---------------
 Code/main.py         |  18 +++++--
 2 files changed, 89 insertions(+), 44 deletions(-)

diff --git a/Code/DecisionTree.py b/Code/DecisionTree.py
index d2c66c8..b21f7a4 100644
--- a/Code/DecisionTree.py
+++ b/Code/DecisionTree.py
@@ -10,7 +10,8 @@ je vais avoir besoin de tester les méthodes train, predict et test de votre cod
 """
 
 import numpy as np
-
+import metrics
+import time
 
 # le nom de votre classe
 # NeuralNet pour le modèle Réseaux de Neurones
@@ -18,13 +19,14 @@ import numpy as np
 
 class DecisionTree: #nom de la class à changer
 
-    def __init__(self, **kwargs):
+    def __init__(self, attribute_type, **kwargs):
         """
         c'est un Initializer. 
         Vous pouvez passer d'autre paramètres au besoin,
         c'est à vous d'utiliser vos propres notations
         """
         self.tree=[]
+        self.attribute_type=attribute_type
         
     def plurality_value(self,train_labels):
         values,_,counts = np.unique(train_labels,return_index=True, return_counts=True)
@@ -36,33 +38,39 @@ class DecisionTree: #nom de la class à changer
         total_count = sum(counts)
         entropie_total = -sum(counts/total_count * np.log2(counts/total_count))
         #print("Entropie Total:"+str(entropie_total))
-        #Trouver split
-        attribute_sort_order = np.argsort(train[:,attribute])
-        sorted_labels = train_labels[attribute_sort_order]
-        lags = np.hstack((np.array([False]),sorted_labels[:-1] != sorted_labels[1:]))
-        lags2 = np.hstack((np.array([False]),lags))[:-1]
-        potential_splits = 0.5*train[attribute_sort_order,attribute][lags]+0.5*train[attribute_sort_order,attribute][lags2]
-        if (len(potential_splits)==0):
-            potential_splits = np.array([np.median(train[attribute_sort_order,attribute])])
-        #print("Potential Split:"+str(potential_splits))
-        split_gain = []
-        for v in potential_splits:
-            split_labels_1 = train_labels[train[:,attribute] <= v]
-            split_labels_2 = train_labels[train[:,attribute] > v]
-            _,_,counts1 = np.unique(split_labels_1,return_index=True, return_counts=True)
+        
+        if (self.attribute_type=="continuous"):
+            #Trouver split
+            attribute_sort_order = np.argsort(train[:,attribute])
+            sorted_labels = train_labels[attribute_sort_order]
+            lags = np.hstack((np.array([False]),sorted_labels[:-1] != sorted_labels[1:]))
+            lags2 = np.hstack((np.array([False]),lags))[:-1]
+            potential_splits = 0.5*train[attribute_sort_order,attribute][lags]+0.5*train[attribute_sort_order,attribute][lags2]
+            if (len(potential_splits)==0):
+                potential_splits = np.array([np.median(train[attribute_sort_order,attribute])])
+            #print("Potential Split:"+str(potential_splits))
+            split_gain = []
+            for v in potential_splits:
+                split_labels_1 = train_labels[train[:,attribute] <= v]
+                split_labels_2 = train_labels[train[:,attribute] > v]
+                _,_,counts1 = np.unique(split_labels_1,return_index=True, return_counts=True)
+                total_count1 = sum(counts1)
+                entropie_total1 = -sum(counts1/total_count1 * np.log2(counts1/total_count1))
+                _,_,counts2 = np.unique(split_labels_2,return_index=True, return_counts=True)
+                total_count2 = sum(counts2)
+                entropie_total2 = -sum(counts2/total_count2 * np.log2(counts2/total_count2))
+                split_gain.append(entropie_total-(total_count1/total_count*entropie_total1+total_count2/total_count*entropie_total2))
+            #Valeur unique attribut
+            #print("Split Gain:"+str(split_gain))
+            best_split = potential_splits[np.argmax(split_gain)]
+            best_gain = max(split_gain)
+            return best_gain,best_split
+        if (self.attribute_type=="discrete"):
+            _,counts1 = np.unique(train[:,attribute], return_counts=True)
             total_count1 = sum(counts1)
             entropie_total1 = -sum(counts1/total_count1 * np.log2(counts1/total_count1))
-            _,_,counts2 = np.unique(split_labels_2,return_index=True, return_counts=True)
-            total_count2 = sum(counts2)
-            entropie_total2 = -sum(counts2/total_count2 * np.log2(counts2/total_count2))
-            split_gain.append(entropie_total-(total_count1/total_count*entropie_total1+total_count2/total_count*entropie_total2))
-        #Valeur unique attribut
-        #print("Split Gain:"+str(split_gain))
-        best_split = potential_splits[np.argmax(split_gain)]
-        best_gain = max(split_gain)
-        
-        return best_gain,best_split
-        
+            gain = entropie_total - entropie_total1
+            return gain,None
         
     
     def decision_tree_learning(self,train, train_labels, attributes, parent_examples):
@@ -92,22 +100,35 @@ class DecisionTree: #nom de la class à changer
                 gain, split = self.importance(train, train_labels, a)
                 a_gain.append(gain)
                 a_split.append(split)
+                
             # Calcul du meilleur attribut
+            
             pos_gain_max = np.argmax(a_gain)
             a_max = attr[pos_gain_max]
             a_max_split = a_split[pos_gain_max]
-            tree = []
             attributes[a_max]=0
+            
+            # Nouvel arbre
+            
+            tree = []
+            
             # pour chaque valeur de l'attribut, faire un sous-arbre
-            for v in [True,False]:
-                print("Nouvelle branche: l'attribut "+str(a_max)+"<="+str(a_max_split)+" est: "+str(v))
-                train_pos = np.where((train[:,a_max] <= a_max_split) == v)
-                subtree = self.decision_tree_learning(train[train_pos],train_labels[train_pos],attributes,train_labels)
-                tree.append(("Branche",a_max,a_max_split,v,subtree))
+            if (self.attribute_type=="continuous"):
+                for v in [True,False]:
+                    print("Nouvelle branche: l'attribut "+str(a_max)+"<="+str(a_max_split)+" est: "+str(v))
+                    train_pos = np.where((train[:,a_max] <= a_max_split) == v)
+                    subtree = self.decision_tree_learning(train[train_pos],train_labels[train_pos],attributes,train_labels)
+                    tree.append(("Branche",a_max,a_max_split,v,subtree))
+            if (self.attribute_type=="discrete"):
+                for v in np.unique(train[:,a_max]):
+                    print("Nouvelle branche: l'attribut "+str(a_max)+" est: "+str(v))
+                    train_pos = np.where(train[:,a_max] == v)
+                    subtree = self.decision_tree_learning(train[train_pos],train_labels[train_pos],attributes,train_labels)
+                    tree.append(("Branche",a_max,v,subtree))
         return tree
                 
         
-    def train(self, train, train_labels): #vous pouvez rajouter d'autres attribus au besoin
+    def train(self, train, train_labels,verbose=True): #vous pouvez rajouter d'autres attribus au besoin
         """
         c'est la méthode qui va entrainer votre modèle,
         train est une matrice de taille nxm, avec 
@@ -115,6 +136,7 @@ class DecisionTree: #nom de la class à changer
         m : le mobre d'attribus (le nombre de caractéristiques)
         
         train_labels : est une matrice de taille nx1
+        verbose: afficher les métriques calculées
         
         vous pouvez rajouter d'autres arguments, il suffit juste de
         les expliquer en commentaire
@@ -131,6 +153,8 @@ class DecisionTree: #nom de la class à changer
         
         self.tree = self.decision_tree_learning(train, train_labels, attributes, None)
         
+        return self.test(train, train_labels,verbose)
+        
         
     def extract_tree(self,myTree,exemple):
         for b in myTree:
@@ -138,8 +162,12 @@ class DecisionTree: #nom de la class à changer
             if b[0] == 'Feuille':
                 return b[1]
             # On est dans une branche, on teste le split
-            if ((exemple[b[1]] <= b[2]) == b[3]):
-                return self.extract_tree(b[4],exemple)
+            if self.attribute_type=="continuous":
+                if ((exemple[b[1]] <= b[2]) == b[3]):
+                    return self.extract_tree(b[4],exemple)
+            if self.attribute_type=="discrete":
+                if (exemple[b[1]] == b[2]):
+                    return self.extract_tree(b[3],exemple)
         return None
             
         
@@ -155,15 +183,13 @@ class DecisionTree: #nom de la class à changer
         
         return self.extract_tree(self.tree,exemple)
         
-        
-        
-
-    def test(self, test, test_labels):
+    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 taille nxm, avec 
         n : le nombre d'exemple de test dans le dataset
         m : le mobre d'attribus (le nombre de caractéristiques)
+        verbose: afficher les métriques calculées
         
         test_labels : est une matrice taille nx1
         
@@ -177,6 +203,15 @@ class DecisionTree: #nom de la class à changer
         Bien entendu ces tests doivent etre faits sur les données de test seulement
         
         """
+        start_time = time.time()
+        prediction_test = [self.predict(exemple,label) for exemple,label in zip(test,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,
diff --git a/Code/main.py b/Code/main.py
index e1e875e..aa2e523 100644
--- a/Code/main.py
+++ b/Code/main.py
@@ -8,10 +8,20 @@ import DecisionTree  # importer la classe de l'Arbre de Décision
 # importer d'autres fichiers et classes si vous en avez développés
 # importer d'autres bibliothèques au besoin, sauf celles qui font du machine learning
 
-dt = DecisionTree.DecisionTree()
+train1, train_labels1, test1, test_labels1 = ld.load_iris_dataset(train_ratio = 0.7)
+train2, train_labels2, test2, test_labels2 = ld.load_monks_dataset(1)
+train3, train_labels3, test3, test_labels3 = ld.load_monks_dataset(2)
+train4, train_labels4, test4, test_labels4 = ld.load_monks_dataset(3)
+train5, train_labels5, test5, test_labels5 = ld.load_congressional_dataset(train_ratio = 0.7)
 
-dt.train(train,train_labels)
 
-dt.tree
+dt1 = DecisionTree.DecisionTree(attribute_type="continuous")
+dt1.train(train1, train_labels1)
+dt1.predict(test1[0],test_labels1[0])
+dt1.test(test1, test_labels1)
 
-[(dt.predict(exemple,label),label) for exemple,label in zip(train,train_labels)]
\ No newline at end of file
+
+dt5 = DecisionTree.DecisionTree(attribute_type="discrete")
+dt5.train(train5, train_labels5)
+dt5.predict(test5[0],test_labels5[0])
+dt5.test(test5, test_labels5)
\ No newline at end of file