预测

XGBoost 中有许多具有各种参数的预测函数。本文旨在澄清一些关于预测的困惑,重点关注 Python 绑定,R 包在指定 strict_shape 时类似(参见下文)。

预测选项

xgboost.Booster.predict() 方法有许多不同的预测选项,范围从 pred_contribspred_leaf。输出形状取决于预测类型。对于多类别分类问题,XGBoost 为每个类别构建一棵树,每个类别的树称为一个“组”的树,因此输出维度可能会因使用的模型而改变。在 1.4 版本发布后,我们添加了一个名为 strict_shape 的新参数,可以将其设置为 True 以表示需要更严格的输出。假设您正在使用 xgboost.Booster,以下是可能的返回值列表:

  • 当使用正常预测并将 strict_shape 设置为 True

    输出是一个 2 维数组,第一维表示行,第二维表示组。对于回归/生存/排序/二元分类,这相当于一个列向量,其中 shape[1] == 1。但对于使用 multi:softprob 的多类别分类,列数等于类别数。如果 strict_shape 设置为 False,则 XGBoost 可能会输出 1 维或 2 维数组。

  • 当使用 output_margin 以避免转换并将 strict_shape 设置为 True

    类似于前一种情况,输出是一个 2 维数组,不同之处在于由于转换被丢弃,multi:softmax 的输出形状与 multi:softprob 相同。如果 strict shape 设置为 False,则输出可以具有 1 维或 2 维,具体取决于使用的模型。

  • 当使用 pred_contribs 并将 strict_shape 设置为 True

    输出是一个 3 维数组,形状为 (rows, groups, columns + 1)。是否使用 approx_contribs 不会改变输出形状。如果未设置 strict shape 参数,则输出可以是 2 维或 3 维数组,具体取决于是否使用多类别模型。

  • 当使用 pred_interactions 并将 strict_shape 设置为 True

    输出是一个 4 维数组,形状为 (rows, groups, columns + 1, columns + 1)。与预测贡献情况类似,是否使用 approx_contribs 不会改变输出形状。如果 strict shape 设置为 False,则输出可以有 3 维或 4 维,具体取决于底层模型。

  • 当使用 pred_leaf 并将 strict_shape 设置为 True

    输出是一个 4 维数组,形状为 (n_samples, n_iterations, n_classes, n_trees_in_forest)n_trees_in_forest 在训练期间由 numb_parallel_tree 指定。当 strict shape 设置为 False 时,输出是一个 2 维数组,后 3 个维度合并为 1 个。如果最后一个维度等于 1,则也会被丢弃。在使用 scikit learn 接口中的 apply 方法时,默认将其设置为 False。

对于 R 包,当指定 strict_shape 时,会返回一个 array,其值与 Python 相同,但 R 数组是列主序(column-major),而 Python numpy 数组是行主序(row-major),因此所有维度都是反转的。例如,使用 strict_shape=True 获得的 Python predict_leaf 输出有 4 个维度:(n_samples, n_iterations, n_classes, n_trees_in_forest),而 R 使用 strict_shape=TRUE 则输出 (n_trees_in_forest, n_classes, n_iterations, n_samples)

除了这些预测类型外,还有一个名为 iteration_range 的参数,它类似于模型切片。但它并不是真正将模型拆分成多个堆栈,而是简单地返回指定范围内的树形成的预测。每次迭代创建的树的数量等于 \(trees_i = num\_class \times num\_parallel\_tree\)。因此,如果您正在训练一个大小为 4 的 boosted random forest,数据集是 3 类分类,并且想使用前 2 次迭代的树进行预测,您需要提供 iteration_range=(0, 2)。那么第一次 \(2 \times 3 \times 4\) 棵树将用于此次预测。

提前停止

当模型通过提前停止进行训练时,原生 Python 接口与 sklearn/R 接口之间存在不一致的行为。在 R 和 sklearn 接口上,默认情况下会自动使用 best_iteration,因此预测来自最佳模型。但对于原生 Python 接口,xgboost.Booster.predict()xgboost.Booster.inplace_predict() 方法使用完整模型。用户可以使用 best_iteration 属性和 iteration_range 参数来获得相同的行为。此外,xgboost.callback.EarlyStoppingsave_best 参数可能也会有用。

基础边距

XGBoost 中有一个名为 base_score 的训练参数,以及一个 DMatrix 的元数据,称为 base_margin(如果您使用 scikit-learn 接口,可以在 fit 方法中设置)。它们指定了 boosted 模型的全局偏差。如果提供了后者,则前者将被忽略。base_margin 可用于基于其他模型训练 XGBoost 模型。请参见关于基于预测进行 boosting 的演示。

分阶段预测

使用原生接口和 DMatrix,预测可以分阶段(或缓存)。例如,可以先对前 4 棵树进行预测,然后对 8 棵树进行预测。第一次预测运行后,前 4 棵树的结果会被缓存,因此当您使用 8 棵树运行预测时,XGBoost 可以重复使用之前预测的结果。如果缓存的 DMatrix 对象过期(例如超出作用域并被您语言环境中的垃圾收集器回收),则缓存在下一次预测、训练或评估时自动过期。

原地预测

传统上,XGBoost 只接受 DMatrix 进行预测,对于像 scikit-learn 这样的封装器, DMatrix 的构建发生在内部。我们增加了对原地预测(in-place predict)的支持,以绕过 DMatrix 的构建,因为这既慢又消耗内存。新的预测函数功能有限,但对于简单的推理任务通常足够。它接受 Python 中常见的一些数据类型,如 numpy.ndarrayscipy.sparse.csr_matrixcudf.DataFrame,而不是 xgboost.DMatrix。您可以调用 xgboost.Booster.inplace_predict() 来使用它。请注意,原地预测的输出取决于输入数据类型,当输入数据在 GPU 上时,输出是 cupy.ndarray,否则返回 numpy.ndarray

线程安全

在 1.4 版本发布后,所有预测函数(包括带有 shap value 计算等各种参数的正常 predictinplace_predict)在底层 booster 是 gbtreedart 时是线程安全的,这意味着只要使用树模型,预测本身就应该是线程安全的。但这种安全性仅在预测时得到保证。如果有人尝试在一个线程中训练模型,并在另一个线程中使用同一模型进行预测,则行为是未定义的。这比人们预期的更容易发生,例如我们可能在预测函数内部意外地调用 clf.set_params()

def predict_fn(clf: xgb.XGBClassifier, X):
    X = preprocess(X)
    clf.set_params(n_jobs=1)  # NOT safe!
    return clf.predict_proba(X, iteration_range=(0, 10))

with ThreadPoolExecutor(max_workers=10) as e:
    e.submit(predict_fn, ...)

隐私保护预测

Concrete ML 是由 Zama 开发的一个第三方开源库,它提出了类似于我们的梯度提升类,但由于全同态加密(Fully Homomorphic Encryption),可以直接在加密数据上进行预测。一个简单的示例如下:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from concrete.ml.sklearn import XGBClassifier

x, y = make_classification(n_samples=100, class_sep=2, n_features=30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
    x, y, test_size=10, random_state=42
)

# Train in the clear and quantize the weights
model = XGBClassifier()
model.fit(X_train, y_train)

# Simulate the predictions in the clear
y_pred_clear = model.predict(X_test)

# Compile in FHE
model.compile(X_train)

# Generate keys
model.fhe_circuit.keygen()

# Run the inference on encrypted inputs!
y_pred_fhe = model.predict(X_test, fhe="execute")

print("In clear  :", y_pred_clear)
print("In FHE    :", y_pred_fhe)
print(f"Similarity: {int((y_pred_fhe == y_pred_clear).mean()*100)}%")

更多信息和示例请参阅 Concrete ML 文档