Obteniendo conocimiento de un bosque aleatorio

127

Los bosques aleatorios se consideran cajas negras, pero recientemente estaba pensando qué conocimiento se puede obtener de un bosque aleatorio.

Lo más obvio es la importancia de las variables, en la variante más simple se puede hacer simplemente calculando el número de ocurrencias de una variable.
La segunda cosa en la que estaba pensando son las interacciones. Creo que si el número de árboles es suficientemente grande, entonces se puede probar el número de ocurrencias de pares de variables (algo así como la independencia de chi cuadrado). Lo tercero son las no linealidades de las variables. Mi primera idea fue solo mirar un gráfico de una puntuación Vs variable, pero aún no estoy seguro de si tiene algún sentido.

Añadido 23.01.2012
Motivation

Quiero usar este conocimiento para mejorar un modelo logit. Creo (o al menos eso espero) que es posible encontrar interacciones y no linealidades que se pasaron por alto.

Tomek Tarczynski
fuente
1
Usando R puedes producir un Dotchart de importancia variable medido por un Bosque Aleatorio.
George Dontas
1
un hilo relacionado sobre cómo se calculan las medidas de importancia variable para impulsar el árbol de gradiente estocástico
Antoine
Me doy cuenta de que esto probablemente sea demasiado tarde, pero si solo desea mejorar un modelo logit, ¿por qué no utiliza la regresión logística posterior al lazo? Puede volver a ajustar el modelo utilizando los coeficientes seleccionados después de la selección sin penalización / contracción. Tendrá que ajustar un poco el procedimiento de ajuste, pero esta es una opción mucho más directa que hace exactamente lo que desea.
ilprincipe

Respuestas:

122

Los bosques al azar son apenas una caja negra. Se basan en árboles de decisión, que son muy fáciles de interpretar:

#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)

Esto da como resultado un árbol de decisión 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, este árbol clasifica la observación como "otro". Si es mayor que 4.95, clasifica la observación como "virginica". Un bosque aleatorio es una simple colección de muchos de estos árboles, donde cada uno es entrenado en un subconjunto aleatorio de los datos. Cada árbol "vota" en la clasificación final de cada observación.

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

Incluso puede extraer árboles individuales del rf y observar su estructura. El formato es ligeramente diferente al de los rpartmodelos, pero puede inspeccionar cada árbol si lo desea y ver cómo modela los datos.

Además, ningún modelo es realmente un recuadro negro, porque puede examinar las respuestas pronosticadas frente a las respuestas reales para cada variable en el conjunto de datos. Esta es una buena idea independientemente del tipo de modelo que esté construyendo:

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)

trama

He normalizado las variables (sepal y pétalo de largo y ancho) a un rango de 0-1. La respuesta también es 0-1, donde 0 es otro y 1 es virginica. Como puede ver, el bosque aleatorio es un buen modelo, incluso en el conjunto de prueba.

Además, un bosque aleatorio calculará varias medidas de importancia variable, que pueden ser muy informativas:

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

Esta tabla representa cuánto eliminar cada variable reduce la precisión del modelo. Finalmente, hay muchas otras parcelas que puede hacer a partir de un modelo de bosque aleatorio, para ver lo que está sucediendo en el cuadro negro:

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)])

Puede ver los archivos de ayuda para cada una de estas funciones para tener una mejor idea de lo que muestran.

Zach
fuente
66
Gracias por la respuesta, hay mucha información útil, pero no es exactamente lo que estaba buscando. Tal vez necesito aclarar mejor la motivación que hay detrás de esta pregunta. Quiero usar un bosque aleatorio para mejorar un modelo logit, por mejorar quiero decir agregar alguna interacción o usar una transformación no lineal.
Tomek Tarczynski el
@TomekTarczynski es un problema interesante y similar al que estoy tratando en este momento. Supongo que por "modelo logit" te refieres a regresión logística o algo similar. Estoy usando la regresión logística de lazo (del paquete glmnet R) para seleccionar predictores de un modelo con interacciones entre todos los pares de variables. Todavía no he agregado ningún término no lineal, pero en principio eso también debería ser posible. Supongo que el único problema es decidir qué términos no lineales probar (términos polinómicos, transformaciones exponenciales, etc.). Además, no estoy captando ninguna interacción de orden superior, pero eso también es fácil.
Anne Z.
2
@Tomek, ¿qué no obtienes de esta respuesta? Si está utilizando el paquete randomForest en R, los gráficos que describe Zach deberían ser muy útiles. Específicamente, podría usar varImpPlot para la selección de características en su modelo logit y partialPlot para estimar el tipo de transformación para probar predictores continuos en el modelo logit. Sugeriría que la última gráfica se use para determinar dónde existen relaciones no lineales entre el predictor y la respuesta y luego le permite hacer esa transformación explícitamente o usar una spline en esa variable.
B_Miner
2
@b_miner: solo una suposición, pero parece que Tomek pregunta cómo encontrar interacciones no lineales entre variables porque la regresión logística ya captura las relaciones lineales.
rm999
@ rm999 ¿Cómo define una interacción no lineal en un modelo logit? ¿Términos de interacción creados entre variables transformadas?
B_Miner
52

Hace algún tiempo tuve que justificar un modelo de RF ajustado a algunos químicos en mi empresa. Pasé bastante tiempo probando diferentes técnicas de visualización. Durante el proceso, accidentalmente se me ocurrieron algunas técnicas nuevas que puse en un paquete R ( forestFloor ) específicamente para visualizaciones aleatorias del bosque.

El enfoque clásico son las parcelas de dependencia parcial respaldadas por: Rminer (el análisis de sensibilidad basado en datos es una dependencia parcial reinventada) o la parcela parcial en el paquete randomForest . Encuentro el paquete de dependencia parcial iceBOX como una forma elegante de descubrir interacciones. No he usado el paquete edarf , pero parece tener algunas visualizaciones finas dedicadas a RF. El paquete ggRandomForest también contiene un gran conjunto de visualizaciones útiles.

Actualmente forestFloor admite objetos randomForest (está en camino la compatibilidad con otras implementaciones de RF). También se pueden calcular las contribuciones de características para los árboles impulsados ​​por gradiente, ya que estos árboles después del entrenamiento no son muy diferentes de los árboles forestales aleatorios. Entonces forestFloor podría soportar XGBoost en el futuro. Las parcelas de dependencia parcial son completamente invariantes modelo.

Todos los paquetes tienen en común visualizar la estructura de mapeo geométrico de un modelo desde el espacio de características hasta el espacio objetivo. Una curva sinusoidal y = sin (x) sería un mapeo de xay y se puede trazar en 2D. Trazar un mapeo de RF directamente a menudo requeriría demasiadas dimensiones. En cambio, la estructura general de mapeo se puede proyectar, dividir o descomponer, de modo que toda la estructura de mapeo se reduzca a una secuencia de gráficos marginales 2D. Si su modelo de RF solo ha capturado los efectos principales y no interacciones entre variables, los métodos de visualización clásicos funcionarán bien. Luego puede simplificar la estructura de su modelo de esta maneray=F(X)f1(x1)+f2(x2)+...+fd(xd). Entonces, cada función parcial de cada variable se puede visualizar como la curva sinusoidal. Si su modelo de RF ha capturado interacciones considerables, entonces es más problemático. Los sectores 3D de la estructura pueden visualizar interacciones entre dos entidades y la salida. El problema es saber qué combinación de funciones visualizar ( iceBOX aborda este problema). Además, no es fácil saber si otras interacciones latentes aún no se tienen en cuenta.

En este artículo , utilicé una versión muy temprana de forestFloor para explicar qué relación bioquímica real había capturado un modelo de RF muy pequeño. Y en este artículo describimos minuciosamente las visualizaciones de las contribuciones de características, las visualizaciones del suelo forestal de los bosques aleatorios .

He pegado el ejemplo simulado del paquete forestFloor, donde muestro cómo descubrir una función oculta simulada noisey=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  
)

ingrese la descripción de la imagen aquí

#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) 

ingrese la descripción de la imagen aquí

#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)

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Por último, el código de parcelas de dependencia parcial codificado por A.Liaw descrito por J.Friedman. Que hacen bien para los efectos principales.

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

ingrese la descripción de la imagen aquí

Soren Havelund Welling
fuente
Anteriormente postulé que las contribuciones de características también podrían calcularse para árboles potenciados. Escribí un algoritmo de prueba y es posible. Desafortunadamente, las contribuciones de características para los árboles impulsados ​​no muestran las mismas propiedades beneficiosas que para los árboles embolsados. Los efectos de una característica tienden a dispersarse en todas las contribuciones de características, por lo que la visualización se vuelve bastante confusa.
Soren Havelund Welling
Oups! Encontré un error en mi algoritmo de prueba que hizo que todos los problemas se disiparan. forestFloor podría actualizarse para que funcione bien para árboles impulsados ​​por gradiente.
Soren Havelund Welling
3
¿Se actualiza forestFloor para aceptar objetos gbm?
Misha
Hasta ahora, he realizado una implementación inversa, que envuelve la implementación randomForest en una máquina de refuerzo de gradiente completamente funcional y define algunos métodos para calcular las contribuciones de características. Creo que la implementación en realidad es razonablemente eficiente. Puede encontrar el código aquí: github.com/sorhawell/forestFloor/blob/master/inst/examples/…
Soren Havelund Welling
Hacer un puerto completo es un trabajo duro :) Esta implementación se realizó en muy pocas líneas.
Soren Havelund Welling
24

Para complementar estas excelentes respuestas, mencionaría el uso de árboles potenciados en gradiente (por ejemplo, el paquete GBM en R ). En R, prefiero esto a bosques aleatorios porque se permiten valores faltantes en comparación con randomForest donde se requiere imputación. La importancia variable y los gráficos parciales están disponibles (como en randomForest) para ayudar en la selección de características y la exploración de transformación no lineal en su modelo logit. Además, la interacción variable se aborda con el estadístico H de Friedman ( interact.gbm) con referencia dada como J.H. Friedman and B.E. Popescu (2005). “Predictive Learning via Rule Ensembles.” Section 8.1. Una versión comercial llamada TreeNet está disponible en Salford Systems y esta presentación de video habla de su opinión sobre el video de estimación de interacción variable .

B_Miner
fuente
2
Estoy de acuerdo, los GBM son el siguiente paso lógico de los bosques aleatorios.
Zach
@B_miner: ¡Genial! No sé cómo, pero he pasado por alto GBM. Parece que con GBM es fácil detectar interacciones y no linealidades.
Tomek Tarczynski
15

Respuesta tardía, pero me encontré con un paquete R reciente forestFloor(2015) que te ayuda a realizar esta tarea de "desbloqueo" de manera automatizada. ¡Se ve muy prometedor!

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))

Produce las siguientes parcelas:

ingrese la descripción de la imagen aquí

También proporciona visualización tridimensional si está buscando interacciones.

RUser4512
fuente
44
Eliminé el soporte para ggplot2, en lugar de la última línea, por ejemplo: plot (ff, 1: 9, col = fcol (ff, 1: 4))
Soren Havelund Welling
9

Como mencionó Zach, una forma de entender un modelo es trazar la respuesta a medida que varían los predictores. Puede hacer esto fácilmente para "cualquier" modelo con el paquete plotmo R. Por ejemplo

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")

lo que da

trama

Esto cambia una variable mientras mantiene a las otras en sus valores medios. Para tramas de interacción, cambia dos variables. (Nota agregada en noviembre de 2016: plotmoahora también admite parcelas de dependencia parcial).

El ejemplo anterior usa solo dos variables; Los modelos más complicados se pueden visualizar de manera gradual mirando una o dos variables a la vez. Dado que las "otras" variables se mantienen en sus valores medios, esto muestra solo una porción de los datos, pero aún puede ser útil. Algunos ejemplos se encuentran en la viñeta del paquete plotmo . Otros ejemplos se encuentran en el Capítulo 10 de Trazado de árboles rpart con el paquete rpart.plot .

Stephen Milborrow
fuente
4

Estoy muy interesado en este tipo de preguntas yo mismo. Creo que hay mucha información que podemos obtener de un bosque aleatorio.

Sobre las interacciones, parece que Breiman y Cultier ya han tratado de analizarlo, especialmente para las RF de clasificación.

Que yo sepa, esto no se ha implementado en el paquete randomForest R. Tal vez porque podría no ser tan simple y porque el significado de "interacciones variables" depende mucho de su problema.

Acerca de la no linealidad, no estoy seguro de lo que está buscando, los bosques de regresión se usan para problemas de regresión múltiple no lineal sin antecedentes sobre qué tipo de función no lineal usar.

Rémy Nicolle
fuente
3

Al final del juego, pero hay algunos desarrollos nuevos en este frente, por ejemplo, LIME y SHAP . También un paquete que vale la pena verificar es DALEX (en particular si usa R pero en cualquier caso contiene buenas hojas de trucos, etc.), aunque no parece cubrir las interacciones en este momento. Y estos son todos independientes del modelo, por lo que funcionarán para bosques aleatorios, GBM, redes neuronales, etc.

antike
fuente
(+1) ¡Buenos recursos!
mkt
2

Una ligera modificación de los bosques aleatorios que proporcionan más información sobre los datos son los métodos forestales causales desarrollados recientemente. Vea el paquete GRF R y el documento motivador aquí . La idea es usar los métodos de referencia aleatorios del bosque para encontrar heterogeneidad en los efectos causales.

Un artículo anterior ( aquí ) ofrece un enfoque detallado de un bosque causal simple. La página 9 del documento ofrece un procedimiento paso a paso para cultivar un árbol causal, que luego puede expandirse a un bosque de la manera habitual.Tomado de la página 9 de Athey y Wager 2017

Ecuación 4:

Ecuación 4

Ecuación 5: Ecuación 5

gannawag
fuente
1
Actualizado con enlaces a documentos anteriores y capturas de pantalla de ese documento para mostrar el procedimiento del árbol causal.
gannawag
1

Respuesta tardía relacionada con mi pregunta aquí ( ¿Podemos hacer que Random Forest sea 100% interpretable arreglando la semilla? ):

z1z2

  1. z1mD1(z1)D2(z1)D3(z1)Dm(z1)
  2. mT1(z1,z2)T2(z1,z2)T3(z1,z2)Tm(z1,z2)
  3. jth(j=1,2,...,m)xif^j(xi)(in,jm)
    F^(xi)=>1mj=1mf^j(xi)
  4. F^(xi)(z1,z2)xi
  5. xi
    x1F^(x1) - which is fixed> thanks to (z1,z2)
    x2F^(x2) -> which is fixed thanks to (z1,z2)
    x3→>F^(x3) - which is fixed thanks to (z1,z2)
    x4>→F^(x4) - which is fixed thanks to (z1,>z2)
    ....
  6. x1x2

Esto funciona también para todos los métodos de conjunto basados ​​en la agregación de árboles.

Metariado
fuente