码迷,mamicode.com
首页 > 编程语言 > 详细

spark机器学习系列:(三)用Spark Python构建推荐系统

时间:2016-06-28 20:38:43      阅读:535      评论:0      收藏:0      [点我收藏+]

标签:

上一篇博文详细介绍了如何使用Spark Python进行数据处理和特征提取,本系列从本文开始,将陆续介绍用Spark Python对机器学习模型进行详细的探讨。

推荐引擎或许是最为大众所知的一种机器学习模型。人们或许并不知道它确切是什么,但在使用AmazonNetflixYouTubeTwitterLinkedInFacebook这些流行站点的时候,可能已经接触过了。推荐是这些网站背后的核心组件之一,有时还是一个重要的收入来源。

推荐引擎背后的想法是预测人们可能喜好的物品并通过探寻物品之间的联系来辅助这个过程。从这点上来说,它和同样也做预测的搜索引擎互补。但与搜索引擎不同,推荐引擎试图向人们呈现的相关内容并不一定就是人们所搜索的,其返回的某些结果甚至人们都没听说过。

推荐引擎很适合如下两类常见场景(两者可兼有)。

可选项众多:可选的物品越多,用户就越难找到想要的物品。如果用户知道他们想要什么,那搜索能有所帮助。然而最适合的物品往往并不为用户所事先知道。这时,通过向用户推荐相关物品,其中某些可能用户事先不知道,将能帮助他们发现新物品。

偏个人喜好:当人们主要根据个人喜好来选择物品时,推荐引擎能利用集体智慧,根据其他有类似喜好用户的信息来帮助他们发现所需物品。



1 推荐模型的分类


推荐系统的研究已经相当广泛,也存在很多设计方法。最为流行的两种方法是基于内容的过滤和协同过滤。另外,排名模型等近期也受到不少关注。实践中的方案很多是综合性的,它们将多种方法的元素合并到一个模型中或是进行组合。

1.1 基于内容的过滤

基于内容的过滤利用物品的内容或是属性信息以及某些相似度定义,来求出与该物品类似的物品。这些属性值通常是文本内容(比如标题、名称、标签及该物品的其他元数据)。对多媒体来说,可能还涉及从音频或视频中提取的其他属性。

类似地,对用户的推荐可以根据用户的属性或是描述得出,之后再通过相同的相似度定义来与物品属性做匹配。比如,用户可以表示为他所接触过的各物品属性的综合。该表示可作为该用户的一种描述。之后可以用它来与物品的属性进行比较以找出符合用户描述的物品。

1.2协同过滤

协同过滤是一种借助众包智慧的途径。它利用大量已有的用户偏好来估计用户对其未接触过的物品的喜好程度。其内在的思想是相似度的定义。

在基于用户的方法的中,如果两个用户表现出相似的偏好(即对相同物品的偏好大体相同),那就认为他们的兴趣类似。要对他们中的一个用户推荐一个未知物品,便可选取若干与其类似的用户并根据他们的喜好计算出对各个物品的综合得分,再以得分来推荐物品。其整体的逻辑是,如果其他用户也偏好某些物品,那这些物品很可能值得推荐。

同样也可以借助基于物品的方法来做推荐。这种方法通常根据现有用户对物品的偏好或是评级情况,来计算物品之间的某种相似度。这时,相似用户评级相同的那些物品会被认为更相近。一旦有了物品之间的相似度,便可用用户接触过的物品来表示这个用户,然后找出和这些已知物品相似的那些物品,并将这些物品推荐给用户。同样,与已有物品相似的物品被用来生成一个综合得分,而该得分用于评估未知物品的相似度。

基于用户或物品的方法的得分取决于若干用户或是物品之间依据相似度所构成的集合(即邻居),故它们也常被称为最近邻模型。

最后,也存在不少基于模型的方法是对“用户物品”偏好建模。这样,对未知“用户物品”组合上应用该模型便可得出新的偏好。

1.3 矩阵分解

Spark推荐模型库当前只包含基于矩阵分解(matrix factorization)的实现,由此我们也将重点关注这类模型。它们有吸引人的地方。首先,这些模型在协同过滤中的表现十分出色。而在Netflix Prize等知名比赛中的表现也很拔尖。

1.3.1 显式矩阵分解

当要处理的那些数据是由用户所提供的自身的偏好数据,这些数据被称作显式偏好数据。这类数据包括如物品评级、赞、喜欢等用户对物品的评价。

这些数据可以转换为以用户为行、物品为列的二维矩阵。矩阵的每一个数据表示某个用户对特定物品的偏好。大部分情况下单个用户只会和少部分物品接触,所以该矩阵只有少部分数据非零(即该矩阵很稀疏)。

举个例子,假设我们有如下用户对电影的评级数据

user ratings 数据:

Tom, Star Wars, 5
Jane, Titanic, 4
Bill, Batman, 3
Jane, Star Wars, 2
Bill, Titanic, 3

以user为行,movie为列构造对应rating matrix:

技术分享


MF就是一种直接建模user-item矩阵的方法,利用两个低维度的小矩阵的乘积来表示,属于一种降维的技术。

如果我们有U个用户,I个items,若不经过MF处理,它看来会使这样的:

技术分享                                                                                                                        

值得注意的是,上述评级矩阵通常很稀疏,但因子矩阵却是稠密的,经过MF处理后,表示为两个维度较小的因子矩阵相乘,如图所示:

技术分享


这类模型试图发现对应“用户物品”矩阵内在行为结构的隐含特征(这里表示为因子矩阵),所以也把它们称为隐特征模型。隐含特征或因子不能直接解释,但它可能表示了某些含义,比如对电影的某个导演、种类、风格或某些演员的偏好。

由于是对“用户物品”矩阵直接建模,用这些模型进行预测也相对直接:要计算给定用户对某个物品的预计评级,就从用户因子矩阵和物品因子矩阵分别选取相应的行(用户因子向量)与列(物品因子向量),然后计算两者的点积即可,如下图所示

技术分享

而对于物品之间相似度的计算,可以用最近邻模型中用到的相似度衡量方法。不同的是,这里可以直接利用物品因子向量,将相似度计算转换为对两物品因子向量之间相似度的计算,如下图所示:

技术分享

因子分解类模型的好处在于,一旦建立了模型,对推荐的求解便相对容易。但也有弊端,即当用户和物品的数量很多时,其对应的物品或是用户的因子向量可能达到数以百万计。这将在存储和计算能力上带来挑战。另一个好处是,这类模型的表现通常都很出色。

因子分解类模型也存在某些弱点。相比最近邻模型,这类模型在理解和可解释性上难度都有所增加。另外,其模型训练阶段的计算量也很大。



1.3.2隐式矩阵分解


上面针对的是评级之类的显式偏好数据,但能收集到的偏好数据里也会包含大量的隐式反馈数据。在这类数据中,用户对物品的偏好不会直接给出,而是隐含在用户与物品的交互之中。二元数据(比如用户是否观看了某个电影或是否购买了某个商品)和计数数据(比如用户观看某电影的次数)便是这类数据。

处理隐式数据的方法相当多。MLlib实现了一个特定方法,它将输入的评级数据视为两个矩阵:一个二元偏好矩阵P以及一个信心权重矩阵C

举例来说,假设之前提到的“用户电影”评级实际上是各用户观看电影的次数,那上述两个矩阵会类似下图所示。其中,矩阵P表示用户是否看过某些电影,而矩阵C则以观看的次数来表示信心权重。一般来说,某个用户观看某个电影的次数越多,那我们对该用户的确喜欢该电影的信心也就越强。

技术分享

隐式模型仍然会创建一个用户因子矩阵和一个物品因子矩阵。但是,模型所求解的是偏好矩阵而非评级矩阵的近似。类似地,此时用户因子向量和物品因子向量的点积所得到的分数也不再是一个对评级的估值,而是对某个用户对某一物品偏好的估值(该值的取值虽并不严格地处于01之间,但十分趋近于这个区间)。



1.3.3 最小二乘法


最小二乘法(Alternating Least Squares,ALS)是一种求解矩阵分解问题的最优化方法。它功能强大、效果理想而且被证明相对容易并行化。这使得它很适合如Spark这样的平台。

ALS的实现原理是迭代式求解一系列最小二乘回归问题。在每一次迭代时,固定用户因子矩阵或是物品因子矩阵中的一个,然后用固定的这个矩阵以及评级数据来更新另一个矩阵。之后,被更新的矩阵被固定住,再更新另外一个矩阵。如此迭代,直到模型收敛(或是迭代了预设好的次数)。



2 提取有效特征


这里,我们将采用显式评级数据,而不使用其他用户或物品的元数据以及“用户物品”交互数据。这样,所需的输入数据就只需包括每个评级对应的用户ID、影片ID和具体的星级。本文中使用显式数据也就是用户对movie的rating信息,这个数据来源于网络上的MovieLens标准数据集



rawData = sc.textFile("/Users/youwei.tan/Downloads/ml-100k/u.data")
print rawData.first()
rawRatings = rawData.map(lambda x: x.split(‘\t‘))
rawRatings.take(5)
#数据分别是userId,itemId,rating和timestamp

输出结果:

196	242	3	881250949
[[u‘196‘, u‘242‘, u‘3‘, u‘881250949‘],
 [u‘186‘, u‘302‘, u‘3‘, u‘891717742‘],
 [u‘22‘, u‘377‘, u‘1‘, u‘878887116‘],
 [u‘244‘, u‘51‘, u‘2‘, u‘880606923‘],
 [u‘166‘, u‘346‘, u‘1‘, u‘886397596‘]]

导入RatingALS模块,用于后面建模

from pyspark.mllib.recommendation import Rating, ALS
ratings = rawRatings.map(lambda x: Rating(int(x[0]),int(x[1]),float(x[2])))
print ratings.take(5)

输出结果:

[Rating(user=196, product=242, rating=3.0), 
 Rating(user=186, product=302, rating=3.0), 
 Rating(user=22, product=377, rating=1.0), 
 Rating(user=244, product=51, rating=2.0), 
 Rating(user=166, product=346, rating=1.0)]


3 训练推荐模型


现在可以开始训练模型了,所需的其他参数有以下几个。

rank:对应ALS模型中的因子个数,也就是在低阶近似矩阵中的隐含特征个数。因子个数一般越多越好。但它也会直接影响模型训练和保存时所需的内存开销,尤其是在用户和物品很多的时候。因此实践中该参数常作为训练效果与系统开销之间的调节参数。通常,其合理取值为10200

iterations:对应运行时的迭代次数。ALS能确保每次迭代都能降低评级矩阵的重建误差,但一般经少数次迭代后ALS模型便已能收敛为一个比较合理的好模型。这样,大部分情况下都没必要迭代太多次(10次左右一般就挺好)。

lambda:该参数控制模型的正则化过程,从而控制模型的过拟合情况。其值越高,正则化越严厉。该参数的赋值与实际数据的大小、特征和稀疏程度有关。和其他的机器学习模型一样,正则参数应该通过用非样本的测试数据进行交叉验证来调整。

作为示例,这里将使用的rankiterationslambda参数的值分别为50100.01:

model = ALS.train(ratings, 50, 10, 0.01)
userFeatures = model.userFeatures()
print userFeatures.take(2)

输出结果:

[(4, array(‘d‘, [-0.4886532723903656, -0.5069043636322021, -0.25953221321105957, 0.28663870692253113, 
-0.23391617834568024, -0.1597181111574173, -0.6053574085235596, -0.05088397115468979, -0.13468056917190552, 
-0.06684212386608124, -0.1647985279560089, -0.3268186151981354, 0.04303080216050148, -0.37185072898864746, 
-0.07605815678834915, -0.18150471150875092, 0.4730634391307831, -0.17112521827220917, 0.011302260681986809, 
0.025912854820489883, 0.22327645123004913, -0.553532063961029, -0.19743777811527252, -0.06508021801710129, 
0.11410114169120789, -0.2178303301334381, -0.12165536731481552, 0.1517478972673416, -0.41739341616630554, 
-0.3790307939052582, 0.09140809625387192, 0.438218355178833, -0.40334436297416687, -0.26500797271728516, 
-0.17151066660881042, -0.056731898337602615, 0.014920198358595371, -0.4036138951778412, -0.503244161605835, 
-0.35094475746154785, -0.2583906650543213, 0.15083833038806915, -0.02699241414666176, -0.5867881774902344, 
0.6203442215919495, -0.5354999303817749, 0.49261313676834106, 0.36879995465278625, 0.217197448015213, 
0.2137724757194519])), 
(8, array(‘d‘, [-0.3435080349445343, -0.23504899442195892, 0.01265001017600298, 0.37398409843444824, 
-0.12563304603099823, 0.35209083557128906, 0.14092524349689484, -0.03852889686822891, 0.35797831416130066, 
-0.21024121344089508, -0.4891291558742523, 0.13990764319896698, -0.2572784125804901, 0.7792724370956421, 
0.2034142017364502, 0.5507923364639282, -0.047871965914964676, 0.24563290178775787, 0.14227859675884247, 
-0.3111373484134674, 0.6031113266944885, -0.5081701874732971, 0.39406323432922363, 0.17398110032081604, 
0.7309723496437073, 0.38500913977622986, -0.39698582887649536, 0.3925837576389313, -0.14110979437828064, 
-0.3914717435836792, 0.36928772926330566, 0.6285825967788696, -0.574873149394989, -0.4255753457546234, 
-0.14399465918540955, -0.11449692398309708, 0.32894405722618103, 0.17887713015079498, 0.23037177324295044, 
-0.17776504158973694, -0.17060403525829315, -0.28235891461372375, -0.33164700865745544, -0.05743246525526047, 
0.0831192284822464, -0.007868407294154167, 0.0913839191198349, 0.6946220993995667, 0.3273734152317047, 
-0.22331389784812927]))]

注意,MLlibALS的实现里所用的操作都是延迟性的转换操作。所以,只在当用户因子或物品因子结果RDD调用了执行操作时,实际的计算才会发生。要强制计算

发生,则可调用Spark的执行操作,如count。

print model.userFeatures().count()
print model.productFeatures().count()

输出结果:

943
1682

使用隐式反馈数据训练模型

MLlib中标准的矩阵分解模型用于显式评级数据的处理。若要处理隐式数据,则可使用trainImplicit函数。其调用方式和标准的train模式类似,但多了一个可设置的alpha参数也是一个正则化参数,lambda应通过测试和交叉验证法来设置

alpha参数指定了信心权重所应达到的基准线。该值越高则所训练出的模型越认为用户与他所没评级过的电影之间没有相关性。



4 使用推荐模型


有了训练好的模型后便可用它来做预测。预测通常有两种:为某个用户推荐物品,或找出与某个物品相关或相似的其他物品。

MovieLens 100k数据集生成电影推荐MLlib的推荐模型基于矩阵分解,因此可用模型所求得的因子矩阵来计算用户对物品的预计评级。下面只针对利用MovieLens中显式数据做推荐的情形,使用隐式模型时的方法与之相同。

print len(userFeatures.first()[1])
predictRating = model.predict(789,123)
print predictRating

输出结果:

50
3.18324449604

可以看到,该模型预测用户789对电影123的评级为3.18

predict函数同样可以以(user, item)ID对类型的RDD对象为输入,这时它将为每一对都生成相应的预测得分。我们可以借助这个函数来同时为多个用户和物品

进行预测。

要为某个用户生成K推荐物品,可借助MatrixFactorizationModel所提供的recommendProducts函数来实现。该函数需两个输入参数:usernum。其中user

是用户ID,而num是要推荐的物品个数。

返回值为预测得分最高的前num个物品。这些物品的序列按得分排序。该得分为相应的用户因子向量和各个物品因子向量的点积。

现在,计算给用户789推荐的前10个物品,代码如下:

topKRecs = model.recommendProducts(789,10)
print ‘给用户userId推荐其喜欢的item:‘
for rec in topKRecs:
    print rec
    
#print ratings.groupBy(lambda x : x.user).mapValues(list).lookup(789)
#print ratings.groupBy(lambda x : x.user).map(lambda (x,y): (x, list(y))).lookup(789)

moviesForUser = ratings.groupBy(lambda x : x.user).mapValues(list).lookup(789)
# print moviesForUser
print ‘源数据中用户userId的喜欢的item:‘
for i in sorted(moviesForUser[0],key=lambda x : x.rating,reverse=True):
    print i.product

输出结果:

给用户userId推荐其喜欢的item:
Rating(user=789, product=48, rating=5.572495011959803)
Rating(user=789, product=56, rating=5.510520005211698)
Rating(user=789, product=346, rating=5.295860840700527)
Rating(user=789, product=192, rating=5.08681379224634)
Rating(user=789, product=92, rating=5.01867372270688)
Rating(user=789, product=526, rating=5.016016542149785)
Rating(user=789, product=182, rating=4.9996960801921695)
Rating(user=789, product=179, rating=4.9877606036865005)
Rating(user=789, product=100, rating=4.971601345359906)
Rating(user=789, product=9, rating=4.969371624015052)
源数据中用户userId的喜欢的item:
127
475
9
50
150
276
129
100
741
1012
93
293
181
1008
508
1007
124
1161
294
1
284
1017
111
742
248
249
591
288
762
628
137
151
286

使用recommendProducts来为用户789推荐top10的items,其items顺序为降序。MoviesForUser是从ratings数据中找出的用户789rating最高的数据,仔细看下发现数据和我们的ratings里面找出的数据貌似一个都没有相同的,那么是不是说明我们的算法不给力呢?这个可不一定,想想看,如果推荐系统只是推荐给你看过的电影,那么它一定是一个失败的,并且完全对系统的kpi数据无提升作用,前面提到,MF的实质是通过latent feature去找到与用户过去偏好高的有某些隐性相同特征的电影(这些由整体用户的集体智慧得到),比如可能是某一类型的电影、又或者相同的演员等等,所以这里不能说明推荐系统不给力,但是确实也很难具有解释性。

未完===
























spark机器学习系列:(三)用Spark Python构建推荐系统

标签:

原文地址:http://blog.csdn.net/u013719780/article/details/51775047

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!