使用Mahout搭建推薦系統之入門篇1-搭建REST風格簡單推薦系統
用意: 網絡上有很多關于使用mahout搭建推薦系統的文章,但是還沒有一個從建立推薦系統原型至部署到簡單服務器的完整教程. 雖然部分朋友對推薦系統很感興趣, 但是因hadoop的復雜而卻步. 同時對于那些沒有任何Web開發經驗的朋友來說, 一個完整的小型推薦系統可以很大的激發學習的興趣和動手的沖動. 我覺得動手的沖動比看書的沖動要重要的多.
原型分為兩個系列 : JAVA原型和Python原型.
這篇博客主要是介紹JAVA推薦系統原型: 主要參考[1]
使用MyEclipse和Mahout開發一個REST風格[3]的簡單推薦系統
Library見下圖 core server client json.

<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> "; } }
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
[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