【学习排序】 Learning to Rank中Pointwise关于PRank算法源码实现
? ? 最近終于忙完了Learning to Rank的作業(yè),同時(shí)也學(xué)到了很多東西.我準(zhǔn)備寫幾篇相關(guān)的文章簡單講述自己對(duì)它的理解和認(rèn)識(shí).第一篇準(zhǔn)備講述的就是Learning to Rank中Pointwise的認(rèn)識(shí)及PRank算法的實(shí)現(xiàn).主要從以下四個(gè)方面進(jìn)行講述:
 ? ? 1.學(xué)習(xí)排序(Learning to Rank)概念
 ? ? 2.基于點(diǎn)的排序算法(Pointwise)介紹
 ? ? 3.基于順序回歸(Ordinal Regression-based)的PRank排序算法
 ? ? 4.PRank算法Java\C++實(shí)現(xiàn)及總結(jié)
 
一. 學(xué)習(xí)排序(Learning to Rank)概念
? ??學(xué)習(xí)排序概念推薦轉(zhuǎn)載的文章:機(jī)器學(xué)習(xí)排序之Learning to Rank簡單介紹
 ? ? 1.首先,為什么會(huì)出現(xiàn)學(xué)習(xí)排序呢?
 ? ??傳統(tǒng)的排序方法是通過構(gòu)造一個(gè)排序函數(shù)實(shí)現(xiàn),在Information Retrieval領(lǐng)域一般按照相關(guān)度進(jìn)行排序。比較典型的是搜索引擎中一條查詢query,將返回一個(gè)相關(guān)的文檔document,然后根據(jù)(query,document)之間的相關(guān)度進(jìn)行排序,再返回給用戶。
 ? ? 而隨著影響相關(guān)度的因素(如PageRank)變多,Google目前排序方法考慮了200多種方法。這使得傳統(tǒng)排序方法變得困難,人們就想到通過機(jī)器學(xué)習(xí)來解決這一問題,這就導(dǎo)致了Learning to Rank的誕生。
 ? ? 2.然后是學(xué)習(xí)排序的基本流程如下圖所示.
 ? ? 很明顯它就是基本步驟就是通過訓(xùn)練集數(shù)據(jù)(Train Set)學(xué)習(xí)得到模型h,然后通過該模型去對(duì)測試集數(shù)據(jù)(Test Set)進(jìn)行計(jì)算和排序,最后得到一個(gè)預(yù)測的結(jié)果.
 
? ??3.那么,學(xué)習(xí)排序的數(shù)據(jù)集是怎樣的一個(gè)東西呢?也就是上圖中x、y、h分別代表著什么呢?
? ? 數(shù)據(jù)集可參考微軟136維數(shù)據(jù)——MSLR-WEB10K?它是2010年的數(shù)據(jù).形如:
? ???=============================================================
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0 qid:1 1:3 2:0 3:2 4:2 ... 135:0 136:0?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2 qid:1 1:3 2:3 3:0 4:0 ... 135:0 136:0?
? ? ? ? ? ?=============================================================
? ? ? ? ? ?其數(shù)據(jù)格式: label?qid:id ?feaid:feavalue ?feaid:feavalue ...
? ? 每行表示一個(gè)樣本,相同的查詢請(qǐng)求的樣本qid相同,上面就是兩個(gè)對(duì)qid為“1”的查詢;label表示該樣本和該查詢請(qǐng)求的相關(guān)程度,該label等級(jí)劃分方式為?{Perfect, Excellent,Good, Fair, Bad}?共五個(gè)類別,后面對(duì)應(yīng)的是特征和特征值,我們通常使用的<X,Y>即是<特征量,人工標(biāo)注>.
? ? 同樣你也可以使用比較經(jīng)典的2007的數(shù)據(jù)集——LETOR4.0,它是46維數(shù)據(jù).如下圖所示:
? ??它表示每行相當(dāng)于一個(gè)Document(樣本文檔),第一行是樣本相關(guān)程度,在46維中l(wèi)abel共三個(gè)值:2-完全相關(guān)、1-部分相關(guān)、0-不相關(guān);同時(shí)qid相同表示同一個(gè)查詢對(duì)應(yīng)多行樣本;中間是46維特征之,最后#相當(dāng)于注釋解釋.
? ? 4.如果你還是不清楚,我換成通俗的例子解釋:
?? ?比如,現(xiàn)在你在Google瀏覽器中輸入"Learning to Rank",它就相當(dāng)于一個(gè)qid.而下面列出的各個(gè)鏈接就是多個(gè)樣本集合,其中每一個(gè)都有200多種影響因素(如其中一種PageRank).在學(xué)習(xí)過程中需要找到一個(gè)模型來預(yù)測新查詢文檔的得分,并排序計(jì)算出用戶最想要的結(jié)果.
? ? PS:這是我的個(gè)人理解,如果有錯(cuò)誤或不足之處,歡迎提出! ?
二. 基于點(diǎn)的排序算法(Pointwise)介紹
? ??機(jī)器學(xué)習(xí)解決排序?qū)W習(xí)問題可分為3類:
 ? ? 1.基于回歸排序?qū)W習(xí)(regression-based algorithms):序列轉(zhuǎn)為實(shí)數(shù)
 ? ? 2.基于分類排序?qū)W習(xí)(classification-based algorithms):二值分類
 ? ? 3.基于順序回歸排序?qū)W習(xí)(ordinal regression-based algorithms)
 ? ? 但是這里我想講述的是最常見的分類,它們應(yīng)該與上面是交叉的:
 ? ? 1.基于點(diǎn)的LTR算法——Pointwise Approach
 ? ? 2.基于對(duì)的LTR算法——Pairwise Approach
 ? ? 3.基于列的LTR算法——Listwise Approach
? ??Pointwise處理對(duì)象是一篇文檔,將文檔轉(zhuǎn)化為特征向量后,機(jī)器學(xué)習(xí)系統(tǒng)根據(jù)訓(xùn)練得出的模型對(duì)文檔進(jìn)行打分(注意:訓(xùn)練集學(xué)習(xí)出權(quán)重模型去給測試集文檔打分是LTR中非常經(jīng)典的用法),打分的順序即為搜索排序的結(jié)果.
 ? ? Score(x)=w1*F1+w2*F2+w3*F3+...+w136*F136
 ? ? 其中w1-w136為136維對(duì)應(yīng)權(quán)重參數(shù),由訓(xùn)練集訓(xùn)練得到;F1-F136為測試文檔給出136個(gè)特征值.
 ? ? 原數(shù)據(jù)有5個(gè)類標(biāo)(0-4代表相關(guān)程度:Perfect>Excellent>Good>Fair>Bad),則設(shè)置5個(gè)閾值來區(qū)分所得分?jǐn)?shù)的分類.如果得分大于相關(guān)閾值,則劃分為相應(yīng)的類.常見算法包括:Prank、McRank
 ? ? 下面是我自己畫的一張圖,其中四根紅線是四個(gè)閾值,它把這些文檔集劃分為了五個(gè)不同類.每當(dāng)一個(gè)新的文檔來測試,它都會(huì)根據(jù)已有模型計(jì)算出相應(yīng)分?jǐn)?shù),再根據(jù)分?jǐn)?shù)和閾值劃分類即可.
 
三. PRank算法介紹
? ? PRank算法是基于點(diǎn)的排序?qū)W習(xí),順序回歸學(xué)習(xí)問題.其算法主要參考Kolby Crammer & Yoram Singer(From:The HeBrew University,以色列希伯來大學(xué))論文《Pranking with Ranking》.網(wǎng)址如下:
 ? ??http://papers.nips.cc/paper/2023-pranking-with-ranking.pdf
 ? ? 算法過程如下:
 
? ? 對(duì)于46維數(shù)據(jù)而言,它存在3個(gè)類標(biāo)(0-2).故上述算法中初始閾值b[0]=b[1]=b[2]=0,b[3]=正無窮.
? ? 注意它只有一層循環(huán)For(1...T)表示樣本集的總行數(shù),而沒有進(jìn)行迭代(CSDN三國那個(gè)例子含迭代錯(cuò)誤);它主要是通過預(yù)測標(biāo)號(hào)y~和實(shí)際標(biāo)號(hào)y進(jìn)行對(duì)比,來更新權(quán)重和閾值.
? ? 在H排序決策函數(shù)中,它通過K個(gè)閾值b把空間劃分為K個(gè)連續(xù)的子空間,每個(gè)子空間對(duì)應(yīng)一個(gè)序列號(hào),即滿足所有的樣本x都有相同的排序結(jié)果.對(duì)每個(gè)樣本,先計(jì)算權(quán)重w與xi的內(nèi)積w·x,找出所有滿足w·x-br中最小的br,并將此br對(duì)應(yīng)的序標(biāo)號(hào)xi作為排序模型對(duì)樣本的預(yù)測排序結(jié)果.
? ? 推薦中文資料:南開大學(xué)論文《基于PRank算法的主動(dòng)排序?qū)W習(xí)算法》
四. PRank算法Java\C++實(shí)現(xiàn)及總結(jié)
? ? 1.Java代碼實(shí)現(xiàn)
 ? ? 代碼中有詳細(xì)注釋,每個(gè)步驟都是按照上面的算法進(jìn)行設(shè)計(jì)的.左圖是主函數(shù),它主要包括:讀取文件并解析數(shù)據(jù)、寫數(shù)據(jù)(該函數(shù)可注釋掉,它是我用于驗(yàn)證讀取是否正確時(shí)寫的)、學(xué)習(xí)排序模型和打分預(yù)測.右圖是預(yù)測排序結(jié)果的算法.
 
? ? 2.C++代碼實(shí)現(xiàn)
? ? 該部分代碼參考自新浪播客:
? ??http://blog.sina.com.cn/s/blog_4c98b960010008xn.html
? ? 運(yùn)行結(jié)果過程如下圖所示,通過train.txt數(shù)據(jù)集得到model.txt,里面存儲(chǔ)的是46個(gè)權(quán)重.如:
? ? -0.052744 1.886342 1.002179 -6.400005 -1.824795 0.000000 0.000000 ..
? ? 然后通過該模型對(duì)test.txt進(jìn)行打分預(yù)測,同時(shí)計(jì)算正確率(已標(biāo)注Label=預(yù)測Label).
#include <iostream> #include <fstream> #include <limits> #include <iomanip>using namespace std;#define K 3 //排序的序數(shù),即如排成全相關(guān),部分相關(guān),不相關(guān),序數(shù)就是3 #define N 46 //特征的維數(shù)double *w; //權(quán)值 int *b; //偏置項(xiàng) int *y; int *t;//從文件中獲得特征值 X 存儲(chǔ)特征向量 yt 存儲(chǔ)標(biāo)簽 bool getData(double *x,int &yt,ifstream &fin) {if (fin.eof())return false;char data[1024];int index = 1;fin.getline(data,1024);char *p = data;char q[100];q[0] = p[0];q[1] = '\0';yt = atoi(q) + 1; // 標(biāo)簽 p = p+8;//跳過qid:xx的冒號(hào)for( ; *p != '\0'; ++p){if(*p == ':'){++p;int i = 0;for(i=0; *p != ' '; i++, p++){q[i] = *p;}q[i] = '\0'; x[index ++] = atof(q);}}return true; }//各變量進(jìn)行初始化 void Initialize() {w = new double[N+1];b = new int[K+1];y = new int[K+1];t = new int[K+1];int i;int r;for(i=1; i<=N;i++)w[i] = 0 ;for(r=1;r<=K-1;r++)b[r] = 0;b[K] = std::numeric_limits<int>::max();//無窮大 }//利用Prank算法進(jìn)行訓(xùn)練 void PrankTraining(double *x,int yt) {int i;int r;double wx = 0; //存儲(chǔ) W*X 的計(jì)算結(jié)果 for(i =1; i<=N; i++) //計(jì)算 W*X wx += w[i] * x[i];for(r =1; r<=K; r++) //找到滿足 W*X-b<0 的最小 r {if(wx - b[r] <0 )break;}int yy = r ; //預(yù)測值 if (yy == yt) //預(yù)測正確,直接返回 {return;} else //預(yù)測錯(cuò)誤,權(quán)值更新 {for(r=1; r<K; r++){if(yt <= r)y[r] = -1;elsey[r] = 1;}for(r=1; r<K; r++){if ((wx-b[r])*y[r] <= 0){t[r] = y[r];}elset[r] = 0;}//更新 W 和 b int sumt = 0;for(r=1; r<K; r++)sumt = sumt + t[r];for(i=1;i<=N;i++) //更新 W w[i] = w[i] + sumt*x[i];for(r=1; r<K; r++) //更新 b b[r] = b[r] - t[r];} }//利用得到的model進(jìn)行測試 int Pranking(double *x) {int i;int r;double wx = 0;for(i=1; i<=N; i++)wx = wx + w[i] * x[i];for(r=1; r<=K; r++)if(wx - b[r] <0 ){cout<< " "<<wx;break;}return r; }int main(int argc,char **argv) {int right=0,wrong=0;//排正確和錯(cuò)誤的樣本數(shù)//輸入訓(xùn)練數(shù)據(jù)文件名 string sin_train = "train.txt";ifstream fin_train(sin_train.c_str());if(fin_train.fail()){cout << "can't open the traningsetFile!"<<endl;return -1;}//輸入輸出模型文件名 string sout_model = "model.txt";ofstream fout_model(sout_model.c_str()); if(fout_model.fail()){cout << "can't open the ModelFile!"<<endl;return -1;}//輸入測試數(shù)據(jù)文件名string sin_test = "test.txt";ifstream fin_test(sin_test.c_str()); if(fin_test.fail()){cout << "can't open the testsetFile!"<<endl;return -1;}// 輸入輸出結(jié)果文件名string sout_result = "result.txt";ofstream fout_result(sout_result.c_str()); if(fout_result.fail()){cout << "open resultFile failed!"<<endl;return -1;}double *tr = new double[N+1]; // 特征向量 int yt; // 標(biāo)簽 Initialize(); //初始化權(quán)值w和偏置項(xiàng)b int i = 0;//讀入訓(xùn)練數(shù)據(jù)進(jìn)行訓(xùn)練得到modelwhile(true){if (getData(tr,yt,fin_train)){PrankTraining(tr,yt);//訓(xùn)練}elsebreak;}//將得到的w和b寫入文件char buff[128];cout<<"訓(xùn)練出的w為:\n";for(i=1; i<=N; i++) //寫 w{cout<<setw(8)<<w[i]<<'\t';memset(buff,0,sizeof(buff)); sprintf(buff,"%f",w[i]);fout_model << buff << " ";}fout_model<<endl;cout<<"\n\n訓(xùn)練出的b為:\n";for(i = 1; i<K;i++) //寫 b{cout<<b[i]<<'\t';memset(buff,0,sizeof(buff)); sprintf(buff,"%d",b[i]);fout_model << buff << " ";}//讀入測試數(shù)據(jù)進(jìn)行測試得到正確率while(true){if (getData(tr,yt,fin_test)){int yy = Pranking(tr);char p[2];p[0] = yy -1 + 48;p[1] = '\0';fout_result << p << endl;if (yy == yt)right ++;elsewrong ++;}elsebreak;}cout<<"\n\n排正確的個(gè)數(shù)為"<<right<<",錯(cuò)誤的個(gè)數(shù)為"<<wrong<<",正確率為%"<<right*100*1.0/(right+wrong)<<endl;cout<<b[0]<<'\t'<<b[1]<<'\t'<<b[2];//釋放申請(qǐng)的空間并關(guān)閉文件 delete []w; delete []y;delete []t;delete []b;delete []tr;fin_train.close();fin_test.close();fout_result.close();fout_model.close();system("PAUSE");return 0; }
五. 總結(jié)與問題
? ?  最后講述在該算法中你可能遇到的問題和我的體會(huì):
 ? ? 1.由于它是讀取文件,可能文件很大(幾百兆或上G).最初我設(shè)計(jì)的數(shù)組是double feature[10000][136],用來存儲(chǔ)每行特征值,但是如果行數(shù)太大時(shí),What can do?此時(shí)我們應(yīng)該設(shè)置動(dòng)態(tài)數(shù)組<List<List<Float>>>x解決.
 ? ? 2.最初閱讀了CSDN的Prank代碼,它迭代了1萬次,最后查看原文發(fā)現(xiàn)它并沒有迭代.所以你可以參考C++那部分代碼,每次只需要讀取一行數(shù)據(jù)處理,并記住上一次的46維權(quán)重和閾值即可.
 ? ? 3.為什么我從136維數(shù)據(jù)轉(zhuǎn)變成了46維數(shù)據(jù)?
 ? ? 你打開136維特征值數(shù)據(jù)時(shí),你會(huì)發(fā)現(xiàn)它的值特別大,不論是Pointwise,還是Pairwise和Listwise都可能出現(xiàn)越界,一次內(nèi)積求和可能就10的7次方數(shù)據(jù)了.但是46維數(shù)據(jù),每個(gè)特征值都是非常小的,所以如果用136維數(shù)據(jù),你需要對(duì)數(shù)據(jù)進(jìn)行歸一化處理,即數(shù)據(jù)縮小至-1到1之間.
 ? ? 4.評(píng)價(jià)Pointwise、Pairwise和Listwise指標(biāo)通常是MAP和NDCG@k,后面講述基于對(duì)的學(xué)習(xí)排序和基于列的學(xué)習(xí)排序會(huì)具體介紹.
 ? ? 5.你可能會(huì)發(fā)現(xiàn)數(shù)據(jù)集中存在vail驗(yàn)證集,以及交叉驗(yàn)證、交叉熵、梯度下降后面都會(huì)講述.但由于相對(duì)于算法,我對(duì)開發(fā)更感興趣,很多東西也是一知半解的.
 ? ? 6.最后要求該算法到Hadoop或Spark實(shí)現(xiàn)并行化處理,但算法的機(jī)制是串行化.有一定的方法,但我沒有實(shí)現(xiàn).我們做的是一種偽并行化處理,即模型得到權(quán)重后進(jìn)行并行化計(jì)算分?jǐn)?shù)排序.
 ? ? 最后簡單附上我們的實(shí)驗(yàn)結(jié)果,后面的算法實(shí)驗(yàn)結(jié)果是基于MAP和NDCG@k
 
? ?希望文章對(duì)大家有所幫助!主要是現(xiàn)在看到LTR很多都是理論介紹,論文也沒有具體代碼,而開源的RankLib有點(diǎn)看不懂,所以提出了自己的認(rèn)識(shí)及代碼執(zhí)行.我也是才接觸這個(gè)一個(gè)月,可能過程中存在錯(cuò)誤或不足之處,歡迎提出建議~同時(shí)感謝一起奮斗的伙伴,尤其是Pu哥.
? ? ? ?(By:Eastmount 2015-01-28 夜5點(diǎn)半? ??http://blog.csdn.net/eastmount/)
 
總結(jié)
以上是生活随笔為你收集整理的【学习排序】 Learning to Rank中Pointwise关于PRank算法源码实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: [android] 解决DatePick
- 下一篇: 【学习排序】 Learning to R
