for(用户u尚未表达的偏好)每个物品i
for(对i有偏好的)每个其他用户v
计算u和v之间的相似度s
按权重为s将v对i的偏好并入平均值
return 值最高的物品(按加权平均排序)
这种方法还是太慢了,一般应用过程中会计算出一个最相似用户邻域,然后仅仅考虑这些用户的评价:
for 每个其他用户w
计算用户u和用户w的相似度s
按相似度排序后,将位置考前的用户作为邻域n
for(n中用户有偏好,而u中用户无偏好的)每个物品i
for(n中用户对i有偏好)每个其他用户v
计算用户u和用户v的相似度
按权重s将v对i的偏好并入平均值
这里就不放出代码了,因为前面章节有,在chapter2里面。这里大致介绍下它的构成。
-server -d64 -Xmx768M -XX:+UseParallelGC -XX:+UseParallelOldGC,书中还提到了-XX:+NewRatio=12不过这个我添加进去,出现错误。
接下来要谈谈邻域的问题,之前的推荐系统里面有个语句是NearestNUserNeighborhood第一个参数就是可以修改邻域的大小,可以改成10,100等等。当然我们也可以用阈值来设置邻域,这个时候就是ThresholdUserNeighborhood,第一个参数就是一个比例,这里用的是标准皮尔逊相关系数作为相似性的度量,这个阈值可以在(-1,1)之间。
下面少谈公式,公式可以百度,主要谈谈这些相似度应用场景以及实际环境表达的含义。
皮尔逊相关系数是一个在-1到1之间的数,他度量两个一一对应的数列之间的线性相关程度。在统计学中就是两个序列协方差与二者方差乘积的比值。协方差计算的是来两个序列变化趋势一直的绝对量,什么意思呢,就是指两个序列,相对于各自的均值点向同一方向移动得越远,协方差就越大,除以方差就是为了归一化。在我们这里可以度量两个用户针对同一物品偏好值变化趋势的一致性。
缺点也很明显,他没有没有考虑两个用户同时给出偏好值的物品数目,两个评价200个商品的用户肯定比两个只评价2个商品的用户相似。还有就是如果两个用户交集很少也是不能计算相关性的,当然这种情况两个用户相关性也可以看做很小。还有种情况是任何一个序列出现偏好值相同的情况,相关系数都是未定义的。
这个时候我们可以通过加权来解决上述问题,加权的目的就是使基于较多物品计算相关系数时,使正相关值向1移动,负相关向-1移动,如果基于较少的物品计算相关系数则可以让相关值向偏好值的均值移动。如果需要使用加权,可以在PearsonCorrelationSimilarity中加入第二个参数Weighting.WEIGHTED即可。
欧式距离很简单,也是非常常见的距离定义方式,这里可以计算出任何用户的相似度,当用户之间交集很少时,这里的相似度并不靠谱。函数名字是EuclideanDistanceSimilarity。
这里计算的是两个向量夹角余弦值,函数名字是CosineMeasureSimilarity。
这个是皮尔逊相关系数的变体,这个相关系数基于的是偏好值的相对排名,这里只保留了偏好值最本质的东西,它们的顺序。这个斯皮尔曼相关系数速度很慢,学术价值大于实用价值。SpearmanCorrelationSimilarity。
这个完全抛弃了偏好,只关心用户是否表达过偏好。TanimotoCoefficientSimilarity,称之为谷本系数,也叫Jaccard系数。它是两个用户共同表达过偏好的物品数目/至少一个用户表达过偏好的物品数目。一般只有当偏好值为布尔或是没有偏好值可用采用此方法。
LogLikelihoodSimilarity,对数似然比的相似度。它是一种不考虑具体偏好值的度量方法。它试图反映两个用户由于机缘巧合发生重叠的不可能性。这种相似度往往优于基于谷本系数的相似度。具体的数学推导可以去看看。
算法流程:
for(用户u尚未表达偏好的)每个物品i:
for(用户u表达偏好的)每个物品j:
计算i和j之间的相似度s
按权重为s将u对j的偏好并入平均值
return 值最好的物品(按加权平均排序)
这里要用GenericItemBasedRecommender来取代GenericUserBasedRecommender,下面放出核心代码非常简洁:
public Recommender buildRecommender(DataModel model) throws TasteException {
ItemSimilarity similarity = new PearsonCorrelationSimilarity(model);
return new GenericItemBasedRecommender(model, similarity);
}
这里没有计算物品邻域的步骤,这是因为这里计算物品i和j的相似度,其中j已经是用户表达过偏好的了。常常user数目大于item数目,因此基于物品的算法速度运行很快。
slope-one是基于新物品与用户评估过的物品之间的平均偏好值差异来预测用户对新物品的偏好值。举个例子吧:
算法流程如下:
//预处理,完成所有物品对之间的偏好值差异
for每个物品i
for每个其他物品j
for对i和j均有偏好的每个用户u
将物品对(i与j)间的偏好值差异加入u的偏好
//最终算法
for用户u未表达过偏好的每个物品i
for用户u表达过偏好的每个物品j
找到j与i之间的平均偏好值差异
添加该差异u对j的偏好值
添加其至平均值
return值最好的物品(按平均差异排序)
优点十分明显,在线部分执行很快,我们可以预先计算好物品之间偏好值的差异,如果一个偏好值发生改变,只需更新部分值即可。有个缺点就是物品对之间的偏好值差异所需要的内存是物品数的平方,因此内存增长很快。
放点代码出来吧:
public Recommender buildRecommender(DataModel model)throws TasteException {
DiffStorage diffStorage = new MemoryDiffStorage( model, Weighting.UNWEIGHTED, Long.MAX_VALUE);
return new SlopeOneRecommender( model,Weighting.UNWEIGHTED,Weighting.UNWEIGHTED,diffStorage);
}
当然mahout0.9已经没有这个算法了,我没有找到。去网上搜了下,0.8中就没有了。
SVD原理很简单,是一种降维的手段,用来提取特征,得到小的数据集。第一个参数是特征个数,第二个参数是拉姆达,用来控制正则化分解器,最后一个参数是需要执行的训练步骤数。
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.common.Weighting;
import org.apache.mahout.cf.taste.eval.IRStatistics;
import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
import org.apache.mahout.cf.taste.eval.RecommenderIRStatsEvaluator;
import org.apache.mahout.cf.taste.impl.eval.GenericRecommenderIRStatsEvaluator;
import org.apache.mahout.cf.taste.impl.model.GenericBooleanPrefDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.file.*;
import org.apache.mahout.cf.taste.impl.neighborhood.*;
import org.apache.mahout.cf.taste.impl.recommender.*;
import org.apache.mahout.cf.taste.impl.recommender.svd.ALSWRFactorizer;
import org.apache.mahout.cf.taste.impl.recommender.svd.SVDRecommender;
import org.apache.mahout.cf.taste.impl.similarity.*;
import org.apache.mahout.cf.taste.model.*;
import org.apache.mahout.cf.taste.neighborhood.*;
import org.apache.mahout.cf.taste.recommender.*;
import org.apache.mahout.cf.taste.similarity.*;
import org.apache.mahout.common.RandomUtils;
import java.io.*;
class RecommenderIntro {
public static void main(String[] args) throws Exception {
RandomUtils.useTestSeed();
DataModel model = new GenericDataModel(GenericDataModel.toDataMap(new FileDataModel(new File("/Users/ericxk/Downloads/ml-100k/ub.base"))));
RecommenderIRStatsEvaluator evaluator = new GenericRecommenderIRStatsEvaluator();
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
public Recommender buildRecommender(DataModel model)throws TasteException {
return new SVDRecommender(model,new ALSWRFactorizer(model,10,0.05,10));
}
};
IRStatistics stats = evaluator.evaluate(recommenderBuilder, null, model, null, 2, GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD, 1.0);
System.out.println(stats.getPrecision());
System.out.println(stats.getRecall());
}
}
这个很简单就是knn,不常用,因为速度慢。
ItemSimilarity similarity = new LogLikelihoodSimilarity(model);
Optimizer optimizer = new NonNegativeQuadraticOptimizer();
return new KnnItemBasedRecommender(model, similarity, optimizer, 10);
这个运行很快,因为可以一来就把簇分好,但是缺点也很明显,因为同一簇的是一样的推荐,不够个性化。
UserSimilarity similarity = new LogLikelihoodSimilarity(model);
ClusterSimilarity clusterSimilarity = new FarthestNeighborClusterSimilarity(similarity);
return new TreeClusteringRecommender(model, clusterSimilarity, 10);
当然还有不过Mahout正在紧密锣鼓添加中,常见的还有基于内容,关联规则,基于模型等等。