Commit initial
This commit is contained in:
commit
235d5f6305
40
Controller/EntityDetermination.py
Normal file
40
Controller/EntityDetermination.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# -*-coding:utf8 -*
|
||||
|
||||
from anytree.search import find
|
||||
|
||||
class EntityDetermination:
|
||||
"""Détermine si le critère de recherche d'un arbre est true ou false pour une entité"""
|
||||
def __init__(self, tree, entity):
|
||||
self.entity = entity
|
||||
self.reference = tree.reference
|
||||
|
||||
|
||||
|
||||
# On démarre les recherches à partir du noeud initial
|
||||
self._find_next_node(tree.nodes[0])
|
||||
|
||||
def _find_next_node(self, current_node):
|
||||
"""Identifie la valeur de l'entité pour le critère courant et retourne le suivant, ou le résultat"""
|
||||
# On identifie la valeur de l'entité pour le critère courant
|
||||
criteria_value = self.entity[current_node.name]
|
||||
|
||||
# Pour ce critère, on récupère le noeud enfant
|
||||
node = find(current_node, lambda node: node.name == criteria_value and node.parent == current_node)
|
||||
|
||||
# Si cette valeur n'est pas référencée, on est bien incapable de déterminer quoi que ce soit
|
||||
if node == None:
|
||||
self._print_result("inconnue. Que voulez-vous, même la technologie a ses limites...")
|
||||
else:
|
||||
next_node = node.children[0]
|
||||
|
||||
# Si le gamin de ce noeud est true ou false : on a un résultat
|
||||
if next_node.name in ("True", "False"):
|
||||
self._print_result(next_node.name)
|
||||
# sinon on recommence
|
||||
else:
|
||||
self._find_next_node(next_node)
|
||||
|
||||
def _print_result(self, result):
|
||||
"""Affiche le résultat"""
|
||||
print("Notre ami.e", self.entity["Nom"], "est elle de type", self.reference, "?", "\nLa réponse est",
|
||||
result)
|
57
Model/Criteria.py
Executable file
57
Model/Criteria.py
Executable file
|
@ -0,0 +1,57 @@
|
|||
# -*-coding:utf8 -*
|
||||
|
||||
from math import log
|
||||
|
||||
|
||||
class Criteria :
|
||||
"""Classe Critère - valeurs d'un critère pour l'éléments recherché"
|
||||
Attributs :
|
||||
@string name = nom du critère
|
||||
@dict{@dict{@int} values = totaux des correspondances des différentes valeurs pour ce critère
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, criteriaList, referenceList):
|
||||
"""Constructeur - liste les valeurs possibles du critère """
|
||||
# Definition du nom et suppression des header de la liste
|
||||
self.name = criteriaList[0]
|
||||
self.referenceList = referenceList.copy()
|
||||
self.criteriaList = criteriaList.copy()
|
||||
del self.criteriaList[0]
|
||||
del self.referenceList[0]
|
||||
|
||||
# Définition des différentes valeurs
|
||||
self.values = {}
|
||||
for key, value in enumerate(self.criteriaList):
|
||||
# Ajout de la valeur si nouvelle
|
||||
if value not in self.values.keys():
|
||||
self.values[value] = {"True": 0, "False": 0}
|
||||
# Et incrément de la valeur du critère correspondant à l'élément de référence recherché
|
||||
if self.referenceList[key] == "O" or self.referenceList[key] is True or self.referenceList[key] == "Oui":
|
||||
self.values[value]["True"] += 1
|
||||
elif self.referenceList[key] == "N" or self.referenceList[key] is False or self.referenceList[key] == "Non":
|
||||
self.values[value]["False"] += 1
|
||||
|
||||
def get_entropy(self):
|
||||
"""Calcule et retourne l'entropie du critère"""
|
||||
# On récupère les totaux
|
||||
totals = {"all": 0}
|
||||
for key, value in self.values.items():
|
||||
totals[key] = value["True"] + value["False"]
|
||||
totals["all"] += totals[key]
|
||||
|
||||
# Maintenant qu'on a tout ce qu'il nous faut, on peut lancer le calcul !
|
||||
entropy = 0
|
||||
for key, value in self.values.items():
|
||||
entropy += totals[key]/totals["all"] \
|
||||
* ((self._entropy_frag(value["True"]/totals[key]))
|
||||
+ (self._entropy_frag(value["False"]/totals[key])))
|
||||
|
||||
return entropy
|
||||
|
||||
@staticmethod
|
||||
def _entropy_frag(prob):
|
||||
"""Un morceau du calcul d'entropie, permet d'alléger la formule"""
|
||||
if prob == 0:
|
||||
return 0
|
||||
return -1 * prob * (log(prob)/log(2))
|
98
Model/DecisionTree.py
Normal file
98
Model/DecisionTree.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
# -*-coding:utf8 -*
|
||||
|
||||
from Model.Criteria import Criteria
|
||||
from Model.Table import Table
|
||||
from anytree import Node
|
||||
|
||||
class DecisionTree:
|
||||
"""Classe DecisionTree permettant de contruire un arbre de décision
|
||||
|
||||
Atributs :
|
||||
@Table table # tableau de données
|
||||
@String reference # le critère à déterminer, pour lequel on construit notre arbre
|
||||
@List(@Node) # Liste des noeuds
|
||||
"""
|
||||
|
||||
def __init__(self, data, reference):
|
||||
self.table = Table(data)
|
||||
self.nodes = list()
|
||||
self.reference = reference
|
||||
|
||||
# On récupère l'identité du premier noeud et on le définit
|
||||
first_criteria = self._criteria_next_node(self.table)
|
||||
root_node = self._add_node(first_criteria.name, None)
|
||||
# Puis on lance la machine pour avancer dans l'arbre
|
||||
self._create_branch(self.table, first_criteria, root_node)
|
||||
|
||||
|
||||
def _criteria_next_node(self, table):
|
||||
"""Détermine le critère en prochain noeud à partir d'un tableau"""
|
||||
|
||||
# On établit la liste des critères
|
||||
criteria_list = table.get_criteria_list(self.reference)
|
||||
|
||||
# initialisation des variables
|
||||
maxEntropy = 1.1
|
||||
nextNode = Criteria
|
||||
|
||||
# On détemrine l'entropie de chaque critère
|
||||
for crit in criteria_list:
|
||||
criteria = Criteria(crit, table.get_column(table.table[0].index(self.reference)))
|
||||
# Si c'est la plus petite jusqu'alors, c'est ce critère qui sera le prochain noeud
|
||||
if criteria.get_entropy() < maxEntropy:
|
||||
nextNode = criteria
|
||||
maxEntropy = criteria.get_entropy()
|
||||
|
||||
return nextNode
|
||||
|
||||
def _create_branch(self, current_table, criteria, node):
|
||||
"""détermine les branches partant d'un critère positionné en noeud"""
|
||||
|
||||
for value, counts in criteria.values.items():
|
||||
# on replace le noeud parent correctement et on ajoute le critère en tant que noeud
|
||||
parent_node = node
|
||||
parent_node = self._add_node(value, parent_node)
|
||||
# Si un total est à 0, ou qu'il n'y plus que 3 colonne dans le tableau on est en fin de branhce !
|
||||
# Ou aussi qu'il n'y a qu'une seule option !
|
||||
if 0 in counts.values() or len(current_table.table[0]) == 3:
|
||||
result = None
|
||||
# On définit le résultat en fonction de la valeur la plus importante
|
||||
if counts["False"] > counts["True"]:
|
||||
result = "False"
|
||||
else: # Par défaut (en cas d'égalité notamment), on prend true
|
||||
result = "True"
|
||||
#On ajoute le noeud final
|
||||
self._add_node(result, parent_node)
|
||||
|
||||
# sinon il faut créer un nouveau tableau à partir de l'ancien
|
||||
else:
|
||||
new_table = Table(current_table.remove_criteria(criteria.name, value))
|
||||
|
||||
# Et continuer à avancer dans l'arbre
|
||||
next_criteria = self._criteria_next_node(new_table)
|
||||
|
||||
# Si le critère suivant n'a plus qu'une entrée possible, on est également en fin de branche !
|
||||
if len(next_criteria.values) < 2:
|
||||
# On l'ajoute donc en noeud final, selon sa valeur la plus importante
|
||||
for values in next_criteria.values.values():
|
||||
result = max(values, key=values.get)
|
||||
self._add_node(result, parent_node)
|
||||
|
||||
# Sinon, le critère est un nouveau noeud et on continue
|
||||
else:
|
||||
new_parent = self._add_node(next_criteria.name, parent_node)
|
||||
self._create_branch(new_table, next_criteria, new_parent)
|
||||
|
||||
def _add_node(self, node, parent):
|
||||
"""Crée un nouveau noeud dans l'arbre. Renvoie ce nouveau noeud"""
|
||||
self.nodes.append(Node(node, parent=parent))
|
||||
return self.nodes[-1]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
44
Model/Table.py
Normal file
44
Model/Table.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*-coding:utf8 -*
|
||||
|
||||
|
||||
class Table:
|
||||
|
||||
"""Classe Tableau. Instancie un tableau de données manipulable"""
|
||||
|
||||
def __init__(self, table):
|
||||
self.table = table
|
||||
|
||||
def get_column(self, key):
|
||||
"""retourne toutes les valeurs d'une colonne sous la forme d'une liste"""
|
||||
column = list()
|
||||
for i, entry in enumerate(self.table):
|
||||
column.append(entry[key])
|
||||
return column
|
||||
|
||||
def remove_criteria(self, criteria, value):
|
||||
"""Génère un nouveau tableau à partir d'un autre
|
||||
en enelvant les données liées à un critère et en ne gardant que la valeur de ce critère"""
|
||||
newTable = []
|
||||
|
||||
# récupère l'indice du critère à virer
|
||||
index = self.table[0].index(criteria)
|
||||
|
||||
# on vire allègrement cet indice de toutes les entrées du tableau qui matchent et on les ajoute au nouveau
|
||||
for entry in self.table:
|
||||
if entry[index] == value or entry[index] == criteria:
|
||||
newEntry = entry.copy()
|
||||
del newEntry[index]
|
||||
newTable.append(newEntry)
|
||||
|
||||
return newTable
|
||||
|
||||
def get_criteria_list(self, reference=""):
|
||||
"""Retourne la liste des critères à analyser dans un tableau,
|
||||
en ignorant une colonne de référence si spécifiée"""
|
||||
# On parcourt la ligne d'entête pour récupérer les colonnes à scanner
|
||||
toScan = []
|
||||
for i, header in enumerate(self.table[0]):
|
||||
# on ne prend pas la colonne nom qui est inutile, ni la colonne de référence
|
||||
if i != 0 and header != reference:
|
||||
toScan.append(self.get_column(i))
|
||||
return toScan
|
74
main.py
Executable file
74
main.py
Executable file
|
@ -0,0 +1,74 @@
|
|||
# -*-coding:utf8 -*
|
||||
|
||||
from anytree import RenderTree
|
||||
from Model.DecisionTree import DecisionTree
|
||||
from Controller.EntityDetermination import EntityDetermination
|
||||
|
||||
# Données initiales
|
||||
data = [
|
||||
["Nom", "Cape", "Argent", "Tech", "Pouvoir", "Héro"],
|
||||
["Spiderman", "N", "N", "N", "O", "O"],
|
||||
["Poutine", "N", "O", "O", "?", "N"],
|
||||
["Batman", "O", "O", "O", "N", "O"],
|
||||
["Jocker", "N", "O", "O", "N", "N"],
|
||||
["Rorschach", "N", "N", "N", "?", "O"],
|
||||
["Deadpool", "N", "N", "O", "O", "O"],
|
||||
["Merckel", "N", "O", "O", "N", "N"],
|
||||
["D'Artagnan", "O", "N", "N", "N", "N"],
|
||||
["César", "O", "O", "O", "N", "N"],
|
||||
["Tesla", "N", "N", "O", "?", "O"],
|
||||
["Edison", "N", "O", "O", "N", "N"],
|
||||
["Homer Simpson", "N", "N", "N", "N", "N"],
|
||||
["Sherlock Holmes", "N", "O", "N", "?", "O"],
|
||||
["Moriarty", "N", "O", "O", "?", "N"]
|
||||
]
|
||||
|
||||
""" Tableau de data secondaire. Utile pour faire des tests sur la première partie du cours
|
||||
dataGolf = [
|
||||
["Jour", "Climat", "Température", "Humidité", "Vent", "Golf"],
|
||||
["1 ", "Pluie ", "+", "+", "Non ", "N"],
|
||||
["2 ", "Pluie ", "+", "+", "Oui ", "N"],
|
||||
["3 ", "Nuage ", "+", "+", "Non ", "O"],
|
||||
["4 ", "Soleil", "~", "+", "Non ", "O"],
|
||||
["5 ", "Soleil", "-", "~", "Non ", "O"],
|
||||
["6 ", "Soleil", "-", "~", "Oui ", "N"],
|
||||
["7 ", "Nuage ", "-", "~", "Oui ", "O"],
|
||||
["8 ", "Pluie ", "~", "+", "Non ", "N"],
|
||||
["9 ", "Pluie ", "-", "~", "Non ", "O"],
|
||||
["10 ", "Soleil", "~", "~", "Non ", "O"],
|
||||
["11 ", "Pluie ", "~", "~", "Oui ", "O"],
|
||||
["12 ", "Nuage ", "~", "+", "Oui ", "O"],
|
||||
["13 ", "Nuage ", "+", "~", "Non ", "O"],
|
||||
["14 ", "Soleil", "~", "+", "Oui ", "N"]
|
||||
]
|
||||
"""
|
||||
# On détermine notre critère de référence : celui qu'on va chercher à déterminer
|
||||
# On peut aussi directment passer une chaine de caractère si jamais
|
||||
# elle doit juste correspondre à un header du tableau (ex : Héro)
|
||||
# Le critère doit aussi contenir des valeurs "propres" : oui/non, o/n, True/False...
|
||||
reference = data[0][5]
|
||||
|
||||
# Création de l'arbre et affichage (ça fait toujours plaisir après s'être saoûlé à le construire)
|
||||
|
||||
tree = DecisionTree(data, reference)
|
||||
print("ooooh, le bel arbre de décision ! \n", RenderTree(tree.nodes[0]).by_attr("name"),
|
||||
"\n==============================================================================\n")
|
||||
|
||||
# Nouvelle entité : est-ce un héro ?
|
||||
# Idéalement il faudrait rentrer les infos à la mano via
|
||||
# un prompt interactif qui demande selon l'arbre décisionnel,
|
||||
# pis ajouter les entités ainsi récoltées à la base
|
||||
# pour affiner les résultats,
|
||||
# mais bon j'ai un mémoire à faire alors c'est en dur dans le code
|
||||
entity = {
|
||||
"Nom": "Moustache",
|
||||
"Cape": "N",
|
||||
"Argent": "N",
|
||||
"Tech": "O",
|
||||
"Pouvoir": "?",
|
||||
"Héro": "?"
|
||||
}
|
||||
|
||||
EntityDetermination(tree, entity)
|
||||
|
||||
input("\n\nAppuyez sur Entrée pour fermer le programme...")
|
Loading…
Reference in a new issue