Je vais utiliser le code Sklearn , car il est généralement beaucoup plus propre que le R
code.
Voici l'implémentation de la propriété feature_importances de GradientBoostingClassifier (j'ai supprimé certaines lignes de code qui gênent le contenu conceptuel)
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
C'est assez facile à comprendre. self.estimators_
est un tableau contenant les arbres individuels dans le booster, de sorte que la boucle for parcourt les arbres individuels. Il y a un hickup avec le
stage_sum = sum(tree.feature_importances_
for tree in stage) / len(stage)
cela prend en charge le cas de réponse non binaire. Ici, nous ajustons plusieurs arbres dans chaque étape d'une manière one-vs-all. Son concept le plus simple est de se concentrer sur le cas binaire, où la somme a un sommand, et c'est juste tree.feature_importances_
. Donc, dans le cas binaire, nous pouvons réécrire tout cela comme
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
Donc, en mots, résumez les caractéristiques des arbres individuels, puis divisez-les par le nombre total d’arbres . Il reste à voir comment calculer l’importance des caractéristiques pour un seul arbre.
Le calcul de l'importance d'un arbre est implémenté au niveau de cython , mais il est toujours possible de le suivre. Voici une version nettoyée du code
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
C'est assez simple. Parcourez les nœuds de l’arbre. Tant que vous n'êtes pas à un nœud feuille, calculez la réduction pondérée de la pureté du nœud à partir de la scission au niveau de ce nœud et attribuez-la à l'entité scindée le
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)
Ensuite, divisez le tout par le poids total des données (dans la plupart des cas, le nombre d'observations).
importances /= nodes[0].weighted_n_node_samples
Il convient de rappeler que l’ impureté est un nom commun que la métrique doit utiliser pour déterminer la division à faire lors de la croissance d’un arbre. Dans cette optique, nous résumons simplement à quel point le fractionnement de chaque fonction nous a permis de réduire l’impureté dans toutes les divisions de l’arbre.
Dans le contexte de l’intensification du gradient, ces arbres sont toujours des arbres de régression (minimiser les erreurs carrées goulûment) adaptées au gradient de la fonction de perte.