zoukankan      html  css  js  c++  java
  • >>《learning to rank(ltr)

    learning to rank(ltr) - daiwk-github 博客learning to rank(ltr) - daiwk-github 博客 - 作者: daiwk

    目录

    learning to rank 简介

    参考:

    https://github.com/PaddlePaddle/models/tree/develop/legacy/ltr

    参考刘铁岩老师的书 Learning to Rank for Information Retrieval

    还有 ppt:http://wwwconference.org/www2009/pdf/T7A-LEARNING%20TO%20RANK%20TUTORIAL.pdf

    李航老师的书 Learning to rank for information retrieval and natural language processing

    对比各种 wise 的优缺点:https://blog.csdn.net/lipengcn/article/details/80373744

    pairwise 的排序算法用于推荐系统的排序任务中为什么效果差于 pointwise 的 ctr?

    point-wise

    pair-wise

    gbrank

    https://www.cnblogs.com/bentuwuying/p/6684585.html

    ranksvm 和 IRsvm

    https://www.cnblogs.com/bentuwuying/p/6683832.html

    ranknet

    ranknet 用的是 crossentropy 的 loss:https://blog.csdn.net/puqutogether/article/details/42124491

    ranknet 的文章:https://icml.cc/2015/wp-content/uploads/2015/06/icml_ranking.pdf

    list-wise

    lambdarank

    NDCG 是一个处处非平滑的函数,直接以它为目标函数进行优化是不可行的。

    LambdaRank 提供了一种思路:绕过目标函数本身,直接构造一个特殊的梯度(称之为 Lambda 梯度),按照梯度的方向修正模型参数,最终能达到拟合 NDCG 的方法。通过该梯度构造出的深度学习网络称之为 LambdaDNN。

    LambdaRank 模型是通过 Pairwise 来构造的,通常将同 Query 下有点击样本和无点击样本构造成一个样本 Pair

    了解一下 LambdaMART:https://blog.csdn.net/huagong_adu/article/details/40710305

    ltr 的常见网络结构

    pairwise

    以 paddle 为例:

    我们希望训练一个 dnn 来描述 “语义相似度”。期望的输入是两个词,输出一个 float 值,表示这两个字有多么相似。这个相似度最好和人们的直观感受相对应,而对于具体数值其实并不是很关心。比如,我们训练出来的模型计算出 “北京 vs 上海 = 1.2”,“北京 vs 鼠标 = 0.12”,看起来就挺合理的。

    在这种情况下,需要训练的并不是 “某一条 instance 对应一个 label”,而是 “北京和上海的语义相似度,大于北京和鼠标的语义相似度”。这时候就需要引入 pairwise 的训练。在这个例子中,每一条样本有 4 个 slot 和一个 label:slot0 是北京,slot1 是上海,slot2 是北京,slot3 是鼠标,label 是 1。

    假设模型已经训练好了,我们需要的是 “两个输入一个输出”,但是训练的时候却是“四个输入一个输出”。这需要我们把配置写成“共享参数”。即,在“4 个输入” 的情况下,这 4 个输入的前 2 个所使用的网络,和后 2 个使用的网络,是一样的。获得两个 “子网络” 的输出之后,再在最后接一个 pn-pair 层,把两个子网络的输出和 label 接在一起。

    结构如下:

    ## data
    Layer(type = "data", name = "input0_left", size=10000)
    Layer(type = "data", name = "input1_left", size=10000)
    Layer(type = "data", name = "input0_right", size=10000)
    Layer(type = "data", name = "input1_right", size=10000)
    
    
    ## 左侧的网络和右侧的网络,注意连接方式和参数名字都是一样的,只是layer和input的不同。
    Layer(inputs = [Input("input0_left", parameter_name = "_layer1_1.w0"),], name = "layer1_0_left", bias = Bias(parameter_name = "_layer1_1.wbias"), active_type = "tanh", type = "fc", size = 128)
    Layer(inputs = [Input("input1_left", parameter_name = "_layer1_1.w0"),], name = "layer1_1_left", bias = Bias(parameter_name = "_layer1_1.wbias"), active_type = "tanh", type = "fc", size = 128)
    Layer(inputs = [Input("input0_right", parameter_name = "_layer1_1.w0"),], name = "layer1_0_right", bias = Bias(parameter_name = "_layer1_1.wbias"), active_type = "tanh", type = "fc", size = 128)
    Layer(inputs = [Input("input1_right", parameter_name = "_layer1_1.w0"),], name = "layer1_1_right", bias = Bias(parameter_name = "_layer1_1.wbias"), active_type = "tanh", type = "fc", size = 128)
    
    ## 加多两层
    Layer(inputs = [Input("layer1_0_left", parameter_name = "_layer2_2.w"), Input("layer1_1_left", parameter_name = "_layer2_3.w")], name = "layer2_left", bias = Bias(parameter_name = "_layer2.bias"), active_type = "tanh", type = "fc", size = 64)
    Layer(inputs = [Input("layer1_0_right", parameter_name = "_layer2_2.w"), Input("layer1_1_right", parameter_name = "_layer2_3.w")], name = "layer2_right", bias = Bias(parameter_name = "_layer2.bias"), active_type = "tanh", type = "fc", size = 64)
    Layer(inputs = [Input("layer2_left", parameter_name = "_layerO.w")], name = "output_left", bias = Bias(parameter_name = "_layerO.bias"), type = "fc", size = 1)
    Layer(inputs = [Input("layer2_right", parameter_name = "_layerO.w")], name = "output_right", bias = Bias(parameter_name = "_layerO.bias"), type = "fc", size = 1)
    
    ## 输出cost
    Layer(inputs = ["output_left", "output_right", "label"], name = "cost", type = "rank-cost")
    
    Inputs("input0_left", "input1_left", "input0_right", "input1_right", "label") 
    Outputs("cost")

    listwise 的 lambdarank

    业界公认的 LTR 方法性能排序为 Listwise~=Pairwise » Pointwise。基于 listwise 的 lambdarank 在 Yahoo! LTR challenge 中夺得了冠军。lambdaRank 训练速度相对 pairwise 方法提升 20 倍,同时保持效果不变。

    Listwise 方法的几个优势:

    • 原始数据无需组 pair,从而避免了因组 pair 导致的数据量、数据大小的成倍增长。这也一定程度上加快了训练过程。
    • 优化目标为 NDCG,通过指定 NDCG 截断个数,可以忽略大量尾部带噪声的样本的排序,从而集中优化前几位的排序。
    • 直接利用原始数据的打分信息进行排序学习,避免了通过分数大小组 pair 带来的信息损失。

    在 paddle 中使用 lambdaRank 需要注意数据输入格式。为了保证同一个 query 下的 title 组成的样本(即一个 list)不被随机分拆、打乱,用户需要把同一个 query 的所有样本组成一个 sequence 输入网络(可以使用 ProtoDataProvider 或者 PyDataProvider 实现)。另外,用户还需要输入每一条样本的打分,以计算 NDCG 并更新梯度。

    DataLayer(name = "query", size = 5000000)
    DataLayer(name = "title", size = 5000000)
    DataLayer(name = "score", size = 1)
    DataLayer(name = "label", size = 1)
    Layer(name = "emb", type = "fc", size = 128, active_type = "relu", inputs = Input("query", parameter_name="emb.w"), bias = Bias(parameter_name="emb.bias"))
    Layer(name = "output", type = "fc", size = 1, inputs = "emb")
     
    Layer(name = "cost", type = "lambda_cost", NDCG_num = 8, max_sort_size = -1, inputs = ["output", "score"])
     
    Inputs("query","title","score","label")
    Outputs("cost")
    • 实际上这里并不需要用到 label,只是为了兼容 DataProvider 而作占位。
    • lambda_cost 的输出是当前 batch 的平均 NDCG。
    • lambda_cost 参数说明:
      • NDCG_num 指定 NDCG 截断个数,这里即计算 NDCG@8。
      • max_sort_size 指定部分排序的个数。由 lambdaRank 算法原理,该数必须大于等于 NDCG_num。其数值越大,则用于计算梯度的 pair 越多,从而信息越多,效果越好。默认为 - 1,此时同一 list 下的所有样本均会组成 pair 用于更新梯度(即信息利用最全面)。
    • 由于需要计算 NDCG,因此用户需要保证所有 list 的样本数量均大于等于 NDCG_num,并且保证不含有打分全为 0 的 list。

    应用

    美团点评的 lambdarank

    大众点评搜索基于知识图谱的深度学习排序实践

    用户 ID 的 Embedding

    常用方法是直接将用户 ID 经过 Embedding 后作为特征接入到模型中,但是最后上线的效果却不尽如人意。通过分析用户的行为数据,我们发现相当一部分用户 ID 的行为数据较为稀疏,导致用户 ID 的 Embedding 没有充分收敛,未能充分刻画用户的偏好信息。

    Airbnb 发表在 KDD 2018 上的文章 Real-time Personalization using Embeddings for Search Ranking at Airbnb 为这种问题提供了一种解决思路——利用用户基础画像和行为数据对用户 ID 进行聚类。Airbnb 的主要场景是为旅游用户提供民宿短租服务,一般用户一年旅游的次数在 1-2 次之间,因此 Airbnb 的用户行为数据相比点评搜索会更为稀疏一些。

    如上图所示,将用户画像特征和行为特征进行离散分桶,拼接特征名和所属桶号,得到的聚类 ID 为:US_lt1_pn3_pg3_r3_5s4_c2_b1_bd2_bt2_nu3

    采取了类似 Airbnb 的方案,稀疏性的问题得到了很好的解决,并且这样做还获得了一些额外的收益。大众点评作为一个本地化的生活信息服务平台,大部分用户的行为都集中自己的常驻地,导致用户到达一个新地方时,排序个性化明显不足。通过这种聚类的方式,将异地有相同行为的用户聚集在一起,也能解决一部分跨站的个性化问题。

    lambdadnn 的使用

    Lambda 梯度需要对同 Query 下的样本进行计算,但是正常情况下所有的样本是随机 Shuffle 到各个 Worker 的。因此我们需要对样本进行预处理:

    • 通过 QueryId 进行 Shuffle,将同一个 Query 的样本聚合在一起,同一个 Query 的样本打包进一个 TFRecord。
    • 由于每次请求 Query 召回的 Doc 数不一样,对于可变 Size 的 Query 样本在拉取数据进行训练时需要注意,TF 会自动补齐 Mini-Batch 内每个样本大小一致,导致输入数据中存在大量无意义的默认值样本。这里我们提供两点处理方式:
      • MR 过程中对 Key 进行处理,使得多个 Query 的样本聚合在一起,然后在训练的时候进行动态切分。
      • 读取到补齐的样本,根据设定的补齐标记获取索引位,去除补齐数据。

    还进行了如下优化:

    • 将 ID 类特征的映射等操作一并在预处理中完成,减少多轮 Training 过程中的重复计算。
    • 将样本转 TfRecord,利用 RecordDataSet 方式读取数据并计算处理,Worker 的计算性能大概提升了 10 倍。
    • Concat 多个 Categorical 特征,组合成 Multi-Hot 的 Tensor 进行一次 Embedding_Lookup 操作,减少 Map 操作的同时有助于参数做分片存储计算。
    • 稀疏 Tensor 在计算梯度以及正则化处理时保留索引值,仅对有数值的部分进行更新操作。
    • 多个 PS 服务器间进行分片存储大规模 Tensor 变量,减少 Worker 同步更新的通讯压力,减少更新阻塞,达到更平滑的梯度更新效果。

    整体下来,对于 30 亿左右的样本量、上亿级别的特征维度,一轮迭代大概在半小时内完成。适当的增加并行计算的资源,可以达到分钟级的训练任务。

    NDCG 的计算公式中,折损的权重是随着位置呈指数变化的。然而实际曝光点击率随位置变化的曲线与 NDCG 的理论折损值存在着较大的差异。

    对于移动端的场景来说,用户在下拉滑动列表进行浏览时,视觉的焦点会随着滑屏、翻页而发生变动。例如用户翻到第二页时,往往会重新聚焦,因此,会发现第二页头部的曝光点击率实际上是高于第一页尾部位置的。我们尝试了两种方案去微调 NDCG 中的指数位置折损:

    • 根据实际曝光点击率拟合折损曲线:根据实际统计到的曝光点击率数据,拟合公式替代 NDCG 中的指数折损公式,绘制的曲线如图 12 所示。
    • 计算 Position Bias 作为位置折损:Position Bias 在业界有较多的讨论,其中 [7][8] 将用户点击商户的过程分为观察和点击两个步骤:a. 用户需要首先看到该商户,而看到商户的概率取决于所在的位置;b. 看到商户后点击商户的概率只与商户的相关性有关。步骤 a 计算的概率即为 Position Bias,这块内容可以讨论的东西很多,这里不再详述。

    经过上述对 NDCG 计算改造训练出的 LambdaDNN 模型,相较 Base 树模型和 Pointwise DNN 模型,在业务指标上有了非常显著的提升。

    Lambda 梯度除了与 DNN 网络相结合外,事实上可以与绝大部分常见的网络结构相结合。为了进一步学习到更多交叉特征,我们在 LambdaDNN 的基础上分别尝试了 LambdaDeepFM 和 LambdaDCN 网络;其中 DCN 网络是一种加入 Cross 的并行网络结构,交叉的网络每一层的输出特征与第一层的原始输入特征进行显性的两两交叉,相当于每一层学习特征交叉的映射去拟合层之间的残差。

    离线的对比实验表明,Lambda 梯度与 DCN 网络结合之后充分发挥了 DCN 网络的特点,简洁的多项式交叉设计有效地提升模型的训练效果。

    近期 Google 开源的 TF Ranking(参考 https://daiwk.github.io/posts/platform-tf-ranking.html) 提出的 Groupwise 模型也对我们有一些启发。目前绝大部分的 Listwise 方法只是体现在模型训练阶段,在打分预测阶段依然是 Pointwise 的,即只会考虑当前商户相关的特征,而不会考虑列表上下文的结果,未来我们也会在这个方向上进行一些探索。

  • 相关阅读:
    (转)java web自定义分页标签
    关于在springmvc下使用@RequestBody报http status 415的错误解决办法
    (转)解决点击a标签返回页面顶部的问题
    优先队列详解priority_queue .RP
    7.23 学习问题
    7.24 学习问题
    7.25 学习问题
    python装饰器学习笔记
    SQL数据库简单操作
    form属性method="get/post
  • 原文地址:https://www.cnblogs.com/cx2016/p/13252960.html
Copyright © 2011-2022 走看看