mahout的taste框架是協同過濾算法的實現。它支持DataModel,如文件、數據庫、NoSQL存儲等,也支持Hadoop的MapReduce。這裏主要分析的基於MR的實現。
基於MR的CF實現主要流程就在org.apache.mahout.cf.taste.Hadoop.item.RecommenderJob類中(注意mahout有兩個RecommendJob,要看清楚是哪一個包)。這 個類的run方法就包含了所有的步驟。從上到下,完整的其實有10步(中間計算item相似度其實拆分成了3個job,我們也當做是一個phase吧), 也就是說,如果指定了所有的必要參數,運行一次item-based CF算法,會執行12個JOB,當然有的步驟是可以忽略的,下面會講。以下就是詳細的每一步驟的分析
phase1: itemIDIndex
這步主要是將itemId轉成一個int。這裏設計上其實有點小問題,如果item的數量非常多,比如超過int的最大值,那就有可能會出現重合了。所以用long其實更爲合適。
input:用戶評分文件(這也是我們最原始的輸入了),格式一般爲:userId t itemId t score。注意輸入必須是textfile的。可能是爲了方便測試吧,mahout的很多包默認輸出都是textfile格式的。
map:(index, itemId)
reduce: (index, itemId)
phase2: toUserVector
input:用戶評分文件
param: --userBooleanData如果這個參數爲true,則會忽略評分列,對於買或不買這類數據有時需要指這定這個值。
map: (userId, itemId,pref)
reduce: 以用戶爲key,輸出成向量形式è (userId, VectorWritable<itemId, pref>)
phase3: countUser,計算用戶數
map: (userId)
reduce: 輸出總用戶數count
phase4: maybePruneAndTranspose
input: phase2的輸出:userVector
param: --maxCooccurrences
map: (userId,Vector<itemId, pref>) è(itemId,DistributedRowMatrix<userId,pref>),注意如果指定了—maxCooccurrences參數,這裏會有裁剪,www.codesky.net 每個userId最多對maxCooccurrences的itemId打分
這裏的DistributedRowMatrix,分佈式行矩陣:行:itemId, 列:userId
reduce: (itemId, VectorWritable<userId,pref>)
phase5: RowSimilarityJob
這一步比較關鍵,計算item相似度,它拆分成了三個JOB。
param: --numberOfColumns, --similarityClassname,--maxSimilaritiesPerRow(默認:100)
job1:weight
input:phase4的輸出
map: (itemId, VectorWritable <userId, pref>) ==>(userId, WeightedOccurrence<itemId, pref, weight>)
這裏的weight,對於歐氏向量距離,或者Pearson距離等,均爲Double.NaN,即無效。在LoglikelihoodVectorSimilarity中有用到weight的值。
reduce:(userId, WeightedOccurrenceArray<itemId, pref, weight>)
job2:pairwise similarity *item相似度計算*
map: 對同一用戶的所有item-rating,輸出兩兩item之間的關係 ==>(WeightedRowPair<itemA, itemB, weightA, weightB>, coocurrence<userId,valueA, valueB>) (同上,這裏的權重weightA,B對於歐氏距離等可以忽略)
reduce: 在這端,以<itemA,itemB>爲key聚合了來自不同map的所有用戶的打分,最後輸出itemA和B的對稱相似度(即以itemA爲key或以itemB爲key)==>(SimilarityMatrixEntryKey<itemA,similarity>, MatrixEntryWritable<WeightedRowPair<itemA, itemB,weightA, weightB>>) ,(SimilarityMatrixEntryKey<itemB,similarity>, MatrixEntryWritable<WeightedRowPair<itemB, itemA,weightB, weightA>>)
job3:entries2vectors *彙總item的相似items*
param: --maxSimilaritiesPerRow
map: (itemA, itemB, similarity) & (itemB,itemA, similarity) 這裏在group的時候按相似度降序做了排序,如果有--maxSimilaritiesPerRow參數,則會做裁剪。
reduce: (itemA, VectorWritable <item,similarity>)
至此,item相似度計算完畢。
phase6: prePartialMultiply1
input: phase5的最後輸出(即item相似度)
map: 直接輸出item對應的相似items,這裏用VectorOrPrefWritable做了封裝,表明有可能是相似度向量,也有可能是對item的打分,並且對item爲自己的,將相似度設爲Double.NaN,以過濾自身。è(itemId,VectorOrPrefWritable<item, similarity>)
reduce: IdentityReducer
phase7: prePartialMultiply2
input: phase2的輸出userVectors
map: 輸出:(itemId, VectorOrPrefWritable<userId, pref>)
這裏默認考慮用戶對10個item的評分,可以通過maxPrefsPerUserConsidered參數調整。
如果指定了usersFile,則在setup時會把所有的userId讀入內存,用於過濾。如果map輸入數據的userID不在usersFile中,則會被忽略。注意,這是mahout的設計bug,對於比較大的數據集,很有可能造成OOM(事實上在我的測試程序中已經出現OOM了…),這種bug下面還會出現。輸出的是用戶的評分,同phase6的VectorOrPrefWritable的封裝。
reduce: IdentityReducer
phase8: partialMultiply
input: 6和7的輸出:prePartialMultiply1, prePartialMultiply2
map: Identity。由於6和7的輸出的key均爲itemId,因而在reduce端同一item的相似item以及對應的用戶評分會聚合到一起。
reduce:(itemId, VectorAndPrefsWritable<similarityMatrix, List<userId>,List<pref>>) 沒做特殊處理,直接合在一起,輸出相似度矩陣,所有的userId及對item的打分。
phase9: itemFiltering
將過濾文件輸出成<userId, itemId>。如果指定了--filterFile參數,則在最後的聚合推薦中會過濾userId對應的items。這一步在實際中多數是可以忽略的,只要不指定這個參數即可。
phase10: aggregateAndRecommend
map: 對每個用戶,輸出對當前item的評分,以及與當前item的所有相似itemsè(userId, PrefAndSimilarityColumnWritable<pref,vector<item, similarity>>)
reduce: 聚合了這個用戶所有的評分歷史,以及相似items,計算對該用戶的推薦結果 è (userId, List<itemId>)。
注意在reduce的setup中,會將phase1產生的所有itemId到index的映射讀入內存,這裏只要Item數據集稍大,就會OOM。這是比較嚴重的設計bug。
事實上,如果item是正規的整數,而不是guid之類的,phase1和這一步的讀入內存是完全可以略掉的。這樣的話就完全可以在企業級的數據集上使用(我的測試數據集是15億+的user-item-rating,1.5億+的用戶,在最後這一步掛掉了,前面所有phase都能跑成功)。
至此,已經形成了推薦結果,CF完成。
以上的所有步驟中,phase5的計算item相似度是最慢的(這個其實也很直覺)。
轉:http://www.codesky.net/article/201206/171862.html