《R的极客理想——高级开发篇 A》一一2.1 用R重写Mahout协同过滤算法

2.1 用R重写Mahout协同过滤算法

问题
如何用R语言实现推荐算法?

引言
推荐系统在互联网应用中很常见,比如亚马逊为你推荐购书列表,豆瓣为你推荐电影列表。Mahout是Hahoop家族用于机器学习的分步式计算框架,主要包括三类算法,即推荐算法、聚类算法和分类算法。本节将用R语言来重写推荐部分的基于用户的协同过滤算法。用R语言重写Mahout的基于用户的协同过滤推荐算法,将完全按照Mahout的思路和设计进行实现,并与Mahout的计算结果进行对比。
2.1.1 Mahout的推荐算法模型
首先我们需要了解一下,协同过滤算法在Mahout中是如何实现的。Mahout的推荐算法定义了一套标准化的模型构建过程和调用过程,以基于用户的协同过滤算法(UserCF)为例,如图2-1所示。

图2-1 Mahout推荐算法模型(摘自《Mahout In Action》)
从图2-1中我们可以看到,基于用户的协同过滤算法是被模块化的,通过4个模块进行统一的方法调用。首先,创建数据模型(DataModel),然后定义用户的相似度算法(UserSimilarity),接下来定义用户近邻算法(UserNeighborhood),最后调用推荐算法(Recommender)完成计算过程。而基于物品的协同过滤算法(ItemCF)过程也是类似的,去掉第三步计算用户的近邻算法就行了。
本节中将用R语言对Mahout的0.5版本算法进行重新实现,下面是Mahout在Maven中版本定义。

org.apache.mahout
mahout-core
0.5

我们选用一组比较简单的测试数据集testCF.csv,数据集分为3列:用户ID、物品ID以及用户对物品的打分。一共21行数据。
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
我们先看一下如何用Java语言使用Mahout库的API,实现基于用户的协同过滤算法的代码。Java程序实现代码如下所示。
/**

  • UserCF测试,单机算法实现程序
    */
    public class UserCF {

    f?inal static int NEIGHBORHOOD_NUM = 2; // 取2个近邻
    f?inal static int RECOMMENDER_NUM = 3; // 保留3个推荐结果

    public static void main(String[] args) throws IOException, TasteException {
    String f?ile = "item.csv"; // 读入数据集
    DataModel model = new FileDataModel(new File(f?ile)); // 加载数据到内存对象DataModel
    UserSimilarity user = new EuclideanDistanceSimilarity(model);
    // 定义用户相似度距离
    NearestNUserNeighborhood neighbor = new NearestNUserNeighborhood
    (NEIGHBORHOOD_NUM, user, model); // 定义近邻
    Recommender r = new GenericUserBasedRecommender(model, neighbor, user);
    // 创建推荐模型
    LongPrimitiveIterator iter = model.getUserIDs(); // 取得用户列表

    while (iter.hasNext()) {            // 循环用户列表,计算给每个用户的推荐结果
        long uid = iter.nextLong();
        List list = r.recommend(uid, RECOMMENDER_NUM);
        System.out.printf("uid:%s", uid);
        for (RecommendedItem ritem : list) {
           System.out.printf("(%s,%f)", ritem.getItemID(), ritem.getValue());
               // 打印推荐结果
        }
        System.out.println();
    }
    

    }
    }
    运行Java程序,输出推荐结果。
    uid:1(104,4.250000)(106,4.000000)
    uid:2(105,3.956999)
    uid:3(103,3.185407)(102,2.802432)
    uid:4(102,3.000000)
    uid:5
    对结果的解读如下。
    给uid=1的用户,推荐计算得分最高的2个物品,104和106。
    给uid=2的用户,推荐计算得分最高的1个物品,105。
    给uid=3的用户,推荐计算得分最高的2个物品,103和102。
    给uid=4的用户,推荐计算得分最高的1个物品,102。
    给uid=5的用户,没有推荐。
    2.1.2 R语言模型实现
    接下来,我们使用R语言重写Mahout的实现,R语言代码将完全按照Mahout源代码的程序设计思路进行实现。为保证与Java程序思路一致,R代码用了for循环实现,这里暂时不考虑R程序的性能。
    用R语言构建算法模型,将按照下面的5个步骤进行:
    (1)建立数据模型
    (2)欧氏距离相似度算法
    (3)用户近邻算法
    (4)推荐算法
    (5)运行程序
    1.?建立数据模型
    创建数据模型的函数FileDataModel(),主要用于从CSV文件中读取数据,然后以R语言中矩阵类型(matrix)加载到内存中。
    FileDataModel<-function(f?ile){ # 数据模型函数
    data<-read.csv(f?ile,header=FALSE) # 读CSV文件到内存
    names(data)<-c("uid","iid","pref") # 增加列名

    user <- unique(data$uid) # 计算用户数
    item <- unique(sort(data$iid)) # 计算产品数
    uidx <- match(data$uid, user)
    iidx <- match(data$iid, item)
    M <- matrix(0, length(user), length(item)) # 定义存储矩阵
    i <- cbind(uidx, iidx, pref=data$pref)

    for(n in 1:nrow(i)){ # 给矩阵赋值
    M[i[n,][1],i[n,][2]]<-i[n,][3]
    }

    dimnames(M)[[2]]<-item
    M # 返回矩阵数据
    }
    2.?欧氏距离相似度算法
    我们在计算用户相似度算法的时候可以有多种选择,如欧氏距离相似度算法、皮尔森相似度算法、余弦相似度算法、Spearman秩相关系数相似度算法、曼哈顿距离相似度算法、对数似然相似度算法等,很多算法都有对应的R语言函数,直接调用就可以了。按照Mahout的代码实现思路,它对于基础的算法做了一些优化,因此我们也需要完全从底层重写这些算法。
    下面将以欧氏距离相似度算法为例,创建欧氏距离相似度的计算函数Euclidean-
    DistanceSimilarity(),加载用户物品的矩阵数据,通过欧氏距离来计算用户的相似度。
    EuclideanDistanceSimilarity<-function(M){
    row<-nrow(M)
    s<-matrix(0, row, row) # 相似度矩阵

    for(z1 in 1:row){
    for(z2 in 1:row){
    if(z1<z2){
    num<-intersect(which(M[z1,]!=0),which(M[z2,]!=0)) # 可计算的列

    sum<-0
    for(z3 in num){
      sum<-sum+(M[z1,][z3]-M[z2,][z3])^2
    }
    
    s[z2,z1]<-length(num)/(1+sqrt(sum))
    
    if(s[z2,z1]>1) s[z2,z1]<-1      # 对算法的阈值进行限制
    if(s[z2,z1]<-1) s[z2,z1]<- -1
    

    }
    }
    }

    ts<-t(s) # 补全三角矩阵
    w<-which(upper.tri(ts))
    s[w]<-ts[w]
    s # 返回用户相似度矩阵
    }
    3.?用户近邻算法
    我们在计算用户近邻的时候,也有2种算法来选择。一种是以个数来计算的,选出最近的前几个;另一种是以百分比来计算的,选出最近的前百分之几的数量。下面将以个数计算为例,选出最近的前N个用户。创建最近邻计算的函数NearestNUserNeighborhood(),传入用户的相似度矩阵和个数,就能计算出用户最近邻了。
    NearestNUserNeighborhood<-function(S,n){
    row<-nrow(S)
    neighbor<-matrix(0, row, n)
    for(z1 in 1:row){
    for(z2 in 1:n){
    m<-which.max(S[,z1])
    neighbor[z1,][z2]<-m
    S[,z1][m]=0
    }
    }
    neighbor # 返回前n个最近邻
    }
    4.?推荐算法
    我们在计算推荐的时候,也有几种算法可以选择,如基于用户的推荐算法、基于物品的推荐算法、slopeOne推荐算法、itemKNN推荐算法、SVD推荐算法、TreeCluster推荐算法,这几种推荐算法要与上面定义的数据和相似度算法进行匹配才能一起使用。
    下面我们创建基于用户的推荐算法计算的函数UserBasedRecommender(),通过用户物品数据矩阵、用户相似度矩阵、用户最近邻,按基于用户的协同过滤算法实现,计算出推荐结果。
    UserBasedRecommender<-function(uid,n,M,S,N){
    row<-ncol(N)
    col<-ncol(M)
    r<-matrix(0, row, col)
    N1<-N[uid,]
    for(z1 in 1:length(N1)){
    num<-intersect(which(M[uid,]==0),which(M[N1[z1],]!=0)) # 可计算的列
    for(z2 in num){
    r[z1,z2]=M[N1[z1],z2]*S[uid,N1[z1]]
    }
    }

    print(r) # 打印每个用户的推荐矩阵

    sum<-colSums(r)
    s2<-matrix(0, 2, col)
    for(z1 in 1:length(N1)){
    num<-intersect(which(colSums(r)!=0),which(M[N1[z1],]!=0))
    for(z2 in num){
    s2[1,][z2]<-s2[1,][z2]+S[uid,N1[z1]]
    s2[2,][z2]<-s2[2,][z2]+1
    }
    }

    s2[,which(s2[2,]==1)]=10000
    s2<-s2[-2,]

    r2<-matrix(0, n, 2)
    rr<-sum/s2
    item <-dimnames(M)[[2]]
    for(z1 in 1:n){
    w<-which.max(rr)
    if(rr[w]>0.5){
    r2[z1,1]<-item[which.max(rr)]
    r2[z1,2]<-as.double(rr[w])
    rr[w]=0
    }
    }
    r2
    }
    这样基于用户的协同过滤算法的函数,我们都已经用R程序实现了。下面按照调用关系,我们运行这些功能函数。
    5.?运行程序

    FILE<-"item.csv" # 数据文件
    NEIGHBORHOOD_NUM<-2 # 取2个最大近邻
    RECOMMENDER_NUM<-3 # 保留最多3个推荐结果

M<-FileDataModel(FILE) # 把数据文件,换成矩阵加载到内存
S<-EuclideanDistanceSimilarity(M) # 计算用户相似度矩阵
N<-NearestNUserNeighborhood(S,NEIGHBORHOOD_NUM)  # 计算用户近邻

R1<-UserBasedRecommender(1,RECOMMENDER_NUM,M,S,N);R1  # 查看对User=1的推荐结果
[,1] [,2]
[1,] "104" "4.25"
[2,] "106" "4"
[3,] "0" "0"
R2<-UserBasedRecommender(2,RECOMMENDER_NUM,M,S,N);R2  # 查看对User=2的推荐结果
[,1] [,2]
[1,] "105" "3.95699903407931"
[2,] "0" "0"
[3,] "0" "0"
R3<-UserBasedRecommender(3,RECOMMENDER_NUM,M,S,N);R3  # 查看对User=3的推荐结果
[,1] [,2]
[1,] "103" "3.18540697329411"
[2,] "102" "2.80243217111765"
[3,] "0" "0"
R4<-UserBasedRecommender(4,RECOMMENDER_NUM,M,S,N);R4  # 查看对User=4的推荐结果
[,1] [,2]
[1,] "102" "3"
[2,] "0" "0"
[3,] "0" "0"
R5<-UserBasedRecommender(5,RECOMMENDER_NUM,M,S,N);R5  # 查看对User=5的推荐结果
[,1] [,2]
[1,] 0 0
[2,] 0 0
[3,] 0 0
最后我们看到计算结果,同调用Mahout的API的Java程序的计算结果是一致的。
2.1.3 算法实现的原理——矩阵变换
那么我们算法实现的原理是什么呢?所谓协同过滤算法,其实就是矩阵变换的结果!请大家下面留意每一步的矩阵变换。从原始数据文件开始,如下所示。
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
第一步的矩阵变换,通过FileDataModel()函数,输出用户物品矩阵。
101 102 103 104 105 106 107
[1,] 5.0 3.0 2.5 0.0 0.0 0 0
[2,] 2.0 2.5 5.0 2.0 0.0 0 0
[3,] 2.5 0.0 0.0 4.0 4.5 0 5
[4,] 5.0 0.0 3.0 4.5 0.0 4 0
[5,] 4.0 3.0 2.0 4.0 3.5 4 0
第二步,利用欧氏相似度算法进行矩阵变换,运行EuclideanDistanceSimilarity()函数,结果如下所示。
[,1] [,2] [,3] [,4] [,5]
[1,] 0.0000000 0.6076560 0.2857143 1.0000000 1.0000000
[2,] 0.6076560 0.0000000 0.6532633 0.5568464 0.7761999
[3,] 0.2857143 0.6532633 0.0000000 0.5634581 1.0000000
[4,] 1.0000000 0.5568464 0.5634581 0.0000000 1.0000000
[5,] 1.0000000 0.7761999 1.0000000 1.0000000 0.0000000
第三步,通过用户相似度矩阵,计算出用户最近邻,运行EuclideanDistanceSimilarity()函数,结果如下所示。
top1 top2
[1,] 4 5
[2,] 5 3
[3,] 5 2
[4,] 1 5
[5,] 1 3
第四步,把上面的矩阵合并计算,通过基于用户的推荐算法,得到每个用户的推荐矩阵。以uid=1的用户为例,运行UserBasedRecommender()函数,结果如下所示。
101 102 103 104 105 106 107
4 0 0 0 4.5 0.0 4 0
5 0 0 0 4.0 3.5 4 0
打开注释行:UserBasedRecommender()函数的代码注释print(r) #打印每个用户的推荐矩阵。
第五步,过滤推荐矩阵的结果,取前2个得分最高的推荐结果返回。以uid=1的用户为例,结果如下所示。
推荐物品 物品得分
[1,] "104" "4.25"
[2,] "106" "4"
通过这5步矩阵变换,我们就可以清楚地看到基于用户的协同过滤算法的本质了。当然,Mahout所提供的算法。都是以矩阵为基础的可以处理海量数据的算法。如果我们的数据量很小,可以把上面的算法过程优化并改进,降低矩阵计算的复杂度。
2.1.4 算法总结
本节只是用R语言重写了Mahout的基于用户的、欧氏距离相似度、个数最近邻的协同过滤算法,重写其他的算法过程也是类似的。另外,在读Mahout源代码的过程中发现,Mahout做各种算法时,都有自己的优化,并不是简单地套用教课书上的算法公式。比如,计算基于欧氏距离的用户相似度时,并不是标准的欧氏距离算法,而是改进的欧氏距离算法。
similar = 1/(1+sqrt( (a-b)^2 + (a-c)^2 )) # 欧氏距离算法
similar = n/(1+sqrt( (a-b)^2 + (a-c)^2 )) # 改进的欧氏距离算法
公式解释:
a, b是被两个用户都打分的物品,也可能是1个或者多个
n是被两个用户都打分的物品的个数
当similar>1时,则similar=1
当similar<-1时,则similar=-1
通过算法的优化,Mahout可以给出更准确的推荐结果。所以,想用Mahout做推荐引擎的同学,就要对自己要求高一点,不仅要会Java,会Mahout,还要知道底层算法以及Mahout对算法的改动,这样Mahout在你手中才能发挥出真正的威力!关于Mahout的更多文章,请查阅笔者的博客(http://blog.fens.me/hadoop-mahout-roadmap/)。

时间: 2024-08-24 07:29:28

《R的极客理想——高级开发篇 A》一一2.1 用R重写Mahout协同过滤算法的相关文章

《R的极客理想——高级开发篇 A》一一1.1 R语言知识体系概览

1.1 R语言知识体系概览 问题 如何高效地学习R语言? 引言 最近遇到很多想转行做数据分析的程序员,他们刚开始学习R语言.很多人以为有了其他语言的编程背景,学习R语言就是一件很简单的事情,因而一味地追求速度,但不求甚解.有人说2周就能掌握R语言,但其实掌握的仅仅是R语言的语法,只能算是入门. R语言的知识体系并非语法这么简单,如果都不了解R的全貌,何谈学好R语言呢?本节将介绍R语言的知识体系结构,并告诉读者如何才能高效地学习R语言.1.1.1 R语言的知识体系结构 R语言是一门统计语言,主要用

《R的极客理想——高级开发篇 A》导读

前 言 为什么要写这本书本书是我写的"R的极客理想"系列丛书的第二本,主要介绍了R语言本身的核心技术.R语言的高级开发应用.R语言与其他学科及知识领域的跨学科综合应用.其实,早在我的第一本书<R的极客理想--工具篇>中,就已经介绍了R语言的30多个工具包的使用方法,并以IT人的角度,告诉读者如何高效地使用第三方R包,把原有IT知识运用到R语言的学习过程中.但由于一本书的篇幅有限,<R的极客理想--工具篇>只讲了如何使用R语言,没有讲原理.而本书将弥补这一遗憾,主

《R的极客理想——高级开发篇 A》一一2.4 R语言中的遗传算法

2.4 R语言中的遗传算法 问题 如何用R语言进行遗传算法的计算? 引言 人类总是在生活中摸索规律,把规律总结为经验,再把经验传给后人,让后人发现更多的规律,每一次知识的传递都是一次进化的过程,最终形成了人类的智慧.自然界的规律,让人类适者生存地活了下来,聪明的科学家又把生物进化的规律,总结成遗传算法,扩展到了更广的领域中.本节将带你走进遗传算法的世界.2.4.1 遗传算法介绍 遗传算法是一种解决最优化的搜索算法,是进化算法的一种.进化算法最初借鉴了达尔文的进化论和孟德尔的遗传学说,从生物进化的

《R的极客理想——高级开发篇 A》一一2.3 二条均线打天下

2.3 二条均线打天下 问题如何用R语言编写金融算法模型? 引言移动平均线(MA)是股市中最常用的一种技术分析方法,用来在大行情的波动段找到有效的交易信号.移动平均线不仅简单,而且有效,对股市操作具有神奇的指导作用.据金融从业人员说,均线模型有效地打败了大部分的主观策略,是炒股.炒期货的必备基本工具.那么本节将深入研究一下均线模型如何在股市中发挥作用.2.3.1 移动平均线移动平均(moving average,MA)线是以道·琼斯的"平均成本概念"为理论基础,采用统计学中"

《R的极客理想——高级开发篇 A》一一第2章 R语言的算法实现

第2章 R语言的算法实现 本章用R语言实现了4个算法案例,包括协同过滤算法.PageRank算法.均线模型算法和遗传算法,希望这些案例可以帮助读者理解R语言在实际业务中的应用.

《R的极客理想——高级开发篇 A》一一1.5 R语言的导数计算

1.5 R语言的导数计算 问题 如何用R语言进行导数计算? 引言 高等数学是每个大学生都要学习的一门数学基础课,同时也可能是考完试后最容易忘记的一门知识.我在学习高数的时候绞尽脑汁,但始终都不知道为何而学,生活和工作基本用不到,就算是在计算机行业和金融行业,能直接用到高数的地方也少之又少,学术和实际应用真是相差太远了. 不过,R语言为我打开了一扇高数应用的大门,R语言不仅能方便地实现高等数学的计算,还可以很容易地把一篇论文中的高数公式应用于产品的实践中.因为R语言我重新学习了高数,让生活中充满数

《R的极客理想——高级开发篇 A》一一2.2 PageRank算法R语言实现

2.2 PageRank算法R语言实现 问题 如何用R语言实现PageRank算法? 引言 Google搜索,早已成为我每天必用的工具,我无数次惊叹它搜索结果的准确性.同时,我也在做Google的SEO,推广自己的博客.经过几个月尝试,我的博客PR到2了,外链也有几万个.总结下来,还是感叹PageRank的神奇.笔者认为PageRank是改变互联网的算法!2.2.1 PageRank算法介绍 PageRank是Google专有的算法,用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度.

《R的极客理想——高级开发篇 A》一一1.2 R语言中的数学计算

1.2 R语言中的数学计算 问题 如何用R语言进行数学计算? 引言 R语言是统计语言,生来就对数学有良好的支持,用R语言做数学的计算题特别方便.如果计算器中能嵌入R语言的计算函数,那么绝对是一种高科技产品.我真的把R语言当成我的计算器了!1.2.1 基本计算 R语言对数学计算有着非常好的支持,本节将完整介绍初等数学中的各种计算操作. 本节的系统环境是: Windows 7 64bit R: 3.1.1 x86_64-w64-mingw32/x64 (64-bit) 用R语言实现四则运算操作,包括

《R的极客理想——高级开发篇 A》一一第1章 R语言知识体系和数学函数

第1章 R语言知识体系和数学函数 本章为全书开篇,主要介绍了R语言知识体系结构及学习资料.利用R语言实现数学计算和统计计算以及连续型分布函数的可视化,可帮助读者全面理解R语言,并利用R语言快速地处理基础学科的计算问题.