Voy a usar el sklearn código, ya que es en general mucho más limpio que el R
có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.