Learning to Rank (LTR) , 也被叫作排序学习, 是搜索中的重要技术, 其目的是根据候选文档和查询语句的相关性对候选文档进行排序, 或者选取topk文档. 好比在搜索引擎中, 须要根据用户问题选取最相关的搜索结果展现到首页. 下图是搜索引擎的搜索结果
html
根据损失函数可把LTR分为三种:python
能够看出该损失函数一次考虑两个候选文档.
3. Listwise, 该类型算法的损失函数会考虑多个候选文档, 这是本文的重点, 下面会详细介绍.算法
本文主要介绍了本人在学习研究过程当中发明的一种新的Listwise损失函数, 以及该损失函数的使用效果. 若是读者对LTR任务及其算法还不够熟悉, 建议先去学习LTR相关知识, 同时本人博文天然语言处理中的负样本挖掘 (分类与排序任务中如何选择负样本) 也和本文关系较大, 能够先进行阅读.网络
\(q\)表明用户搜索问题, 好比"如何成为宇航员", \(D\)表明候选文档集合,\(d^+\)表明和\(q\)相关的文档,\(d^-\)表明和\(q\)不相关的文档, \(d^+_i\)表明第\(i\)个和\(q\)相关的文档, LTR的目标就是根据\(q\)找到最相关的文档\(d\)函数
本次学习目标是训练一个打分器 scorer, 它能够衡量q和d的相关性, \(scorer(q, d)\)就是相关性分数,分值越大越相关. 当前主流方法下, scorer通常选用深度神经网络模型.学习
损失函数不一样, 构造训练数据的方法也会不一样:优化
-Pointwise, 能够构造回归数据集, 相关的数据设为1, 不相关设为0.
-Pairwise, 可构造triplet类型的数据集, 形如(\(q,d^+, d^-\))
-Listwise, 可构造这种类型的训练集: (\(q,d^+_1,d^+_2..., d^+_n , d^-_1, d^-_2, ..., d^-_{n+m}\)), 一个正例仍是多个正例也会影响到损失函数的构造, 本文提出的损失函数是针对多正例多负例的状况.搜索引擎
在上一小结咱们能够知道,训练集是以下形式 (\(q,d^+_1,d^+_2..., d^+_n , d^-_1, d^-_2, ..., d^-_{n+m}\)), 对于一个q, 有n个相关的文档和m个不相关的文档, 那么咱们一共能够获取m+n个分值:\((score_1,score_2,...,score_n,...,score_{n+m})\), 咱们但愿打分器对相关文档打分趋近于正无穷, 对不相关文档打分趋近于负无穷.spa
对m+n个分值作一个softmax获得\(p_1,p_2,...,p_n,...,p_{n+m}\), 此时\(p_i\)能够看做是第i个候选文档与q相关的几率, 显然咱们但愿\(p_1,p_2,...,p_n\)越大越好, \(p_{n+1},...,p_{m+n}\)越小越好, 即趋近于0. 所以咱们暂时的优化目标是\(\sum_{i=1}^{n}{p_i} \rightarrow 1\).code
可是这个优化目标是不合理的, 假设\(p_1=1\), 其余值全为0, 虽然知足了上面的要求, 但这并非咱们想要的. 由于咱们不只但愿\(\sum_{i=1}^{n}{p_i} \rightarrow 1\), 还但愿相关候选文档的每个p值都要足够大, 即咱们但愿: n个候选文档都与q相关的几率是最大的, 因此咱们真正的优化目标是:
当前状况下, 损失函数已经能够经过代码实现了, 可是咱们还能够作一些化简工做, \(\prod_{i=1}^{n}{p_i}\)是存在最大值的, 根据均值不等式可得:
对两边取对数:
这样是否是感受清爽多了, 而后咱们把它转换成损失函数的形式:
因此咱们的训练目标就是\(\min{(loss)}\)
在获取到最终的损失函数后, 咱们还须要用代码来实现, 实现代码以下:
# A simple example for my listwise loss function # Assuming that n=3, m=4 # In[1] # scores scores = torch.tensor([[3,4.3,5.3,0.5,0.25,0.25,1]]) print(scores) print(scores.shape) ''' tensor([[0.3000, 0.3000, 0.3000, 0.0250, 0.0250, 0.0250, 0.0250]]) torch.Size([1, 7]) ''' # In[2] # log softmax log_prob = torch.nn.functional.log_softmax(scores,dim=1) print(log_prob) ''' tensor([[-2.7073, -1.4073, -0.4073, -5.2073, -5.4573, -5.4573, -4.7073]]) ''' # In[3] # compute loss n = 3. mask = torch.tensor([[1,1,1,0,0,0,0]]) # number of 1 is n loss = -1*n*torch.log(torch.tensor([[n]])) - torch.sum(log_prob*mask,dim=1,keepdim=True) print(loss) loss = loss.mean() print(loss) ''' tensor([[1.2261]]) tensor(1.2261) '''
该示例代码仅展示了batch_size为1的状况, 在batch_size大于1时, 每一条数据都有不一样的m和n, 为了能一块儿送入模型计算分值, 须要灵活的使用mask. 本人在实际使用该损失函数时,一共使用了两种mask, 分别mask每条数据全部候选文档和每条数据的相关文档, 供你们参考使用.
因为评测数据使用的是内部数据, 代码和数据都没法公开, 所以只能对使用效果作简单总结:
下面是我的使用经验:
该损失函数仍是比较简单的, 只须要简单的数学知识就能够自行推导, 在实际使用中也取得了较好的效果, 但愿也可以帮助到你们. 若是你们有更好的作法欢迎告诉我.
文章能够转载, 但请注明出处: