Les arbres de décision (ID3)

Solutions du code

Module moteur_id3/id3.py

from math import log
from .noeud_de_decision import NoeudDeDecision

class ID3:
    """ Algorithme ID3. """

    def construit_arbre(self, donnees):
        """ Construit un arbre de décision à partir des données d'apprentissage.

            :param list donnees: les données d'apprentissage\
            ``[classe, {attribut -> valeur}, ...]``.
            :return: une instance de NoeudDeDecision correspondant à la racine de\
            l'arbre de décision.
        """

        # Nous devons extraire les domaines de valeur des
        # attributs, puisqu'ils sont nécessaires pour
        # construire l'arbre.
        attributs = {}
        for donnee in donnees:
            for attribut, valeur in donnee[1].items():
                valeurs = attributs.get(attribut)
                if valeurs is None:
                    valeurs = set()
                    attributs[attribut] = valeurs
                valeurs.add(valeur)

        arbre = self.construit_arbre_recur(donnees, attributs)

        return arbre

    def construit_arbre_recur(self, donnees, attributs):
        """ Construit rédurcivement un arbre de décision à partir
            des données d'apprentissage et d'un dictionnaire liant
            les attributs à la liste de leurs valeurs possibles.

            :param list donnees: les données d'apprentissage\
            ``[classe, {attribut -> valeur}, ...]``.
            :param attributs: un dictionnaire qui associe chaque\
            attribut A à son domaine de valeurs a_j.
            :return: une instance de NoeudDeDecision correspondant à la racine de\
            l'arbre de décision.
        """

        def classe_unique(donnees):
            """ Vérifie que toutes les données appartiennent à la même classe. """

            if len(donnees) == 0:
                return True
            premiere_classe = donnees[0][0]
            for donnee in donnees:
                if donnee[0] != premiere_classe:
                    return False
            return True

        if donnees == []:
            return None

        # Si toutes les données restantes font partie de la même classe,
        # on peut retourner un noeud terminal.
        elif classe_unique(donnees):
            return NoeudDeDecision(None, donnees)

        else:
            # Sélectionne l'attribut qui réduit au maximum l'entropie.
            h_C_As_attribs = [(self.h_C_A(donnees, attribut, attributs[attribut]),
                               attribut) for attribut in attributs]

            attribut = min(h_C_As_attribs, key=lambda h_a: h_a[0])[1]

            # Crée les sous-arbres de manière récursive.
            attributs_restants = attributs.copy()
            del attributs_restants[attribut]

            partitions = self.partitionne(donnees, attribut, attributs[attribut])

            enfants = {}
            for valeur, partition in partitions.items():
                enfants[valeur] = self.construit_arbre_recur(partition,
                                                             attributs_restants)

            return NoeudDeDecision(attribut, donnees, enfants)

    def partitionne(self, donnees, attribut, valeurs):
        """ Partitionne les données sur les valeurs a_j de l'attribut A.

            :param list donnees: les données à partitioner.
            :param attribut: l'attribut A de partitionnement.
            :param list valeurs: les valeurs a_j de l'attribut A.
            :return: un dictionnaire qui associe à chaque valeur a_j de\
            l'attribut A une liste l_j contenant les données pour lesquelles A\
            vaut a_j.
        """
        partitions = {valeur: [] for valeur in valeurs}

        for donnee in donnees:
            partition = partitions[donnee[1][attribut]]
            partition.append(donnee)

        return partitions

    def p_aj(self, donnees, attribut, valeur):
        """ p(a_j) - la probabilité que la valeur de l'attribut A soit a_j.

            :param list donnees: les données d'apprentissage.
            :param attribut: l'attribut A.
            :param valeur: la valeur a_j de l'attribut A.
            :return: p(a_j)
        """
        # Nombre de données.
        nombre_donnees = len(donnees)

        # Permet d'éviter les divisions par 0.
        if nombre_donnees == 0:
            return 0.0

        # Nombre d'occurrences de la valeur a_j parmi les données.
        nombre_aj = 0
        for donnee in donnees:
            if donnee[1][attribut] == valeur:
                nombre_aj += 1

        # p(a_j) = nombre d'occurrences de la valeur a_j parmi les données /
        #          nombre de données.
        return nombre_aj / nombre_donnees

    def p_ci_aj(self, donnees, attribut, valeur, classe):
        """ p(c_i|a_j) - la probabilité conditionnelle que la classe C soit c_i\
            étant donné que l'attribut A vaut a_j.

            :param list donnees: les données d'apprentissage.
            :param attribut: l'attribut A.
            :param valeur: la valeur a_j de l'attribut A.
            :param classe: la valeur c_i de la classe C.
            :return: p(c_i | a_j)
        """
        # Nombre d'occurrences de la valeur a_j parmi les données.
        donnees_aj = [donnee for donnee in donnees if donnee[1][attribut] == valeur]
        nombre_aj = len(donnees_aj)

        # Permet d'éviter les divisions par 0.
        if nombre_aj == 0:
            return 0

        # Nombre d'occurrences de la classe c_i parmi les données pour lesquelles
        # A vaut a_j.
        donnees_ci = [donnee for donnee in donnees_aj if donnee[0] == classe]
        nombre_ci = len(donnees_ci)

        # p(c_i|a_j) = nombre d'occurrences de la classe c_i parmi les données
        #              pour lesquelles A vaut a_j /
        #              nombre d'occurrences de la valeur a_j parmi les données.
        return nombre_ci / nombre_aj

    def h_C_aj(self, donnees, attribut, valeur):
        """ H(C|a_j) - l'entropie de la classe parmi les données pour lesquelles\
            l'attribut A vaut a_j.

            :param list donnees: les données d'apprentissage.
            :param attribut: l'attribut A.
            :param valeur: la valeur a_j de l'attribut A.
            :return: H(C|a_j)
        """
        # Les classes attestées dans les exemples.
        classes = list(set([donnee[0] for donnee in donnees]))

        # Calcule p(c_i|a_j) pour chaque classe c_i.
        p_ci_ajs = [self.p_ci_aj(donnees, attribut, valeur, classe)
                    for classe in classes]

        # Si p vaut 0 -> plog(p) vaut 0.
        return -sum([p_ci_aj * log(p_ci_aj, 2.0)
                    for p_ci_aj in p_ci_ajs
                    if p_ci_aj != 0])

    def h_C_A(self, donnees, attribut, valeurs):
        """ H(C|A) - l'entropie de la classe après avoir choisi de partitionner\
            les données suivant les valeurs de l'attribut A.

            :param list donnees: les données d'apprentissage.
            :param attribut: l'attribut A.
            :param list valeurs: les valeurs a_j de l'attribut A.
            :return: H(C|A)
        """
        # Calcule P(a_j) pour chaque valeur a_j de l'attribut A.
        p_ajs = [self.p_aj(donnees, attribut, valeur) for valeur in valeurs]

        # Calcule H_C_aj pour chaque valeur a_j de l'attribut A.
        h_c_ajs = [self.h_C_aj(donnees, attribut, valeur)
                   for valeur in valeurs]

        return sum([p_aj * h_c_aj for p_aj, h_c_aj in zip(p_ajs, h_c_ajs)])

Documentation du code

class moteur_id3.noeud_de_decision.NoeudDeDecision(attribut, donnees, enfants=None)

Un noeud dans un arbre de décision.

__init__(attribut, donnees, enfants=None)
Paramètres:
  • attribut – l’attribut de partitionnement du noeud (None si le noeud est un noeud terminal).
  • donnees (list) – la liste des données qui tombent dans la sous-classification du noeud.
  • enfants – un dictionnaire associant un fils (sous-noeud) à chaque valeur de l’attribut du noeud (None si le noeud est terminal).
__repr__()

Représentation sous forme de string de l’arbre de décision duquel le noeud courant est la racine.

classe()

Si le noeud est terminal, retourne la classe des données qui tombent dans la sous-classification (dans ce cas, toutes les données font partie de la même classe.

classifie(donnee)

Classifie une donnée à l’aide de l’arbre de décision duquel le noeud courant est la racine.

Paramètres:donnee – la donnée à classifier.
Retourne:la classe de la donnée selon le noeud de décision courant.
repr_arbre(level=0)

Représentation sous forme de string de l’arbre de décision duquel le noeud courant est la racine.

terminal()

Vérifie si le noeud courant est terminal.

class moteur_id3.id3.ID3

Algorithme ID3.

construit_arbre(donnees)

Construit un arbre de décision à partir des données d’apprentissage.

Paramètres:donnees (list) – les données d’apprentissage [classe, {attribut -> valeur}, ...].
Retourne:une instance de NoeudDeDecision correspondant à la racine de l’arbre de décision.
construit_arbre_recur(donnees, attributs)

Construit rédurcivement un arbre de décision à partir des données d’apprentissage et d’un dictionnaire liant les attributs à la liste de leurs valeurs possibles.

Paramètres:
  • donnees (list) – les données d’apprentissage [classe, {attribut -> valeur}, ...].
  • attributs – un dictionnaire qui associe chaque attribut A à son domaine de valeurs a_j.
Retourne:

une instance de NoeudDeDecision correspondant à la racine de l’arbre de décision.

h_C_A(donnees, attribut, valeurs)

H(C|A) - l’entropie de la classe après avoir choisi de partitionner les données suivant les valeurs de l’attribut A.

Paramètres:
  • donnees (list) – les données d’apprentissage.
  • attribut – l’attribut A.
  • valeurs (list) – les valeurs a_j de l’attribut A.
Retourne:

H(C|A)

h_C_aj(donnees, attribut, valeur)

H(C|a_j) - l’entropie de la classe parmi les données pour lesquelles l’attribut A vaut a_j.

Paramètres:
  • donnees (list) – les données d’apprentissage.
  • attribut – l’attribut A.
  • valeur – la valeur a_j de l’attribut A.
Retourne:

H(C|a_j)

p_aj(donnees, attribut, valeur)

p(a_j) - la probabilité que la valeur de l’attribut A soit a_j.

Paramètres:
  • donnees (list) – les données d’apprentissage.
  • attribut – l’attribut A.
  • valeur – la valeur a_j de l’attribut A.
Retourne:

p(a_j)

p_ci_aj(donnees, attribut, valeur, classe)

p(c_i|a_j) - la probabilité conditionnelle que la classe C soit c_i étant donné que l’attribut A vaut a_j.

Paramètres:
  • donnees (list) – les données d’apprentissage.
  • attribut – l’attribut A.
  • valeur – la valeur a_j de l’attribut A.
  • classe – la valeur c_i de la classe C.
Retourne:

p(c_i | a_j)

partitionne(donnees, attribut, valeurs)

Partitionne les données sur les valeurs a_j de l’attribut A.

Paramètres:
  • donnees (list) – les données à partitioner.
  • attribut – l’attribut A de partitionnement.
  • valeurs (list) – les valeurs a_j de l’attribut A.
Retourne:

un dictionnaire qui associe à chaque valeur a_j de l’attribut A une liste l_j contenant les données pour lesquelles A vaut a_j.