Я буду использовать код sklearn , так как он, как правило, намного чище, чем R
код.
Вот реализация свойства feature_importances класса GradientBoostingClassifier (я удалил несколько строк кода, которые мешают концептуальным вещам)
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
Это довольно легко понять. self.estimators_
это массив, содержащий отдельные деревья в бустере, поэтому цикл for выполняет итерации по отдельным деревьям. Есть один сбой с
stage_sum = sum(tree.feature_importances_
for tree in stage) / len(stage)
это заботится о случае недвоичного ответа. Здесь мы вписываем несколько деревьев на каждом этапе по принципу «один против всех». Проще всего концептуально сосредоточиться на двоичном случае, где сумма имеет одно слагаемое, и это справедливо tree.feature_importances_
. Таким образом, в двоичном случае, мы можем переписать все это как
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
Итак, на словах, суммируйте значения свойств отдельных деревьев, а затем разделите на общее количество деревьев . Осталось посмотреть, как рассчитать значения объектов для одного дерева.
Вычисление важности дерева реализовано на уровне Cython , но оно все еще можно отслеживать. Вот очищенная версия кода
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
Это довольно просто. Итерация по узлам дерева. Пока вы не находитесь на листовом узле, рассчитайте взвешенное снижение чистоты узла из разделения на этом узле и отнесите его к объекту, который был разделен на
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)
Затем, когда это будет сделано, разделите все это на общий вес данных (в большинстве случаев количество наблюдений)
importances /= nodes[0].weighted_n_node_samples
Стоит напомнить, что примесь - это общее название для метрики, которое используется при определении того, что разделить, чтобы сделать при выращивании дерева. В этом свете мы просто суммируем, насколько расщепление на каждом объекте позволило нам уменьшить примеси во всех расщеплениях в дереве.
В контексте повышения градиента эти деревья всегда являются деревьями регрессии (жадно минимизируют квадратичную ошибку), подходящими для градиента функции потерь.