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