日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【学习排序】 Learning to Rank中Pointwise关于PRank算法源码实现

發布時間:2024/5/28 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【学习排序】 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代碼實現
? ?
代碼中有詳細注釋,每個步驟都是按照上面的算法進行設計的.左圖是主函數,它主要包括:讀取文件并解析數據、寫數據(該函數可注釋掉,它是我用于驗證讀取是否正確時寫的)、學習排序模型和打分預測.右圖是預測排序結果的算法.

? ?代碼如下: package com.example.pointwise;import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List;/*** Pointwise基于點學習排序(Learning to Rank)的Prank算法* @author Eastmount YXZ* 參考資料* 該算法從136維數據集改成46維數據集,中間可能有注釋不一致現象* (原始論文) http://papers.nips.cc/paper/2023-pranking-with-ranking.pdf* (新浪) http://blog.sina.com.cn/s/blog_4c98b960010008xn.html* (CSDN)http://blog.csdn.net/pennyliang/article/details/17333373*/ public class Prank {public int RANK_NUM = 10000; //記錄總樣本數 (總行數)public int RANK_CATA = 46; //排序的特征維數 (數據集136維 后改為46維)public int RANK_ITER = 1; //排序的迭代次數 (原文迭代1次)public int RANK_LABEL= 3; //排序劃分的閾值 (微軟數據集劃分5類 0-4) 3維全相關,部分相關,不相關//采用該方法實現動態數組添加數據List<Float> weight = null; //特征值的權重向量 (46個 136個)//訓練集數據 每行共48個數據 (46個特征值 二維數組-feature[行號][46] + 真實Label值0-2 + qid值)List<List<Float>> x = null; Float [] b = null; //閾值數 K+1個(RANK_LABEL+1)public int sumLabel = 0; //文件總行數 (標記數)/** * 函數功能 讀取文件* 參數 String filePath 文件路徑*/public void ReadTxtFile(String filePath) throws IOException {String encoding="GBK";File file = new File(filePath); //文件BufferedReader bufferedReader = null;try {//判斷文件是否存在if(file.isFile() && file.exists()) { //輸入流InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding); bufferedReader = new BufferedReader(read);String lineTxt = null;sumLabel =0; //記錄總樣本數x = new ArrayList<List<Float>> ();//按行讀取數據并分解數據while((lineTxt = bufferedReader.readLine()) != null) {String str = null;int lengthLine = lineTxt.length();List<Float> subList=new ArrayList<Float>();x.add(subList);//獲取數據 字符串空格分隔String arrays[] = lineTxt.split(" ");for(int i=2; i<arrays.length; i++) {if(i>=48) { //#號后跳出 后面注釋不進行讀取continue;}//獲取特征:特征值 如1:0.0004String subArrays[] = arrays[i].split(":");int number = Integer.parseInt(subArrays[0]); //判斷特征 float value = Float.parseFloat(subArrays[1]); subList.add(value); }//獲取每行樣本的Label值 i=0 (五個等級0-4)subList.add(Float.parseFloat(arrays[0]));//獲取qid值 i=1String subArrays[] = arrays[1].split(":");subList.add(Float.parseFloat(subArrays[1]));//總行數+1sumLabel++; } //End 按行讀取read.close();} else {System.out.println("找不到指定的文件\n");}} catch (Exception e) {System.out.println("讀取文件內容出錯");e.printStackTrace();} finally {bufferedReader.close();}}/*** 函數 寫文件* 參數 String filePath 文件路徑* 注意 該函數還是136維數據,但算法該成46維 故不使用該函數 */public void WriteTxtFile(String filePath) {try {System.out.println("文件輸出");String encoding = "GBK";FileWriter fileWriter = new FileWriter(filePath);//按行寫文件for(int i=0; i<sumLabel; i++) {fileWriter.write("樣本行數"+i+"\r\n");fileWriter.flush(); String value;//寫數據特征值 136for(int j=0;j<136;j++) {value = String.valueOf(x.get(i).get(j)); //輸出第i行 第j個特征值 fileWriter.write(value+" ");}//label等級 qidfileWriter.write("\r\n");value = String.valueOf(x.get(i).get(136)); //labelfileWriter.write(value+" ");value = String.valueOf(x.get(i).get(137));fileWriter.write(value+" ");fileWriter.write("\r\n");}fileWriter.close();} catch(Exception e) {e.printStackTrace();}} /*** 學習排序* 主要功能計算136維權重w和劃分五個等級的閾值b*/public void LearningToRank() {int realRank; //真實Label等級int predictRank; //預測Label等級Float[] y= new Float[RANK_LABEL+1]; //new labelFloat tao [] = new Float[RANK_LABEL+1];//初始化權重 全為0weight = new ArrayList<Float>();for(int i=0; i< RANK_CATA; i++){ //特征向量的維數weight.add((float) 0.0);}//初始化閾值 b[0]=b[1]=[2]=0 b[3]=正無窮大b=new Float[RANK_LABEL+1];for(int i=0; i<RANK_LABEL; i++){ //b[0] b[1] b[2]b[i] = (float) 0.0;}b[RANK_LABEL] = Float.POSITIVE_INFINITY; //b[3]/** 開始計算權重 * 注意:迭代主要參照CSDN博客,它沒有退出.同時沒有損失計算,其結果差別不大* 同時原論文中Loop 1...T是總行數 并沒有講述迭代*/for(int iter = 0; iter < RANK_ITER; iter++){ //總的迭代次數 RANK_ITER=1for(int i=0; i< RANK_NUM; i++){ //總樣本數 可以設置讀取txt中部分//測試順序predictRank = 1;//權重*特征向量-閾值float sumWX = (float) 0.0;for(int z=0; z<46; z++) {sumWX += weight.get(z)*x.get(i).get(z); } //預測排名for(int r=1;r<=RANK_LABEL;r++) { //閾值數 RANK_LABEL=3if(sumWX-b[r]<0) {predictRank = r;break;}}//獲取真實等級 即數據集中第一個Label數字realRank = Math.round(x.get(i).get(46)); //四舍五入并轉整數if(realRank!=predictRank) {for(int r=1; r < RANK_LABEL; r++){//若136維數據 5個值時if(realRank <= r) { // y形如 1 1 -1 -1 -1y[r] = (float)-1;}else {y[r] = (float)1;}}float tao_sum = (float) 0.0; //tau和for(int r=1; r < RANK_LABEL; r++) { //三個等級//權重*特征向量-閾值if((sumWX - b[r]) * y[r] <= 0) {tao[r] = y[r];} else {tao[r] = (float) 0.0;}tao_sum += tao[r]; }//更新數據for(int z=0; z<RANK_CATA; z++) { //136維權重float newWeight = weight.get(z) +tao_sum*x.get(i).get(z);weight.set(z, newWeight);} for(int r=1;r < RANK_LABEL;++r) { //5個閾值b[r] = b[r] - tao[r]; }} //End ifelse {continue;}} //End 樣本總數} //End 迭代次數}/*** 函數 預測排序結果* 主要 通過LearningToRank()函數計算的得分計算分數,再根據閾值劃分等級*/public void PredictNewLabel() {float rightCount = 0;float score = (float) 0.0;for(int i=0; i < RANK_NUM; i++){int predict_r = 1;//權重*特征向量-閾值 (W*X-B)float sumWX = (float) 0.0; for(int z=0; z<46; z++) {sumWX = sumWX + weight.get(z) * x.get(i).get(z);}for(int r=1; r<= RANK_LABEL; r++){ //5if(sumWX < b[r]){score = sumWX;predict_r = r;break;}}//計算正確概率if(predict_r == Math.round(x.get(i).get(46))) //46維數據 46-label 47-qid 0-45特征值{rightCount++;}System.out.println("predict="+predict_r+" score="+score+" real="+x.get(i).get(46));}//輸出結果System.out.println("正確率:"+rightCount/(float)RANK_NUM);System.out.println("輸出閾值");for(int i= 1;i<4;i++){System.out.println(b[i]+" ");}}/*** 主函數 */public static void main(String[] args) {String fileInput = "train.txt";String fileOutput = "output.txt";String fileRank = "rank.txt";//實例化Prank prank = new Prank();try {//第一步 讀取文件并解析數據prank.ReadTxtFile(fileInput);//第二步 輸出解析的基礎數據//prank.WriteTxtFile(fileOutput);//第三步 學習排序訓練模型prank.LearningToRank();//第四步 測試打分排序prank.PredictNewLabel();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** End*/ }? ?運行結果如下圖所示,算法流程分析都很清楚,同時我采用的是下標從0開始取.b[1]和[2]兩個閾值即可劃分為3個不同的類,b[3]=Infinity.但是預測結果總是一個值,不知道為什么?可能算法中有些細節錯誤,糾結了我很長時間.如果知道希望告知.下面是采用C++實現.

? ? 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算法源码实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。