我将使用sklearn代码,因为它通常比R
代码干净得多。
这是GradientBoostingClassifier的feature_importances属性的实现(我删除了一些妨碍概念性内容的代码行)
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_
是在booster中包含各个树的数组,因此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
值得回顾的是,杂质是度量标准的通用名称,该度量标准用于确定生长树木时进行的分割。有鉴于此,我们只是简单地总结了每个特征的分割量,使我们能够减少树中所有分割处的杂质。
在梯度增强的情况下,这些树始终是适合损失函数梯度的回归树(贪婪地最小化平方误差)。