來源:http://www.ibm.com/developerworks/cn/java/j-lo-mahout/index.htmlhtml
推薦引擎利用特殊的信息過濾(IF,Information Filtering)技術,將不一樣的內容(例如電影、音樂、書籍、新聞、圖片、網頁等)推薦給可能感興趣的用戶。一般狀況下,推薦引擎的實現是經過將用戶 的我的喜愛與特定的參考特徵進行比較,並試圖預測用戶對一些未評分項目的喜愛程度。參考特徵的選取多是從項目自己的信息中提取的,或是基於用戶所在的社 會或社團環境。java
根據如何抽取參考特徵,咱們能夠將推薦引擎分爲如下四大類:mysql
隨 着互聯網上數據和內容的不斷增加,人們愈來愈重視推薦引擎在互聯網應用中的做用。可想而知,因爲互聯網上的數據過多,用戶很難找到本身想要的信息,經過提 供搜索功能來解決這個問題是遠遠不夠的。推薦引擎能夠經過分析用戶的行爲來預測用戶的喜愛,使用戶能更容易找到他們潛在須要的信息。這裏以電子商務應用中 的推薦引擎爲例來講明推薦引擎在互聯網應用中的重要性。web
電子商務推薦系統 (E-Commence Recommendation System) 向客戶提供商品信息和購買建議,模擬銷售人員幫助客戶完成購買過程。智能推薦系統的做用能夠歸納爲:將電子商務網站的瀏覽者轉變爲購買者 ,提升電子商務網站的交叉銷售能力,提升客戶對電子商務網站的忠誠度。算法
電子商務推薦系統的界面表現形式有如下幾種:sql
Apache Mahout 是 Apache Software Foundation(ASF) 旗下的一個開源項目,提供一些可擴展的機器學習領域經典算法的實現,旨在幫助開發人員更加方便快捷地建立智能應用程序。經典算法包括聚類、分類、協同過 濾、進化編程等等,而且,在 Mahout 的最近版本中還加入了對 Apache Hadoop 的支持,使這些算法能夠更高效的運行在雲計算環境中。數據庫
Taste 是 Apache Mahout 提供的一個協同過濾算法的高效實現,它是一個基於 Java 實現的可擴展的,高效的推薦引擎。Taste 既實現了最基本的基於用戶的和基於內容的推薦算法,同時也提供了擴展接口,使用戶能夠方便的定義和實現本身的推薦算法。同時,Taste 不只僅只適用於 Java 應用程序,它能夠做爲內部服務器的一個組件以 HTTP 和 Web Service 的形式向外界提供推薦的邏輯。Taste 的設計使它能知足企業對推薦引擎在性能、靈活性和可擴展性等方面的要求。apache
Taste 由如下五個主要的組件組成:編程
安裝 Taste 的軟件需求:json
安裝 Taste 並運行 Demo:
http://localhost:8080/[your_app]/RecommenderService.jws
WSDL 文件:http://localhost:8080/[your_app]/RecommenderService.jws?wsdl
也能夠經過簡單的 HTTP 請求調用這個 Web 服務:
http://localhost:8080/[your_app]/RecommenderService.jws?
method=recommend&userID=1&howMany=10
根據上面的步驟,咱們能夠獲得一個簡單的推薦引擎 demo 環境,下面介紹如何使用 Taste 方便地構建自定義的推薦引擎。
直接使用 Mahout 的項目環境進行編碼,須要使用 Ant 或者 Maven 進行編譯,整個過程比較複雜,這裏咱們將構建推薦引擎所須要的工具包從 Mahout 工程中抽取出來,從而方便的構建自定義的推薦引擎。
在 Eclipse 中建立 Web 應用的工程 MovieSite,將 demo 時生成的推薦引擎 Web 應用的 war 包解壓縮,將 lib 下的 jar 文件拷貝到 MovieSite 的 lib 目錄下。這樣咱們就能夠方便的編寫本身的推薦引擎。
這裏咱們想要編寫一個電影推薦引擎,第一步須要對數據進行建模,分析應用中涉及的主要實體以及實體間的關係,從而設計數據庫存儲,程序中的類,以及推薦引擎的 DataModel。
數據模型中存在如下實體:
下面咱們就基於這個數據模型設計數據庫的存儲以及推薦引擎的 DataModel。
1 .建立 MySQL 數據庫存儲電影和用戶的信息,用戶的喜愛信息以及電影的類似度。
CREATE DATABASE movie; USE movie; CREATE TABLE movies ( // 保存電影相關的信息。 id INTEGER NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, published_year varchar(4) default NULL, type varchar(100) default NULL, -- ...more movie information... PRIMARY KEY (id) ); CREATE TABLE users ( // 保存用戶信息 id INTEGER NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, email varchar(100) default NULL, -- ...more user information... PRIMARY KEY (id) ); CREATE TABLE movie_preferences ( // 保存用戶對電影的評分,即喜愛程度 userID INTEGER NOT NULL, movieID INTEGER NOT NULL, preference INTEGER NOT NULL DEFAULT 0, timestamp INTEGER not null default 0, FOREIGN KEY (userID) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (movieID) REFERENCES movies(id) ON DELETE CASCADE ); CREATE TABLE movie_similarity ( // 保存電影和電影的類似程度 movieID1 INTEGER NOT NULL, movieID2 INTEGER NOT NULL, similarity DOUBLE NOT NULL DEFAULT 0, FOREIGN KEY (movieID1) REFERENCES movies(id) ON DELETE CASCADE, FOREIGN KEY (movieID2) REFERENCES movies(id) ON DELETE CASCADE ); CREATE INDEX movie_preferences_index1 ON movie_preferences ( userID , movieID ); CREATE INDEX movie_preferences_index2 ON movie_preferences ( userID ); CREATE INDEX movie_preferences_index3 ON movie_preferences ( movieID );
在實際應用中,咱們須要將應用中的實例數據寫入到數據庫中。做爲例子,這裏將從 GroupLen 下載的數據源寫入數據庫。
因爲上面採用數據庫存儲用戶的喜愛信息,這裏須要基於數據庫的推薦引擎實現。這裏擴展 MySQLJDBCDataModel 實現電影推薦引擎的 DataModel 實例。
public class MovieDataModel extends MySQLJDBCDataModel { // 保存用戶對電影的評分的數據庫表名 public final static String PERFERENCETABLE = "movie_preferences"; public final static String USERID_COLUMN = "userID"; // 表中用戶標識的列名 public final static String ITEMID_COLUMN = "movieID"; // 表中電影標識的列名 public final static String PERFERENCE_COLUMN = "preference"; // 表中評分的列名 public MovieDataModel(String dataSourceName) throws TasteException { super(lookupDataSource(dataSourceName), PERFERENCETABLE, USERID_COLUMN, ITEMID_COLUMN, PERFERENCE_COLUMN); } public MovieDataModel() { //DBUtil.getDataSource() 將返回應用的數據源 // 此應用是 J2EE 應用,因此這裏會採用 JDNI 的方式建立數據庫連接。 super(DBUtil.getDataSource(), PERFERENCETABLE, USERID_COLUMN, ITEMID_COLUMN, PERFERENCE_COLUMN); } }
前面介紹了數據建模和 DataModel 的實現,下面來詳細介紹推薦引擎的實現。如前面介紹的,Taste 既實現了最基本的基於用戶的和基於內容的推薦算法,同時也提供了擴展接口,使用戶能夠方便的定義和實現本身的推薦算法。下面詳細介紹如何擴展 Taste 的推薦引擎接口,實現基於用戶類似度的推薦引擎,基於內容類似度的推薦引擎,以及 Slope One 的推薦引擎。Slope One 是一種很是快速簡單的基於項目的推薦方法,須要使用用戶的評分信息。
public class UserBasedRecommender implements Recommender { private final Recommender recommender; public UserBasedRecommender() throws IOException, TasteException { this(new MovieDataModel()); } public UserBasedRecommender(DataModel model) throws TasteException { UserSimilarity userSimilarity = new PearsonCorrelationSimilarity(model); userSimilarity.setPreferenceInferrer(new AveragingPreferenceInferrer(model)); UserNeighborhood neighborhood = new NearestNUserNeighborhood(3, userSimilarity, model); recommender = new CachingRecommender( new GenericUserBasedRecommender(model, neighborhood, userSimilarity)); } // 對外提供的推薦的接口,參數爲用戶標識和推薦項的個數 public List<RecommendedItem> recommend(long userID, int howMany) throws TasteException { return recommender.recommend(userID, howMany); } public List<RecommendedItem> recommend(long userID, int howMany, Rescorer<Long> rescorer) throws TasteException { return recommender.recommend(userID, howMany, rescorer); } // 如下方法都是實現 Recommender 的接口 public float estimatePreference(long userID, long itemID) throws TasteException { return recommender.estimatePreference(userID, itemID); } public void setPreference(long userID, long itemID, float value) throws TasteException { recommender.setPreference(userID, itemID, value); } public void removePreference(long userID, long itemID) throws TasteException { recommender.removePreference(userID, itemID); } public DataModel getDataModel() { return recommender.getDataModel(); } public void refresh(Collection<Refreshable> alreadyRefreshed) { recommender.refresh(alreadyRefreshed); } public String toString() { return "UserBasedRecommender[recommender:" + recommender + ']'; } }
從上面的代碼示例清單 3 能夠看出,實現一個推薦引擎須要實現 Recommender 接口,它通常是對於某種 Taste 提供的推薦引擎的擴展,這是對 GenericUserBasedRecommender 進行的擴展,其中最重要的方法就是實例化推薦引擎的構造方法,通常其中涉及如下步驟:
public class ItemBasedRecommender implements Recommender { private final Recommender recommender; public ItemBasedRecommender() throws IOException, TasteException { this(new MovieDataModel()); } public ItemBasedRecommender(DataModel dataModel) throws TasteException { Collection<GenericItemSimilarity.ItemItemSimilarity> correlations = MovieSimilarityTable.getAllMovieSimilarities(); ItemSimilarity itemSimilarity = new GenericItemSimilarity(correlations); recommender = new CachingRecommender(new EmbededItemBasedRecommender( new GenericItemBasedRecommender(dataModel, itemSimilarity))); } public List<RecommendedItem> recommend(long userID, int howMany) throws TasteException { return recommender.recommend(userID, howMany); } ......... //EmbededItemBasedRecommender 類的定義 private static final class EmbededItemBasedRecommender implements Recommender { // 包含一個 GenericItemBasedRecommender 實例; private final GenericItemBasedRecommender recommender; private EmbededItemBasedRecommender(GenericItemBasedRecommender recommender) { this.recommender = recommender; } public List<RecommendedItem> recommend(long userID, int howMany, Rescorer<Long> rescorer) throws TasteException { FastIDSet itemIDs = recommender.getDataModel().getItemIDsFromUser(userID); return recommender.mostSimilarItems(itemIDs.toArray(), howMany, null); } ........ }
從上面的代碼示例清單 4 能夠看出,與上一個實現相似它是對 GenericItemBasedRecommender 的擴展,它的構造方法涉及如下步驟:
public final class MovieRecommender implements Recommender { private final Recommender recommender; public MovieRecommender() throws IOException, TasteException { this(new MovieDataModel()); } public MovieRecommender(DataModel dataModel) throws TasteException { // 建立一個 SlopeOneRecommender 的實例 recommender = new CachingRecommender(new SlopeOneRecommender(dataModel)); } // 對外提供的推薦的接口,參數爲用戶標識和推薦項的個數 public List<RecommendedItem> recommend(long userID, int howMany) throws TasteException { return recommender.recommend(userID, howMany); } ........ }
Slope One 是一種很是快速簡單的基於項目的推薦方法,它只須要使用用戶的評分信息。具體的實現,只須要在咱們的推薦引擎中包含一個 SlopeOneRecommender 的實例。
完 成了推薦引擎的設計與實現,下面咱們須要設計一些 REST API,向外暴露推薦功能。爲了提升推薦引擎的處理效率,這裏採用 Singleton 模式實現一個推薦引擎的單例 MovieRecommenderSingleton。在 Servlet 啓動的時候初始化推薦引擎的單例,之後每次調用推薦方法。
public class MovieRecommenderServlet extends HttpServlet { private static final int NUM_TOP_PREFERENCES = 20; private static final int DEFAULT_HOW_MANY = 20; private Recommender recommender; @Override public void init(ServletConfig config) throws ServletException { super.init(config); // 從 web.xml 中讀取須要建立的推薦引擎類名 /* * <servlet> * <servlet-name>movie-recommender</servlet-name> * <display-name>Movie Recommender</display-name> * <description>Movie recommender servlet</description> * <servlet-class> * com.ibm.taste.example.movie.servlet.MovieRecommenderServlet * </servlet-class> * <init-param> * <param-name>recommender-class</param-name> * <param-value> * com.ibm.taste.example.movie.recommender.UserBasedRecommender * </param-value> * </init-param> * <load-on-startup>1</load-on-startup> * </servlet> */ String recommenderClassName = config.getInitParameter("recommender-class"); if (recommenderClassName == null) { throw new ServletException( "Servlet init-param \"recommender-class\" is not defined"); } try { MovieRecommenderSingleton.initializeIfNeeded(recommenderClassName); } catch (TasteException te) { throw new ServletException(te); } recommender = MovieRecommenderSingleton.getInstance().getRecommender(); } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { //Parameters.USER_ID = "userID" String userIDString = request.getParameter(Parameters.USER_ID); if (userIDString == null) { throw new ServletException("userID was not specified"); } long userID = Long.parseLong(userIDString); String howManyString = request.getParameter(Parameters.COUNT); //Parameters.COUNT = "count" int howMany = howManyString == null ? DEFAULT_HOW_MANY : Integer.parseInt(howManyString); String format = request.getParameter(Parameters.FORMAT); //Parameters.FORMAT = "format" if (format == null) { format = "json"; } try { // 爲指定用戶計算推薦的電影 List<RecommendedItem> items = recommender.recommend(userID, howMany); // 加載電影的相關信息,RecommendMovieList 是保存了一組推薦電影的相關信息和 // 引擎計算獲得的他們的 ranking RecommendMovieList movieList = new RecommendMovieList(items); if ("text".equals(format)) { writePlainText(response, movieList); } else if ("json".equals(format)) { writeJSON(response, movieList); } else { throw new ServletException("Bad format parameter: " + format); } } catch (TasteException te) { throw new ServletException(te); } catch (IOException ioe) { throw new ServletException(ioe); } } //details please refer to the src code }
以上完成了電影推薦引擎服務器端的編程,下面咱們使用 FireFox 的插件 Poster 測試一下 HTTP 請求,查看推薦引擎的返回結果。對任意一個用戶,推薦引擎應該基於必定的規則計算獲得一組電影以及預計的評分,爲了有更好的用戶體驗,引擎在拿到推薦電影 序號的列表後,從電影信息數據庫中查詢獲得電影的相關信息,包括電影的名稱,發表時間以及類型等信息。這裏咱們採用 JSON 做爲推薦引擎的響應格式。
實現一個推薦引擎的最後一步就是編寫客戶端代碼,爲電影推薦引擎提供一個友好的用戶界面。下面展現一下咱們爲電影推薦引擎寫的一個簡單的用戶界面:右邊紅色框中的是該用戶已經打分的電影列表,左邊藍色框中是推薦引擎爲用戶推薦的電影列表。
首先,展現一下基於用戶的推薦引擎的推薦結果,推薦引擎會根據用戶已打分的電影找到用戶的「鄰居」,將「鄰居」們比較喜歡的電影推薦給當前用戶。
其次,圖 6 展現了基於內容的推薦引擎的推薦結果,推薦引擎會根據用戶已打分的電影找到類似的電影,推薦給當前用戶。
最後,展現 SlopeOne 推薦引擎的推薦結果,這種推薦引擎計算速度較快,效果很好,是一種很是快速簡單的基於項目的推薦方法。
目 前幾乎全部大型的電子商務系統,都不一樣程度地使用了各類形式的推薦引擎。推薦技術的使用,不只大大的提升了用戶購物的體驗,增長了用戶的粘着度,並且電子 商務公司也因爲推薦系統的應用而大大的提升了交叉銷售的可能,從而大大的提升了營業額。今天,你有本身的商品推薦系統麼?
借鑑於電子商務的 成功經驗,咱們能夠把推薦技術應用到其餘的領域。像咱們在文章中所演示的那樣,你能夠建立一個電影的推薦引擎。若是你是一個 blogger,那麼你能夠建立一個博客的推薦引擎,若是你是一個新聞提供商,你可使用推薦技術爲不一樣的用戶推薦它可能關心的新聞,等等。
今天,你推薦了麼?