Ich werde den sklearn- Code verwenden, da dieser im Allgemeinen viel sauberer ist als der R
Code.
Hier ist die Implementierung der Eigenschaft feature_importances des GradientBoostingClassifier (ich habe einige Codezeilen entfernt, die den konzeptionellen Aspekten im Wege stehen)
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
Das ist ziemlich einfach zu verstehen. self.estimators_
ist ein Array, das die einzelnen Bäume im Booster enthält, sodass die for-Schleife über die einzelnen Bäume iteriert. Es gibt ein Problem mit der
stage_sum = sum(tree.feature_importances_
for tree in stage) / len(stage)
Dies kümmert sich um den nicht-binären Antwortfall. Hier passen wir mehrere Bäume in jeder Stufe in einer Eins-gegen-Alles-Weise an. Es ist konzeptionell am einfachsten, sich auf den Binärfall zu konzentrieren, in dem die Summe einen Summanden hat, und das ist gerecht tree.feature_importances_
. Im Binärfall können wir das also alles umschreiben als
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
Also, in Worten, fasse die Wichtigkeit der Merkmale der einzelnen Bäume zusammen und dividiere sie durch die Gesamtzahl der Bäume . Es bleibt abzuwarten, wie die Feature-Wichtigkeiten für einen einzelnen Baum berechnet werden.
Die Wichtigkeitsberechnung eines Baums wird auf Cython-Ebene implementiert , ist jedoch nachvollziehbar. Hier ist eine bereinigte Version des Codes
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
Das ist ziemlich einfach. Iterieren Sie durch die Knoten des Baums. Solange Sie sich nicht an einem Blattknoten befinden, berechnen Sie die gewichtete Verringerung der Knotenreinheit aus der Aufteilung an diesem Knoten und ordnen Sie sie dem Feature zu, für das die Aufteilung ausgeführt wurde
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)
Teilen Sie dann alles durch das Gesamtgewicht der Daten (in den meisten Fällen die Anzahl der Beobachtungen).
importances /= nodes[0].weighted_n_node_samples
Es lohnt sich daran zu erinnern, dass die Verunreinigung ein gebräuchlicher Name für die Metrik ist, um zu bestimmen, welche Aufteilung beim Wachsen eines Baums vorgenommen werden soll. In diesem Licht fassen wir einfach zusammen, wie viel Aufteilung für jedes Feature es uns ermöglicht hat, die Verunreinigung über alle Aufteilungen im Baum hinweg zu reduzieren.
Im Kontext der Gradientenverstärkung sind diese Bäume immer Regressionsbäume (quadratische Fehler gierig minimieren), die an den Gradienten der Verlustfunktion angepasst sind.