单调约束

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

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

\[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(无约束)。