特征交互约束
决策树是发现自变量(特征)之间交互作用的强大工具。在遍历路径中一起出现的变量彼此之间存在交互作用,因为子节点的条件取决于父节点的条件。例如,下图高亮显示的红色路径包含三个变量:\(x_1\)、\(x_7\) 和 \(x_{10}\),因此高亮显示的预测(在高亮显示的叶节点处)是 \(x_1\)、\(x_7\) 和 \(x_{10}\) 之间交互作用的产物。
当树的深度大于 1 时,许多变量仅基于最小化训练损失而进行交互,结果决策树可能会捕获虚假关系(噪声),而不是跨不同数据集泛化的合法关系。特征交互约束允许用户决定哪些变量可以交互,哪些变量不能交互。
潜在的好处包括
通过专注于有效的交互作用(无论是通过领域特定知识还是通过对交互作用进行排名的算法),获得更好的预测性能
预测中的噪声更少;更好的泛化能力
用户对模型可以拟合的内容拥有更多控制权。例如,即使某些交互作用表现良好,用户也可能因监管限制而将其排除。
一个简单的例子
特征交互约束以允许交互的变量组表示。例如,约束 [0, 1]
表示变量 \(x_0\) 和 \(x_1\) 允许彼此交互,但不能与其他变量交互。同样,[2, 3, 4]
表示 \(x_2\)、\(x_3\) 和 \(x_4\) 允许彼此交互,但不能与其他变量交互。一组特征交互约束表示为嵌套列表,例如 [[0, 1], [2, 3, 4]]
,其中每个内部列表是允许彼此交互的特征索引组。
在下图中,左侧决策树违反了第一个约束([0, 1]
),而右侧决策树符合第一个和第二个约束([0, 1]
、[2, 3, 4]
)。
禁止 |
允许 |
在 XGBoost 中强制执行特征交互约束
在 XGBoost 中强制执行特征交互约束非常简单。这里我们将使用 Python 给出示例,但相同的通用思想也适用于其他平台。
假设以下代码在没有特征交互约束的情况下拟合您的模型
model_no_constraints = xgb.train(params, dtrain,
num_boost_round = 1000, evals = evallist,
early_stopping_rounds = 10)
那么使用特征交互约束进行拟合只需要添加一个参数
params_constrained = params.copy()
# Use nested list to define feature interaction constraints
params_constrained['interaction_constraints'] = '[[0, 2], [1, 3, 4], [5, 6]]'
# Features 0 and 2 are allowed to interact with each other but with no other feature
# Features 1, 3, 4 are allowed to interact with one another but with no other feature
# Features 5 and 6 are allowed to interact with each other but with no other feature
model_with_constraints = xgb.train(params_constrained, dtrain,
num_boost_round = 1000, evals = evallist,
early_stopping_rounds = 10)
使用特征名称代替
XGBoost 的 Python 和 R 包支持使用特征名称而不是特征索引来指定约束。给定一个包含列 ["f0", "f1", "f2"]
的数据框,特征交互约束可以指定为 [["f0", "f2"]]
(Python)或 list(c("f0", "f2"))
(R,当将它们传递给函数 xgboost()
时)。
高级主题
交互约束的直觉很简单。用户可能对不同特征之间的关系有先验知识,并在模型构建期间将其编码为约束。但指定约束也存在一些微妙之处。以约束 [[1, 2], [2, 3, 4]]
为例。第二个特征出现在两个不同的交互集 [1, 2]
和 [2, 3, 4]
中。因此,允许与 2
交互的特征的并集是 {1, 3, 4}
。在下图中,根节点在特征 2
处分裂。因为它的所有后代都应该能够与它交互,所以在第二层,所有 4 个特征都是合法的分裂候选。乍一看,这可能看起来像忽略了指定的约束集,但事实并非如此。

{1, 2, 3, 4}
表示合法的分裂特征集。
这导致了特征交互约束的一些有趣含义。以 [[0, 1], [0, 1, 2], [1, 2]]
为另一个例子。假设我们的训练数据集中只有 3 个可用特征用于演示目的,细心的读者可能已经发现上述约束与 [[0, 1, 2]]
相同。因为无论根节点选择哪个特征进行分裂,其所有后代都允许将每个特征作为合法的分裂候选,而不会违反交互约束。
最后一个例子,我们使用 [[0, 1], [1, 3, 4]]
并选择特征 0
作为根节点的分裂。在构建的树的第二层,1
是除 0
本身之外唯一的合法分裂候选,因为它们属于同一个约束集。按照我们下面示例树的增长路径,第二层的节点在特征 1
处分裂。但由于 1
也属于第二个约束集 [1, 3, 4]
,因此在第三层,我们允许将所有特征作为分裂候选,并且仍然符合其祖先的交互约束。

{0, 1, 3, 4}
表示合法的分裂特征集。