最近使用mahout的itemBase协同过滤算法,研究了下他的源码,记录如下,以备后忘……
其算法实现大致分四个主要的部分:
1.将输入数据转化成矩阵
2.计算相似性
3.还是转化数据格式,为计算预测、推荐做准备
4.预测评分并做推荐
下面分别详细介绍:
- PreparePreferenceMatrixJob
1.1 itemIDIndex
input:启动计算时指定的--input路径 output:***/preparePreferenceMatrix/itemIDIndex
mapper:ItemIdIndexMapper,将输入中的userID,itemID,pref分隔后,将Long型itemID转化成int型 的index。输出类型(new VarIntWritable(index), new VarLongWritable(itemID));
reducer:ItemIDIndexReducer,index为key聚合itemID取出最小的itemID输出。输出类型同mapper
1.2 toUserVectors
input:启动计算时指定的--input路径 output:***/preparePreferenceMatrix/userVectors
mapper:ToEntityPrefsMapper,输出类型(new VarLongWritable(userID), new EntityPrefWritable(itemID, prefValue))
reducer:ToUserVectorsReducer,将map输出中的itemID转成index,通过userID聚合<itemID,pref>,输出类型:VarLongWritable,VectorWritable
注:在此job的reducer中会累加计数user的数量到一个变量中。然后在此job完成后将用户数写到numUsers.bin中,便于后续job读取用户数。
1.3 toItemVectors
input:1.2的输出 output:***/preparePreferenceMatrix/ratingMatrix
mapper:ToItemVectorsMapper,将1.2中<userID,VectorWritable<index,pref>……>,遍历Vector输出成<index,VectorWritable<userID,pref>……>
reducer:ToItemVectorsReducer,由于map中key是不知道是不是到下一个key的,所以map中写文件的方式是map中每一个userID(是key)的每个评分(是VectorWritable)都写一次,并以index作为key,并将此时的userID,pref给set到Vector中这样同一个index中的vector在reduce聚合到一起中必有重复的,所以此处reduce就是做merge的。
至此准备工作就已经完成了
2. 计算相似度RowSimilarityJob
2.1 normsAndTranspose
input:1.3的输出(***/preparePreferenceMatrix/ratingMatrix) output:***/weights
mapper:VectorNormMapper,将ratingMatrix中的<itemID,<userID,pref>……>形式的数据,再转化成<userID,<itemID,pref>……>的形式,同时记录每个item的norms(double类型,如欧氏距离的话就是平方和),如果需要过滤最小评分的话会记录item的评分数量和最大评分。最后将norms,nonZeroEntries,maxValues输出
reducer:MergeVectorsReducer
同1.3mapper中输出的vector会有重复的,在此进行merge输出,并将map中norms,nonZeroEntries,maxValues的输出写入相应的hdfs文件中。
这个job同时有一个MergeVectorsCombiner,只是为了merge的。
2.2 pairwiseSimilarity
input:2.1的输出(***/weights) output:***/pairwiseSimilarity
mapper:CooccurrencesMapper
由于2.1的输出是<userID,<itemID,pref>……>形式的,所以一个人同时对某两个商品的评分就很容易得到,so,两个循环遍历每个用户的评分列表,计算评分商品两两间的相似(多种相似度算法),输出:<itemID,<itemID,aggregateValue>……> (aggregateValue取的是乘积,[X1-X2]2=X12-2*X1X2+X22,此处计算乘积是为了便于reducer计算两两之间的相似度)
reducer:SimilarityReducer
同样是先mager,但么有调用Vectors中的方法,而是从新写了一个不知道为什么?
然后调用相应的相似度算法计算两两之间的相似度,以欧氏距离为例(较简单)会调用norms中计算好的平方和。输出为两两间的相似度<itemID,<itemID,pref>……>
2.3 asMatrix
input:2.2的输出(***/pairwiseSimilarity) output:***/similarityMatrix
mapper:UnsymmetrifyMapper
2.2中输出的两两之间的相似度,此处取出topK个输出,但这只是整个相似度矩阵的一半,所以需要输出另一半,在此是和上面的一样,每一个item都会输出一个vector出来,这样reducer必要做merge,当然另一半是输出全部的,topK要到reducermerge后做。
reducer:MergeToTopKSimilaritiesReducer
同mapper中所说,此处完成merge和取topK,实际上就一半的矩阵需要处理。
至此相似度就已经算完了
3 下面开始为推荐做准备
3.1 prePartialMultiply1
input:2.3的输出(***/similarityMatrixPath) output:***/prePartialMultiplyPath1
mapper:SimilarityMatrixRowWrapperMapper,直接输出,只是添加了自身的相似度为NAN,输出类型VectorOrPrefWritable,这里是前者Vecotr
reducer:Reducer,hadoop自身的,没啥说的,正常聚合。
3.2 prePartialMultiply2
input:1.2的输出(***/preparePreferenceMatrix/userVectors) output:***/prePartialMultiplyPath2
mapper:UserVectorSplitterMapper
在此只是如果有usersToRecommendFor的话就只输出usersToRecommendFor中的用户的评分向量,没有的话默认是输出全部的用户评分向量。还有过滤下每个用户的评分数量,默认是去10个评分,来预测。
reducer:Reducer,同上
其实prePartialMultiply1和prePartialMultiply2只是将数据稍做处理,然后输出相同的数据类型,这样便于下步将评分数据和item的相似数据聚合。
3.3 partialMultiply
input:3.1和3.2的输出(***/prePartialMultiplyPath1、2) output:***/prePartialMultiplyPath
mapper:Mapper
reducer:ToVectorAndPrefReducer,将聚合在一起的VectorOrPrefsWritable类型,封装成VectorAndPrefsWritable类型。
4 预测并推荐
4.1 itemFiltering 推荐商品过滤
input:启动时指定的路径(***/filterFile) output:***/explicitFilterPath
mapper:ItemFilterMapper
reducer:ItemFilterAsVectorAndPrefsReducer
较简单,只是最后聚合输出成VectorAndPrefsWritable类型,跟3.3保持一致
4.2 aggregateAndRecommend 聚合推荐(关键的一步)
input:3.3的输出(***/prePartialMultiplyPath) output:启动时指定的路径
mapper:PartialMultiplyMapper
VectorAndPrefsWritable保存的是对同一个item评过分的用户的评分及此item的相似矩阵,此map输出以userID为key,以PrefAndSimilarityColumnWritable<pref,simiMartixVactor>类型为value,这样同一个user评过分的商品评分及相似矩阵都可以聚到一起,供reducer做预测评分。
reducer:AggregateAndRecommendReducer
reduce声明了Vector numerators(分子) 和Vector denominators(分母),numerators使用相似向量乘以评分,得到多个向量,再将向量相加得到,denominators是直接将多个评分得到的向量相加得到,预测评分是用numerators/denominators
最后通过writeRecommendedItems(userID, recommendationVector, context)采用优先队列取到topK的推荐数据