Obtenir des connaissances à partir d'une forêt aléatoire

127

Les forêts aléatoires sont considérées comme des boîtes noires, mais récemment, je pensais quelles connaissances peuvent être obtenues à partir d'une forêt aléatoire.

La chose la plus évidente est l’importance des variables, dans la variante la plus simple, il suffit de calculer le nombre d’occurrences d’une variable.
La deuxième chose à laquelle je pensais, ce sont les interactions. Je pense que si le nombre d’arbres est suffisamment grand, le nombre d’occurrences de paires de variables peut être testé (quelque chose comme l’indépendance du chi carré). La troisième chose sont les non linéarités de variables. Ma première idée consistait simplement à regarder un graphique d’un score Vs variable, mais je ne suis pas encore sûr que cela ait un sens.

Ajouté 23.01.2012
Motivation

Je souhaite utiliser ces connaissances pour améliorer un modèle logit. Je pense (ou du moins j'espère) qu'il est possible de trouver des interactions et des non-linéarités négligées.

Tomek Tarczynski
la source
1
En utilisant R, vous pouvez produire un diagramme en point d’importance variable, mesuré par une forêt aléatoire.
George Dontas
1
un fil connexe sur la manière dont les mesures d'importance variable sont calculées pour le renforcement d'arbre à gradient stochastique
Antoine
Je réalise que c'est probablement trop tard, mais si vous voulez simplement améliorer un modèle logit, pourquoi n'utilisez-vous pas la régression logistique post-lasso? Vous pouvez simplement réajuster le modèle en utilisant les coefficients sélectionnés après la sélection, sans pénalisation / retrait. Vous devrez modifier un peu la procédure de réglage, mais il s'agit d'une option beaucoup plus directe qui fait exactement ce que vous voulez.
ilprincipe

Réponses:

122

Les forêts aléatoires sont à peine une boîte noire. Ils sont basés sur des arbres de décision, très faciles à interpréter:

#Setup a binary classification problem
require(randomForest)
data(iris)
set.seed(1)
dat <- iris
dat$Species <- factor(ifelse(dat$Species=='virginica','virginica','other'))
trainrows <- runif(nrow(dat)) > 0.3
train <- dat[trainrows,]
test <- dat[!trainrows,]

#Build a decision tree
require(rpart)
model.rpart <- rpart(Species~., train)

Cela donne un arbre de décision simple:

> model.rpart
n= 111 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 111 35 other (0.68468468 0.31531532)  
  2) Petal.Length< 4.95 77  3 other (0.96103896 0.03896104) *
  3) Petal.Length>=4.95 34  2 virginica (0.05882353 0.94117647) *

Si Petal.Length <4,95, cette arborescence classe l’observation dans la catégorie "autre". S'il est supérieur à 4,95, l'observation est classée comme "virginica". Une forêt aléatoire est une simple collection de nombreux arbres de ce type, où chacun est formé sur un sous-ensemble aléatoire de données. Chaque arbre "vote" ensuite sur le classement final de chaque observation.

model.rf <- randomForest(Species~., train, ntree=25, proximity=TRUE, importance=TRUE, nodesize=5)
> getTree(model.rf, k=1, labelVar=TRUE)
  left daughter right daughter    split var split point status prediction
1             2              3  Petal.Width        1.70      1       <NA>
2             4              5 Petal.Length        4.95      1       <NA>
3             6              7 Petal.Length        4.95      1       <NA>
4             0              0         <NA>        0.00     -1      other
5             0              0         <NA>        0.00     -1  virginica
6             0              0         <NA>        0.00     -1      other
7             0              0         <NA>        0.00     -1  virginica

Vous pouvez même extraire des arbres individuels de la radio et regarder leur structure. Le format est légèrement différent de celui des rpartmodèles, mais vous pouvez inspecter chaque arbre si vous le souhaitez et voir comment il modélise les données.

De plus, aucun modèle n'est vraiment une boîte noire, car vous pouvez examiner les réponses prévues par rapport aux réponses réelles pour chaque variable de l'ensemble de données. C'est une bonne idée quel que soit le type de modèle que vous construisez:

library(ggplot2)
pSpecies <- predict(model.rf,test,'vote')[,2]
plotData <- lapply(names(test[,1:4]), function(x){
  out <- data.frame(
    var = x,
    type = c(rep('Actual',nrow(test)),rep('Predicted',nrow(test))),
    value = c(test[,x],test[,x]),
    species = c(as.numeric(test$Species)-1,pSpecies)
    )
  out$value <- out$value-min(out$value) #Normalize to [0,1]
  out$value <- out$value/max(out$value)
  out
})
plotData <- do.call(rbind,plotData)
qplot(value, species, data=plotData, facets = type ~ var, geom='smooth', span = 0.5)

terrain

J'ai normalisé les variables (longueur et largeur des sépales et des pétales) sur une plage de 0 à 1. La réponse est également 0-1, 0 étant autre et 1 virginica. Comme vous pouvez le constater, la forêt aléatoire est un bon modèle, même sur l’ensemble de tests.

De plus, une forêt aléatoire calculera diverses mesures d'importance variable, ce qui peut être très informatif:

> importance(model.rf, type=1)
             MeanDecreaseAccuracy
Sepal.Length           0.28567162
Sepal.Width           -0.08584199
Petal.Length           0.64705819
Petal.Width            0.58176828

Ce tableau représente combien le fait de supprimer chaque variable réduit la précision du modèle. Enfin, il existe de nombreux autres tracés que vous pouvez créer à partir d'un modèle de forêt aléatoire pour voir ce qui se passe dans la boîte noire:

plot(model.rf)
plot(margin(model.rf)) 
MDSplot(model.rf, iris$Species, k=5)
plot(outlier(model.rf), type="h", col=c("red", "green", "blue")[as.numeric(dat$Species)])

Vous pouvez afficher les fichiers d'aide de chacune de ces fonctions pour avoir une meilleure idée de ce qu'ils affichent.

Zach
la source
6
Merci pour la réponse, il y a beaucoup d'informations utiles, mais ce n'est pas exactement ce que je cherchais. Peut-être devrais-je mieux préciser la motivation qui sous-tend cette question. Je veux utiliser une forêt aléatoire pour améliorer un modèle logit, par améliorer, je veux dire ajouter une interaction ou utiliser une transformation non linéaire.
Tomek Tarczynski
@TomekTarczynski, c'est un problème intéressant et semblable à celui que je traite actuellement. Je suppose que par "modèle logit", vous voulez dire une régression logistique ou quelque chose de similaire? J'utilise une régression logistique lasso (du paquet glmnet R) pour sélectionner des prédicteurs dans un modèle avec des interactions entre toutes les paires de variables. Je n'ai pas encore ajouté de termes non linéaires - mais en principe, cela devrait également être possible. Le seul problème que je suppose est de décider quels termes non linéaires essayer (termes polynomiaux, transformations exponentielles, etc.?). De plus, je ne détecte aucune interaction d'ordre supérieur, mais c'est aussi facile.
Anne Z.
2
@Tomek, que recevez-vous de cette réponse? Si vous utilisez le package randomForest dans R, les graphes décrits par Zach devraient être très utiles. Plus précisément, vous pouvez utiliser varImpPlot pour la sélection des fonctionnalités dans votre modèle logit et partialPlot pour estimer le type de transformation à essayer sur des prédicteurs continus dans le modèle logit. Je suggérerais que ce dernier graphique soit utilisé pour déterminer l'emplacement des relations non linéaires entre le prédicteur et la réponse, puis vous permet d'effectuer cette transformation de manière explicite ou d'utiliser une spline sur cette variable.
B_Miner
2
@b_miner - juste une supposition, mais il semblerait que tomek demande comment trouver des interactions non linéaires entre variables, car la régression logistique capture déjà les relations linéaires.
rm999
@ rm999 Comment définissez-vous une interaction non linéaire dans un modèle logit? Termes d'interaction créés entre des variables transformées?
B_Miner
52

Il y a quelque temps, j'ai dû justifier un ajustement de modèle RF à certains chimistes de mon entreprise. J'ai passé pas mal de temps à essayer différentes techniques de visualisation. Au cours du processus, j’ai aussi accidentellement mis au point de nouvelles techniques que j’ai intégrées dans un package R ( forestFloor ) spécifiquement pour la visualisation aléatoire de forêts.

L'approche classique consiste en des diagrammes de dépendance partielle supportés par: Rminer (l'analyse de sensibilité basée sur les données est une dépendance partielle réinventée), ou partialPlot dans le package randomForest . Je trouve le paquet de dépendance partielle iceBOX comme un moyen élégant de découvrir les interactions. Je n'ai pas utilisé le paquet edarf , mais semble avoir de belles visualisations dédiées à RF. Le package ggRandomForest contient également un grand nombre de visualisations utiles.

Actuellement, forestFloor prend en charge les objets randomForest (la prise en charge d'autres implémentations RF est en cours). Des contributions peuvent également être calculées pour les arbres boostés par gradient, étant donné que ces arbres après leur entraînement ne sont pas très différents des arbres forestiers aléatoires. Ainsi, forestFloor pourrait prendre en charge XGBoost à l’avenir. Les diagrammes de dépendance partielle sont complètement invariants au modèle.

Tous les packages ont en commun de visualiser la structure de mappage géométrique d'un modèle, de l'espace des fonctions à l'espace cible. Une courbe sinusoïdale y = sin (x) serait une application de x à y et peut être tracée en 2D. Tracer directement un mappage RF nécessiterait souvent trop de dimensions. Au lieu de cela, la structure de cartographie globale peut être projetée, découpée en tranches ou décomposée, de sorte que toute la structure de cartographie se résume en une séquence de tracés marginaux 2D. Si votre modèle RF a uniquement capturé les effets principaux et aucune interaction entre les variables, les méthodes de visualisation classiques conviendront parfaitement. Ensuite, vous pouvez simplifier la structure de votre modèle comme ceci:y=F(X)f1(x1)+f2(x2)+...+fd(xd). Ensuite, chaque fonction partielle de chaque variable peut être visualisée comme une courbe sinusoïdale. Si votre modèle RF a capturé des interactions considérables, il est alors plus problématique. Les tranches 3D de la structure permettent de visualiser les interactions entre deux entités et la sortie. Le problème est de savoir quelle combinaison de fonctionnalités doit être visualisée ( iceBOX résout ce problème). De plus, il n'est pas facile de dire si d'autres interactions latentes ne sont toujours pas prises en compte.

Dans cet article , j’ai utilisé une version très ancienne de forestFloor pour expliquer quelle relation biochimique réelle un très petit modèle RF avait capturé. Et dans cet article, nous décrivons en détail les visualisations des contributions des entités, Visualisations de sol forestier de forêts aléatoires .

J'ai collé l'exemple simulé du paquet forestFloor, dans lequel je montre comment découvrir une fonction masquée simulée bruity=x12+sin(x2π)+2x3x4+

#1 - Regression example:
set.seed(1234)
library(forestFloor)
library(randomForest)

#simulate data y = x1^2+sin(x2*pi)+x3*x4 + noise
obs = 5000 #how many observations/samples
vars = 6   #how many variables/features
#create 6 normal distr. uncorr. variables
X = data.frame(replicate(vars,rnorm(obs)))
#create target by hidden function
Y = with(X, X1^2 + sin(X2*pi) + 2 * X3 * X4 + 0.5 * rnorm(obs)) 

#grow a forest
rfo = randomForest(
  X, #features, data.frame or matrix. Recommended to name columns.
  Y, #targets, vector of integers or floats
  keep.inbag = TRUE,  # mandatory,
  importance = TRUE,  # recommended, else ordering by giniImpurity (unstable)
  sampsize = 1500 ,   # optional, reduce tree sizes to compute faster
  ntree = if(interactive()) 500 else 50 #speedup CRAN testing
)

#compute forestFloor object, often only 5-10% time of growing forest
ff = forestFloor(
  rf.fit = rfo,       # mandatory
  X = X,              # mandatory
  calc_np = FALSE,    # TRUE or FALSE both works, makes no difference
  binary_reg = FALSE  # takes no effect here when rfo$type="regression"
)


#plot partial functions of most important variables first
plot(ff,                       # forestFloor object
     plot_seq = 1:6,           # optional sequence of features to plot
     orderByImportance=TRUE    # if TRUE index sequence by importance, else by X column  
)

entrez la description de l'image ici

#Non interacting features are well displayed, whereas X3 and X4 are not
#by applying color gradient, interactions reveal themself 
#also a k-nearest neighbor fit is applied to evaluate goodness-of-fit
Col=fcol(ff,3,orderByImportance=FALSE) #create color gradient see help(fcol)
plot(ff,col=Col,plot_GOF=TRUE) 

entrez la description de l'image ici

#feature contributions of X3 and X4 are well explained in the context of X3 and X4
# as GOF R^2>.8


show3d(ff,3:4,col=Col,plot_GOF=TRUE,orderByImportance=FALSE)

entrez la description de l'image ici entrez la description de l'image ici

Enfin, le code des diagrammes de dépendance partielle codé par A.Liaw décrit par J.Friedman. Ce qui convient pour les effets principaux.

par(mfrow=c(2,3))
for(i in 1:6) partialPlot(rfo,X,x.var=names(X)[i])

entrez la description de l'image ici

Soren Havelund Welling
la source
J'ai postulé plus tôt que les contributions de fonctionnalité pourraient également être calculées pour les arbres boostés. J'ai écrit un algorithme de test et c'est possible. Malheureusement, les contributions aux fonctionnalités pour les arbres boostés ne présentent pas les mêmes propriétés bénéfiques que pour les arbres ensachés. Les effets d'une caractéristique ont tendance à se disperser dans toutes les contributions, ce qui rend la visualisation assez confuse.
Soren Havelund Welling
Oups! J'ai trouvé un bug dans mon algorithme de test qui a dissipé tous les problèmes. forestFloor pourrait être mis à jour pour fonctionner parfaitement pour les arbres boostés par gradient.
Soren Havelund Welling
3
forestFloor est-il mis à jour pour accepter les objets gbm?
Misha
Jusqu'à présent, j'ai effectué une implémentation inversée, qui englobe l'implémentation de randomForest dans une machine de renforcement de gradient entièrement fonctionnelle et définit certaines méthodes de calcul des contributions d'entités. Je pense que la mise en œuvre est en fait raisonnablement efficace. Vous pouvez trouver le code ici: github.com/sorhawell/forestFloor/blob/master/inst/examples/…
Soren Havelund Welling
Faire un portage complet est un travail difficile :) Cette implémentation a été réalisée en assez peu de lignes.
Soren Havelund Welling
24

Pour compléter ces réponses précises, je mentionnerais l’utilisation d’arbres à gradient renforcé (par exemple, le paquetage GBM dans R ). Dans R, je préfère ceci aux forêts aléatoires car les valeurs manquantes sont autorisées par rapport à randomForest où l'imputation est requise. Des graphes d'importance variable et partiels sont disponibles (comme dans randomForest) pour vous aider à sélectionner des fonctionnalités et à explorer la transformation non linéaire dans votre modèle logit. En outre, l’interaction variable est traitée avec la statistique H de Friedman ( interact.gbm), la référence étant donnée par J.H. Friedman and B.E. Popescu (2005). “Predictive Learning via Rule Ensembles.” Section 8.1. Une version commerciale appelée TreeNet est disponible chez Salford Systems et cette présentation vidéo décrit leur interprétation de la vidéo d' estimation par interaction variable .

B_Miner
la source
2
Je suis d'accord, les GBM sont la prochaine étape logique des forêts aléatoires.
Zach
@B_miner: Génial! Je ne sais pas comment, mais j'ai négligé la GBM. Il semble que l’utilisation de GBM soit facile à détecter, mais aussi à détecter les interactions et les non-linéarités.
Tomek Tarczynski
15

Réponse tardive, mais je suis tombé sur un ensemble récent de R forestFloor(2015) qui vous aide à faire cette tâche « unblackboxing » de façon automatisée. Cela semble très prometteur!

library(forestFloor)
library(randomForest)
#simulate data
obs=1000
vars = 18
X = data.frame(replicate(vars,rnorm(obs)))
Y = with(X, X1^2 + sin(X2*pi) + 2 * X3 * X4 + 1 * rnorm(obs))
#grow a forest, remeber to include inbag
rfo=randomForest(X,Y,keep.inbag = TRUE,sampsize=250,ntree=50)
#compute topology
ff = forestFloor(rfo,X)
#ggPlotForestFloor(ff,1:9)
plot(ff,1:9,col=fcol(ff))

Produit les parcelles suivantes:

entrez la description de l'image ici

Il fournit également une visualisation en trois dimensions si vous recherchez des interactions.

RUser4512
la source
4
J'ai supprimé le support pour ggplot2, au lieu de la dernière ligne, essayez par exemple: plot (ff, 1: 9, col = fcol (ff, 1: 4))
Soren Havelund Welling
9

Comme mentionné par Zach, une façon de comprendre un modèle consiste à tracer la réponse à mesure que les prédicteurs varient. Vous pouvez le faire facilement pour "n'importe quel" modèle avec le package plotmo R. Par exemple

library(randomForest)
data <- iris
data$Species <- factor(ifelse(data$Species=='virginica','virginica','other'))
mod <- randomForest(Species~Sepal.Length+Sepal.Width, data=data)
library(plotmo)
plotmo(mod, type="prob")

qui donne

terrain

Cela change une variable tout en maintenant les autres à leurs valeurs médianes. Pour les diagrammes d'interaction, deux variables sont modifiées. (Remarque ajoutée en novembre 2016: prend plotmodésormais également en charge les tracés de dépendance partiels.)

L'exemple ci-dessus utilise seulement deux variables; Des modèles plus compliqués peuvent être visualisés de manière fragmentée en examinant une ou deux variables à la fois. Puisque les "autres" variables sont maintenues à leurs valeurs médianes, cela ne montre qu'une tranche des données, mais peut toujours être utile. Quelques exemples sont dans la vignette du paquet plotmo . Vous trouverez d'autres exemples au chapitre 10 de la section Tracer des arbres rpart avec le package rpart.plot .

Stephen Milborrow
la source
4

Je suis très intéressé par ce type de questions moi-même. Je pense qu'il y a beaucoup d'informations que nous pouvons extraire d'une forêt aléatoire.

À propos des interactions, il semble que Breiman et Cultier aient déjà essayé de s’y intéresser, en particulier pour les RF de classification.

À ma connaissance, cela n’a pas été implémenté dans le package randomForest R. Peut-être parce que cela n’est peut-être pas aussi simple et que la signification de "interactions variables" est très dépendante de votre problème.

En ce qui concerne la non-linéarité, je ne sais pas ce que vous recherchez, les forêts de régression sont utilisées pour les problèmes de régression multiple non linéaires sans aucun préalable sur le type de fonction non linéaire à utiliser.

Rémy Nicolle
la source
3

En fin de partie, mais il y a de nouveaux développements dans ce domaine, par exemple LIME et SHAP . Un paquet à vérifier également est DALEX (en particulier si vous utilisez R mais contient en tout cas de jolis cheatsheets, etc.), mais ne semble pas couvrir les interactions pour le moment. Et ceux-ci sont tous agnostiques envers les modèles, ils fonctionneront donc pour les forêts aléatoires, les GBM, les réseaux de neurones, etc.

antike
la source
(+1) Belles ressources!
mardi
2

Les méthodes de forêt causale développées récemment constituent une légère modification des forêts aléatoires fournissant davantage d'informations sur les données. Voir le paquet GRF R et le papier de motivation ici . L'idée est d'utiliser les méthodes de base de la forêt aléatoire pour trouver l'hétérogénéité des effets causals.

Un article précédent ( ici ) donne une approche détaillée d’une forêt causale simple. La page 9 du document décrit une procédure étape par étape pour la croissance d'un arbre causal, qui peut ensuite être étendu à une forêt de la manière habituelle.Tiré de la page 9 de Athey et Wager 2017

Équation 4:

Équation 4

Équation 5: Équation 5

gannawag
la source
1
Mise à jour avec des liens vers des documents antérieurs et des captures d'écran de ce document pour afficher la procédure de l'arbre causal.
Gannawag
1

Réponse tardive liée à ma question ici ( Pouvons-nous rendre Random Forest 100% interprétable en réparant la graine? ):

Soit la graine dans la création d'un ensemble de formation boosté, et soit la graine dans la sélection du sous-ensemble de fonctionnalités (pour simplifier, je ne liste ici que 2 types de graines).z1z2

  1. A partir de , ensembles d'entraînement boostés sont créés: , , , ..., . z1mD1(z1)D2(z1)D3(z1)Dm(z1)
  2. À partir de ces ensembles de transfert, sont créés les arbres de décision correspondants, et réglés par validation croisée: , , , ..., .mT1(z1,z2)T2(z1,z2)T3(z1,z2)Tm(z1,z2)
  3. Notons les prédictions de l' arborescence pour un individu (provenant d'un ensemble de formation ou de test, peu importe), sous la . Par conséquent, les prédictions finales des arbres d'ensemble sont: jth(j=1,2,...,m)xif^j(xi)(in,jm)
    F^(xi)=>1mj=1mf^j(xi)
  4. Une fois le modèle validé et stable (la signification de ne dépend pas fortement de la paire ). Je commence à créer toutes les combinaisons possibles de mes fonctionnalités , ce qui me donne un très grand ensemble ( ).F^(xi)(z1,z2)xi
  5. Appliquer ma forêt sur chaque me donne les prédictions correspondantes:xi
    x1F^(x1) - which is fixed> thanks to (z1,z2)
    x2F^(x2) -> which is fixed thanks to (z1,z2)
    x ' 4 > F (x ' 4 ) - qui est fixée grâce à  ( z 1 , > z 2 ) . . . .
    x3→>F^(x3) - which is fixed thanks to (z1,z2)
    x4>→F^(x4) - which is fixed thanks to (z1,>z2)
    ....
  6. Ce dernier peut être facilement représenté sous la forme d' un seul (énorme) arbre . Par exemple: : (Age = 18, sexe = M, ...), = (Age = 18, sexe = F, ...), ... peut être regroupé pour créer une feuille. x ' 2x1x2

Cela fonctionne aussi pour toutes les méthodes d'ensemble basées sur l'agrégation d'arbres.

Métariat
la source