预测
XGBoost 中有许多带有各种参数的预测函数。本文档旨在阐明围绕预测的一些困惑,重点关注 Python 绑定,R 包在指定 strict_shape
时(参见下文)与 Python 类似。
预测选项
xgboost.Booster.predict()
方法有许多不同的预测选项,从 pred_contribs
到 pred_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.ndarray
、scipy.sparse.csr_matrix
和 cudf.DataFrame
,而不是 xgboost.DMatrix
。您可以调用 xgboost.Booster.inplace_predict()
来使用它。请注意,原地预测的输出取决于输入数据类型,当输入是 GPU 数据时,输出是 cupy.ndarray
,否则返回 numpy.ndarray
。
线程安全
在 1.4 版本发布后,所有预测函数,包括带有各种参数的常规 predict
(如 shap 值计算)和 inplace_predict
,在底层 booster 为 gbtree
或 dart
时都是线程安全的,这意味着只要使用树模型,预测本身应该是线程安全的。但安全保证仅限于预测。如果一个人试图在一个线程中训练模型,并在另一个线程中使用相同的模型进行预测,则行为是未定义的。这种情况比人们想象的更容易发生,例如我们可能会在预测函数内部意外地调用 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 文档。