单调性约束

在建模问题或项目中,可接受模型的函数形式常常会受到某种约束。这可能是出于业务考虑,也可能是因为所研究的科学问题类型。在某些情况下,如果对真实关系具有某种性质有非常强烈的先验信念,则可以使用约束来提高模型的预测性能。

在这种情况下,一种常见的约束类型是某些特征与预测响应之间存在**单调**关系

\[f(x_1, x_2, \ldots, x, \ldots, x_{n-1}, x_n) \leq f(x_1, x_2, \ldots, x', \ldots, x_{n-1}, x_n)\]

\(x \leq x'\) 时,这是一个**递增约束**;或者

\[f(x_1, x_2, \ldots, x, \ldots, x_{n-1}, x_n) \geq f(x_1, x_2, \ldots, x', \ldots, x_{n-1}, x_n)\]

\(x \leq x'\) 时,这是一个**递减约束**。

XGBoost 能够对增强模型中使用的任何特征施加单调性约束。

一个简单的例子

为了说明这一点,让我们根据以下方案创建一些具有两个特征和一个响应的模拟数据

\[y = 5 x_1 + \sin(10 \pi x_1) - 5 x_2 - \cos(10 \pi x_2) + N(0, 0.01) x_1, x_2 \in [0, 1]\]

响应通常随 \(x_1\) 特征的增加而增加,但叠加了一个正弦变化,导致真实效果是非单调的。对于 \(x_2\) 特征,变化是递减的并带有正弦变化。

Data in sinusoidal fit

让我们在不施加任何单调性约束的情况下,将增强树模型拟合到这些数据

Fit of Model with No Constraint

黑线显示了从模型推断出的每个特征的趋势。为了绘制这些图,将区分的特征 \(x_i\) 在一维值网格上输入到模型中,而所有其他特征(在这种情况下只有一个其他特征)都设置为它们的平均值。我们看到模型很好地捕捉了带有叠加振荡波的总体趋势。

这是相同的模型,但拟合了单调性约束

Fit of Model with Constraint

我们看到了约束的效果。对于每个变量,趋势的总体方向仍然很明显,但振荡行为不再存在,因为它会违反我们施加的约束。

在 XGBoost 中强制执行单调性约束

在 XGBoost 中强制执行单调性约束非常简单。这里我们将使用 Python 举例,但同样的一般思想也适用于其他平台。

假设以下代码在没有单调性约束的情况下拟合您的模型

model_no_constraints = xgb.train(params, dtrain,
                                 num_boost_round = 1000, evals = evallist,
                                 early_stopping_rounds = 10)

那么,在有单调性约束的情况下拟合只需添加一个参数即可

params_constrained = params.copy()
params_constrained['monotone_constraints'] = (1,-1)

model_with_constraints = xgb.train(params_constrained, dtrain,
                                   num_boost_round = 1000, evals = evallist,
                                   early_stopping_rounds = 10)

在此示例中,训练数据 X 有两列,通过使用参数值 (1,-1),我们告诉 XGBoost 对第一个预测器施加递增约束,对第二个预测器施加递减约束。

其他一些例子

  • (1,0):对第一个预测器施加递增约束,对第二个预测器不施加约束。

  • (0,-1):对第一个预测器不施加约束,对第二个预测器施加递减约束。

注意

**'hist' 树构建算法注意事项**。如果 tree_method 设置为 histapprox,启用单调性约束可能会产生不必要的浅层树。这是因为 hist 方法减少了在每个分裂处要考虑的候选分裂数量。单调性约束可能会清除所有可用的分裂候选,在这种情况下不会进行分裂。为了减少这种影响,您可能需要增加 max_bin 参数以考虑更多的分裂候选。

使用特征名称

XGBoost 的 Python 和 R 包支持使用特征名称而不是特征索引来指定约束。给定一个包含列 ["f0", "f1", "f2"] 的数据框,单调性约束可以指定为 {"f0": 1, "f2": -1} (Python) 或 list(f0=1, f2=-1) (R,在使用 'xgboost()' 时,但不使用 'xgb.train()' 时),而 "f1" 将默认为 0 (无约束)。