到目前为止,前面三篇文章我们已经讲过了基于物品协同过滤的原理,算法在Spark平台上的并行化实现,算法的持久化实现。前面得到的推荐结果只是根据特定的一个用户推荐相应物品,本篇要讲的是在Spark平台上实现批量推荐用户,包括串行化与并行化的实现。
本篇内容:
1.批量推荐串行化实现(略讲)
2.批量推荐并行化实现(详)
3.实现代码
4.两种方式结果对比
1.串行化实现
批量推荐,就是给一批用户,根据计算得到的相似度矩阵或模型进行批量推荐,最简单的实现方式就是串行化实现,即用一个for循环每次对一个用户推荐。
这种方式的优点是实现简单,逻辑清晰有条理;缺点是所需时间长,不能充分利用现有资源,造成资源浪费。
具体如何实现不再细讲,后面会有参考代码。
2.并行化实现
并行化实现是本文讲解的重点,通过并行化的批量推荐,可以充分利用现有资源,发挥Spark平台的优势,减少系统响应时间,更适合于实际使用。缺点是逻辑复杂,难以理解,难以实现。
接下来我会一步一步的讲解如何在Spark上实现并行化批量推荐。
1.针对一批用户,对这批用户建立用户RDD,此处我以四个用户为例:
user_id=sc.parallelize(['100','200','260','300'])
2.得到用户列表RDD后,我们要考虑的问题是如何对这批用户实现并行化推荐,而并行化的关键问题便是实现数据独立。此处我用了两次笛卡尔积以实现并行化。对于批量推荐,我们要做的主要分为几步,首先根据用户列表找到每个用户观看过的电影,然后根据每个用户观看过的电影在相似度矩阵中计算,得到相似的电影,最后根据相似度进排序进行推荐。
3.首先对用户列表RDD与用户观看过的电影user_item做笛卡尔积,然后通过filter函数进行过滤操作,过滤掉用户列表user_id与user_item中的user_id不同的项,取出相同的项,取出的RDD即为用户列表对应的用户看过的电影。取出的数据结构为:[(user_id,[item_id,item_id,.....]),(user_id,[....]),.....],此处我们已经得到每个用户观看的电影RDD。
see_list=user_id.cartesian(user_item)
print(see_list.first())
see_list=see_list.filter(lambda x:(x[0]==x[1][0])).map(lambda x:(x[1][0],x[1][1]))
4.然后用得出的用户看过的列表see_list与相似度矩阵W做笛卡尔积,得到的RDD结构为
((user_id,[item_id,item_id,......]),(item_id,[(item_id,rating),(item_id,rating),.....]))
然后通过filter函数过滤,用自己编写的find函数找到电影在用户观看列表中的电影,取出这些项。然后根据与他们相似的物品的评分高低取出N个相似的物品。即每个用户的每部看过的电影的相似的N个物品。得到RDD的数据结构为:
(user_id,[(item_id,rating),(item_id,rating),......])
def find(x,list_item):
for i in list_item:
if i==x:
return True
return False
similar=see_list.cartesian(W).filter(lambda x:find(x[1][0],x[0][1])).map(lambda x:(x[0][0],get(x[1][1],5)))
5.之后用一个groupbykey函数将相同用户id的推荐电影整合到一个RDD元素中,然后对所有相似的电影,相同的电影id相似度评分求和,排序得到前N个相似度评分最高的电影,给相应的user_id推荐这些电影。
def getall(L):
List=[ ]
for l in L:
List=judge(l,List)
List.sort(key=lambda x:x[1],reverse=True)
return List
def judge(l,List):
for s in List:
if s[0]==l[0]:
k=s[1]+l[1]
m=s[0]
List.remove(s)
s=(m,k)
List.append(s)
return List
List.append(l)
return List
sim=similar.reduceByKey(lambda x,y:x+y)
recommend=sim.map(lambda x:(x[0],getall(x[1]))).collect()
print("Result:")
for r in recommend:
print(r)
3.实现代码
串行化代码:
from pyspark import SparkContext
from pyspark import SparkConf
from RecommendClass import itemCF
import time
import math
import sys
def CreatSparkContext():
sparkConf=SparkConf() .setAppName("WordCounts").set("spark.ui.showConsoleProgress","false")
sc=SparkContext(conf=sparkConf)
print("master="+sc.master)
SetLogger(sc)
SetPath(sc)
return(sc)
def SetLogger(sc):
logger=sc._jvm.org.apache.log4j
logger.LogManager.getLogger("org").setLevel(logger.Level.ERROR)
logger.LogManager.getLogger("akka").setLevel(logger.Level.ERROR)
logger.LogManager.getRootLogger().setLevel(logger.Level.ERROR)
def SetPath(sc):
global Path
if sc.master[0:5]=="local":
Path="file:/home/hduser/pythonwork/PythonProject/"
else:
Path="hdfs://localhost:9000/user/hduser/"
def choose(x):
if x in ['0','1','2','3','4','5','6','7','8','9','.']:
return True
return False
def prepardata(sc):
rawUserData=sc.textFile(Path+"module.txt")
print(rawUserData.first())
print(rawUserData.first()[0])
rawRatings=rawUserData.map(lambda line:line.split(","))
print(rawRatings.first())
ratingsRDD=rawRatings.map(lambda x:(filter(str.isdigit,x[0].encode('gbk')),filter(str.isdigit,x[1].encode('gbk')),float(filter(choose,x[2].encode('gbk')))))
print(ratingsRDD.first())
return(ratingsRDD)
def recommend(W,user_item,user_id,k):
user_see_item=user_item.filter(lambda x:x[0]==user_id).map(lambda x:x[1]).collect()[0]
sim_item=W.filter(lambda x:find(x[0],user_see_item)).map(lambda x:x[1])
recommend_item=sim_item.flatMap(lambda x:get(x,k)).reduceByKey(lambda x,y:x+y)
recommend_item=recommend_item.sortBy(lambda x:x[1],False)
return recommend_item.take(k)
def find(x,list_item):
for i in list_item:
if i==x:
return True
return False
def get(x,k):
x.sort(key=lambda x:x[1],reverse=True)
return x[:k]
if __name__=="__main__":
start=time.time()
sc=CreatSparkContext()
print("prepare data")
w=prepardata(sc)
W=w.map(lambda x:(x[0],(x[1],x[2])))
W=W.groupByKey().map(lambda x:(x[0],list(x[1])))
rawUserData=sc.textFile("file:/home/hduser/pythonwork/PythonProject/data/u.data")
rawRatings=rawUserData.map(lambda line:line.split("\t")[:2])
ratingsRDD=rawRatings.map(lambda x:(filter(str.isdigit,x[0].encode('gbk')),filter(str.isdigit,x[1].encode('gbk'))))
user_item=ratingsRDD.groupByKey().map(lambda x:(x[0],list(x[1])))
user_id=sc.parallelize(['200','100','260','300'])
see_list=user_id.cartesian(user_item)
print(see_list.first())
see_list=see_list.filter(lambda x:(x[0]==x[1][0])).map(lambda x:(x[1][0],x[1][1])).collect()
for L in see_list:
sim_item=W.filter(lambda x:find(x[0],L[1])).map(lambda x:x[1])
recommend_item=sim_item.flatMap(lambda x:get(x,5)).reduceByKey(lambda x,y:x+y)
recommend_item=recommend_item.sortBy(lambda x:x[1],False).take(5)
print(L[0])
for r in recommend_item:
print(r)
print("Result:")
end=time.time()
T=end-start
print(T)
并行化代码:
from pyspark import SparkContext
from pyspark import SparkConf
from RecommendClass import itemCF
import math
import sys
import time
def CreatSparkContext():
sparkConf=SparkConf() .setAppName("WordCounts").set("spark.ui.showConsoleProgress","false")
sc=SparkContext(conf=sparkConf)
print("master="+sc.master)
SetLogger(sc)
SetPath(sc)
return(sc)
def SetLogger(sc):
logger=sc._jvm.org.apache.log4j
logger.LogManager.getLogger("org").setLevel(logger.Level.ERROR)
logger.LogManager.getLogger("akka").setLevel(logger.Level.ERROR)
logger.LogManager.getRootLogger().setLevel(logger.Level.ERROR)
def SetPath(sc):
global Path
if sc.master[0:5]=="local":
Path="file:/home/hduser/pythonwork/PythonProject/"
else:
Path="hdfs://localhost:9000/user/hduser/"
def choose(x):
if x in ['0','1','2','3','4','5','6','7','8','9','.']:
return True
return False
def prepardata(sc):
rawUserData=sc.textFile(Path+"module.txt")
print(rawUserData.first())
print(rawUserData.first()[0])
rawRatings=rawUserData.map(lambda line:line.split(","))
print(rawRatings.first())
ratingsRDD=rawRatings.map(lambda x:(filter(str.isdigit,x[0].encode('gbk')),filter(str.isdigit,x[1].encode('gbk')),float(filter(choose,x[2].encode('gbk')))))
print(ratingsRDD.first())
return(ratingsRDD)
def recommend(W,user_item,user_id,k):
user_see_item=user_item.filter(lambda x:x[0]==user_id).map(lambda x:x[1]).collect()[0]
sim_item=W.filter(lambda x:find(x[0],user_see_item)).map(lambda x:x[1])
recommend_item=sim_item.flatMap(lambda x:get(x,k)).reduceByKey(lambda x,y:x+y)
recommend_item=recommend_item.sortBy(lambda x:x[1],False)
return recommend_item.take(k)
def find(x,list_item):
for i in list_item:
if i==x:
return True
return False
def get(x,k):
x.sort(key=lambda x:x[1],reverse=True)
return x[:k]
def getall(L):
List=[ ]
for l in L:
List=judge(l,List)
List.sort(key=lambda x:x[1],reverse=True)
return List
def judge(l,List):
for s in List:
if s[0]==l[0]:
k=s[1]+l[1]
m=s[0]
List.remove(s)
s=(m,k)
List.append(s)
return List
List.append(l)
return List
if __name__=="__main__":
start=time.time()
sc=CreatSparkContext()
print("prepare data")
w=prepardata(sc)
W=w.map(lambda x:(x[0],(x[1],x[2])))
W=W.groupByKey().map(lambda x:(x[0],list(x[1])))
rawUserData=sc.textFile("file:/home/hduser/pythonwork/PythonProject/data/u.data")
rawRatings=rawUserData.map(lambda line:line.split("\t")[:2])
ratingsRDD=rawRatings.map(lambda x:(filter(str.isdigit,x[0].encode('gbk')),filter(str.isdigit,x[1].encode('gbk'))))
user_item=ratingsRDD.groupByKey().map(lambda x:(x[0],list(x[1])))
user_id=sc.parallelize(['100','200','260','300'])
see_list=user_id.cartesian(user_item)
print(see_list.first())
see_list=see_list.filter(lambda x:(x[0]==x[1][0])).map(lambda x:(x[1][0],x[1][1]))
similar=see_list.cartesian(W).filter(lambda x:find(x[1][0],x[0][1])).map(lambda x:(x[0][0],get(x[1][1],5)))
#sim=similar.groupByKey().map(lambda x:(x[0],list([x[1]])))
sim=similar.reduceByKey(lambda x,y:x+y)
print(sim.first())
#recommend=sim.reduceByKey(lambda x,y:x+y).sortBy(lambda x:x[1],False).take(5)
recommend=sim.map(lambda x:(x[0],getall(x[1]))).collect()
print("Result:")
for r in recommend:
print(r)
end=time.time()
print("time:")
print(end-start)
4.结果对比
对于两种方式实现的批量用户推荐,我在我配置的Spark平台上分别跑了一遍,在代码中引入时间函数以计时,得到的结果如下:
并行化结果:
串行化结果:
通过结果对照我们发现,并行化得到结果所需时间远大于串行化得到结果所花时间。通过分析我们确定了以下几种原因:
1.并行化方式并未实现并行化,反而加重了计算难度与复杂性,导致时间成本大大增加。
2.并行化做两次笛卡尔积,得到的数据量呈指数级上升,相比串行的循环处理的时间增加。
3.我们只在我们配置的本地服务器上运行,只有一台服务器运算,并没有用到Spark分布式服务器运算的特点,猜想在分布式多服务器Spark平台上跑的时候会大大降低响应时间。
我们将在后续验证我们的猜想以及解决该问题。