Importancia variable relativa para impulsar

33

Estoy buscando una explicación de cómo se calcula la importancia variable relativa en los árboles potenciados por gradientes que no es demasiado general / simplista como:

Las medidas se basan en la cantidad de veces que se selecciona una variable para dividir, ponderada por la mejora al cuadrado del modelo como resultado de cada división y promediada sobre todos los árboles . [ Elith y col. 2008, una guía de trabajo a los árboles de regresión impulsado ]

Y eso es menos abstracto que:

yoj2^(T)=t=1J-1yot2^1(vt=j)

Donde la suma es sobre el no terminal nodos de la árbol de nodos -terminal , es la variable de división asociado con el nodo , y es la mejora empírico correspondiente de error cuadrático como resultado de la división, definida como , donde son los medios de respuesta hija izquierdo y derecho respectivamente, y son las sumas correspondientes de los pesos. J T v t t ^ i 2 t i 2 ( R l , R r ) = w l w rtJTvttyot2^ ¯ y l , ¯ y r wl,wryo2(Rl,Rr)=wlwrwl+wr(yl¯-yr¯)2yl¯,yr¯wl,wr[ Friedman 2001, aproximación de la función codiciosa: una máquina de aumento de gradiente ]

Finalmente, no encontré que los Elementos de aprendizaje estadístico (Hastie et al. 2008) sean una lectura muy útil aquí, ya que la sección relevante (10.13.1 página 367) sabe muy similar a la segunda referencia anterior (que podría explicarse por el hecho de que Friedman es coautor del libro).

PD: Sé que las medidas de importancia variable relativa están dadas por el summary.gbm en el paquete gbm R. Traté de explorar el código fuente, pero parece que no puedo encontrar dónde tiene lugar el cálculo real.

Puntos de Brownie: Me pregunto cómo conseguir estas parcelas en R.

Antoine
fuente
Acabo de agregar una nueva respuesta a la pregunta vinculada sobre cómo trazar la importancia variable por clase que puede ser útil stackoverflow.com/a/51952918/3277050
24

Respuestas:

55

Voy a usar el sklearn código, ya que es en general mucho más limpio que el Rcódigo.

Aquí está la implementación de la propiedad feature_importances del GradientBoostingClassifier (eliminé algunas líneas de código que se interponen en el camino de las cosas conceptuales)

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for stage in self.estimators_:
        stage_sum = sum(tree.feature_importances_
                        for tree in stage) / len(stage)
        total_sum += stage_sum

    importances = total_sum / len(self.estimators_)
    return importances

Esto es bastante fácil de entender. self.estimators_es una matriz que contiene los árboles individuales en el refuerzo, por lo que el ciclo for está iterando sobre los árboles individuales. Hay un problema con el

stage_sum = sum(tree.feature_importances_
                for tree in stage) / len(stage)

esto se ocupa del caso de respuesta no binaria. Aquí ajustamos múltiples árboles en cada etapa de una manera uno contra todos. Es conceptualmente más simple enfocarse en el caso binario, donde la suma tiene un sumando, y esto es justo tree.feature_importances_. Entonces, en el caso binario, podemos reescribir todo esto como

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for tree in self.estimators_:
        total_sum += tree.feature_importances_ 
    importances = total_sum / len(self.estimators_)
    return importances

Entonces, en palabras, resumir las características de los árboles individuales, luego dividir por el número total de árboles . Queda por ver cómo calcular la importancia de las características para un solo árbol.

El cálculo de la importancia de un árbol se implementa a nivel de cython , pero aún se puede seguir. Aquí hay una versión limpia del código

cpdef compute_feature_importances(self, normalize=True):
    """Computes the importance of each feature (aka variable)."""

    while node != end_node:
        if node.left_child != _TREE_LEAF:
            # ... and node.right_child != _TREE_LEAF:
            left = &nodes[node.left_child]
            right = &nodes[node.right_child]

            importance_data[node.feature] += (
                node.weighted_n_node_samples * node.impurity -
                left.weighted_n_node_samples * left.impurity -
                right.weighted_n_node_samples * right.impurity)
        node += 1

    importances /= nodes[0].weighted_n_node_samples

    return importances

Esto es bastante simple Iterar a través de los nodos del árbol. Mientras no se encuentre en un nodo hoja, calcule la reducción ponderada de la pureza del nodo a partir de la división en este nodo, y atribuya a la función que se dividió en

importance_data[node.feature] += (
    node.weighted_n_node_samples * node.impurity -
    left.weighted_n_node_samples * left.impurity -
    right.weighted_n_node_samples * right.impurity)

Luego, cuando termine, divídalo todo por el peso total de los datos (en la mayoría de los casos, el número de observaciones)

importances /= nodes[0].weighted_n_node_samples

Vale la pena recordar que la impureza es un nombre común para la métrica que se usa al determinar qué división hacer al cultivar un árbol. En ese sentido, simplemente estamos resumiendo cuánto dividir en cada característica nos permitió reducir la impureza en todas las divisiones del árbol.

En el contexto del aumento de gradiente, estos árboles son siempre árboles de regresión (minimizar el error al cuadrado con avidez) ajustados al gradiente de la función de pérdida.

Matthew Drury
fuente
Muchas gracias por esta respuesta tan detallada. Permíteme un poco de tiempo para revisarlo cuidadosamente antes de aceptarlo.
Antoine
44
Si bien parece que se pueden usar varios criterios de impureza, el índice de Gini no fue el criterio utilizado por Friedman. Como mencioné en mi pregunta y en la línea 878 de su tercer enlace, Friedman usó el criterio de impureza de error cuadrático medio con puntaje de mejora . Si pudiera actualizar esta sección de su respuesta, sería genial. Y sí, tienes razón, parece que los pesos son de hecho el número de observaciones.
Antoine
3
¿O tal vez su respuesta sería aún mejor para mantener las partes sobre el índice de Gini y el criterio original de Friedman, haciendo hincapié en que el primero se utiliza para la clasificación y el segundo para la regresión?
Antoine
Antoine, gracias por esta actualización. Es realmente útil saber que el error cuadrático medio es el criterio de mejora utilizado para los árboles de regresión. No era obvio cómo se usaría eso para la clasificación. Sin embargo, incluso en el aumento de gradiente para la clasificación, creo que los árboles de regresión todavía se están utilizando, a diferencia de los árboles de clasificación. Al menos en Python, el análisis de regresión se está realizando sobre el error actual en cada etapa de refuerzo.
Bastante Nerdy el
Ustedes tienen razón sobre los árboles de regresión.
Matthew Drury el