在机器学习领域,梯度提升树是一种流行的集成学习方法,它通过构建多个决策树来提高模型的预测性能。然而,在某些情况下,希望模型能够遵循特定的趋势,例如,与某个特征正相关或负相关。这时,可以在训练过程中对特征施加单调性约束,以引导模型学习到期望的趋势。
为了展示单调约束的效果,构建了一个人工数据集。在这个数据集中,目标值与第一个特征总体上呈正相关,而与第二个特征总体上呈负相关。这种相关性中包含了一些随机和非随机的变化。通过在训练过程中对特征施加单调性增加或减少的约束,模型能够更好地遵循总体趋势,而不是受到这些变化的影响。
下面是一个使用Python中的scikit-learn库来实现这一过程的示例代码。首先,导入必要的库,并设置随机数生成器的种子,以确保结果的可重复性。然后,生成了两个特征的数据,并添加了一些噪声。目标值与第一个特征正相关,与第二个特征负相关,但这种关系中包含了一些正弦和余弦函数的波动。
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.inspection import PartialDependenceDisplay
# 设置随机数生成器的种子
rng = np.random.RandomState(0)
n_samples = 1000
f_0 = rng.rand(n_samples)
f_1 = rng.rand(n_samples)
X = np.c_[f_0, f_1]
noise = rng.normal(loc=0.0, scale=0.01, size=n_samples)
# 目标值与第一个特征正相关,与第二个特征负相关
y = 5 * f_0 + np.sin(10 * np.pi * f_0) - 5 * f_1 - np.cos(10 * np.pi * f_1) + noise
接下来,首先在没有任何约束的情况下对这个数据集进行建模。然后,对同一个数据集施加单调性增加和减少的约束,并再次进行建模。通过比较两个模型的部分依赖图,可以观察到,没有约束的模型捕捉到了数据的波动,而有约束的模型则遵循了总体趋势,忽略了局部变化。
# 无约束模型
gbdt_no_cst = HistGradientBoostingRegressor()
gbdt_no_cst.fit(X, y)
# 有单调性约束的模型
gbdt_with_monotonic_cst = HistGradientBoostingRegressor(monotonic_cst=[1, -1])
gbdt_with_monotonic_cst.fit(X, y)
# 显示两个模型的部分依赖图
fig, ax = plt.subplots()
disp = PartialDependenceDisplay.from_estimator(gbdt_no_cst, X, features=[0, 1], feature_names=("第一特征", "第二特征"), line_kw={"linewidth": 4, "label": "无约束", "color": "tab:blue"}, ax=ax)
PartialDependenceDisplay.from_estimator(gbdt_with_monotonic_cst, X, features=[0, 1], line_kw={"linewidth": 4, "label": "有约束", "color": "tab:orange"}, ax=disp.axes_)
for f_idx in (0, 1):
disp.axes_[0, f_idx].plot(X[:, f_idx], y, "o", alpha=0.3, zorder=-1, color="tab:green")
disp.axes_[0, f_idx].set_ylim(-6, 6)
plt.legend()
fig.suptitle("单调约束对部分依赖的影响")
plt.show()
通过上述代码,可以看到,单调约束有效地引导了模型的学习过程,使其能够更好地捕捉数据的总体趋势。此外,如果训练数据中包含了特征名称,还可以通过传递一个字典来指定单调约束,这为模型训练提供了更多的灵活性。
import pandas as pd
# 将特征数据转换为DataFrame,并指定特征名称
X_df = pd.DataFrame(X, columns=["f_0", "f_1"])
# 使用特征名称指定单调约束
gbdt_with_monotonic_cst_df = HistGradientBoostingRegressor(monotonic_cst={"f_0": 1, "f_1": -1})
gbdt_with_monotonic_cst_df.fit(X_df, y)
# 验证两个模型的预测结果是否一致
np.allclose(gbdt_with_monotonic_cst_df.predict(X_df), gbdt_with_monotonic_cst.predict(X))