Journée MAD — Réseau SO-MATE
L’arbre de décision est un outil relativement récent (1984 pour l’algorithme le plus utilisé, CART) d’exploration des données et de prédiction.
Il peut servir à expliquer une variable qualitative (on parle alors d’arbre de classification) ou quantitative (arbre de régression).
Par rapport à d’autres méthodes classiques (SVM, analyse factorielle discriminante, réseau de neurones, etc.), l’arbre de décision possède de nombreux avantages :
Il y a néanmoins quelques désavantages :
En pratique, un arbre de décision est donc surtout un outil exploratoire ou descriptif, ou un outil décisionnel « tout-terrain » lorsqu’on doit faire de la prévision avec des données de mauvaise qualité (ou de très grande dimension).
Figure 1 : Un exemple d’arbre de décision avec R
Quelques éléments de notation pour ce qui suit :
L’algorithme CART (Breiman et al., 1984) fonctionne selon un principe de partitionnement binaire récursif : le but est d’obtenir des groupes d’individus aussi homogènes que possible (par rapport à \(Y\)), en posant une succession de questions binaires (oui / non) relatives aux variables explicatives.
Une fois ces groupes (ce partitionnement) obtenus, un modèle simple est construit à l’intérieur de chacun d’entre eux.
Ce descriptif de l’algorithme laisse pour l’instant trois grandes questions en suspens :
Supposons que l’on veuille expliquer le prix d’une voiture (variable continue exprimée en milliers de dollars) en fonction de deux variables continues : sa puissance exprimée en chevaux (HP, horsepower), et le diamètre des roues exprimé en pouces (Wheel.base).
library(dplyr)
library(rpart)
data(car90)
head(select(car90, Price, HP, Wheel.base))
| Price | HP | Wheel.base | |
|---|---|---|---|
| Acura Integra | 11950 | 130 | 102 |
| Acura Legend | 24760 | 160 | 109 |
| Audi 100 | 26900 | 130 | 106 |
| Audi 80 | 18900 | 108 | 100 |
| BMW 325i | 24650 | 168 | 101 |
| BMW 535i | 33200 | 208 | 109 |
On représente ci-dessous le lien entre le prix des voitures et ces deux variables :
## Représentation du prix en fonction des caractéristiques :
library(ggplot2)
ggplot(car90, aes(x=HP, y=Wheel.base, size=Price, color=Price)) +
geom_point() +
theme_bw(base_size = 16) +
guides(size = "none")
Figure 2 : Représentation du lien entre les caractéristiques et le prix des voitures.
car90L’arbre va créer une partition constituée de paquets de véhicules homogènes :
library(rpart.plot)
arbre1 <- rpart(Price ~ HP + Wheel.base, data = car90)
rpart.plot(arbre1, digits = -1, extra = 1)
Figure 3 : Arbre de régression (non élagué, obtenu par défaut).
L’arbre de régression opère en fait le découpage suivant sur le nuage de points :
Figure 4 : Un point de vue équivalent à la Figure 3.
Mais la représentation par un arbre se généralise aisément à une situation où l’on dispose de très nombreuses variables explicatives, alors qu’il ne sera pas possible de généraliser la représentation ci-dessus si on travaille en dimension supérieure à 2.
Supposons que l’arbre a été construit et fixé à une taille convenable. Quelle valeur peut-on attribuer à chaque feuille ?
Comme les feuilles sont supposées homogènes (en termes de la variable réponse \(Y\)), on fait généralement le choix d’un simple ajustement par une constante, qui est la moyenne de la variable réponse pour les individus appartenant à cette feuille.
Cette stratégie est mathématiquement optimale si on souhaite affecter une valeur constante à chaque feuille (au sens où la moyenne est la constante qui minimise l’erreur du modèle au sein de chaque feuille).
Lorsqu’on se place à un certain noeud de l’arbre, la séparation en deux branches en fonction d’une certaine variable \(X_j\) est de la forme suivante :
Critère mathématique
L’idée est de trouver la variable \(X_j\) et le seuil \(s\) qui permettent de maximiser la variance inter-groupes lors de cette séparation en deux branches (et donc de minimiser la variance intra-groupe, cf. théorème de Huygens).
Lorsqu’on sépare un noeud \(t\) en deux noeuds \(t_L\) et \(t_R\), on souhaite donc maximiser la quantité suivante (Genuer & Poggi, 2020) :
\[ V(t) - \left( \frac{\# t_L}{\# t} \, V(t_L) + \frac{\# t_R}{\# t} \, V(t_R) \right) \]
où pour un noeud \(t\), on note \(V(t) = \frac{1}{\# t} \sum_{i : x_i \in t} (y_i - \bar{y_t})^2\) la variance de la variable réponse pour les observations appartenant à ce noeud.
Mais comment trouver la bonne variable \(X_j\) et le bon seuil \(s\) ?
Pour cela, l’algorithme procède par force brute : il teste toutes les séparations possibles (i.e., tous les seuils \(s\) possibles pour toutes les variables numériques, et tous les sous-ensembles \(I\) pour toutes les variables qualitatives).
À l’issue de cette exploration exhaustive des séparations possibles, l’algorithme retient la séparation offrant le meilleur gain d’information.
À quel moment le processus récursif s’arrête-t-il ? Un arbre doit être arrêté lorsqu’il atteint une taille optimale :
Il faut donc rechercher le nombre de feuilles réalisant le meilleur compromis entre ces deux extrêmes.
Quelques critères d’arrêt possibles :
Le principe général est souvent de donner, pour les critères 1 à 2, des valeurs aboutissant à un arbre contenant probablement un peu trop de feuilles. Ce sera alors le critère 4 (la sélection selon la capacité prédictive estimée en validation croisée) qui permettra l’élagage de l’arbre à la taille optimale.
On commence usuellement par construire un arbre volontairement trop grand :
## Construire un arbre basique pour expliquer le prix :
arbre1 <- rpart(Price ~ HP + Wheel.base, data = car90, cp = 0)
rpart.plot(arbre1, digits = -3, extra = 1)
Figure 5 : Arbre de régression (quasi-) maximal, à élaguer.
On procède ensuite à l’élagage de l’arbre maximal par validation croisée :
## Recherche de la taille optimale par LOOCV :
arbre2 <- rpart(
formula = Price ~ HP + Wheel.base,
data = car90,
cp = 0,
xval = nrow(car90) # effectuer une LOOCV
)
plotcp(arbre2)
Figure 6 : Erreur obtenue en fonction de la taille de l’arbre.
Lors de l’élagage, on recherche par LOOCV (l’argument argument xval doit alors être égal au nombre d’individus) la coupure optimale que l’on peut effectuer sur l’arbre1 (i.e., l’arbre « maximal »).
La fonction plotcp() trace un graphique de complexité (Fig. 6), donnant l’erreur relative commise en fonction du nombre de feuilles terminales.
On peut alors construire l’arbre optimal. On élague l’arbre2 avec prune() :
## Élagage de l'arbre à une taille optimale :
arbre3 <- prune(arbre2, cp = 0.045)
rpart.plot(arbre3, extra = 1, digits = -3)
Figure 7 : Arbre optimal (au sens de la LOOCV).
Cet arbre diffère légèrement de l’arbre obtenu avec les paramètres par défaut (arbre1), qui possédait 6 feuilles terminales.
Quand une variable \(Z\) a été retenue pour créer un noeud, on opère une partition (en deux groupes) des individus en fonction des valeurs qu’ils prennent pour cette variable.
Si une autre variable \(Z_1\) du jeu de données permet de définir sensiblement la même partition (i.e., les deux mêmes sous-groupes), on dit alors que \(Z_1\) est une variable substitut à \(Z\).
Ainsi, pour chaque noeud de l’arbre, on forme une liste des meilleures variables substituts. Si un individu donné (notamment s’il s’agit d’un nouvel individu à prédire) possède une valeur manquante pour une variable apparaissant sur l’arbre, on utilise alors à la place une variable substitut.
Tout ce qui précède reste vrai si la variable réponse \(Y\) est catégorielle. On a uniquement deux adaptations :
Une alternative populaire lorsque l’on travaille essentiellement avec des données catégorielles est CHAID, basée sur des tests du \(\chi^2\). Deux grandes différences avec CART :
Il existe de nombreux autres algorithmes : C4.5, QUEST, CRUISE, CTREE, etc.
Ils peuvent donner des résultats très différents pour un même problème, comme on peut le voir ci-dessous (image extraite de Loh (2014)).
Figure 8 : Différents algorithmes d’arbre appliqués au même jeu de données.
Les CIT (conditional inference trees) sont une alternative intéressante à l’algorithme historique CART. Les points communs restent nombreux :
Pour une bonne introduction, voir par exemple Levshina (2020).
Un peu comme pour l’algorithme CHAID, les CIT sont basés sur une série de tests d’hypothèses.
Pour tester ces hypothèses, on effectue des permutations de la variable réponse \(Y\).
Outre les différences déjà signalées, l’algorithme des CIT se distingue de CART par un point important :
library(palmerpenguins)
library(partykit)
cit <- ctree(species ~ ., data = penguins)
plot(cit)
Figure 9 : Exemple d’arbre d’inférence conditionnel.
Pour changer un peu : voici l’arbre de classification obtenu pour prédire l’espèce des manchots dans le jeu de données penguins.
Figure 10 : Principe d’une forêt aléatoire. [Source
Une forêt aléatoire est construite à partir de nombreux arbres de décision (généralement quelques centaines), et fournit un résultat moyenné à partir de tous ces arbres : c’est une forme de model averaging.
Mais dans la mesure où la construction d’un arbre est purement déterministe, comment fait-on pour obtenir un forêt d’arbres différents les uns des autres ?
En partant du classique jeu de données iris, on peut construire une forêt aléatoire (constituée d’arbres de classification) avec l’instruction suivante :
library(randomForest)
foret <- randomForest(
Species ~ .,
data = iris,
ntree = 999,
importance = TRUE
)
print(foret) # résumé des résultats de la forêt aléatoire
Call:
randomForest(formula = Species ~ ., data = iris, ntree = 999, importance = TRUE)
Type of random forest: classification
Number of trees: 999
No. of variables tried at each split: 2
OOB estimate of error rate: 4.67%
Confusion matrix:
setosa versicolor virginica class.error
setosa 50 0 0 0.00
versicolor 0 47 3 0.06
virginica 0 4 46 0.08
plot() du modèle, on obtient un graphique donnant l’évolution de l’erreur de prédiction en fonction du nombre d’arbre de la forêt.tuneRF() permet quant à elle d’optimiser un paramètre important : le nombre de variables considérées à chaque split.Les modèles de forêt aléatoire sont partiellement des « boîtes noires », mais offrent des outils d’interprétation. En particulier, on peut identifier facilement les variables contribuant le plus fortement au modèle.
Pour cela, on calcule un score d’importance pour chaque variable, basé sur une idée astucieuse. On permute aléatoirement les valeurs d’une variable explicative, et si elle est importante dans le modèle, cela doit alors faire fortement décroître la précision des résultats.
Les variables les plus importantes sont donc celles qui occasionnent la plus forte baisse de précision (MeanDecreaseAccuracy) lorsqu’elles sont perturbées aléatoirement.
## Afficher les scores d'importance :
print(foret$importance)
On voit qu’ici, ce sont les deux variables relatives aux pétales qui sont les plus importantes dans le modèle.
Une forêt d’arbres d’inférence conditionnelle est l’équivalent des forêts aléatoires « classiques », appliquées aux arbres d’inférence conditionnelle.
On peut utiliser par exemple la fonction cforest() du package {partykit} :
library(partykit)
cforet <- cforest(species ~ bill_length_mm + bill_depth_mm +
flipper_length_mm + body_mass_g + sex,
data = penguins)
Puis on peut comparer les valeurs prédites et les classes réelles, par exemple avec :
table(Réel = penguins$species,
Prédit = cforet$fitted$"(response)")
Prédit
Réel Adelie Chinstrap Gentoo
Adelie 152 0 0
Chinstrap 0 68 0
Gentoo 0 0 124
Les forêts aléatoires conditionnelles reprennent globalement la logique des scores d’importance des forêts aléatoires « traditionnelles ».
Seule une légère modification est apportée au calcul des scores d’importance (Strobl et al., 2008). Par exemple, dans le cas de deux prédicteurs qualitatifs \(X_1\) et \(X_2\), les permutations aléatoires effectuées pour \(X_1\) sont effectuées conditionnellement à \(X_2\) : les valeurs de \(X_1\) sont permutées aléatoirement à l’intérieur de chaque niveau de \(X_2\), plutôt que globalement.
## Scores d'importance conditionnels :
varimp(cforet, conditional = TRUE)
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex
2.7618525 0.3880622 0.3009558 0.1675204 0.1004135
Créé par Frédéric Santos