Learning to Rank模型總結

LTR介紹

搜索排序主要有兩個步驟:

  1. query-doc匹配:尋找與當前輸入的query相關度高的docs
  2. 高相關度docs精確排序:選取更多特徵並按照用戶點擊該doc的可能性大小精確排序

Learning to Rank就是一類目前最常用的,通過機器學習實現步驟2的算法,它主要包含單文檔方法(pointwise)、文檔對方法(pairwise)和文檔列表(listwise)三種類型。

pointwise

  • 對於某一個query,將每個doc分別判斷與這個query的相關程度,由此將docs排序問題轉化爲了分類(比如相關、不相關)或迴歸問題(相關程度越大,迴歸函數的值越大)。
  • pointwise方法只將query與單個doc建模,建模時未將其他docs作爲特徵進行學習,也就無法考慮到不同docs之間的順序關係。

pairwise

並不關心某一個doc與query相關程度的具體數值,而是將排序問題轉化爲任意兩個不同docs [公式]和[公式]誰與當前query更相關的相對順序的排序問題,一般分爲 [公式]比[公式] 更相關、更不相關和相關程度相等三個類別,分別記爲{+1, -1, 0},由此便又轉化爲了分類問題

listwise

將一個query對應的所有相關文檔看作一個整體,作爲單個訓練樣本

經典算法

RankNet

屬於pairwise方法,使用{+1, -1, 0}作爲對應的類別標籤,然後使用文檔對<docidocj><doci,docj>作爲樣本的輸入特徵,由此將排序問題轉化爲了分類問題

有一個好處:無需對每個doc與query的相關性進行精確標註(實際大規模數據應用場景下很難獲得),只需獲得docs之間的相對相關性,相對容易獲得,可通過搜索日誌、點擊率數據等方式獲得

xi和xj代表doci和docj的特徵,f(x,w)代表打分函數,x和w分別代表輸入特徵和參數,則文檔i和文檔j的分數是
在這裏插入圖片描述
借用了sigmoid函數來定義doci比docj更相關:
在這裏插入圖片描述
由於Pij是[0,1],但是真實標籤是Sij {1,-1,0},所以要映射一下,真實概率:
在這裏插入圖片描述
利用交叉熵作爲損失函數:
在這裏插入圖片描述
在這裏插入圖片描述
所以Cij關於任一待優化參數 w 的偏導數爲
在這裏插入圖片描述
SGD優化:
在這裏插入圖片描述
由於式中的:
在這裏插入圖片描述
可以記
在這裏插入圖片描述
總loss
在這裏插入圖片描述

如何加速訓練?
我們把所有文檔對<doci,docj>,都把排序高的放前面,則Sij只有{1}
在這裏插入圖片描述
在這裏插入圖片描述
式(14)的含義是:對於文檔i:我們首先找到所有相關性排在文檔i後面的文檔j(組成{i,j} ),並找到所有相關性排在文檔i前面的文檔k(組成 {k,i} )(排在前面的文檔代表相關性更強);再ij求和,其組成了第一項,對所有的ki 求和,其組成了第二項。由於第一項和第二項的求和符號互不關聯(互相沒有聯繫),所以第二項中的k可改爲j。

舉例:
在這裏插入圖片描述
在這裏插入圖片描述
若使用他們進行優化迭代,便將SGD算法轉化爲了mini-batch SGD算法,如式(21)所示。此時,RankNet在單次迭代時會對同一query下所有docs遍歷後更新權值,訓練時間得以從n2n^2 降至nn ,n爲單條query下docs的平均數,它被稱爲RankNet算法的加速訓練
在這裏插入圖片描述

LambdaRank

RankNet以錯誤pair最少爲優化目標,但是僅以錯誤pair數來評價排序的好壞是不夠的,像NDCG或者ERR等信息檢索中的評價指標就只關注top k個結果的排序。由於這些指標不可導或導數不存在,當我們採用RankNet算法時,往往無法以它們爲優化目標(損失函數)進行迭代,所以RankNet的優化目標和信息檢索評價指標之間還是存在差距的。以下圖爲例:
左邊排序1,右邊排序2
在這裏插入圖片描述
藍色表示相關文檔,灰色表示不相關文檔,RankNet以Error pair(錯誤文檔對數目)的方式計算cost。左邊排序1排序錯誤的文檔對(pair)共有13對,故cost爲13,右邊排序2通過把第一個相關文檔下調3個位置,第二個相關文檔上條5個位置,將cost降爲11,但是像NDCG或者ERR等指標只關注top k個結果的排序,在優化過程中下調前面相關文檔的位置不是我們想要得到的結果。上圖排序2左邊黑色的箭頭表示RankNet下一輪的調序方向和強度,但我們真正需要的是右邊紅色箭頭代表的方向和強度,即更關注靠前位置的相關文檔的排序位置的提升。LambdaRank正是基於這個思想演化而來,其中Lambda指的就是紅色箭頭,代表下一次迭代優化的方向和強度,也就是梯度。

具體來說,由於需要對現有的loss或loss的梯度進行改進,而NDCG等指標又不可導,我們便跳過loss,直接簡單粗暴地在RankNet加速算法形式的梯度上(式(22))再乘一項,以此新定義了一個Lambda梯度,如式(23)所示。其中Z表示評價指標,可取NDCG、ERR等指標。把交換兩個文檔的位置引起的評價指標的變化作爲其中一個因子

損失函數的梯度代表了文檔下一次迭代優化的方向和強度,由於引入了更關注頭部正確率的評價指標,Lambda梯度得以讓位置靠前的優質文檔排序位置進一步提升。有效避免了排位靠前的優質文檔的位置被下調的情況發生。LambdaRank相比RankNet的優勢在於分解因式後訓練速度變快,同時考慮了評價指標,直接對問題求解,效果更明顯。此外需要注意的是,由於之前我們並未對得分函數s = f(x,w) 具體規定,所以它的選擇比較自由,可以是RankNet中使用的NN,也可以是LambdaMART使用的MART,還可以是GBDT等

信息檢索常用指標

MAP

  1. Precision
    在這裏插入圖片描述
  2. Recall
    在這裏插入圖片描述
  3. Average precision(AveP)
    把準確率看做是召回率的函數,即:P=f(R)P=f(R),也就是隨着召回率從0到1,準確率的變化情況。AveP計算方式可以簡單的認爲是:
    在這裏插入圖片描述
    其中RR表示相關文檔的總個數,position(r)position(r)
    表示,結果列表從前往後看,第rr個相關文檔在列表中的位置。比如,有三個相關文檔,位置分別爲1、3、6,那麼AveP=1/3×(1/1+2/3+3/6)AveP=1/3×(1/1+2/3+3/6)。在編程的時候需要注意,位置和第i個相關文檔,都是從1開始的,不是從0開始的

AveP意義是在召回率從0到1逐步提高的同時,對每個R位置上的P進行相加,也即要保證準確率比較高,才能使最後的AveP比較大

  1. Mean average precision(MAP):
    通常會用多個查詢語句來衡量檢索系統的性能,所以應該對多個查詢語句的AveP(the mean of average precision scores),求均值
    在這裏插入圖片描述

nDCG

在MAP計算公式中,文檔只有相關不相關兩種,而在nDCG中,文檔的相關度可以分多個等級進行打分

  1. Cumulative Gain(CG)
    表示前p個位置累計得到的效益,公式如下
    在這裏插入圖片描述
    其中relireli表示第i個文檔的相關度等級,如:2表示非常相關,1表示相關,0表示無關,-1表示垃圾文件
  2. Discounted cumulative gain(DCG)
    由於在CGp的計算中對位置信息不敏感,比如檢索到了三個文檔相關度依次是{3,-1,1}和{-1,1,3},顯然前面的排序更優,但是它們的CG相同,所以要引入對位置信息的度量計算
    在這裏插入圖片描述
  3. Ideal DCG(IDCG)
    IDCG是理想情況下的DCG,即對於一個查詢語句和p來說,DCG的最大值
    在這裏插入圖片描述
    其中REL|REL|表示,文檔按照相關性從大到小的順序排序,取前p個文檔組成的集合。也就是按照最優的方式對文檔進行排序
  4. Normalize DCG(nDCG)
    由於每個查詢語句所能檢索到的結果文檔集合長度不一,p值的不同會對DCG的計算有較大的影響。所以不能對不同查詢語句的DCG進行求平均,需要進行歸一化處理
    在這裏插入圖片描述

ERR

  1. Mean reciprocal rank (MRR)
    MRR是指多個查詢語句的排名倒數的均值,其中rankirank_i表示第i個查詢語句的第一個正確答案的排名
    在這裏插入圖片描述
  2. Expected reciprocal rank (ERR)
    區別RR是計算第一個相關文檔的位置的倒數,ERR表示用戶的需求被滿足時停止的位置的倒數的期望。首先是計算用戶在位置rr停止的概率PPrPP_r,如下所示
    在這裏插入圖片描述
    其中RiR_i是關於文檔相關度等級的函數,可以選取如下的函數
    在這裏插入圖片描述
    那麼ERR的計算公式如下:
    在這裏插入圖片描述
    更通用一點,ERR不一定計算用戶需求滿足時停止的位置的倒數的期望,可以是其它基於位置的函數φ(r)φ(r),只要滿足φ(0)=1φ(0)=1,且φ(r)0φ(r)→0隨着rr→∞

指標代碼實現

import numpy as np


def average_precision(gt, pred):

    if not gt:
        return 0.0

    score = 0.0
    num_hits = 0.0
    for i,p in enumerate(pred):
        if p in gt and p not in pred[:i]:
            num_hits += 1.0
            
            score += num_hits / (i + 1.0)

    return score / max(1.0, len(gt))


def NDCG(gt, pred, use_graded_scores=False):
    score = 0.0
    for rank, item in enumerate(pred):
        if item in gt:
            if use_graded_scores:
                grade = 1.0 / (gt.index(item) + 1)
            else:
                grade = 1.0
            score += grade / np.log2(rank + 2)

    norm = 0.0
    for rank in range(len(gt)):
        if use_graded_scores:
            grade = 1.0 / (rank + 1)
        else:
            grade = 1.0
        norm += grade / np.log2(rank + 2)
    return score / max(0.3, norm)


def metrics(gt, pred, metrics_map):

    out = np.zeros((len(metrics_map),), np.float32)

    if ('MAP' in metrics_map):
        avg_precision = average_precision(gt=gt, pred=pred)
        out[metrics_map.index('MAP')] = avg_precision

    if ('RPrec' in metrics_map):
        intersec = len(gt & set(pred[:len(gt)]))
        out[metrics_map.index('RPrec')] = intersec / max(1., float(len(gt)))

    if 'MRR' in metrics_map:
        score = 0.0
        for rank, item in enumerate(pred):
            if item in gt:
                score = 1.0 / (rank + 1.0)
                break
        out[metrics_map.index('MRR')] = score

    if 'MRR@10' in metrics_map:
        score = 0.0
        for rank, item in enumerate(pred[:10]):
            if item in gt:
                score = 1.0 / (rank + 1.0)
                break
        out[metrics_map.index('MRR@10')] = score

    if ('NDCG' in metrics_map):
        out[metrics_map.index('NDCG')] = NDCG(gt, pred)

    return out

測試

gt_doc_ids = {0,1,2}
pred_doc_ids = [9,8,7,6,5,4,3,2,1,0]
result = metrics(
              gt=gt_doc_ids, pred=pred_doc_ids, metrics_map=METRICS_MAP)
print(result)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章