我将使用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_
是一个包含助推器中的各个树的数组,因此 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
值得回顾的是,杂质是度量标准的通用名称,用于确定在生长树时进行哪些拆分。有鉴于此,我们只是简单地总结了每个特征的分裂程度,使我们能够减少树中所有分裂的杂质。
在梯度提升的背景下,这些树总是回归树(贪婪地最小化平方误差),适合损失函数的梯度。