使用Mahout搭建推薦系統之入門篇3-Mahout源碼初探

用意: 但願瞭解Mahout中數據的存儲方式, 它如何避免java object帶來的冗餘開銷。學完知識,要進行些實戰
去分析數據。

花了些時間看了看Mahout的源碼和官方資料,記錄下本身的一些收穫。文字寫了不少, 有點囉嗦了, 可是這些東西都是我這段時間學習推薦系統的一些感悟,但願感興趣的朋友能夠耐心看看,指點指點。
1、Mahout內容補充
     1. Mahout本質上是一個開源的機器學習框架.  http://mloss.org/software/ 有大量的機器學習開源框架, mahout也在其中.
     2. Hadoop是大象的意思, Mahout是騎象人的意思。 Mahout是Hadoop生態圈中的機器學習算法庫. 它的全部算法都基於斯坦福大學的一篇論文"基於多核的Map-Reduce機器學習算法"(Map-Reduce for Machine Learning on Multicore)  http://pan.baidu.com/s/1cMXzG。論文摘要提到,只要知足他們的統計查詢模型(Statistic Query Model)的算法均可以寫成多核Map-Reduce形式。
     3. Mahout由一系列jar包組成,包括推薦、分類以及聚類庫, 數學操做庫(例如線性代數,統計學)以及自定義的Java數據結構類(爲了優化內存的使用)。
     如 上篇博客所示, 挑取taste推薦系統庫以及部分數學操做庫和Java數據結構類既能夠做爲普通的jar包同樣引入你的工程進行開發, 徹底無需hadoop. 固然因爲不少算法空間和時間都是平方級別以上的複雜度, 一臺PC沒法知足秒級別的業務需求, 可是它這個jar包特性方便咱們在單機上進行小數據實驗,而不用先糾結在hadoop的問題上.

2、主要內容
1. 充分地掌握推薦系統算法和數據結構內部實現.
2. 熟練地分析全部算法的空間和時間消耗並跑程序進行測試.
3. 選擇合適的算法和數據結構, 進行10K級別的movielens數據實戰, 並進行調參
4. 最終進行服務器性能測試, 測試內存消耗. 

     真實的數據是GB\TB甚至PB級別的, 例如Google Picasa中的照片級別是數十億的, 天天都有上百萬的新照片加入, 對它們進行聚類操做工做量巨大. 咱們因爲計算能力有限和考慮到學習曲線問題, 使用10K級別數據, 以後慢慢向上提高數據量。

3、知識準備
    在使用Mahout推薦系統以前, 首先應該熟悉user-based和item-based協同過濾, 基於內容的推薦以及SVD的基本思想. 能夠參考 https://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/的相關介紹.
     協同過濾的基本思路:以用戶爲中心的話, 每一個人都是由一個物品集表示, 相同愛好越多的人將被分爲一個圈子,這個圈子裏面和你都比較親近,固然也有親近的差別。經過爲每一個用戶推薦圈子裏面最受歡迎的物品(經過親近程度加權而來)。
     SVD的基本思路: 以音樂爲例, 音樂有做曲家、專輯等大量屬性, 可是更有價值的是做曲風格, 樂器等潛在的特徵。經過挖掘這些潛在特徵與音樂屬性以及潛在特徵與用戶之間的關係, 最終得到最好的推薦。此外,SVD還能實現降維做用.
    基於內容的推薦:若是咱們喜歡的是文章, 也能夠經過文章的詞語, 標題等來進行推薦.

4、Mahout庫初探
【參考Mahout0.9 API, 預計2013年11月10日左右發佈】 https://builds.apache.org/job/Mahout-Quality/javadoc/ 
4.1 Mahout中和推薦系統相關內容有四個部分:
    Preference類: 表示每一個用戶或者物品的屬性, 如{1: {2: 1.0, 3: 2.0}} 表示用戶2和3分別對1進行了評價, 評分分別爲1.0和2.0
    DataModel類: 表示全部的數據集合, 即Preference的聚合. DataModel能夠是代碼輸入或者HTTP參數獲取, 也能夠從文件或者數據庫中讀取.
    Similarity類: 表示全部Preference二者之間的類似度, 全部的用戶或者物品都經過Similarty創建起了聯繫, 因此這個是很是重要的, Similarity的選擇和數據的類型有很大關係. Neighberhood記錄每一個用戶或者物品類似的物品集, 方便後期調用.
    Recommender類: 表示推薦算法, 整個系統的核心。 如何使用前面的Preference, DataModel, Similarity以及Neigherhood都將結合在Recommender來來使用。 

 一個優秀的推薦系統由如下五個部分構成:
    1. 高質量和高數量的數據. Garbage in, Garbage out.
    2. 高效存儲的數據結構以及高效的數學運算庫
    3. 優秀的算法架構和調參技術
    4. 優秀的架構設計, 防止數據的屢次拷貝, 而且利於數據的更新和一致性.
    5. 優秀的用戶體驗和人機交互設計

我主要想了解一下Mahout中數據結構的設計方法以及算法設計. 
4.2 數據格式和相關函數API:
     要玩數據的前提是, 先把數據搞到手, 常見的數據格式有兩種, 非結構化數據和結構化數據. 結構化數據如文章, 圖片, 未清晰的數據等, 這類數據的可用性不佳. 咱們如今主要講的是結構化數據, 如博客1所示, 按規範格式存在文件裏面的數據以及代碼生成的數據. 對於標準化的數據, 計算機將其加載到內存中成爲In-memory數據, 每每將數據封裝成爲一個數據結構對象, 從而實現複用並提高可用性; 此外, 因爲In-memory數據不持久, 文件表示方法單一, 每每使用數據庫來保存數據, 方便動態訪問以及更高級的結構化存儲.
     下面介紹一下Mahout中提供給用戶存儲結構化數據的類:

     Class FileDataModel: 讀取一個有分隔符的數據文件,分隔符僅支持tab和逗號(comma)。 
     數據格式: userID,itemID[,preference[,timestamp]]
     【要求:數據必須是2列或者4列;全部數據格式必須一致, 即分隔符數量一致;超過4列的數據, 空行以及以'#'開頭的數據都會被忽略。】
     示例:如123,456或者123,456,3,129050099059 或者123,456,,129050099059。

     若是一我的只是看過某部電影, 則沒有preference項, timestamp表示時間戳.  
     格式:preference自動解析爲double類型; userID和itemID解析爲longs類型, timestamp自動解析爲longs, 可是能夠經過重寫 readTimestampFromString(String)方法來本身解析.
     自定義讀取方法: 重寫processLine和  processLineWithoutID(String, FastByIDMap, FastByIDMap)
     文件格式:FileDataModel能夠讀取.zip和.gz格式的壓縮包數據。
     
     Class MysqlJDBCModel: 讀取數據庫中的數據, 爲了提高數據查詢效率. userID和itemID不能爲空, 二者均須要創建索引, 主鍵primary key爲userID和itemID的composition. 在Mysql中, 須要使用BIGINT和FLOAT類型, 調整buffer和查詢cache的大小, Mysql的Connector/J driver中的cachePreparedStatements須要設置爲true.

     Class FileIDMigrator: 專門用於讀取一系列的字符串並生成相應的MD5標識碼在一個FastByIDMap<String>(相似於Java SE中的HashMap<String>)中, 用途暫時不明確. 例如數據是一個電影名列表, 文件每行只有一個電影名, 那麼FileIDMigrator將生成對應的MD5碼。
     
【源碼中的數據存儲均由GenericDataModel和GenericBooleanPrefDataModel來存儲. 】
它們提供一下的比較重要的方法:
getUserIDs: 獲取全部用戶的ID
getPreferencesFromUser: 獲取某個用戶對全部物品的評分.
getItemIDsFromUser: 獲取某個用戶評論過的全部物品.
getItemIDs: 獲取全部物品的ID.
getPreferencesForItem: 獲取某個物品的全部評分.
getPreferenceValue: 獲取某個用戶對某個物品的評分.
getPreferenceTime: 獲取某個用戶對某個物品的評分時間.
getNumItems: 獲取全部的物品總數
getNumUsers: 獲取全部的用戶綜述.
getNumUsersWithPreferenceFor 1 item 獲取對某個item評論過的用戶數目.
getNumUsersWithPreferenceFor 2 items 獲取對兩個items同時評論過的用戶數目.
hasPreferenceValues: 判斷是否有評分, 用於區分BooleanDataModel.
getMaxPreference: 最大評分
getMinPreference: 最小評分
setPreference 改變某個用戶對某個物品的評分, 並不改變數據源的數據.
removePreference 去除某個用戶對某個物品的評分, 並不改變數據源的數據. 

真實的數據存儲在GenericDataModel的下面幾個成員變量中, 下面介紹一下這些數據結構

private final long[] userIDs; html

private final FastByIDMap<PreferenceArray> preferenceFromUsers; java

private final long[] itemIDs; web

private final FastByIDMap<PreferenceArray> preferenceForItems;

4.3  數據高效存儲

高效的數據結構 算法

     下面介紹下Mahout節省內存的機制, 並介紹一些基本的內存消耗參數. sql

     Preference [找時間把右邊這個java內存消耗圖重畫一下, 來源[3] http://algs4.cs.princeton.edu/14analysis/ 這本算法書網站作的是極好的, 各類java算法動態圖]

     Preference不容許修改userID以及itemID, 如要刪除部分數據, 只能經過修改value或者經過DataModel的removePreference方法來刪除. 數據庫

     存儲Preference最理想的內存開銷是20bytes(long爲8bytes, float爲4bytes, 8*2 + 4 = 20bytes), 因爲object overhead(包括一個reference指針8bytes(64位爲16bytes), 以及16bytes的其它開銷), 因爲20+24=44bytes, 不爲8的倍數, 還須要4bytes的padding開銷. 因此爲了存儲20bytes的數據使用了多大48bytes的內存, 140%的冗餘.  apache

    若是是100M的數據, 將額外消耗2.8GBytes的內存, 這是巨大的浪費. 爲此係統引入PreferenceItemArray和PreferenceUserArray來存儲與某個物品或者用戶有關的全部數據. 最終每一個Preference僅需12*2 = 24bytes(因爲一個數據須要存儲兩次). 比理想的20bytes只多了4bytes, 冗餘度爲20%. 

以PreferenceUserArray爲例, 結構以下 服務器

    此外FastIDSets和FastMap使用線性查找而不是鏈式查找, 能夠節約空間且避免使用Entry Object. 同時FastIDSet避免使用Map. 待深刻了解.  數據結構

    總結: FastIDSet平均爲每一個成員消耗14bytes, FastByIDMap爲每一個entry消耗28bytes. GenericDataModel爲每一個Preference消耗28bytes. [後面會使用System.gc(), Runtime.totalMemory()和Runtime.freeMemory()方法來進行內存消耗的測試] 架構

此外: 系統須要保存一個類似矩陣, 類似矩陣或者Slope-One類似矩陣的空間複雜度上限爲O(itemsNum**2) 或者O(usersNum**2).

4.4 更新數據
如何更新和刪除數據?
若是標準數據爲foo.txt.gz, 數據爲1, 102, 4.0形式. 那麼能夠在foo.*.txt.gz中加入以下所示的數據
1, 108, 3.0 
1, 103, 
並使用recommender.refresh(null)來更新數據. [1, 108]數據項的value講更新爲3.0, 而[1, 103]數據項將會被刪除.
此外, 爲避免數據被頻繁的更新, DataModel中有一個加載間隔時間參數delta.

東西寫得有點雜了, 具體我怎麼用這些東西作實驗的, 放在一篇博客中。

[1] Sean Owen "Mahout in Action"   http://book.douban.com/subject/4893547/
相關文章
相關標籤/搜索