LambdaMART簡介——基於Ranklib源碼(一 lambda計算)


學習Machine Learning,閱讀文獻,看各種數學公式的推導,其實是一件很枯燥的事情。有的時候即使理解了數學推導過程,也仍然會一知半解,離自己寫程序實現,似乎還有一道鴻溝。所幸的是,現在很多主流的Machine Learning方法,網上都有open source的實現,進一步的閱讀這些源碼,多做一些實驗,有助於深入的理解方法。

Ranklib就是一套優秀的Learning to Rank領域的開源實現,其主頁在:http://people.cs.umass.edu/~vdang/ranklib.html,從主頁中可以看到實現了哪些方法。其中由微軟發布的LambdaMART是IR業內常用的Learning to Rank模型,本文介紹RanklibV2.1(當前最新的時RanklibV2.3,應該大同小異)中的LambdaMART實現,用以幫助理解paper中闡述的方法。

LambdaMART.java中的LambdaMART.learn()是學習流程的管控函數,學習過程主要有下面四步構成:

1. 計算deltaNDCG以及lambda;

2. 以lambda作為label訓練一棵regression tree;

3. 在tree的每個葉子節點通過預測的regression lambda值還原出gamma,即最終輸出得分;

4. 用3的模型預測所有訓練集合上的得分(+learningRate*gamma),然后用這個得分對每個query的結果排序,計算新的每個query的base ndcg,以此為基礎回到第1步,組成森林。

重復這個步驟,直到滿足下列兩個收斂條件之一:

1. 樹的個數達到訓練參數設置;

2. Random Forest在validation集合上沒有變好。

下面用一組實際的數據來說明整個計算過程,假設我們有10個query的訓練數據,每個query下有10個doc,每個q-d對有10個feature,如下:

 1 0 qid:1830 1:0.002736 2:0.000000 3:0.000000 4:0.000000 5:0.002736 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 2 0 qid:1830 1:0.025992 2:0.125000 3:0.000000 4:0.000000 5:0.027360 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 3 0 qid:1830 1:0.001368 2:0.000000 3:0.000000 4:0.000000 5:0.001368 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 4 1 qid:1830 1:0.188782 2:0.375000 3:0.333333 4:1.000000 5:0.195622 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 5 1 qid:1830 1:0.077975 2:0.500000 3:0.666667 4:0.000000 5:0.086183 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 6 0 qid:1830 1:0.075239 2:0.125000 3:0.333333 4:0.000000 5:0.077975 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 7 1 qid:1830 1:0.079343 2:0.250000 3:0.666667 4:0.000000 5:0.084815 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 8 1 qid:1830 1:0.147743 2:0.000000 3:0.000000 4:0.000000 5:0.147743 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
 9 0 qid:1830 1:0.058824 2:0.000000 3:0.000000 4:0.000000 5:0.058824 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
10 0 qid:1830 1:0.071135 2:0.125000 3:0.333333 4:0.000000 5:0.073871 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
11 1 qid:1840 1:0.007364 2:0.200000 3:1.000000 4:0.500000 5:0.013158 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
12 1 qid:1840 1:0.097202 2:0.000000 3:0.000000 4:0.000000 5:0.096491 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
13 2 qid:1840 1:0.169367 2:0.000000 3:0.500000 4:0.000000 5:0.169591 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000
14 ......

為了簡便,省略了余下的數據。上面的數據格式是按照Ranklib readme中要求的格式組織(類似於svmlight),除了行號之外,第一列是q-d對的實際label(人標注數據),第二列是qid,后面10列都是feature。

這份數據每組qid中的doc初始順序可以是隨機的,也可以是從實際的系統中獲得的當前順序。總之這個是計算ndcg的初始狀態。對於qid=1830,它的10個doc的初始順序的label序列是:0, 0, 0, 1, 1, 0, 1, 1, 0, 0(雖然這份序列中只有label值為0和1的,實際中也會有2,3等,由自己的標注標准決定)。我們知道dcg的計算公式是:

\begin{equation} dcg(i)=\frac{2^{label(i)}-1}{log_{2}{(i+1)}} \end{equation}

i表示當前doc在這個qid下的位置(從1開始,避免分母為0),label(i)是doc(i)的標注值。而一個query的dcg則是其下所有doc的加和:

\begin{equation} dcg(query)=\sum_{i}^{ }\frac{2^{label(i)}-1}{log_{2}{(i+1)}} \end{equation}

 根據上式可以計算初始狀態下每個qid的dcg:

 

        $ dcg(qid=1830)=\frac{2^{0}-1}{log_{2}{(1+1)}}+\frac{2^{0}-1}{log_{2}{(2+1)}}+...+\frac{2^{0}-1}{log_{2}{(10+1)}} $

                                 $ =0+0+0+0.431+0.387+0+0.333+0.315+0+0=1.466 $

 

要計算ndcg,還需要計算理想集的dcg,將初始狀態按照label排序,qid=1830得到的序列是1,1,1,1,0,0,0,0,0,0,計算dcg:

 

        $ ideal\_dcg(qid=1830)=\frac{2^{1}-1}{log_{2}{(1+1)}}+\frac{2^{1}-1}{log_{2}{(2+1)}}+...+\frac{2^{0}-1}{log_{2}{(10+1)}} $

                                           $ =1+0.631+0.5+0.431+0+0+0+0+0+0=2.562 $

 

兩者相除得到初始狀態下qid=1830的ndcg:

 

        $ ndcg(qid=1830)=\frac{dcg(qid=1830)}{ideal\_ndcg(qid=1830)}=\frac{1.466}{2.562}=0.572 $

 

 下面要計算每一個doc的deltaNDCG,公式如下:

\begin{equation} deltaNDCG(i,j)=\left |ndcg(original\ sequence)-ndcg(swap(i,j)\ sequence)\right | \end{equation}

deltaNDCG(i,j)是將位置i和位置j的位置互換后產生的ndcg變化(其他位置均不變),顯然有相同label的deltaNDCG(i,j)=0。

在qid=1830的初始序列0, 0, 0, 1, 1, 0, 1, 1, 0, 0,由於前3的label都一樣,所以deltaNDCG(1,2)=deltaNDCG(1,3)=0,不為0的是deltaNDCG(1,4), deltaNDCG(1,5), deltaNDCG(1,7), deltaNDCG(1,8)。

將1,4位置互換,序列變為1, 0, 0, 0, 1, 0, 1, 1, 0, 0,計算得到dcg=2.036,整個deltaNDCG(1,4)的計算過程如下:

 

        $ dcg(qid=1830,swap(1,4))=\frac{2^{1}-1}{log_{2}{(1+1)}}+\frac{2^{0}-1}{log_{2}{(2+1)}}+...+\frac{2^{0}-1}{log_{2}{(10+1)}} $

                                                  $ =1+0+0+0+0.387+0+0.333+0.315+0+0=2.036 $

        $ ndcg(swap(1,4))=\frac{dcg(swap(1,4))}{ideal\_dcg}=\frac{2.036}{2.562}=0.795 $

        $ deltaNDCG(1,4)=detalNDCG(4,1)=\left |ndcg(original\ sequence)-ndcg(swap(1,4))\right |=\left |0.572-0.795\right |=0.222 $

 

同樣過程可以計算出deltaNDCG(1,5)=0.239, deltaNDCG(1,7)=0.260, deltaNDCG(1,8)=0.267等。

進一步,要計算lambda(i),根據paper,還需要ρ值,ρ可以理解為doci比docj差的概率,其計算公式為:

\begin{equation} \rho _{ij}=\frac{1}{1+e^{\sigma (s_i-s_j)}} \end{equation}

Ranklib中直接取σ=1(σ的值決定rho的S曲線陡峭程度),如下圖,藍,紅,綠三種顏色分別對應σ=1,2,4時ρ函數的曲線情形(橫坐標是si-sj):

初始時,模型為空,所有模型預測得分都是0,所以si=sj=0,ρij≡1/2,lambda(i,j)的計算公式為:

\begin{equation} \lambda _{ij}=\rho_{ij}*\left |deltaNDCG(i,j)\right | \end{equation}

 

上式為Ranklib中實際使用的公式,而在paper中,還需要再乘以-σ,在σ=1時,就是符號正好相反,這兩種方式是等價的,符號並不影響模型訓練結果(其實大可以把代碼中lambda的值前面加一個負號,只是注意在每輪計算train, valid和最后計算test的ndcg的時候,模型預測的得分modelScores要按升序排列——越負的doc越好,而不是源代碼中按降序。最后訓練出的模型是一樣的,這說明這兩種方式完全對稱,所以符號的問題可以省略。甚至不乘以-σ,更符合人的習慣——分數越大越好,降序排列結果。):

\begin{equation} \lambda _{i}=\sum_{j(label(i)>label(j))}{\lambda_{ij}}-\sum_{j(label(i)<label(j))}{\lambda_{ij}} \end{equation}

計算lambda(1),由於label(1)=0,qid=1830中的其他doc的label都大於或者等於0,所以lamda(1)的計算中所有的lambda(1,j)都為負項。將之前計算的各deltaNDCG(1,j)代入,且初始狀態下ρij≡1/2,所以:

 

        $ \lambda_1=-0.5*(deltaNDCG(1,3)+deltaNDCG(1,4)+deltaNDCG(1,6)+deltaNDCG(1,7)) $

            $ =-0.5*(0.222+ 0.239+ 0.260+ 0.267)=-0.495 $

 

可以計算出初始狀態下qid=1830各個doc的lambda值,如下:

 1 qId=1830    0.000   0.000   0.000   -0.111  -0.120  0.000   -0.130  -0.134  0.000   0.000   lambda(1): -0.495
 2 qId=1830    0.000   0.000   0.000   -0.039  -0.048  0.000   -0.058  -0.062  0.000   0.000   lambda(2): -0.206
 3 qId=1830    0.000   0.000   0.000   -0.014  -0.022  0.000   -0.033  -0.036  0.000   0.000   lambda(3): -0.104
 4 qId=1830    0.111   0.039   0.014   0.000   0.000   0.015   0.000   0.000   0.025   0.028   lambda(4): 0.231 
 5 qId=1830    0.120   0.048   0.022   0.000   0.000   0.006   0.000   0.000   0.017   0.019   lambda(5): 0.231 
 6 qId=1830    0.000   0.000   0.000   -0.015  -0.006  0.000   -0.004  -0.008  0.000   0.000   lambda(6): -0.033
 7 qId=1830    0.130   0.058   0.033   0.000   0.000   0.004   0.000   0.000   0.006   0.009   lambda(7): 0.240 
 8 qId=1830    0.134   0.062   0.036   0.000   0.000   0.008   0.000   0.000   0.003   0.005   lambda(8): 0.247 
 9 qId=1830    0.000   0.000   0.000   -0.025  -0.017  0.000   -0.006  -0.003  0.000   0.000   lambda(9): -0.051
10 qId=1830    0.000   0.000   0.000   -0.028  -0.019  0.000   -0.009  -0.005  0.000   0.000   lambda(10): -0.061

 上表中每一列都是考慮了符號的lamda(i,j),即如果label(i)<label(j),則為負值,反之為正值,每行結尾的lamda(i)是前面的加和,即為最終的lambda(i)。

可以看到,lambda(i)在系統中表達了doc(i)上升或者下降的強度,label越高,位置越后,lambda(i)為正值,越大,表示趨向上升的方向,力度也越大;label越小,位置越靠前,lambda(i)為負值,越小,表示趨向下降的方向,力度也大(lambda(i)的絕對值表達了力度。)

然后Regression Tree開始以每個doc的lamda值為目標,訓練模型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM