单调约束

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

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

\[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\) 特征,变化是递减的,并伴随一个正弦变化。

正弦拟合中的数据

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

无约束模型的拟合

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

以下是相同的模型,但带有单调性约束:

带约束的模型拟合

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

在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 包支持使用特征名称而不是特征索引来指定约束。给定一个包含列 ["f0", "f1", "f2"] 的数据框,单调约束可以指定为 {"f0": 1, "f2": -1},而 "f1" 将默认为 ``0``(无约束)。