在信息检索的背景下,学习排序的目标是训练一个模型,将一组查询结果排列成有序列表[1]。对于监督学习排序,预测器是以特征矩阵编码的样本文档,标签是每个样本的相关性程度。相关性程度可以是多级(分级)的,也可以是二进制的(相关或不相关)。训练样本通常根据它们的查询索引分组,每个查询组包含多个查询结果。
XGBoost通过一组目标函数和性能指标实现学习排序。默认目标是基于LambdaMART
算法的rank:ndcg
,该算法本质上是LambdaRank
框架[3]对梯度提升树的一种调整。有关该算法的历史和摘要,请参阅[5]。XGBoost中的实现具有确定性GPU计算、分布式训练、位置去偏和两种不同的成对构建策略。
LambdaMART
是一个成对排名模型,它比较查询组中每一对样本的相关性程度,并为每一对计算一个代理梯度。默认目标rank:ndcg
使用从ndcg指标导出的替代梯度。为了训练XGBoost模型,需要一个额外的排序数组,称为qid
,用于指定输入样本的查询组。示例输入如下:
QID | Label | Feature |
---|---|---|
1 | 0 | |
1 | 1 | |
1 | 0 | |
2 | 0 | |
2 | 1 | |
2 | 0 |
样本根据它们的查询索引按非递减的顺序进行排序。在上面的示例中,前三个样本属于第一个查询,接下来的四个样本属于第二个查询。为了简单起见,在以下代码片段中,将使用一个合成的二元学习-to-rank 数据集,其中二元标签表示结果是否相关,并随机分配查询组索引给每个样本。
from sklearn.datasets import make_classification
import numpy as np
import xgboost as xgb
# Make a synthetic ranking dataset for demonstration
seed = 1994
X, y = make_classification(random_state=seed)
rng = np.random.default_rng(seed)
n_query_groups = 3
qid = rng.integers(0, 3, size=X.shape[0])
# Sort the inputs based on query index
sorted_idx = np.argsort(qid)
X = X[sorted_idx, :]
y = y[sorted_idx]
qid = qid[sorted_idx]
使用 scikit-learn 估计器接口训练排序模型的最简单方法是继续上一个代码片段,可以训练一个简单的排序模型而不进行调整:
ranker = xgb.XGBRanker(tree_method="hist", lambdarank_num_pair_per_sample=8, objective="rank:ndcg", lambdarank_pair_method="topk")
ranker.fit(X, y, qid=qid)
由于目前scikit-learn 中还没有学习排名(learning-to-rank)的接口。因此,xgboost.XGBRanker 类没有完全遵循 scikit-learn 估计器指南,并且不能直接用于 scikit-learn 的一些实用功能。例如,scikit-learn 中的 auc_score
和 ndcg_score
没有考虑查询组信息也没有考虑成对损失。大多数指标都是作为 XGBoost 的一部分实现的,但要使用 scikit-learn 的实用程序,如 sklearn.model_selection.cross_validation()
,需要进行一些调整,以便将 qid
作为额外参数传递给 xgboost.XGBRanker.score()
。给定一个数据帧 X(无论是 pandas 还是 cuDF),按照以下方式添加 qid
列:
df = pd.DataFrame(X, columns=[str(i) for i in range(X.shape[1])])
df["qid"] = qid
ranker.fit(df, y) # No need to pass qid as a separate argument
from sklearn.model_selection import StratifiedGroupKFold, cross_val_score
# Works with cv in scikit-learn, along with HPO utilities like GridSearchCV
kfold = StratifiedGroupKFold(shuffle=False)
cross_val_score(ranker, df, y, cv=kfold, groups=df.qid)
上述代码片段使用LambdaMART和NDCG@8指标构建了一个模型。排名器的输出是相关性分数:
scores = ranker.predict(X)
sorted_idx = np.argsort(scores)[::-1]
# Sort the relevance scores from most relevant to least relevant
scores = scores[sorted_idx]
获取查询结果的真实相关度通常是昂贵且费力的,需要人工标注者逐个标注所有结果。当这样的标注任务不可行时,可能会想要改为在用户点击数据上训练学习排名模型,因为点击数据相对容易收集。直接使用点击数据的另一个优点是它可以反映最新的用户偏好[1]。然而,用户点击通常是有偏的,因为用户倾向于选择显示在更高位置的结果。用户点击也是噪声的,用户可能会意外点击不相关的文档。为了缓解这些问题,XGBoost实现了无偏LambdaMART
[4]算法来消除位置依赖的点击数据的偏差。可以通过lambdarank_unbiased
参数启用该特性;有关相关选项,请参见排名学习参数(rank:ndcg, rank:map, rank:pairwise
)。
XGBoost基于不同的度量标准实现了不同的LambdaMART
目标。在这里列出它们作为参考。除了作为目标函数使用的度量标准之外,XGBoost还实现了用于评估的度量标准,如pre
(用于精确度)。请参阅参数以获取可用选项,并查看以下部分以了解如何根据有效对数的数量选择这些目标。
归一化折扣累积增益(Normalized Discounted Cumulative Gain NDCG)
可用于二进制相关性和多级相关性。目标的名称是 rank:ndcg
。Mean average precision MAP
)是一个二进制度量标准。当相关性标签为0或1时,可以使用它。目标的名称是 rank:map
。LambdaMART
算法使用学习排名度量(如NDCG
)来缩放逻辑损失,以期将排名信息包含到损失函数中。rank:pairwise
损失是成对损失的原始版本,也称为 RankNet 损失 [7] 或成对逻辑损失。与 rank:map
和 rank:ndcg
不同,不进行缩放()。
缩放LTR度量是否实际上更有效仍有争论;[8] 提供了一般lambda损失函数的理论基础和对框架的一些见解。
XGBoost实现了两种用于构建文档对以进行
梯度计算的方法,第一种是均值mean方法,另一种是topk
方法。首选策略可以通过 lambdarank_pair_method
参数指定。
对于平均值策略,XGBoost为查询列表中的每个文档采样lambdarank_num_pair_per_sample
个文档对。例如,给定一个包含3个文档的列表,如果将lambdarank_num_pair_per_sample
设置为2,XGBoost将随机采样6个文档对,假设这些文档的标签不同。另一方面,如果将成对方法设置为topk
,XGBoost将构建大约
数量的文档对,其中每个样本在顶部位置有
*。这里计算的成对数量是一个近似值,因为跳过了具有相同标签的文档对。
学习排序是一项复杂的任务,也是一个积极研究的领域。训练一个泛化性能良好的模型并不是一件简单的事情。XGBoost中有多个损失函数以及一组超参数。本节包含一些有关如何选择超参数的提示,作为起点。可以通过调整这些超参数来进一步优化模型。
首先要考虑的问题是如何选择与手头任务相匹配的目标。如果输入数据具有多级相关度度量,那么应该使用 rank:ndcg
或 rank:pairwise
。然而,当输入具有二进制标签时,有多个基于目标度量的选项。[6] 对此主题提供了一些建议,并鼓励用户查看他们的工作中所做的分析。选择应基于有效对数的数量,这指的是能够生成非零梯度并有助于训练的对数的数量。具有MRR
的LambdaMART
的有效对数最少,因为当对包含高于顶部相关文档的非相关文档时,梯度仅在这种情况下才为非零。因此,它在XGBoost中没有实现。由于NDCG
是一个多级度量,通常会生成比MAP
更多的有效对数。
然而,当存在足够多的有效对时,[6] 表明将目标度量与目标函数匹配是重要的。当目标度量为MAP
且您使用可以提供足够多有效对的大型数据集时,rank:map
在理论上可以产生比 rank:ndcg
更高的MAP
值。
对于有效对数的选择也适用于对方法(lambdarank_pair_method
)和每个样本的对数(lambdarank_num_pair_per_sample
)的选择。例如,mean
方法考虑的对数比NDCG@10多,因此前者生成更多有效对并提供比后者更多的细粒度。此外,使用mean
策略可以通过随机采样帮助模型泛化。然而,与其使用所有对,有人可能希望将训练集重点放在前
个文档上,以更好地适应其实际应用。
在使用mean策略生成对的情况下,其中目标度量(如NDCG
)是在整个查询列表上计算的,用户可以指定应为每个文档生成多少对,通过设置 lambdarank_num_pair_per_sample
。XGBoost将为查询组中的每个元素随机采样 lambdarank_num_pair_per_sample
对(
)。通常,将其设置为1可以产生合理的结果。在由于生成的有效对数不足而导致性能不足的情况下,将 lambdarank_num_pair_per_sample
设置为较高的值。生成更多的文档对,也将生成更多的有效对。
另一方面,如果优先考虑前
个文档,则 lambdarank_num_pair_per_sample
应设置为略高于
(带有一些更多的文档)以获得良好的训练结果。
总结:如果有大量的训练数据:
topk
策略另一方面,如果有相对较少的训练数据:
NDCG
或RankNet
损失(rank:pairwise
)。mean
策略,以获得更多的有效对对于选择的任何方法,可以通过修改lambdarank_num_pair_per_sample
来控制生成的对的数量。
XGBoost实现了与多个框架集成的分布式学习排序,包括Dask、Spark和PySpark。接口与单节点相似。有关详细信息,请参阅相应XGBoost接口的文档。将查询组分散到多个工作器上在理论上是合理的,但可能会影响模型的准确性。对于大多数用例,小的差异通常不是问题,因为在使用分布式训练时,通常训练数据的量很大。因此,用户不需要基于查询组对数据进行分区。只要每个数据分区按查询ID正确排序,XGBoost就可以相应地聚合样本梯度。
Reproducible Result
与任何其他任务一样,XGBoost在相同的硬件和软件环境(以及数据分区,如果使用了分布式接口)下应该生成可复现的结果。即使底层环境发生了变化,结果仍应保持一致。然而,当 lambdarank_pair_method 设置为 mean 时,XGBoost 使用随机抽样,结果可能取决于所使用的平台。在Windows上使用的随机数生成器(Microsoft Visual C++)与在其他平台(如Linux,GCC,Clang)上使用的不同1,因此在这些平台之间的输出会有显着差异。
[1] minstd_rand 在 MSVC 上的实现与 GCC 和 Thrust 上的实现产生相同的输出。
像任何其他任务一样,XGBoost在相同的硬件和软件环境(以及数据分区,如果使用了分布式接口)下应该生成可复现的结果。即使底层环境发生了变化,结果仍应保持一致。然而,当 lambdarank_pair_method
设置为 mean
时,XGBoost 使用随机抽样,结果可能取决于所使用的平台。在Windows上使用的随机数生成器(Microsoft Visual C++)与在其他平台(如Linux,GCC,Clang)上使用的不同,因此在这些平台之间的输出会有显着差异。
[1] Tie-Yan Liu. 2009. “Learning to Rank for Information Retrieval”. Found. Trends Inf. Retr. 3, 3 (March 2009), 225–331.
[2] Christopher J. C. Burges, Robert Ragno, and Quoc Viet Le. 2006. “Learning to rank with nonsmooth cost functions”. In Proceedings of the 19th International Conference on Neural Information Processing Systems (NIPS’06). MIT Press, Cambridge, MA, USA, 193–200.
[3] Wu, Q., Burges, C.J.C., Svore, K.M. et al. “Adapting boosting for information retrieval measures”. Inf Retrieval 13, 254–270 (2010).
[4] Ziniu Hu, Yang Wang, Qu Peng, Hang Li. “Unbiased LambdaMART: An Unbiased Pairwise Learning-to-Rank Algorithm”. Proceedings of the 2019 World Wide Web Conference.
[5] Burges, Chris J.C. “From RankNet to LambdaRank to LambdaMART: An Overview”. MSR-TR-2010-82
[6] Pinar Donmez, Krysta M. Svore, and Christopher J.C. Burges. 2009. “On the local optimality of LambdaRank”. In Proceedings of the 32nd international ACM SIGIR conference on Research and development in information retrieval (SIGIR ‘09). Association for Computing Machinery, New York, NY, USA, 460–467.
[7] Chris Burges, Tal Shaked, Erin Renshaw, Ari Lazier, Matt Deeds, Nicole Hamilton, and Greg Hullender. 2005. “Learning to rank using gradient descent”. In Proceedings of the 22nd international conference on Machine learning (ICML ‘05). Association for Computing Machinery, New York, NY, USA, 89–96.
[8] Xuanhui Wang and Cheng Li and Nadav Golbandi and Mike Bendersky and Marc Najork. 2018. “The LambdaLoss Framework for Ranking Metric Optimization”. Proceedings of The 27th ACM International Conference on Information and Knowledge Management (CIKM ‘18).
[9] minstd_rand implementation is different on MSVC. The implementations from GCC and Thrust produce the same output.