使用Mahout搭建推薦系統之入門篇1-搭建REST風格簡單推薦系統

jopen 11年前發布 | 60K 次閱讀 Mahout 推薦引擎

     用意: 網絡上有很多關于使用mahout搭建推薦系統的文章,但是還沒有一個從建立推薦系統原型至部署到簡單服務器的完整教程. 雖然部分朋友對推薦系統很感興趣, 但是因hadoop的復雜而卻步.  同時對于那些沒有任何Web開發經驗的朋友來說, 一個完整的小型推薦系統可以很大的激發學習的興趣和動手的沖動. 我覺得動手的沖動比看書的沖動要重要的多.

     原型分為兩個系列 : JAVA原型和Python原型.

這篇博客主要是介紹JAVA推薦系統原型: 主要參考[1]

使用MyEclipse和Mahout開發一個REST風格[3]的簡單推薦系統

一. 搭建一個Hello world的REST服務器
1. 選擇建立MyEclise Web Service Project
    填寫Project Name, 填寫完畢后,注意Context root URL的內容.兩者是一致的.當然你也可以讓兩者不一樣,自己選擇一個名字.如Project Name為testHello.而URL為/rs.(URL十分重要,是最終WEB訪問路徑的一部分)

     Library見下圖 core server client json.

使用Mahout搭建推薦系統之入門篇1-搭建REST風格簡單推薦系統
2. 打開/WEB-INF下的web-xml可以看到

<servlet-mapping>
<servlet-name>JAX-RS REST Servlet</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>

其中的url-pattern將是WEB路徑的一部分,具體見下文.

2. 在/src目錄下加入HelloRS文件. 內容如下


import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;


//設置路徑為http://域名:端口/ConTextRootURL/url-pattern + /hello
//以我的機子為例:http://localhost:8080/rs/rest/hello

@Path("/recommend")
public class HelloRS {

  // 這個方法將返回普通文本
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String sayPlainTextHello() {
    return "Hello REST";
  }

  // 這個方法將返回XML文件
  @GET
  @Produces(MediaType.TEXT_XML)
  public String sayXMLHello() {
    return "<?xml version=\"1.0\"?>" + "<hello> Hello REST" + "</hello>";
  }

  // 這個方法將返回HTML文件
  @GET
  @Produces(MediaType.TEXT_HTML)
  public String sayHtmlHello() {
    return "<html> " + "<title>" + "Hello REST" + "</title>"
        + "<body><h1>" + "Hello REST" + "</body></h1>" + "</html> ";
  }

}


3. 選擇Project,使用Run as->MyEclipse Server Application即可.
使用 http://localhost:8080/rs/rest/hello 既可以看到返回Hello REST了.

修改文件后Tomcat將會自動檢測并布置.
注:有任何問題,看console中的日志輸出,找到Exception或者error, Google一下.

二. 利用mahout搭建簡單的推薦系統,數據使用代碼生成

1. 在Java Build Path引入mahout相關庫文件, 下載地址: http://pan.baidu.com/s/1Ea8pI
mahout核心類: 提供推薦Model等核心類
    mahout-core-0.5.jar
    mahout-math-0.5.jar
輔助類: 提供Log和部分數學公式類.
    uncommons-maths-1.2.jar
    google-collections-1.0-rc2.jar
    guava-r0.3.jar
    slf4j-api-1.6.0.jar

2. 在src下新建一個RecommenderIntro.java文件,代碼如下:

import org.apache.mahout.cf.taste.impl.model.file.*;
import org.apache.mahout.cf.taste.impl.neighborhood.*;
import org.apache.mahout.cf.taste.impl.recommender.*;
import org.apache.mahout.cf.taste.impl.similarity.*;
import org.apache.mahout.cf.taste.model.*;
import org.apache.mahout.cf.taste.neighborhood.*;
import org.apache.mahout.cf.taste.recommender.*;
import org.apache.mahout.cf.taste.similarity.*;
import java.io.*;
import java.util.*;

class RecommenderIntro {

     private FileDataModel model;
     private PearsonCorrelationSimilarity similarity;
     private NearestNUserNeighborhood neighborhood;
     private GenericUserBasedRecommender recommender;



    // 從filename中讀取數據(用戶id, 物品id, 評分rate), 生成數據類model, 相似類similarity以及最相近的鄰居類(2個)

     public RecommenderIntro(String filename) throws Exception {
          model = new FileDataModel(new File(filename));

          similarity = new PearsonCorrelationSimilarity(model);
          neighborhood =
               new NearestNUserNeighborhood(2, similarity, model);

          recommender = new GenericUserBasedRecommender(
                    model, neighborhood, similarity);
}



// 對用戶userid推薦前num個物品.

public List<RecommendedItem> SimpleRecommend(int userid, int num) throws Exception {

     List<RecommendedItem> recommendations =
     recommender.recommend(1, 1);

     return recommendations;

}

}

代碼介紹: 

    本算法是最簡單的基于用戶的協同過濾. 現實解釋:你想別人給你推薦一個電影,你會從一堆人中找到與你最熟悉的幾個人推薦電影給你,然后找到被推薦次數最多的電影. Model類用來存儲數據, mahout為了節約內存, 數據結構設計的很好,下次找個機會聊聊. Similarity計算兩個人之間的相似性, 而Neighborhood則是為每個人保存最相似的2個人.最后recommener結合model\neighborhood和similarity來為某個user推薦N個好友. 

3. 修改helloRS文件修改如下所示:

    將intro.csv文件放到src/目錄下

import java.util.List;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.apache.mahout.cf.taste.recommender.RecommendedItem;

//Sets the path to base URL + /hello
@Path("/recommend")
public class HelloRS {

    private RecommenderIntro recommender = null;
    private String filename = null;

    // This method is called if TEXT_PLAIN is request
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String sayPlainTextHello(
            @DefaultValue("1") @QueryParam("id") String id,
            @DefaultValue("1") @QueryParam("num") String num) throws Exception {

        int userId = Integer.valueOf(id);
        int rankNum = Integer.valueOf(num);
        String resultStr = getRecommender(userId, rankNum);

        return resultStr;
    }

    // This method is called if XML is request
    @GET
    @Produces(MediaType.TEXT_XML)
    public String sayXMLHello(@DefaultValue("1") @QueryParam("id") String id,
            @DefaultValue("1") @QueryParam("num") String num) throws Exception {
        int userId = Integer.valueOf(id);
        int rankNum = Integer.valueOf(num);
        String resultStr = getRecommender(userId, rankNum);

        return "<?xml version=\"1.0\"?>" + "<hello> " + resultStr + "</hello>";
    }

    // This method is called if HTML is request
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String sayHtmlHello(@DefaultValue("1") @QueryParam("id") String id,
            @DefaultValue("1") @QueryParam("num") String num) throws Exception {

        System.out.println(id + " " + num);
        int userId = Integer.valueOf(id);
        int rankNum = Integer.valueOf(num);
        String resultStr = getRecommender(userId, rankNum);

        return "<html> " + "<title>" + "Hello REST" + "</title>" + "<body><h1>"
                + resultStr + "</body></h1>" + "</html> ";
    }

    private String getRecommender(int userId, int num) throws Exception {

        if (filename == null) {
            String classPath = this.getClass().getClassLoader()
                    .getResource("/").getPath();

            classPath = classPath.replace("%20", "\\ ");
            filename = classPath + "intro.csv";
            System.out.println(filename);
        }

        if (recommender == null)
            recommender = new RecommenderIntro(filename);

        List<RecommendedItem> recommendedList = recommender.SimpleRecommend(
                userId, num);
        String resultStr = "Result=" + recommendedList.get(0).getItemID() + " "
                + recommendedList.get(0).getValue();

        return resultStr;
    }

}

代碼介紹: 代碼提供了XML\HTML和普通文本三個格式, 以瀏覽器默認的HTML格式為例. 

如果瀏覽器 輸入 http://localhost:8080/rs/rest/recommend?id=1&num=1 

參數表@DefaultValue("1") @QueryParam("id") String id, @DefaultValue("1") @QueryParam("num") String num表示獲得參數

id = "1", num = "1". 之后通過getRecommender來初始化Recommender并獲得數據. QueryParam表示GET方法的數據.

注: 由于intro.csv數據集比較少,所有部分id和num值無法返回合適的結果.

注: 由與intro.csv最終會部署在tomcat上,所以需要獲得tomcat中class的路徑.

注: recommender作為成員函數,保證每一個函數都引用同一份數據,保證一致性.

獲取路徑的方法如下:

String classPath = this.getClass().getClassLoader()
                    .getResource("/").getPath();

4. 運行代碼,即可使用http://localhost:8080/rs/rest/recommend?id=1&num=1 即可在瀏覽器中訪問.

返回:

Result=104 4.257081


一些問題: 
注意: 碰到tomcat端口設置問題: 8080 被占用.原因是已啟動了tomcat了.使用/etc/init.d/tomcat6 stop讓程序停止即可.
注意: Myeclipse安裝路徑中不要出現空格,否則會出現問題.

參考資料:
[1] Sean Owen "Mahout in Action"  http://book.douban.com/subject/4893547/

[2] Lars Vogel  REST with Java (JAX-RS) using Jersey - Tutorial http://www.vogella.com/articles/REST/article.html[3] REST 參考豆瓣API http://developers.douban.com/wiki/?title=movie_v2#reviews

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!