预测

XGBoost 中有许多带有各种参数的预测函数。本文档旨在阐明围绕预测的一些困惑,重点关注 Python 绑定,R 包在指定 strict_shape 时(参见下文)与 Python 类似。

预测选项

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

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

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

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

    与前一种情况类似,输出是一个二维数组,不同之处在于 multi:softmax 具有与 multi:softprob 等效的输出形状,因为去除了转换。如果 strict_shape 设置为 False,则输出可能具有一维或二维,具体取决于使用的模型。

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

    输出是一个三维数组,形状为 (rows, groups, columns + 1)。是否使用 approx_contribs 不会改变输出形状。如果未设置 strict_shape 参数,它可能是二维或三维数组,具体取决于是否使用多分类模型。

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

    输出是一个四维数组,形状为 (rows, groups, columns + 1, columns + 1)。与预测贡献案例类似,是否使用 approx_contribs 不会改变输出形状。如果 strict_shape 设置为 False,则它可能具有三维或四维,具体取决于底层模型。

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

    输出是一个四维数组,形状为 (n_samples, n_iterations, n_classes, n_trees_in_forest)n_trees_in_forest 在训练期间由 numb_parallel_tree 指定。当 strict_shape 设置为 False 时,输出是一个二维数组,后三个维度被连接成一个。如果最后一个维度等于 1,则该维度将被删除。在使用 scikit learn 接口中的 apply 方法时,此参数默认设置为 False。

对于 R 包,当指定 strict_shape 时,将返回一个 array,其值与 Python 相同,但 R 数组是列主序的,而 Python numpy 数组是行主序的,因此所有维度都颠倒了。例如,Python predict_leaf 输出通过设置 strict_shape=True 获得,具有 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 随机森林,用于 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.EarlyStopping 中的 save_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 的构建,因为这既慢又耗内存。新的预测函数功能有限,但通常足以满足简单的推理任务。它接受 Python 中一些常见的数据类型,如 numpy.ndarrayscipy.sparse.csr_matrixcudf.DataFrame,而不是 xgboost.DMatrix。您可以调用 xgboost.Booster.inplace_predict() 来使用它。请注意,原地预测的输出取决于输入数据类型,当输入是 GPU 数据时,输出是 cupy.ndarray,否则返回 numpy.ndarray

线程安全

在 1.4 版本发布后,所有预测函数,包括带有各种参数的常规 predict(如 shap 值计算)和 inplace_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 开发的第三方开源库,它提供了与我们类似的梯度 boosting 类,但由于全同态加密,可以直接对加密数据进行预测。一个简单的示例如下:

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 文档