分类数据

注意

截至 XGBoost 1.6,此功能仍是实验性的,功能有限。仅 Python 包得到完全支持。

版本 3.0 新增: 支持使用 factor 的 R 包。

从版本 1.5 开始,XGBoost Python 包已对分类数据提供实验性支持,可供公开测试。对于数值数据,分裂条件定义为 \(value < threshold\),而对于分类数据,分裂取决于使用划分还是独热编码。对于基于划分的分裂,分裂指定为 \(value \in categories\),其中 categories 是一个特征中的类别集合。如果使用独热编码,则分裂定义为 \(value == category\)。计划在未来版本中提供更高级的分类分裂策略,本教程详细介绍了如何将数据类型告知 XGBoost。

使用 scikit-learn 接口进行训练

将分类数据传递给 XGBoost 最简单的方法是使用 dataframe 和 scikit-learn 接口,例如 XGBClassifier。为了准备数据,用户需要将输入预测变量的数据类型指定为 category。对于 pandas/cudf Dataframe,可以通过以下方式实现:

X["cat_feature"].astype("category")

对于所有表示分类特征的列。之后,用户可以告诉 XGBoost 启用分类数据训练。假设您正在使用 XGBClassifier 处理分类问题,请指定参数 enable_categorical

# Supported tree methods are `approx` and `hist`.
clf = xgb.XGBClassifier(tree_method="hist", enable_categorical=True, device="cuda")
# X is the dataframe we created in previous snippet
clf.fit(X, y)
# Must use JSON/UBJSON for serialization, otherwise the information is lost.
clf.save_model("categorical-model.json")

训练完成后,大多数其他功能都可以利用该模型。例如,可以绘制模型并计算全局特征重要性

# Get a graph
graph = xgb.to_graphviz(clf, num_trees=1)
# Or get a matplotlib axis
ax = xgb.plot_tree(clf, num_trees=1)
# Get feature importances
clf.feature_importances_

dask 的 scikit-learn 接口与单节点版本相似。基本思想是创建具有分类特征类型的 dataframe,并通过设置 enable_categorical 参数告诉 XGBoost 使用它。有关使用独热编码的 scikit-learn 接口处理分类数据的实际示例,请参阅 分类数据入门。使用独热编码数据与 XGBoost 分类数据支持之间的比较可在 使用 cat_in_the_dat 数据集训练 XGBoost 中找到。

最优划分

版本 1.6 新增。

最优划分是一种在每次节点分裂时对分类预测变量进行划分的技术,对于数值输出的最优性证明最初由 [1] 提出。该算法在决策树中得到了应用 [2],后来 LightGBM [3] 将其引入到梯度提升树的背景下,现在 XGBoost 也将其作为处理分类分裂的可选功能。更具体地说,Fisher 的证明 [1] 表明,在尝试根据这些离散值的度量之间的距离将一组离散值划分为组时,只需要查看排序后的划分,而无需枚举所有可能的排列。在决策树的背景下,离散值是类别,度量是输出叶节点值。直观地说,我们希望将输出相似叶节点值的类别分组。在寻找分裂时,我们首先对梯度直方图进行排序以准备连续的划分,然后根据这些排序值枚举分裂。XGBoost 的一个相关参数是 max_cat_to_onehot,它控制对每个特征是使用独热编码还是划分,详情请参阅 分类特征参数

使用原生接口

scikit-learn 接口对用户友好,但缺少一些只有原生接口才有的功能。例如,用户无法直接计算 SHAP 值。此外,原生接口支持更多数据类型。要使用带有分类数据的原生接口,我们需要将类似参数传递给 DMatrixQuantileDMatrixtrain 函数。对于 dataframe 输入

# X is a dataframe we created in previous snippet
Xy = xgb.DMatrix(X, y, enable_categorical=True)
booster = xgb.train({"tree_method": "hist", "max_cat_to_onehot": 5}, Xy)
# Must use JSON for serialization, otherwise the information is lost
booster.save_model("categorical-model.json")

SHAP 值计算

SHAP = booster.predict(Xy, pred_interactions=True)

# categorical features are listed as "c"
print(booster.feature_types)

对于其他类型的输入,例如 numpy array,我们可以通过在 DMatrix 中使用 feature_types 参数来告知 XGBoost 特征类型

# "q" is numerical feature, while "c" is categorical feature
ft = ["q", "c", "c"]
X: np.ndarray = load_my_data()
assert X.shape[1] == 3
Xy = xgb.DMatrix(X, y, feature_types=ft, enable_categorical=True)

对于数值数据,特征类型可以是 "q""float",而分类特征则指定为 "c"。XGBoost 中的 Dask 模块具有相同的接口,因此 dask.Array 也可以用于分类数据。最后,sklearn 接口 XGBRegressor 具有相同的参数。

数据一致性

XGBoost 接受参数来指示哪些特征被视为分类特征,这可以通过 dataframe 的 dtypes 或通过 feature_types 参数指定。然而,XGBoost 本身不存储关于类别最初如何编码的信息。例如,给定一个将音乐流派映射到整数编码的编码方案

{"acoustic": 0, "indie": 1, "blues": 2, "country": 3}

XGBoost 不知道输入中的这种映射,因此无法将其存储在模型中。这种映射通常发生在用户的数据工程管道中,使用诸如 sklearn.preprocessing.OrdinalEncoder 的列转换器。为了确保 XGBoost 的结果正确,用户需要在训练数据和测试数据中保持数据转换管道的一致性。应该警惕以下错误:

X_train["genre"] = X_train["genre"].astype("category")
reg = xgb.XGBRegressor(enable_categorical=True).fit(X_train, y_train)

# invalid encoding
X_test["genre"] = X_test["genre"].astype("category")
reg.predict(X_test)

在上面的代码片段中,训练数据和测试数据是分开编码的,导致了两种不同的编码方案和无效的预测结果。有关使用顺序编码器的实际示例,请参阅 分类数据特征工程管道

杂项

默认情况下,XGBoost 假定输入类别是从 0 到类别数 \([0, n\_categories)\) 的整数。然而,用户可能由于训练数据集中的错误或缺失值而提供无效的输入值。这可能是负值、32 位浮点数无法精确表示的整数值,或者大于实际唯一类别数的值。在训练期间,这是被验证的,但为了性能原因,在预测时它被视为与未选择的类别相同。

参考文献

[1] Walter D. Fisher. “关于最大同质性分组 (On Grouping for Maximum Homogeneity)”。Journal of the American Statistical Association. Vol. 53, No. 284 (1958年12月), pp. 789-798。

[2] Trevor Hastie, Robert Tibshirani, Jerome Friedman. “统计学习基础 (The Elements of Statistical Learning)”。Springer Series in Statistics Springer New York Inc. (2001)。

[3] Guolin Ke, Qi Meng, Thomas Finley, Taifeng Wang, Wei Chen, Weidong Ma, Qiwei Ye, Tie-Yan Liu. “LightGBM: 一种高效的梯度提升决策树 (LightGBM: A Highly Efficient Gradient Boosting Decision Tree)。” Advances in Neural Information Processing Systems 30 (NIPS 2017), pp. 3149-3157。