基於評分數據的推薦算法實現:slopeone和矩陣分解

幾年推薦算法研究得比較火熱,得益於netflix的百萬大獎。推薦算法有多種分法,有人喜歡分成基於內容和基於用戶行爲的,而主流的文獻還是從算法分得多:即neighborhood-based和基於factorization的。 neighbor-based方法比較早,主流的user-base和item-base,其思想都是猜測用戶會喜歡和他口味一致的東西。矩陣分解直接把預測問題轉換成一個估計對評分矩陣的補全問題。這兩種方法都有不少的綜述文章了。除了user、item-base,基於圖模型、slopeone應該也算neighborhood-based的方法。
矩陣分解的方法我最早是看到Yehuda Koren的論文裏介紹的,一時沒看懂,N久以後的後來看了項亮博士的論文才明白(博士論文綜述信息量總是特別大~)。矩陣分解的方法比協同過濾的效果好(主要應該是RMSE)的主要原因我覺得在於它做了全局優化目標優化,而協同過濾的特點主要還是能捕捉小部分強相關的數據信息卻忽視全局信息。Koren巧妙地把兩者結合起來,使得推薦效果達到了更好。

我實現了koren那篇factors in the neighbor中的item neighborhood的方法,當然一路看下來從最基本的分解開始實現、加入用戶偏好、看到效果慢慢才感受到關係分解的巧妙(建議閱讀項亮博士的論文或者koren自己寫的一篇矩陣分解的介紹性文章,代碼重構已經放入項目,見博客的置頂帖
這裏簡單介紹一下koren的方法,從最早SVD的方法開始:評分依據是用戶對電影類型的偏好 以及 每個電影類型成分
          r_ui = pu * qi ,
(其中r_ui表示對用戶u的電影i評分,pu是一個向量,表示用戶u潛在喜歡哪些類型的電影,qi是一個向量表示電影i在各種類型中佔有多少比例,例如一部電影可以既有恐怖也有懸疑也有愛情…… pu 和qi通過數據集中的打分rui來估算,使所有的(rui - pu*qi)最小化,通過隨機梯度下降來求,建議閱讀項亮博士的論文)
RSVD在SVD的基礎上增加了用戶和項目的偏好,有些用戶喜歡打高分,有些用戶苛刻,有的電影好看,普遍被打高分……
          r_ui = avg + bu + bi + pu * qi 
(avg是全局平均分,求解同理)
Koren加入協同信息:
           r_ui = avg + bu + bi + sigma[wij * (ruj - buj)] for j belongs to u
(其中buj = avg + bu + bi, wij可以看作項i對項j的影響, 也就是對一個i打分,我們還要關心用戶u中所有物品對i的影響)
wij是一個二元關係,要保存起來很佔空間,於是可以進行分解,用xi * qi表示,那麼預測打分公式就變成:
      r_ui = avg + bu + bi + qi * {sigma[(ruj - buj)* xi] for j belongs to u}
也就是pu被分解成後面一串,當然這裏不可能介紹太細,大家還是應該去讀一下原文。

說說實現。我代碼用java寫的,開始時候想把數據留在外存,計算的時候一行行地讀進來,發現內存使用很多,想到Java的String實際釋放比較難控制,乾脆改成所有東西存成byte數組,直接用inputstream去讀取。後來發現需要保存每個用戶的打分信息反覆迭代,索性把數據集全讀進內存算了。那麼要存上千萬的rating就得省着點用。itemid和打分用一個int表示3個字節表示itemid,1個字節表示打分,打分中4比特表示個位,4比特表示小數點後一位;數據集按照userid排序,通過一個long表示一個用戶打分數據的起始和終止下標:各4字節。對於movielens 1000W打分需要1000W * 4字節 = 40M,加上 7W 用戶*8 = 560K。就算Netflix 有一億打分、50W用戶也只佔400M而已。我看到還有更瘋狂節省空間的做法是直接把所有信息的範圍用k個bit表示。

矩陣分解方法裏面調整學習速率是有講究的,如果太小,在十幾次迭代都收斂不了,如果太大,那收斂到一定程度還會發散。Koren論文裏有提到netflix上的取值大概在多少合適,另外學習速率每次迭代還是應該減小10%。另外一個影響收斂的因素是初始隨機值,不能太大。

我還實現了slopeone,爲的是比較一下RMSE效果,這個算法網上有比較詳實的介紹,我對它的理解也是從網上。我沒有用Mahout的taste,以前嘗試過很小的數據集就吃了很多內存,而且看了源碼發現嵌套太多,還不如自己寫靠譜。我找了movielens的1000萬數據做實驗,用taste的SVDrecommender吃了6G內存還說不夠。

代碼在這裏:http://download.csdn.net/detail/lgnlgn/4001712,代碼水平有限,各位請勿狠拍。
實驗的RMSE結果是:在movielens上,原始SVD在15次迭代f=100的時候,有0.8115,加入user和item偏好的RSVD,提高到0.8069;這兩個算法迭代一次都只有5秒左右。Item neighborhood的RMSE達到0.7883,一次迭代10秒出頭;加入隱式反饋達到0.7846,一次迭代近18秒。而slopeone只有0.8569,跑起來還不快,誰看看代碼幫找找原因。

後話:
電影打分數據屬於比較優質的數據,netflix有1億打分,估計只是一個很小的部分。而一般互聯網積累的數據大多隻能看做是二值數據,而且稀疏程度比電影數據高,用戶和物品數量比netflix提供的估計大一個數量級,用戶量甚至會大兩個量級。在這種情況下,如何設計開發出一個高性能有效的推薦算法也是值得研究的。協同過濾中圖模型貌似更合適這樣的情況。

----------------------------------------------
後來又跑了一下netflix的數據, 隨機選400W做測試集。SVD在15次迭代f=100時候 RMSE=0.8351, RSVD只提升了0.0002。每次迭代1分鐘左右。而Item neighborhood的RMSE 我試了很多次都跑出NaN。只有初始化縮小100倍的、學習速率調低才跑出來; 沒有隱式反饋f=50的時候RMSE達到0.8268。netflix數據集不保證每個用戶看過多少電影,很多用戶只看1、2部電影。
----------------------------------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章