协同过滤(Collaborative Filtering, CF)是推荐系统广泛使用的一种技术,它主要通过考虑用户(User)与用户之间、物品(Item)与物品之间的相似度(Similarity),来向用户推荐物品,常被用在电商网站中。其中,在推荐系统中最常使用的协同过滤方法,有如下4种:
- 基于用户的协同过滤推荐
- 基于物品的协同过滤推荐
- 基于模型的协同过滤推荐
- 混合协同过滤推荐
上面4种方法中,基于用户的协同过滤推荐、基于物品的协同过滤推荐都是基于内存的协同过滤推荐,一般在数据量较小的应用场景下,可以直接在线使用的实时推荐方法;基于模型的协同过滤推荐一般用于离线计算,它采用机器学习的方法,一般首相将用户偏好行为数据分成2个数据集(有时可能会将数据集分成k个子集,采用交叉验证的方式来提高模型精度),一个为训练集,一个为测试集,使用训练集数据来训练出推荐模型,然后使用测试集数据来评估模型的精度,当满足特定精度时,可以将得到的推荐模型应用于实际线上环境;混合协同过滤推荐,是综合基于内存的协同过滤(基于用户的协同过滤推荐、基于物品的协同过滤推荐)、基于模型的协同过滤推荐这两类方法,克服每一种协同过滤方法本身的缺点,比如基于内存的协同过滤推荐方法不适用于大量数据的推荐应用场景,而基于模型的协同过滤推荐方法由于所基于的数据多是大量历史记录,训练模型时间较长,它不适用于在线推荐场景,综合各种协同过滤推荐方法,取长补短,这样的推荐方法更适合实际复杂的推荐需求。
对象相似性度量
在介绍4种协同过滤推荐方法之前,我们先介绍一下有关相似性度量的问题:
基于协同过滤推荐方法,通过计算用户相似度、物品相似度来进行个性化推荐,那么我们结合Mahout机器学习库来说明各种相似性度量的计算方法,Mahout提供了分别针对用户、物品给出了一些相似性度量计算实现,有些是用户、物品可以共同使用的,如下类图所示:
上图中,绿色表示计算用户之间相似度、物品之间相似度都可以使用的相似性度量,而SpearmanCorrelationSimilarity只能用于计算用户的相似度。各种相似度说明如下所示:
- EuclideanDistanceSimilarity
欧几里德距离,又称欧氏距离,在n维欧式空间中,计算欧几里德距离的公式,如下所示:
其中,x、y表示两个点的坐标,d(x,y)表示点x和y之间的距离。
- PearsonCorrelationSimilarity
对于两个数据集X、Y,他们的相关性可以使用皮尔逊相关系数来表达,计算皮尔逊相关系数的公式,如下所示:
其中,n分别表示两个数据集X与Y中数据点的个数,xi表示数据集X中第i个数据点,yi表示数据集Y中第i个数据点。
- UncenteredCosineSimilarity
余弦相似度通过计算两个向量的夹角的余弦值,来表示两个向量的相似性,它的计算公式,如下所示:
其中,A和B表示两个向量。
Mahout采用了log-likelihood ratio(LLR)的方法计算相似度,对于事件A和事件B,我们考虑两个事件发生的次数,具有如下事件矩阵:
| Event A | Everything but A |
Event B | k11 | k12 |
Everything but B | k21 | k22 |
其中:
k11:事件A与事件B同时发生的次数
k12:B事件发生,A事件未发生
k21:A事件发生,B事件未发生
k22:事件A和事件B都未发生
那么,计算LLR的公式如下:
其中,H表示香农熵。
在Mahout中给出的实现,如下代码所示:
01 | public static double logLikelihoodRatio( long k11, long k12, long k21, long k22) { |
02 | Preconditions.checkArgument(k11 >= 0 && k12 >= 0 && k21 >= 0 && k22 >= 0 ); |
04 | double rowEntropy = entropy(k11 + k12, k21 + k22); |
05 | double columnEntropy = entropy(k11 + k21, k12 + k22); |
06 | double matrixEntropy = entropy(k11, k12, k21, k22); |
07 | if (rowEntropy + columnEntropy < matrixEntropy) { |
11 | return 2.0 * (rowEntropy + columnEntropy - matrixEntropy); |
具体可以查阅相关资料。
- TanimotoCoefficientSimilarity
Tanimoto相关系数来源于Jaccard距离,它表示两个数据集的相异度,即不相似度量。
计算Tanimoto相关系数的公式,如下所示:
其中,A和B表示两个向量。
曼哈顿距离有很多种叫法:城市街区距离、L1距离、L1范数、曼哈顿长度、Taxicab距离(出租车距离)。计算曼哈顿距离的公式,如下所示:
其中,x和y表示两个n维向量。
- SpearmanCorrelationSimilarity
Spearman相关系数的计算公式,如下所示:
其中,di=xi-yi,n表示数据集的大小,xi与yi分别是数据集X、Y中的数据点。
下面,我们会对上面提到的4种协同过滤方法进行详细说明,其中会结合Apache Mahout,并给出相应的实现代码,以求能够更好地理解各种协同过滤方法。
基于用户的协同过滤推荐
基于用户的协同过滤推荐,不考虑用户、物品的属性(特征)信息,它主要是根据用户对物品的偏好(Preference)信息,发掘不同用户之间口味(Taste)的相似性,使用这种用户相似性来进行个性化推荐。基于用户的协同过滤推荐,它是以用户为中心,观察与该用户兴趣相似的一个用户的群体,将这个兴趣相似的用户群体所感兴趣的其他物品,推荐给该用户。下面我们通过一个例子来说明,如下图所示:
如上图,以用户U1为中心,用户U1对物品I12、I13、I14有偏好行为,用户U3对物品I12、I13、I14、I31、I32、I33有偏好行为,而物品I12、I13、I14是用户U1和用户U3共同有偏好行为的物品,那么可以将用户U3有偏好行为但是用户U1没有的物品I31、I32、I33推荐给用户U1。
下面通过使用Mahout机器学习库实现,基于用户的协同过滤推荐,来了解计算用户相似度的方法,代码如下所示:
01 | final DataModel model = new FileDataModel( new File( "src/main/resources/u.data" )); |
02 | UserSimilarity similarity = new PearsonCorrelationSimilarity(model); |
04 | UserNeighborhood neighborhood = new NearestNUserNeighborhood(n, similarity, model); |
06 | final UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity); |
09 | List<RecommendedItem> recommendations = recommender.recommend(userID, topN); |
10 | for (RecommendedItem recommendation : recommendations) { |
11 | System.out.println(recommendation.getItemID() + ": " + recommendation.getValue()); |
对于编号为200的用户,使用皮尔逊相关系数(PearsonCorrelationSimilarity )和NearestNUserNeighborhood计算用户相似度,得到的Top 10推荐物品及其评分列表,如下所示:
基于用户的协同过滤推荐,比较适合于用户数量少于物品数量的应用,这样,通过更多的用户对物品的偏好行为历史数据,维护用户的相似关系,能够更好地计算出用户的相似性。
基于物品的协同过滤推荐
基于物品的协同过滤推荐,也不考虑用户、物品的属性(特征)信息,它也是根据用户对物品的偏好(Preference)信息,发掘不同物品之间的相似性。基于物品的协同过滤推荐,是以物品为中心,通过观察用户对物品的偏好行为,将相似的物品计算出来,可以认为这些相似的物品属于特定的一组类别,然后根据某个用户的历史兴趣计算其所属的类别,然后看该类别是否属于这些成组类别中的一个,最后将属于成组类别所对应的物品推荐给该用户。下面我们也通过一个例子来说明,如下图所示:
上图中,用户U1对物品I5和I6有偏好行为,用户U3对物品I1和I6有偏好行为,那么我们可以向U1推荐用户U3所感兴趣的物品I1。同样,用户U7对物品I3、I4、I7有偏好行为,用户U9对物品I4、I5、I7有偏好行为,那么我们可以向用户U7推荐用户U9所感兴趣的物品I5,因为用户U7和用户U9都对I4、I7有过偏好行为;可以向用户U9推荐用户U7感兴趣的物品I3,这个2个用户可能对彼此其他感兴趣的物品也都会发生偏好行为。
基于用户的协同过滤推荐,使用Mahout机器学习库计算,来了解计算物品相似度的方法,代码如下所示:
01 | final DataModel model = new FileDataModel( new File( "src/main/resources/u.data" )); |
02 | ItemSimilarity similarity = new LogLikelihoodSimilarity(model); |
04 | final ItemBasedRecommender recommender = new GenericItemBasedRecommender(model, similarity); |
07 | List<RecommendedItem> recommendations = recommender.recommend(userID, topN); |
08 | for (RecommendedItem recommendation : recommendations) { |
09 | System.out.println(recommendation.getItemID() + ": " + recommendation.getValue()); |
对于编号为200的用户,使用对数似然(PearsonCorrelationSimilarity )计算物品相似度,得到的Top 10推荐物品及其评分列表,如下所示:
基于物品的协同过滤推荐,比较适合于物品远远少于用户的应用,这样可以更好地维护物品之间的相似关系。
基于模型的协同过滤推荐
基于模型的协同过滤推荐,是采用机器学习的方法,通过离线计算实现推荐的,通常它会首先根据历史数据,将数据集分成训练集和测试集两个数据集,使用训练集进行训练生成推荐模型,然后将推荐模型应用到测试集上,评估模型的优劣,如果模型到达实际所需要的精度,最后可以使用训练得到的推荐模型进行推荐(预测)。可见,这种方法使用离线的历史数据,进行模型训练和评估,需要耗费较长的时间,依赖于实际的数据集规模、机器学习算法计算复杂度。有关推荐模型的计算方法,通过机器学习算法实现,我们简单说明一下。
对于我们的用户历史行为数据,我们关注的是用户与物品之间的关系,可以生成一个用户-物品矩阵,矩阵元素表示用户对物品的偏好行为(或者是评分),如果用户和物品的数量都很大,那么可见这是一个超大稀疏矩阵,因为并不是每用户都对所有的物品感兴趣,只有每个用户感兴趣的物品才会有存在对应的偏好值,没有感兴趣的都为空缺值,最终我们是要从这些空缺值对应的物品中选择出一些用户可能会感兴趣的,推荐给用户,这样才能实现个性化推荐。
下面,我们假设使用用户-物品的评分矩阵来实现推荐,定义评分矩阵为R,那么通过将R分解为另外两个低秩矩阵U和M,由于R是稀疏矩阵,很多位置没有值,只能通过计算使用U与M的乘积近似矩阵R,那么只要最终得到的误差尽量小,能满足实际需要即可,一般使用均方根误差(Root mean square error,RMSE)来衡量,那么就要计算一个目标函数的最小值,如下函数公式所示:
如果计算该目标函数的最小值,可能需要大量的迭代计算,甚至是不可行的,在满足实际需要精度的前提下,那么我们可以允许一定的误差,通过设定多个参数来控制计算。计算矩阵分解有很多方法,如:随机梯度下降(Stochastic gradient descent,SGD)、交替最小二乘法(Alternating Least Squares,ALS)、加权交替最小二乘法(ALS with Weighted-λ-Regularization,ALS-WR),有关实际计算的理论,可以参考相关资料。
Mahout采用了ALS-WR方法实现矩阵分解,目标函数如下公式所示:
其中:
这里,alpha表示的是置信参数,在Mahout中使用隐式反馈(Implicit Feedback )选项时,需要指定该参数;lambda表示的是正则化参数,为了防止求解得到的模型过拟合(Overfitting),在目标函数上,加上一个正则项。
我们可以通过它提供的API来实现一个离线的基于模型的协同过滤推荐,下面我们基于MovieLens数据集,介绍具体使用过程:
我们选择使用MovieLens数据集,2亿的评分数据(大约有500多MB),首先将数据集进行分割:80%用作训练集,20%用作测试集,Mahout提供分割数据集的工具,分割执行如下命令:
1 | bin/mahout splitDataset -i / test /shiyj/data/ml-20m/ratings.csv -o / test /shiyj/data/splitDS -t 0.8 -p 0.2 --tempDir /tmp/mahout/ |
这样,生成2个数据集:/test/shiyj/data/splitDS/probeSet和/test/shiyj/data/splitDS/trainingSet。
使用训练集/test/shiyj/data/splitDS/trainingSet来训练推荐模型,执行如下命令:
1 | bin/mahout parallelALS -i / test /shiyj/data/splitDS/trainingSet -o / test /shiyj/data/output/als --lambda 0.1 --implicitFeedback true --alpha 0.065 --numFeatures 10 --numIterations 10 --numThreadsPerSolver 2 --tempDir /tmp/mahout |
上面命令执行结果,数据会在/test/shiyj/data/output/als/目录下面,可以看到生成如下目录:
1 | /test/shiyj/data/output/als/U |
2 | /test/shiyj/data/output/als/M |
3 | /test/shiyj/data/output/als/userRatings |
这些目录下的文件就是推荐模型对应的数据文件,可以用于后面步骤中的评估和最终推荐。
评估模型使用Mahout的evaluateFactorization命令,需要输入上一步生成的模型文件,如下所示:
1 | bin/mahout evaluateFactorization -i / test /shiyj/data/splitDS/probeSet -o / test /shiyj/data/output/als/rmse --userFeatures / test /shiyj/data/output/als/U --itemFeatures / test /shiyj/data/output/als/M --tempDir /tmp/mahout/rmse |
可以看到,最后输出了RMSE,在文件/test/shiyj/data/output/als/rmse/rmse.txt中。
如果我们训练得到的模型在评估以后,误差能够满足实际推荐业务需要,则可以直接用于实际的推荐系统。Mahout提供了一个基于命令行的推荐方式,可以使用recommendfactorized命令,如下所示:
1 | bin/mahout recommendfactorized -i / test /shiyj/data/output/als/userRatings -o / test /shiyj/data/output/als/recommendations --userFeatures / test /shiyj/data/output/als/U --itemFeatures / test /shiyj/data/output/als/M --numRecommendations 10 --maxRating 5 |
上面指定了,为每个用户选择Top 10个物品作为推荐,并计算出了评分,执行上述命令,得到推荐结果,在目录/test/shiyj/data/output/als/recommendations中,该目录下的文件内容,示例如下所示:
01 | 1 [2571:0.65515196,1214:0.63967377,1210:0.5870833,1206:0.58017606,750:0.53064054,1270:0.52714694,1527:0.492824,5952:0.4770518,480:0.4517607,1580:0.43877804] |
02 | 2 [1196:0.22062394,1210:0.21882197,1198:0.19199324,1240:0.1805339,2571:0.17224884,1200:0.16999668,1291:0.15613373,858:0.14950913,1097:0.14854312,1197:0.1480078] |
03 | 3 [1196:0.8909414,1214:0.8081364,1270:0.6871423,924:0.64723164,858:0.6388401,1136:0.6263018,1291:0.58192265,1036:0.56856805,750:0.55387926,296:0.5497311] |
04 | 4 [457:0.34563774,592:0.3424035,150:0.32874095,590:0.32829174,349:0.3071443,153:0.29904938,316:0.29723072,110:0.28530872,344:0.28501293,434:0.27692935] |
05 | 5 [1:0.64543045,356:0.55862373,733:0.48568383,32:0.48033717,588:0.46519792,62:0.46215564,590:0.4575655,110:0.44682676,597:0.420567,95:0.41918993] |
06 | 6 [648:0.45023045,736:0.4386352,95:0.34667525,786:0.3351175,1073:0.32370603,32:0.31021506,1210:0.30251333,608:0.2933381,7:0.28936782,36:0.28653657] |
07 | 7 [1198:0.52251583,2028:0.51249886,1210:0.5006326,1197:0.48272145,1291:0.46804646,2571:0.45988122,1784:0.45771137,2797:0.4538604,1610:0.45373544,1704:0.4534099] |
08 | 8 [480:0.86542463,318:0.7133561,434:0.6677318,185:0.6504521,208:0.6451925,161:0.64106977,253:0.6124166,34:0.5435114,586:0.5240867,288:0.50809836] |
09 | 9 [2858:0.16401465,2762:0.12627697,2571:0.123330824,2997:0.11046047,296:0.10837068,2028:0.10639984,2329:0.103341825,1704:0.10107514,1089:0.100894466,50:0.098862514] |
10 | 10 [1270:0.32084247,2571:0.2983431,608:0.29643345,1291:0.29354194,780:0.2851402,1036:0.27120706,1214:0.2681493,1197:0.26402688,1097:0.2634927,1200:0.24816594] |
实际应用中,为了加快在线上推荐系统的检索,这些结果,可以考虑将数据存储到ElasticSearch或者SolrCloud系统中,方便查询。
混合协同过滤推荐
我们前面已经了解到,各种推荐方法都有其优点和缺点,那么对于实际复杂的需求,没有一种推荐方法能解决所有的问题,所以根据不同的需求采用组合多种推荐方法,共同来完成实际的推荐需求。
我们引用IBM Developerworks上一篇文章中介绍的,基于混合协推荐的实现方法,常用的有如下几种:
- 加权的混合(Weighted Hybridization): 用线性公式将几种不同的推荐按照一定权重组合起来,具体权重的值需要在测试数据集上反复实验,从而达到最好的推荐效果。
- 切换的混合(Switching Hybridization):其实对于不同的情况(数据量,系统运行状况,用户和物品的数目等),推荐策略可能有很大的不同,那么切换的混合方式,就是允许在不同的情况下,选择最为合适的推荐机制计算推荐。
- 分区的混合(Mixed Hybridization):采用多种推荐机制,并将不同的推荐结果分不同的区显示给用户。
- 分层的混合(Meta-Level Hybridization): 采用多种推荐机制,并将一个推荐机制的结果作为另一个的输入,从而综合各个推荐机制的优缺点,得到更加准确的推荐。
我们可以想到,根据前面介绍的基本协同过滤推荐方法,作为入门,可以这样实现一个混合协同过滤系统:
- 根据用户的历史行为数据,离线计算推荐模型,得到模型A;
- 为了使推荐满足实时行要求,可以选择使用基于用户的协同过滤推荐/基于物品的协同过滤推荐,基于实时用户行为数据,得到模型B;
- 对于用户的冷启动问题,可以考虑采用统计的方式,找到热门的物品推荐给新用户,得到模型C;
- 对于老用户,可以将模型A和B的结果进行一个排序筛选,得到一个新的Top N推荐列表。
这样,得到的推荐系统,就是一个混合推荐系统。