xgboost在LTR(学习排序)中的应用

LTR背景介绍

Learning to rank (LTR) 中文译为“排序学习”,在信息检索(IR)以及 NLP 领域都有着广泛应用,此外在计算广告、推荐系统场景下同样也有应用空间。就我个人所知,LTR 在 2010 年前后受到了相对较多关注,也出现了一批以 LTR 为基础的算法模型。随着 DL 的兴起,近些年 LTR 模型方面的研究已经相对较少,转而更多的以一种“思想”或“做法”的方式出现在了其他模型中。

LTR 模型定义

检索问题可以看做是给定 query 后,系统对文档集合中的候选 doc 进行检索,并对其排序,返回得分最高的 doc。其中排序任务使用一个排序模型 f ( q , d ) f(q,d) f(q,d) 对 doc 进行排序,其中 q q q表示查询, d d d表示候选 doc。 排序模型 f ( q , d ) f(q,d) f(q,d) 可以是 BM25 或者 LMIR 中的概率模型这种不需要训练或者只有少量参数的模型。而 LTR 则是使用机器学习方法,利用特征进行训练,自动建立排序模型 f ( q , d ) f(q,d) f(q,d) 用以对候选 doc 进行排序的方法。其中,对于刚刚接触 IR 的同学这里可能会比较困惑,实际上这里的特征按照与 query/doc 的关系大体上可以分为 {query 端特征, doc 端特征,query-doc 相关特征} 三类。 query 特征是指与 doc 无关的特征,例如 query 自身 embedding 或者在一些场景下可以是 query 的 id 特征,同理 doc 端特征是指与 query 无关的特征,例如 doc 的embedding 或者 id 特征。 而 query-doc 相关特征则可以是将 query-doc 进行联系的特征,例如 BM25 等文本匹配特征、后文会说到的使用 deep match 模型计算的 query-doc 相似特征,以及可以人工生成的 query-doc 之间的交叉特征。

LTR的分类

LTR 通常被划分为 Pointwise、Pairwise 和 Listwise 三大类,三者分别有着不同的输入输出空间,基于不同的假设条件,使用不同的损失函数。
这里的 Pointwise、Pairwise 和 Listwise 可能会让人产生疑惑。 上文提到了在 LTR 里的训练数据是由 <query,doc> 构成的,每一个训练样本都表示的是一条 query 和一个对应 query 的 doc。在训练数据集中,通常存在多条训练数据有相同 query 的情况,这种情况下可以将一个 query 对应的多个 doc 放在一起,组成一个 list。
Pairwise 和 Listwise 主要体现在如何来使用这个训练数据集合上。如果把 每条训练数据独立来看,针对每条训练数据优化模型使其能够准确预测对应的标签,就是 Pointwise , 如果把 M 个 list 分别来看,针对每个 query 对应的 list 考虑其内部两两样本之间的顺序关系,就是 Pairwise , 如果把 M 个 list 分别来看,针对每个 query 对应的 list 考虑其内部所有样本之间的顺序关系,就是 Listwise。

Pointwise 排序学习

Pointwise 的输入空间是单条 <query, doc> 数据的特征向量,输出空间是其相关性得分,根据相关性得分可以对样本进行排序,进而使用上文提到的 Metric 进行评测。Pointwise 算法的 loss/object 函数考虑的是单条数据机器标签。
Pointwise 算法通常会将 ranking 算法转化法回归(regression)、分类(classification)或者序列回归(ordinal regression)问题。

基于回归

基于回归的方法将 doc 与 query 的相关性标签看做连续值,通过最小化相关性标签和预测值之间的误差学习排序模型。

基于回归的一个代表方法是 Subset Ranking with Regression [Cossock and Zhang 2006],模型使用 square loss,针对排序问题对“相关” doc 进行了权重调整。文章还提出了 square loss 是 NDCG-based ranking error 的上界,然而我不太理解这个上界有啥明确使用价值。
总之基于回归的方法太过关注模型预测值与标签之间的误差,导致了并不太关注排序的顺序,因而不太常用。

由于 Pointwise 算法中是将 <query-doc> 独立考虑的,所以既忽略了 query 和 doc 之间的关系(query 与一些doc 相关与一些 doc 无关),又无法对同属同一 query 的 doc 之间的相互顺序关系进行考虑。而对于排序问题来说,相对的位置关系可能比绝对的相关性高低要更重要。总体来说 Pointwise 类算法在 LTR 中还是有较大缺陷,导致应用不多的。

Pairwise 排序学习

如前文所述,Pairwise 类型的 LTR 算法是把 M 个 list 分别来看,针对每个 query 对应的 list 考虑其内部两两样本之间的顺序关系,也就是 Pairwise 的算法每次都是同时“看”两个 doc,并评判其相对位置是否正确的。

在 Pairwise 的算法中通常将 ranking 问题转为 doc pair 上的分类问题:如果 doc pair 中的两个 doc 的相对位置关系正确则为正例,错误即为负例。模型的目标就是减少误分类 doc pair 的比例。此处与 Pointwise 将 ranking 转为分类问题是有区别的,Pointwise 中是对 query 对应的单个 doc 进行分类,而 Pairwise 则会同时关注 query 对应的两个 doc,并对其“顺序”进行分类。这里分类的是“doc 之间的顺序”,而不是 doc。

通常提到 Pairwise 的算法会说其不太关注于绝对的标签或等级,而是关注排序的质量。需要注意的是这并不意味着模型对样本的预测值无法放映样本的质量,由于模型在预测阶段还是对单个<query-doc>样本进行预测,因此这个值是可以反映这条数据的质量好坏的,只是这个值与样本原始标签或等级在数值上可能相差较大。 举个例子来说:有个3个样本标签分别为1,2,3,如果使用回归模型的话,那么三个预测值可能是1.1,2.2,3.3;而使用 Pairwise 模型时,三个预测值可能是 0.1,0.2,0.3,这个预测结果从回归角度看是非常差的结果,但是从排序角度看却是很好的结果。

Listwise 排序学习

Listwise 排序学习方法就是把 M 个 query 对应的 M 个 list 分别来看,针对每个 query 对应的 list 考虑其内部所有样本之间的顺序关系,根据 loss function 的区别可以分为两类:最小化特定评估指标损失(Minimization of Measure-Specific Loss) 以及 最小化非特定评估指标损失(Minimization of Non-measure-Specific Loss)。第一类的算法与特定 metric 相关,可以看做是直接优化方法;第二类算法与 metric 并不直接相关,是间接优化方法。

排序学习算法总结

这部分根据 LTR 传统划分方法,分 Pointwise、Pairwise、Listwise 三部分介绍了几种 LTR 算法,可以看到 Pointwise 类的算法对样本考虑较少,无法充分应对 IR 领域一个 Query 对应多个 Doc 这种普遍情况,其中序列回归的部分只是增加了对标签(等级)之间的顺序关系,并没有考虑到 doc 之间的关系。

Pairwise 类算法则对 doc 之间关系进行了有效的考虑,其中的 Ranking SVM 算法作为基础的 Pairwise 的线性模型,可以在需要简单快速 ranking 的情况下发挥作用。而 RankNet-LambdaMART 系列的 Lambda 梯度设计则在考虑 doc 之间相对位置关系基础上又根据 NDCG 的变换增加了权重,从而可以在某种程度上看做是一类 Listwise 算法。 但是 Pairwise 类型算法同样存在一些缺陷,例如对 query 的 doc-list 内的整体顺序考虑不全、query 之间的训练样本数目的不平衡比 Pointwise 方法更为严重等。另外,Pairwise 的方法对噪音标签更为敏感,一个单个文档上的噪音相关性判断可能导致大量的错误标注的文档对。

基于 Listwise 的算法直观上讲以一种更自然的方式对排序问题进行建模。它可以解决前两种方法固有的一些问题,比如由于查询相关文档数目差异导致的训练样本的不平衡性等。但是 Listwise 的算法同样存在不足之处,例如部分 Listwise 算法计算复杂度高、有着完整排序信息的训练数据相对较难获取等。实际上我似乎很少听到将 Listwise 类算法应用于实际场景的,这一方面可能是我孤陋寡闻,另外一方面也可能是如 LogisticRank 部分所提到的一样,训练数据与实际数据分布相差较大,Listwise 类模型对训练数据的 list 进行了高效优化,可能会导致线上效果不如部分 Pairwise 算法。

LTR 数据预处理

机器学习领域有个说法是“数据决定上限,模型逼近上限”,在训练 LTR 模型时也是如此,想要训练一个号的 LTR 模型首先需要获得优质的训练数据。为了获得优质的训练数据需要考虑两个方面:

  • 如何低成本的获得大量标注数据,从点击日志中挖掘是一个可行的方案
  • 标注数据总是存在噪音的,如何选取 query-doc 及其特征也是需要考虑的

利用xgboost做排序

XGBoost 是原生支持 rank 的,只需要把 model参数中的 objective 设置为objective=“rank:pairwise” 即可。官方文档是这么说的:
XGBoost支持完成排序任务。在排序场景下,数据通常是分组的,我们需要分组信息文件来指定排序任务。XGBoost中用来排序的模型是LambdaRank,此功能尚未完成。目前,我们提供pairwise rank.

XGBoost supports accomplishing ranking tasks. In ranking scenario, data are often grouped and we need the group information file to specify ranking tasks. The model used in XGBoost for ranking is the LambdaRank, this function is not yet completed. Currently, we provide pairwise rank.

下面小试牛刀一把,先把xgb代码库克隆下来:

git clone --recursive https://github.com/dmlc/xgboost

xgboost中的rank任务的目录为:

xgboost/demo/rank/

这个目录里有xgb进行rank的demo
这里改写了一个,代码如下:

import pandas as pd
import numpy as np
from xgboost import DMatrix,train

xgb_rank_params1 ={    
    'booster' : 'gbtree',
    'eta': 0.1,
    'gamma' : 1.0 ,
    'min_child_weight' : 0.1,
    'objective' : 'rank:pairwise',
    'eval_metric' : 'merror',
    'max_depth' : 6,
    'num_boost_round':10,
    'save_period' : 0 
}

xgb_rank_params2 = {
    'bst:max_depth':2, 
    'bst:eta':1, 'silent':1, 
    'objective':'rank:pairwise',
    'nthread':4,
    'eval_metric':'ndcg'
}
  

#generate training dataset
#一共2组*每组3条,6条样本,特征维数是2
n_group=2
n_choice=3  
dtrain=np.random.uniform(0,100,[n_group*n_choice,2])    
#numpy.random.choice(a, size=None, replace=True, p=None)
dtarget=np.array([np.random.choice([0,1,2],3,False) for i in range(n_group)]).flatten()
#n_group用于表示从前到后每组各自有多少样本,前提是样本中各组是连续的,[3,3]表示一共6条样本中前3条是第一组,后3条是第二组
dgroup= np.array([n_choice for i in range(n_group)]).flatten()

# concate Train data, very import here !
xgbTrain = DMatrix(dtrain, label = dtarget)
xgbTrain.set_group(dgroup)

# generate eval data
dtrain_eval=np.random.uniform(0,100,[n_group*n_choice,2])        
xgbTrain_eval = DMatrix(dtrain_eval, label = dtarget)
xgbTrain_eval .set_group(dgroup)
evallist  = [(xgbTrain,'train'),(xgbTrain_eval, 'eval')]

# train model
# xgb_rank_params1加上 evals 这个参数会报错,还没找到原因
# rankModel = train(xgb_rank_params1,xgbTrain,num_boost_round=10)
rankModel = train(xgb_rank_params2,xgbTrain,num_boost_round=20,evals=evallist)

#test dataset
dtest=np.random.uniform(0,100,[n_group*n_choice,2])    
dtestgroup=np.array([n_choice for i in range(n_group)]).flatten()
xgbTest = DMatrix(dtest)
xgbTest.set_group(dgroup)

# test
print(rankModel.predict( xgbTest))

xgb在进行ranking时,模式是pairwise,而pairwise主要的优化目标主要是排序对。根据xgb官方文档,主要使用的是LambdaRank算法,详情参考LambdaRank算法。评估指标NDCG参考NDCG

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值