前言
用Mahout来构建推荐系统,是一件既简单又困难的事情。简单是因为Mahout完整地封装了“协同过滤”算法,并实现了并行化,提供非常简单的API接口;困难是因为我们不了解算法细节,很难去根据业务的场景进行算法配置和调优。
本文将深入算法API去解释Mahout推荐算法底层的一些事。
mahout推荐商品给用户:
recommender.recommender(a,b);第一个参数是向几号用户推荐,第二个参数是推荐的商品数。
用户相似度返回值:
–应用机器学习及数据库知识来解决一些实际数据价值的分析
–从海量数据中提取,转换,数据处理,应用算法挖掘有用信息
–存储访问高效
–数据质量的保证
–海量数据的问题
–数据可视化
–信息提取(图像等信号处理)
–空间数据分析
–主要是设计和分析一些让计算机可以自动学习的算法
–更多利用统计学知识,概率论,离散数学等知识对算法的研究
–更多专注于算法
1)特征分析
–提取能表达数据集的特征字段
2)关联规则
–找出隐藏在数据集中数据项的关系
3)偏差分析
–历史数据和标准差异
–均值、中位数、众数、极差、方差、标准差、偏度、峰度
4)回归分析
–找到数据属性在时间序列上的规律加以判断未来走势
5)分类
–目标是将未知数据集分类
6)聚类
–根据数据相似性和差异性分类
7)推荐
①单机,内存
②Oracle缺乏专业分析,SQL不适合
③数据抽样有问题
–覆盖范围
–聚类、推荐
解决方案:
–机器学习算法MapReduce化!!!
一个来自Apache的、开源的、JAVA的机器学习软件库
当所处理的数据规模远大于单机处理能力时成为一种可选的机器学习工具,建立在Apache的Hadoop分布式计算项目之上
http://mahout.apache.org/users/basics/algorithms.html
1.上传文件到linux
将synthetic_control.data文件上传到linux上
2.启动hadoop高可用集群
3.上传synthetic_control.data文件到hdfs上
hdfs dfs -mkdir testdata
hdfs dfs -put synthetic_control.data /testdata
4.hadoop jar mahout-examples-0.12.2-job.jarorg.apache.mahout.clustering.syntheticcontrol.canopy.Job
5.mahout org.apache.mahout.clustering.syntheticcontrol.canopy.Job
6.显示结果
hadoop fs -ls output
7.将hdfs上的文件输出到linux上
mkdir /dataset
mahout seqdumper -i output/clusters-0-final -o /dataset/clusters-0-final
8.查看文件
cat /dataset/clusters-0-final
分类是什么?
分类是一种简单的决策形式,从正确决策样本中进行学习并作出预测分析。
分类依赖于有指导的学习
为什么用Mahout做分类?
Mahout广泛应用于分类项目,但仅当训练样本个数非常大时,Mahout的优势才会体现出来
分类可以用来做什么?
分类算法是预测分析的核心
可用于:垃圾邮件检测
信用卡欺诈检测
保险或电信业预测公司的客户流失
糖尿病、老年痴呆症等医学上的预测
分类通常是一种通过分析低代价变量组合来确定高代价变量值的方法
聚类、推荐和分类的区别?
聚类:
–依赖于机器自身进行的决策
–聚类算法自己能决定哪些区别是重要的
推荐:
–对多个可能的好答案进行选择和排序
–分类算法试图从很有限的输出集合中做出单一决策,推荐算法会选择许多 可能的答案,并对其进行排序
–推荐系统通常使用用户行为历史,而无视用户和物品的特点,而特点本身 对于分类系统却是很有用的
构建分类系统主要有两个阶段:通过学习算法建立一个模型,然后使用该模型对新数据进行分类。
上图展示了分类过程的两个阶段:上面表示训练分类模型的过程;下面的过程为该模型提供新的输入样本,然后通过为样本分配类别来进行决策。
当生产环境中新样本的目标变量是未知的。而在对算法进行评估时,这些目标变量的值是已知的,但对分类模型不可见。
测试时提交给模型的新样本不能包含在训练数据中,这一点没有显示地在下图中表示出来。通过比较模型选择的结果与已知的目标变量值,就可以评估模型的性能。
² 模型:一个做决策的计算机程序;在分类中,训练算法的输出就是一个模型
² 训练数据:训练样本的一个子集,带有目标变量值得标注,用作学习算法的输入以生成模型(大概占用数据80%~90%)(不断加入以更新模型)
² 测试数据:留存的部分训练样本,隐藏其目标变量值,以便用于评估模型
² 训练:使用训练数据生成模型的学习过程。随后该模型可将预测变量作为输入来估计目标变量的值
² 训练样本:具有特征的实体,将被用作学习算法的输入
² 特征:训练样本或新样本的特性成为特征。过程叫做特征提取。
² 变量:在这个上下文中,指一个特征的值或一个关于多个特征的函数。
² 记录:存储一个样本的容器,这样一个容器由多个字段组成
² 字段:记录的一部分,包含一个特征的值(变量)
² 预测变量:
选做分类模型输入的一个特征,不是所有的特征都会被用到。某些特征可能是 其他特征按某种规则进行组合的结果。
为模型提供线索
² 目标变量:
分类模型试图去预测的一个特征:目标变量是可分类的,决定其类别就是分类系统的目标
垃圾邮件检测就是一个有着二值类别型变量的分类系统
典型分类项目的工作流?
①训练模型(构建分类器大部分精力会花在设计并提取有用的特征上)
定义目标变量(目标变量类别越少越好,如果可降到两个,有使用更多的学习算法可供选择)
搜集历史数据(”垃圾堆里出垃圾”)
定义预测变量(从训练和测试样本中提取的特征)(目标泄露)
选择学习算法
使用学习算法训练模型
②评估模型
在测试数据上运行
调整输入(改变预测变量、改变算法)
③在生产中使用模型
输入新样本,估计未知的目标变量值
在必要时重新训练模型(性能下降时,并不一定就放弃,使用新的、更合适的训练数据重新训练模型也许就足够了)
例子1:
例子2:
提示:
–不是所有特征都对每一个分类问题有用。特定场合和你的具体问题决定了哪些特征会是有效的预测变量。
–了解你的数据是成功的关键。
生成模型
–线性判别分析(LDA)---假设为高斯条件密度模型。
–朴素贝叶斯分类器---假设为条件独立性假设模型。
判别模型
–逻辑回归---最大似然估计,其假设观察到的训练集是由一个依赖于分类器的输出的二元模型所产生。
–感知器(Perceptron)---一个试图去修正在训练集中遇到错误的算法。
–支持向量机---一个试图去最大化决策超平面和训练集中的样本间的边界(margin)的算法。
测试
1.解压
tar -zxvfapache-mahout-distribution-0.12.2-src.tar.gz
2.moreexamples/src/main/resources/donut.csv
3. mahout trainlogistic --inputexamples/src/main/resources/donu
t.csv --output ./model --target color--categories 2 --predictors x y --types numeric --features 20 --passes 100--rate 50
4.mahoutrunlogistic --input examples/src/main/resources/donut.csv --model ./model--auc --confusion
朴素贝叶斯分类器在很多复杂的现实情形中仍能够取得相当好的效果(一种线性分类器,透过特征的线性组合来做出分类决定)
贝叶斯分类器的分类原理是通过某对象的先验概率,利用贝叶斯公式计算出其后验概率,即该对象属于某一类的概率,选择具有最大后验概率的类作为该对象所属的类。
应用贝叶斯网络分类器进行分类主要分成两阶段。第一阶段是贝叶斯网络分类器的学习,即从样本数据中构造分类器,包括结构学习和CPT条件概率表(Conditional Probability Table,CPT)学习;第二阶段是贝叶斯网络分类器的推理,即计算该结点的条件概率,对分类数据进行分类。
测试:
1.解压
tar -zxvf 20news-bydate.tar.gz
用Mahout向量化—>算法算模型—>测试模型
2.合并20news-bydate里面训练集和测试集在一起
上传到hdfs上
hadoop fs -put 20news-all /
查看
hadoop fs -ls /20news-all
3.使用mahout seqdirectory命令将20news-all目录下的所有样本文件转换成
mahout seqdirectory -i /20news-all -o 20news-seq -ow
hadoop fs -ls 20news-seq
4.使用mahout seq2sparse命令将生成的SequenceFile文件转换成
mahout seq2sparse -i 20news-seq -o 20news-vectors -lnorm -nv -wt tfidf
hadoop fs -ls 20news-vectors
4.使用mahout split命令将生成的向量文件分离成两部分,一部分作为训练集,一部分作为测试集。其中训练集数据放到20news-train-vectors目录下,测试集数据放到20news-test-vectors目录下,-rp 40参数表示有40%的数据作为训练集。
mahout split -i20news-vectors/tfidf-vectors -tr 20news-train-vectors -te 20news-test-vectors-rp 40 -ow -seq -xm sequential
5. 使用mahouttrainnb命令训练贝叶斯分类器,将生成的模型放到model目录下naiveBayesModel.bin文件中。该命令也会执行Hadoop M-R作业来进行处理。
mahout trainnb -i 20news-train-vectors -o bayes-model-li labelindex -ow -c
6. 使用mahouttestnb命令测试贝叶斯分类器。
mahout testnb -i 20news-test-vectors -m bayes-model-l labelindex -ow -o 20news-testing -c
7.mahoutseqdumper -i 20news-testing/part-m-00000 -o /20news-testing
cat /20news-testing
统计学,数据挖掘和机器学习中的决策树训练,使用决策树作为预测模型来预测样本的类标。
一棵树的训练过程为:根据一个指标,分裂训练集为几个子集。这个过程不断的在产生的子集里重复递归进行,即递归分割。当一个训练子集的类标都相同时递归停止。
随机森林(Random Forest)使用多颗决策树来改进分类性能。
这一算法首先训练大量的简单分类器,然后通过投票机制得出最终的唯一结果。每个小分类器都是在所有训练样本上针对部分特征训练。
Mahout的并行实现是在模型中并行训练很多分类器。
物以类聚,人以群分
图书、硬币
猿与猴子
特征参数
①利用输入数据生成向量
②将向量写入输入目录
③写入初始簇中心(optional)
④运行聚类作业
⑤从输出目录中读取簇
①欧氏距离测度(EuclideanDistanceMeasure)
②平方欧氏距离测度(SquaredEuclideanDistanceMeasure)
③曼哈顿距离测度(ManhattanDistanceMeasure)
④余弦距离测度(CosineDistanceMeasure)
⑤谷本距离测度(TanimotoDistanceMeasure)
–同时表现夹角和距离的距离测度
⑥加权距离测度(WeightedDistanceMeasure)
k-means聚类实例
canopy聚类的优势在于它得到簇的速度非常快,它只需要遍历一次数据即可得到结果。不需要像k-means那样预先指定簇数量k
注意:如果设定了-k标志,RadomSeedGenerator会覆盖canopy中心文件夹
你可以把它看作是k-means的扩展,模糊k-means尝试从数据集中生成有重叠的簇(FuzzyKMeansDriver)
创建一个空模型,并尝试将点分配给它。然后,模型粗略地上下调整其参数,试着去拟合数据。(狄利克雷初始状态是一个数据点的集合以及一个ModelDistribution)
-md参数
GaussianClusterDistribution
LDA话题建模(用话题建模,LDA将模型和数据拟合)
加权(weighting)
Term Frequency -Inverse Document Frequency
词频
逆文档频率
就是说,如果一个单词在所有文档中被使用的越频繁,那它对向量中的值得作用就会被抵消得越多
文档向量中单词的权重为:
TF:单条文档中的词频DF:词在多少个文章中出现过,在一个文章中出现多次,只计算一次 N:总文章数
seq2sparse -wt参数 tfidf
归一化(normalization)
seq2sparse -n参数
目录
Mahoutt推荐算法,从数据处理能力上,可以划分为2类:
1). 单机内存算法实现
单机内存算法实现:就是在单机下运行的算法,是由cf.taste项目实现的,像我的们熟悉的UserCF,ItemCF都支持单机内存运行,并且参数可以灵活配置。单机算法的基本实例,请参考文章:用Maven构建Mahout项目
单机内存算法的问题在于,受限于单机的资源。对于中等规模的数据,像1G,10G的数据量,有能力进行计算,但是超过100G的数据量,对于单机来说是不可能完成的任务。
2). 基于Hadoop的分步式算法实现
基于Hadoop的分步式算法实现:就是把单机内存算法并行化,把任务分散到多台计算机一起运行。Mahout提供了ItemCF基于Hadoop并行化算法实现。基于Hadoop的分步式算法实现,请参考文章:
Mahout分步式程序开发 基于物品的协同过滤ItemCF
分步式并行算法的问题在于,如何让单机算法并行化。在单机算法中,我们只需要考虑算法,数据结构,内存,CPU就够了,但是分步式算法还要额外考虑很多的情况,比如多节点的数据合并,数据排序,网路通信的效率,节点宕机重算,数据分步式存储等等的很多问题。
Mahout提供了2个评估推荐器的指标,查准率和召回率(查全率),这两个指标是搜索引擎中经典的度量方法。
相关 不相关
检索到 A C
未检索到 B D
被检索到的越多越好,这是追求“查全率”,即A/(A+B),越大越好。
被检索到的,越相关的越多越好,不相关的越少越好,这是追求“查准率”,即A/(A+C),越大越好。
在大规模数据集合中,这两个指标是相互制约的。当希望索引出更多的数据的时候,查准率就会下降,当希望索引更准确的时候,会索引更少的数据。
1). 系统环境:
2). Recommender接口文件:
org.apache.mahout.cf.taste.recommender.Recommender.java
接口中方法的解释:
通过Recommender接口,我可以猜出核心算法,应该会在子类的estimatePreference()方法中进行实现。
3). 通过继承关系到Recommender接口的子类:
推荐算法实现类:
下面将分别介绍每种算法的实现。
测试数据集:item.csv
1,101,5.0
1,102,3.0
1,103,2.5
2,101,2.0
2,102,2.5
2,103,5.0
2,104,2.0
3,101,2.5
3,104,4.0
3,105,4.5
3,107,5.0
4,101,5.0
4,103,3.0
4,104,4.5
4,106,4.0
5,101,4.0
5,102,3.0
5,103,2.0
5,104,4.0
5,105,3.5
5,106,4.0
测试程序:org.conan.mymahout.recommendation.job.RecommenderTest.java
package org.conan.mymahout.recommendation.job;
import java.io.IOException;
import java.util.List;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.common.RandomUtils;
public class RecommenderTest {
final static int NEIGHBORHOOD_NUM = 2;
final static int RECOMMENDER_NUM = 3;
public static void main(String[] args) throws TasteException, IOException {
RandomUtils.useTestSeed();
String file = "datafile/item.csv";
DataModel dataModel = RecommendFactory.buildDataModel(file);
slopeOne(dataModel);
}
public static void userCF(DataModel dataModel) throws TasteException{}
public static void itemCF(DataModel dataModel) throws TasteException{}
public static void slopeOne(DataModel dataModel) throws TasteException{}
...
每种算法都一个单独的方法进行算法测试,如userCF(),itemCF(),slopeOne()….
基于用户的协同过滤,通过不同用户对物品的评分来评测用户之间的相似性,基于用户之间的相似性做出推荐。简单来讲就是:给用户推荐和他兴趣相似的其他用户喜欢的物品。
举例说明:
基于用户的 CF 的基本思想相当简单,基于用户对物品的偏好找到相邻邻居用户,然后将邻居用户喜欢的推荐给当前用户。计算上,就是将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,找到 K 邻居后,根据邻居的相似度权重以及他们对物品的偏好,预测当前用户没有偏好的未涉及物品,计算得到一个排序的物品列表作为推荐。图 2 给出了一个例子,对于用户 A,根据用户的历史偏好,这里只计算得到一个邻居 – 用户 C,然后将用户 C 喜欢的物品 D 推荐给用户 A。
上文中图片和解释文字,摘自: https://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/
算法API: org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender
@Override
public float estimatePreference(long userID, long itemID) throws TasteException {
DataModel model = getDataModel();
Float actualPref = model.getPreferenceValue(userID, itemID);
if (actualPref != null) {
return actualPref;
}
long[] theNeighborhood = neighborhood.getUserNeighborhood(userID);
return doEstimatePreference(userID, theNeighborhood, itemID);
}
protected float doEstimatePreference(long theUserID, long[] theNeighborhood, long itemID) throws TasteException {
if (theNeighborhood.length == 0) {
return Float.NaN;
}
DataModel dataModel = getDataModel();
double preference = 0.0;
double totalSimilarity = 0.0;
int count = 0;
for (long userID : theNeighborhood) {
if (userID != theUserID) {
// See GenericItemBasedRecommender.doEstimatePreference() too
Float pref = dataModel.getPreferenceValue(userID, itemID);
if (pref != null) {
double theSimilarity = similarity.userSimilarity(theUserID, userID);
if (!Double.isNaN(theSimilarity)) {
preference += theSimilarity * pref;
totalSimilarity += theSimilarity;
count++;
}
}
}
}
// Throw out the estimate if it was based on no data points, of course, but also if based on
// just one. This is a bit of a band-aid on the 'stock' item-based algorithm for the moment.
// The reason is that in this case the estimate is, simply, the user's rating for one item
// that happened to have a defined similarity. The similarity score doesn't matter, and that
// seems like a bad situation.
if (count <= 1) {
return Float.NaN;
}
float estimate = (float) (preference / totalSimilarity);
if (capper != null) {
estimate = capper.capEstimate(estimate);
}
return estimate;
}
测试程序:
public static void userCF(DataModel dataModel) throws TasteException {
UserSimilarity userSimilarity = RecommendFactory.userSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
UserNeighborhood userNeighborhood = RecommendFactory.userNeighborhood(RecommendFactory.NEIGHBORHOOD.NEAREST, userSimilarity, dataModel, NEIGHBORHOOD_NUM);
RecommenderBuilder recommenderBuilder = RecommendFactory.userRecommender(userSimilarity, userNeighborhood, true);
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.0
Recommender IR Evaluator: [Precision:0.5,Recall:0.5]
uid:1,(104,4.333333)(106,4.000000)
uid:2,(105,4.049678)
uid:3,(103,3.512787)(102,2.747869)
uid:4,(102,3.000000)
用R语言重写UserCF的实现,请参考文章:用R解析Mahout用户推荐协同过滤算法(UserCF)
基于item的协同过滤,通过用户对不同item的评分来评测item之间的相似性,基于item之间的相似性做出推荐。简单来讲就是:给用户推荐和他之前喜欢的物品相似的物品。
举例说明:
基于物品的 CF 的原理和基于用户的 CF 类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。图 3 给出了一个例子,对于物品 A,根据所有用户的历史偏好,喜欢物品 A 的用户都喜欢物品 C,得出物品 A 和物品 C 比较相似,而用户 C 喜欢物品 A,那么可以推断出用户 C 可能也喜欢物品 C。
上文中图片和解释文字,摘自: https://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/
算法API: org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender
@Override
public float estimatePreference(long userID, long itemID) throws TasteException {
PreferenceArray preferencesFromUser = getDataModel().getPreferencesFromUser(userID);
Float actualPref = getPreferenceForItem(preferencesFromUser, itemID);
if (actualPref != null) {
return actualPref;
}
return doEstimatePreference(userID, preferencesFromUser, itemID);
}
protected float doEstimatePreference(long userID, PreferenceArray preferencesFromUser, long itemID)
throws TasteException {
double preference = 0.0;
double totalSimilarity = 0.0;
int count = 0;
double[] similarities = similarity.itemSimilarities(itemID, preferencesFromUser.getIDs());
for (int i = 0; i < similarities.length; i++) {
double theSimilarity = similarities[i];
if (!Double.isNaN(theSimilarity)) {
// Weights can be negative!
preference += theSimilarity * preferencesFromUser.getValue(i);
totalSimilarity += theSimilarity;
count++;
}
}
// Throw out the estimate if it was based on no data points, of course, but also if based on
// just one. This is a bit of a band-aid on the 'stock' item-based algorithm for the moment.
// The reason is that in this case the estimate is, simply, the user's rating for one item
// that happened to have a defined similarity. The similarity score doesn't matter, and that
// seems like a bad situation.
if (count <= 1) {
return Float.NaN;
}
float estimate = (float) (preference / totalSimilarity);
if (capper != null) {
estimate = capper.capEstimate(estimate);
}
return estimate;
}
测试程序:
public static void itemCF(DataModel dataModel) throws TasteException {
ItemSimilarity itemSimilarity = RecommendFactory.itemSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
RecommenderBuilder recommenderBuilder = RecommendFactory.itemRecommender(itemSimilarity, true);
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:0.8676552772521973
Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
uid:1,(105,3.823529)(104,3.722222)(106,3.478261)
uid:2,(106,2.984848)(105,2.537037)(107,2.000000)
uid:3,(106,3.648649)(102,3.380000)(103,3.312500)
uid:4,(107,4.722222)(105,4.313953)(102,4.025000)
uid:5,(107,3.736842)
这个算法在mahout-0.8版本中,已经被@Deprecated。
SlopeOne是一种简单高效的协同过滤算法。通过均差计算进行评分。SlopeOne论文下载(PDF)
1). 举例说明:
用户X,Y,Z,对于物品A,B进行打分,如下表,求Z对B的打分是多少?
Slope one算法认为:平均值可以代替某两个未知个体之间的打分差异,事物A对事物B的平均差是:((5 - 4) + (4 - 2)) / 2 = 1.5,就得到Z对B的打分是,3-1.5 = 1.5。
Slope one算法将用户的评分之间的关系看作简单的线性关系:
Y = mX + b
2). 平均加权计算:
用户X,Y,Z,对于物品A,B,C进行打分,如下表,求Z对A的打分是多少?
通过这种简单的方式,我们可以快速计算出一个评分项,完成推荐过程!
算法API: org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOneRecommender
@Override
public float estimatePreference(long userID, long itemID) throws TasteException {
DataModel model = getDataModel();
Float actualPref = model.getPreferenceValue(userID, itemID);
if (actualPref != null) {
return actualPref;
}
return doEstimatePreference(userID, itemID);
}
private float doEstimatePreference(long userID, long itemID) throws TasteException {
double count = 0.0;
double totalPreference = 0.0;
PreferenceArray prefs = getDataModel().getPreferencesFromUser(userID);
RunningAverage[] averages = diffStorage.getDiffs(userID, itemID, prefs);
int size = prefs.length();
for (int i = 0; i < size; i++) {
RunningAverage averageDiff = averages[i];
if (averageDiff != null) {
double averageDiffValue = averageDiff.getAverage();
if (weighted) {
double weight = averageDiff.getCount();
if (stdDevWeighted) {
double stdev = ((RunningAverageAndStdDev) averageDiff).getStandardDeviation();
if (!Double.isNaN(stdev)) {
weight /= 1.0 + stdev;
}
// If stdev is NaN, then it is because count is 1. Because we're weighting by count,
// the weight is already relatively low. We effectively assume stdev is 0.0 here and
// that is reasonable enough. Otherwise, dividing by NaN would yield a weight of NaN
// and disqualify this pref entirely
// (Thanks Daemmon)
}
totalPreference += weight * (prefs.getValue(i) + averageDiffValue);
count += weight;
} else {
totalPreference += prefs.getValue(i) + averageDiffValue;
count += 1.0;
}
}
}
if (count <= 0.0) {
RunningAverage itemAverage = diffStorage.getAverageItemPref(itemID);
return itemAverage == null ? Float.NaN : (float) itemAverage.getAverage();
} else {
return (float) (totalPreference / count);
}
}
测试程序:
public static void slopeOne(DataModel dataModel) throws TasteException {
RecommenderBuilder recommenderBuilder = RecommendFactory.slopeOneRecommender();
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.3333333333333333
Recommender IR Evaluator: [Precision:0.25,Recall:0.5]
uid:1,(105,5.750000)(104,5.250000)(106,4.500000)
uid:2,(105,2.286115)(106,1.500000)
uid:3,(106,2.000000)(102,1.666667)(103,1.625000)
uid:4,(105,4.976859)(102,3.509071)
这个算法在mahout-0.8版本中,已经被@Deprecated。
算法来自论文:
This algorithm is based in the paper of Robert M. Bell and Yehuda Koren in ICDM '07.
(TODO未完)
算法API: org.apache.mahout.cf.taste.impl.recommender.knn.KnnItemBasedRecommender
@Override
protected float doEstimatePreference(long theUserID, PreferenceArray preferencesFromUser, long itemID)
throws TasteException {
DataModel dataModel = getDataModel();
int size = preferencesFromUser.length();
FastIDSet possibleItemIDs = new FastIDSet(size);
for (int i = 0; i < size; i++) {
possibleItemIDs.add(preferencesFromUser.getItemID(i));
}
possibleItemIDs.remove(itemID);
List mostSimilar = mostSimilarItems(itemID, possibleItemIDs.iterator(),
neighborhoodSize, null);
long[] theNeighborhood = new long[mostSimilar.size() + 1];
theNeighborhood[0] = -1;
List usersRatedNeighborhood = Lists.newArrayList();
int nOffset = 0;
for (RecommendedItem rec : mostSimilar) {
theNeighborhood[nOffset++] = rec.getItemID();
}
if (!mostSimilar.isEmpty()) {
theNeighborhood[mostSimilar.size()] = itemID;
for (int i = 0; i < theNeighborhood.length; i++) {
PreferenceArray usersNeighborhood = dataModel.getPreferencesForItem(theNeighborhood[i]);
int size1 = usersRatedNeighborhood.isEmpty() ? usersNeighborhood.length() : usersRatedNeighborhood.size();
for (int j = 0; j < size1; j++) {
if (i == 0) {
usersRatedNeighborhood.add(usersNeighborhood.getUserID(j));
} else {
if (j >= usersRatedNeighborhood.size()) {
break;
}
long index = usersRatedNeighborhood.get(j);
if (!usersNeighborhood.hasPrefWithUserID(index) || index == theUserID) {
usersRatedNeighborhood.remove(index);
j--;
}
}
}
}
}
double[] weights = null;
if (!mostSimilar.isEmpty()) {
weights = getInterpolations(itemID, theNeighborhood, usersRatedNeighborhood);
}
int i = 0;
double preference = 0.0;
double totalSimilarity = 0.0;
for (long jitem : theNeighborhood) {
Float pref = dataModel.getPreferenceValue(theUserID, jitem);
if (pref != null) {
double weight = weights[i];
preference += pref * weight;
totalSimilarity += weight;
}
i++;
}
return totalSimilarity == 0.0 ? Float.NaN : (float) (preference / totalSimilarity);
}
}
测试程序:
public static void itemKNN(DataModel dataModel) throws TasteException {
ItemSimilarity itemSimilarity = RecommendFactory.itemSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
RecommenderBuilder recommenderBuilder = RecommendFactory.itemKNNRecommender(itemSimilarity, new NonNegativeQuadraticOptimizer(), 10);
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.5
Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
uid:1,(107,5.000000)(104,3.501168)(106,3.498198)
uid:2,(105,2.878995)(106,2.878086)(107,2.000000)
uid:3,(103,3.667444)(102,3.667161)(106,3.667019)
uid:4,(107,4.750247)(102,4.122755)(105,4.122709)
uid:5,(107,3.833621)
(TODO未完)
算法API: org.apache.mahout.cf.taste.impl.recommender.svd.SVDRecommender
@Override
public float estimatePreference(long userID, long itemID) throws TasteException {
double[] userFeatures = factorization.getUserFeatures(userID);
double[] itemFeatures = factorization.getItemFeatures(itemID);
double estimate = 0;
for (int feature = 0; feature < userFeatures.length; feature++) {
estimate += userFeatures[feature] * itemFeatures[feature];
}
return (float) estimate;
}
测试程序:
public static void svd(DataModel dataModel) throws TasteException {
RecommenderBuilder recommenderBuilder = RecommendFactory.svdRecommender(new ALSWRFactorizer(dataModel, 10, 0.05, 10));
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:0.09990564982096355
Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
uid:1,(104,4.032909)(105,3.390885)(107,1.858541)
uid:2,(105,3.761718)(106,2.951908)(107,1.561116)
uid:3,(103,5.593422)(102,2.458930)(106,-0.091259)
uid:4,(105,4.068329)(102,3.534025)(107,0.206257)
uid:5,(107,0.105169)
这个算法在mahout-0.8版本中,已经被@Deprecated。
(TODO未完)
算法API: org.apache.mahout.cf.taste.impl.recommender.TreeClusteringRecommender
@Override
public float estimatePreference(long userID, long itemID) throws TasteException {
DataModel model = getDataModel();
Float actualPref = model.getPreferenceValue(userID, itemID);
if (actualPref != null) {
return actualPref;
}
buildClusters();
List topRecsForUser = topRecsByUserID.get(userID);
if (topRecsForUser != null) {
for (RecommendedItem item : topRecsForUser) {
if (itemID == item.getItemID()) {
return item.getValue();
}
}
}
// Hmm, we have no idea. The item is not in the user's cluster
return Float.NaN;
}
测试程序:
public static void treeCluster(DataModel dataModel) throws TasteException {
UserSimilarity userSimilarity = RecommendFactory.userSimilarity(RecommendFactory.SIMILARITY.LOGLIKELIHOOD, dataModel);
ClusterSimilarity clusterSimilarity = RecommendFactory.clusterSimilarity(RecommendFactory.SIMILARITY.FARTHEST_NEIGHBOR_CLUSTER, userSimilarity);
RecommenderBuilder recommenderBuilder = RecommendFactory.treeClusterRecommender(clusterSimilarity, 10);
RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
LongPrimitiveIterator iter = dataModel.getUserIDs();
while (iter.hasNext()) {
long uid = iter.nextLong();
List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
RecommendFactory.showItems(uid, list, true);
}
}
程序输出:
AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:NaN
Recommender IR Evaluator: [Precision:NaN,Recall:0.0]
算法及适用场景:
算法评分的结果:
通过对上面几种算法的一平分比较:itemCF,itemKNN,SVD的Rrecision,Recall的评分值是最好的,并且itemCF和 SVD的AVERAGE_ABSOLUTE_DIFFERENCE是最低的,所以,从算法的角度知道了,哪个算法是更准确的或者会索引到更多的数据集。
另外的一些因素: