2.6 Forêt aléatoire
La forêt aléatoire, ou random forest en anglais (RF) est un algorithme introduit par Breiman (2001) qui créé un ensemble d’arbres décisionnels individuels, dont la variable discriminante à chaque nœud est choisie dans un sous-ensemble aléatoire de toutes les p variables utilisées (afin d’introduire une variation aléatoire dans chaque arbre de partitionnement qui est donc toujours différent des autres). Lorsque l’ensemble des arbres est créé, un vote à la majorité est appliqué en utilisant la réponse donnée par chaque arbre pour déterminer à quelle classe appartient l’individu étudié. C’est actuellement un des algorithmes les plus performants de classification supervisée. Par contre, étant donné qu’il faut créer plusieurs centaines d’arbres, le temps de calcul est généralement plus élevé qu’avec des méthodes simples comme ADL, k plus proches voisins, ou l’utilisation d’un arbre unique.
La combinaison de plusieurs classifieurs donne quasi-systématiquement des meilleurs résultats qu’un classifieur seul. On parle alors d’ensemble de classifieurs. La forêt aléatoire en est un excellent exemple. Plusieurs autres techniques existent pour combiner des classifieurs et améliorer les résultats, mais leur développement sort du cadre de ce cours :
stacking : Les résultats d’un ou plusieurs classifieurs sont utilisés comme variables d’entrée pour un autre classifieur. En d’autre terme, les classifieurs sont reliés en série les uns derrière les autres.
bagging : pour bootstrap aggregating améliore la stabilité et la précision de la classification. Quelques méthodes sont implémentées notamment dans le package R {ipred}. Le principe consiste à générer plusieurs sets d’apprentissage artificiellement par échantillonnage avec remplacement (technique de bootstrap), à entraîner des classifieurs sur chaque set, et à ensuite résumer les résultat via un vote à la majorité.
boosting : entraîne successivement des classifieurs en se focalisant sur les erreurs du précédent. L’un des algorithmes les plus performants actuellement, XGBoost fait partie de cette catégorie (voir le package {xgboost} dans R).
Les paramètres à définir sont le nombre d’arbres et le nombre de p attributs à choisir aléatoirement parmi tous les attributs à chaque nœud pour établir la règle de décision. C’est par cette présélection aléatoire de candidats de départ pour les règles que la variation est créée d’un arbre à l’autre. Cette présélection aléatoire est réitérée, naturellement, à chaque nœud de chaque arbre. Les paramètres choisis ici sont de 500 arbres et de 2p/3 attributs conservés aléatoirement à chaque nœud. Ces valeurs se sont montrées efficaces dans des études antérieures, et l’algorithme est, par ailleurs, relativement robuste face aux variations de ces paramètres autour des valeurs par défaut.
2.6.1 Pima avec forêt aléatoire
La forêt aléatoire est disponible dans {mlearning}. Son utilisation est similaire aux autres fonctions du package, avec ici les arguments ntree=
pour le nombre d’arbres, et mtry=
pour le nombre de variables à sélectionner au hasard à chaque nœud. Par défaut, 500 arbres sont construits et les 2/3 des variables sont utilisées. Voyons ce que cela donne avec ces valeurs par défaut :
set.seed(74356)
pima1_rf <- mlRforest(data = pima1, diabetes ~ .)
pima1_rf_conf <- confusion(cvpredict(pima1_rf, cv.k = 10), pima1$diabetes)
plot(pima1_rf_conf)
# 392 items classified with 309 true positives (error = 21.2%)
#
# Global statistics on reweighted data:
# Error rate: 21.2%, F(micro-average): 0.754, F(macro-average): 0.751
#
# Fscore Recall Precision
# neg 0.8471455 0.8778626 0.8185053
# pos 0.6556017 0.6076923 0.7117117
Nous avons ici un peu moins de 22% d’erreur, soit le meilleur score de peu devant l’ADL. Notons que le temps de calcul est déjà non négligeable sur ce petit jeu de données (faites attention à de très gros jeux de données avec cette technique).
set.seed(9764)
pima2_rf <- mlRforest(data = pima2, diabetes ~ .)
pima2_rf_conf <- confusion(cvpredict(pima2_rf, cv.k = 10), pima2$diabetes)
plot(pima2_rf_conf)
# 724 items classified with 557 true positives (error = 23.1%)
#
# Global statistics on reweighted data:
# Error rate: 23.1%, F(micro-average): 0.74, F(macro-average): 0.739
#
# Fscore Recall Precision
# neg 0.8280124 0.8463158 0.8104839
# pos 0.6498952 0.6224900 0.6798246
Avec 23% d’erreur, nous avons encore une fois le meilleur classifieur. Nous pouvons encore effectuer deux choses à partir d’ici : (1) optimiser les paramètres, et (2) étudier l’importance des variables dans la classification. Concernant le nombre d’arbres, un graphique montre la variation de l’erreur en fonction du nombre d’arbres dans notre forêt.
Comme il y a très clairement un aspect probabiliste ici, le graphique indique les erreurs minimales et maximales observées en rouge et en vert, respectivement. En noir, la tendance générale. Nous voyons qu’avec quelques arbres, l’erreur est plus importante, mais elle diminue ici rapidement pour se stabiliser vers les 200 arbres. Donc, si nous voulons gagner du temps de calcul sans perdre en performance, nous pourrions définir ici ntree = 200
dans mlRforest()
. Différentes valeurs pour l’argument mtry()
pourraient aussi parfaitement être étudiées, mais en pratique, la valeur par défaut donne bien souvent déjà les meilleurs résultats.
Un autre graphique présente l’importance des variables : varImpPlot()
du package {randomForest}.
Le critère utilisé est la diminution de l’indice Gini à chaque fois que la variable en question apparaît à un nœud d’un des arbres de la forêt. L’indice Gini, pour une probabilité p, est p(p - 1). Plus les probabilités s’éloignent de 0,5, plus l’indice est petit. Appliqué à la proportion d’items par classe, plus les sous-groupes résultant d’une division dans l’arbre sont purs (c’est-à-dire, non contaminés par des individus appartenant à d’autres classes), plus la probabilité d’une classe s’approche de 1 tandis que celle des autres tend vers zéro… et donc l’indice Gini diminue. Donc, la variable qui a le plus contribué à diminuer Gini est aussi celle qui est la plus discriminante entre les classes, ici c’est clairement glucose
, suivi de insulin
et age
. Par contre, triceps
, pregnant
et pressure
ont une moindre importance.
Sur cette base, nous pouvons décider de simplifier notre modèle en laissant tomber, par exemple, les trois dernières variables de moindre importance et en nous limitant à 200 arbres.
set.seed(85436)
pima1_rf2 <- mlRforest(data = pima1, diabetes ~ glucose + insulin + age + mass + pedigree,
ntree = 200)
pima1_rf2_conf <- confusion(cvpredict(pima1_rf2, cv.k = 10), pima1$diabetes)
plot(pima1_rf2_conf)
# 392 items classified with 306 true positives (error = 21.9%)
#
# Global statistics on reweighted data:
# Error rate: 21.9%, F(micro-average): 0.749, F(macro-average): 0.749
#
# Fscore Recall Precision
# neg 0.8383459 0.8511450 0.8259259
# pos 0.6587302 0.6384615 0.6803279
Effectivement, avec un petit peu moins de 21%, c’est notre meilleur score jusqu’ici, tout en réduisant le nombre d’attributs nécessaires et d’arbres à calculer. Dans le module suivant, nous étudierons encore quelques concepts autour de la classification supervisée afin de réaliser un choix éclairé du meilleur classifieur.
Trucs et astuces
Pour l’instant {mlearning} utilise les fonctions du package {randomForest} qui reprend le code original de Breiman. Cependant, d’autres implémentations plus performantes ont été développées depuis. En particulier, le package R {ranger} implémente une version parallélisée (utilisation de plusieurs cœurs du processeur pour effectuer le calcul en parallèle) bien plus rapide sur les ordinateurs actuels. Si la vitesse est un frein à l’utilisation de la forêt aléatoire dans votre application, allez voir du côté de cette implémentation.
À vous de jouer !
Effectuez maintenant les exercices du tutoriel C02Lb_ml2 (K plus proches voisins, méthode par arbres et forêt aléatoire).
BioDataScience3::run("C02Lb_ml2")