花了几天的时间阅读分析了mahout推荐系统中基于java单机和基于hadoop的分布式mapreduce源码。根据其推荐系统hadoop程序的job划分写了笔记1、2、3。在这里,基于笔记1,2,3做一个总结。
我们先从相似度开始。
什么是相似度,就是我们在构建推荐系统时,基于user或者基于item都需要计算出相应的候选item或者是user。那么在mahout的hadoop程序中,他运用的是基于item的推荐系统,同样的,也需要计算相似度。
计算相似度的公式我在之前的笔记中列举过,有欧几里得,皮尔森等等。
可以看到这些计算公式都需要向量的乘积和平方。
什么是一个向量呢,就是说我们比较item两两之间的相似度的时候,则用户就是维度,这样两个向量就由< item , user>构成,而其中的值就是pref偏好。
然后我们通过将item或者user转化为向量,运用公式计算相似度,就得到了item或者user两两之间的相似度。
那么mahout需要计算相似度就要求向量之间的平方和、内积。他是怎么操作的呢?
我们回到mahout的mapreduce转化。
在准备阶段,我们分别通过输入数据得到了以下的结构:
输入数据:
String: userid item pref
mapreduce:
Long: userid , Vector:
Long: itemid , Vector:
得到这两个结构以后,这两个结构我们开始分开使用。首先我们需要用Long: userid , Vector: < itemid , pref>来计算相似度,的到物品两两之间的相似度矩阵,并且根据用户输入的最大行/列队矩阵向量进行规范剪枝,来缩小矩阵的体积,删去相似度低不会用到推荐系统的向量。具体的过程就不一一阐述,输入输出如下:
输入数据:
Long: userid , Vector: <itemid , pref>
输出数据
Long: itemA , Vector: <itemB , sim> (剪枝后的结果)
接下来我们使用Long: itemid , Vector: < userid , pref>构建基于item的信息集。什么是信息集合呢?因为推荐系统是半离线处理的,所以我们可以计算好所有用户的推荐,然后根据我们的需求给各个用户推荐item,所以这就需要我们构建一个中间数据集合来存储每个item的信息,这个信息中包含了item的相似矩阵、使用item的用户以及这些用户使用item以后的pref打分。我们有了这个中间数据集,稍作变换就可以求出最终的分数预测的数据集。同样,输入输出如下:
输入数据:
Long: itemA , Vector: <itemB , sim> (每个item的相似矩阵)
Long: itemid , Vector: < userid , pref> (用户-商品矩阵)
输出数据
Long: itemA , ( Vector<itemB , sim> , List<userID> , List<pref> )
得到如上中间数据集合以后我们就可以求最后的预测分数了,我们根据预测分数排行以后就可以给每个用户推荐了。过程如下:
输入数据:
Long: itemA , ( Vector<itemB , sim> , List<userID> , List<pref> )
中间结果:
Long: userid , ( pref , Vector<itemB , sim> )
输出数据:
Long: userid , Vector( item , predictPref )
最后将结果输出。
总结了所有过程,可以看到抛去细节mahout做的并不是很难,而且其中有一些来回变换应用在mapreduce上比较占用io读写,其实有很多步骤可以省略,当然具体到工作环境中,有时可以一将多个job简化,比方说mahout的第一个job的第一个mapreduce,它将item的ID做了个内部索引的映射,当我们的itemID并不是特别特别巨大的时候根本没有必要做这一步工作,还有后面构建中间数据集,其实可以略过,将Long: userid , Vector: < itemid , pref>中的itemID根据Long: itemA , Vector: < itemB , sim> (每个item的相似矩阵)进行合并,得到的结果就是我们最后一步的中间结果。
写到这里,有一些对于推荐系统应用在分布式环境的想法。
代码读了三到四天,其实说到头,执行的步骤不是那么复杂,数学支持度也不高,推荐系统的预测结果也并不是那么好,通过这些弊端,有以下两个想法:
第一,编程环境。
不是吐槽hadoop,但是hadoop对于推荐系统这种偏重迭代、数据转化的运算确实有点捉襟见肘,mapreduce执行的操作不多,代码量却是巨额,真正核心的东西没有表达出来,预定义的接口环境变量等等却定义了一堆,我想如果能够简化编程环境,让我们的操作直接贴近核心数据,对真正的数据多做处理可能是一个比较好方向,当然我并不是说mahout所有的代码都是冗余的,自定义的数据类型确实可以贴近数据做一些优化,可是真正用到的核心点并不多,数据量大但结构简单的时候其实意义并不是很大,说到这里就比较推荐RDD编程,抽象程度比较高,可以抽象得直接操作数据,写起来不用考虑太多其他方面,这对数据处理方面都是一大进步。
第二,数据挖掘方法。
推荐系统出来将近二十年了,可是mahout对海量数据做的推荐系统(基于hadoop)运用的还是最基本的协同过滤以及als方法,其实相较于协同过滤,我更推荐als算法一些,因为als算法可以抽象出用户、商品的特征,运算量越大,特征表现越精准细致,根据精确的特征做推荐,可以精确得把握到用户的喜好兴趣。除此之外我们还可以根据用户的行为运用很多不同的方法来构建推荐系统。用户群体聚类、关联规则分析、用户信息抽象(神经网络)等等,多个分类器的集成可以对推荐系统取得更好更优的结果。
同时,相应的另一方面来看,不同的应用场景我们需要使用不同的推荐分析方式,并不是所有的推荐都适用于一种推荐分析方法,所以具体的行业方面我们可以具体来个性化设计。
而且,随着数据分析行业的发展, 这种user-item-pref的方式已经远远无法达到我们的需求,我们有更多的用户信息,诸如个人资料、ip、操作信息、浏览历史、自定义标签等等不同的信息,并不是所有的信息都适用这种CF模式,所以我们需要对数据挖掘的方法进行更新、优化。
下面补充一些关于mahout协同过滤的优化:
首先准备阶段我们只需要得到:userID , Vector< itemID , pref > 即可。itemID的内部索引标准化这一步可以再外部数据输入之前进行标准化,而不用写在mapreduce中。那么prepare的步骤如下:
输入:userID itemID pref(text)
输出:userID , Vector和min_int , Vector
得到userID-itemID矩阵以及每个用户操作数量计数。
接下来我们需要计算相似度矩阵,得到itemA , Vector< itemB , sim >这种形式的相似度矩阵,具体步骤如下:
首先计算norms得到平方和:
map:
userID , Vector
reduce:
itemID , norms (pref平方和)
接下来计算agg(item两两pref乘积),同原mahout操作:
map:
userID , Vector
-> sortby itemID
-> 双重循环对Vector处理得到[ItemA , ](itemA< itemB)
reduce:
itemA , Vector
->对所有itemA和itemB相同的条目进行加和
->通过保存下来的norms,和加和后的[itemA , Iterable]计算相似度
->[itemA , Vector](itemA< itemB) 输出
这样我们就得到了item之间的相似度。
接下来我们进行推荐评估:
map:
读取[itemA Vector]并根据输入:
[userID , Vectornull> >]
对[itemA , Vector]中的itemA和[userID , Vector]的itemID进行map合并转化
reduce:
合并bykey得到[userID , Vector >]
计算预测分数,得到[userID , Vector]输出
转载请注明出处:http://blog.csdn.net/utopia_1919/article/details/51860344