在上一篇文章中,我们了解到了两种相似度评价方法:欧几里得距离和皮尔逊相关度方法,其实
类似功能的算法好多,比如Jaccard系数和曼哈顿距离算法等。有兴趣的同学可以登陆http://en.wikipedia.org/wiki/Metric_%28mathematics%29#Examples ,你可以找到并学习更多用于比较的度量算法,在此不展开。下面我们开始进入下一步:为推荐者打分。
四、为推荐者打分
既然我们已经有了一个对两个人进行评价的方法,那么只要循环调用这个方法,我们就可以很容易的得到所有用户之间的相似度。用与某个用户品味相近的用户的评价为参考,就可以大致推测出该用户对该商品的评分,以此为依据,就可以对指定用户进行推荐。
据此思路,我们在recommendations.py中添加topMatchs方法,具体代码如下:
- #寻找字典中匹配度较高的人并排序
- def topMatchs(prefs, person, n = 5, similarity = sim_pearson):
- scores = [(similarity(prefs, person, other), other)
- for other in prefs if other != person]
- #对scores列表进行排序,评价值最高的在前面
- scores.sort()
- scores.reverse()
- return scores[0:n]
注意这个方法的参数,其中n为所要返回的数据个数,默认为5,另外需要一个函数指针similarity,用来指定计算相似度的方法名称,在这里我们可以指定我们前面写好的两种方法中的任一个,sim_distance或sim_pearson
运行测试语句:
- >>> reload(recommendations)
- >>> print topMatchs(recommendations.critics, 'Toby', 3)
- [(0.9912407071619299, 'Lisa Rose'), (0.9244734516419049, 'Mick LaSalle'), (0.8934051474415647, 'Claudia Puig')]
- >>> print topMatchs(recommendations.critics, 'Lisa Rose', 5)
- [(0.9912407071619299, 'Toby'), (0.7470178808339965, 'Jack Matthews'), (0.5940885257860044, 'Mick LaSalle'),
- (0.5669467095138396, 'Claudia Puig'), (0.40451991747794525, 'Michael Phillips')]
- >>> print topMatchs(recommendations.critics, 'Lisa Rose', 5, sim_distance)
- [(0.4721359549995794, 'Michael Phillips'), (0.4142135623730951, 'Mick LaSalle'), (0.38742588672279304, 'Claudia Puig'),
- (0.3483314773547883, 'Toby'), (0.3405424265831667, 'Jack Matthews')]
这里有必要关注的是'Lisa Rose'的运行结果,正像我之前提到的,这里两种相似度计算方法的结果天差地别,比如我之前提到的'Lisa Rose'与'Jack Matthews'的问题,二者在皮尔逊算法中品味确实很相似,但到了欧几里得算法中则显得品味相差很大,所以在本情境中显然还是用皮尔逊算法合理些。
有了topMatchs函数,我们已经可以很轻易地知道谁与我们的品味更相近,通过查看他们的影评分数,我们就可以参考他们看过的哪些电影中那些我可能会喜欢,有兴趣的读者也不妨把自己的评分加到字典中,试着看看谁与你的品味更相似。
五、基于评价者的推荐
找到与自己兴趣相投的用户并从他们那找到适合自己看的电影固然很好,但其实我们真正想要的是一份对于所有影片的推荐,那样就省得从一堆评论中寻找了有用信息了。
为了解决这个问题,我们用一个简单的加权算法来给影片打分:将所有其他评论者的评分分别乘上他们与用户相似度然后取平均值。这样,相比于与我们品味不相近的人,与我们品味相似的人的评分对最终结果的贡献显然更高,结果也就通常更可信。不过这里要注意一点,由于是根据相似度进行取平均,所以除数不是其他评论者的个数,而是所有其他评论者与我们相似度的和!对于加权算法,这点并不难理解。
我们将所有其他用户的评分及其与Toby的相似度列表如下:
其中Night、Lady、Luck列为他们对对应电影的评分,S.x Xxxx列代表他们评分按相似度的加权值及与相似度的乘积,而sim.sum一行则代表所有相似度之和,最后一行的总计/Sim.Sum则代表最终结果。
于是,我们继续在文件中建立getRecommendations函数来重复上一过程,代码如下 :
- #利用所有其他人评价的加权平均,为某人提供建议
- def getRecommendations(prefs, person, similarity = sim_pearson):
- totals = {} #相似度*评价值之和
- simSums = {} #相似度之和
- for other in prefs:
- #避免和自己比较
- if other == person: continue
- sim = similarity(prefs, person, other)
- #忽略评价值小于等于零的情况
- if sim <= 0: continue
- for moive in prefs[other]:
- #只对自己没有看过的影片做评价
- if moive not in prefs[person] or prefs[person][moive] == 0:
- #计算相似度*评价值之和
- totals.setdefault(moive, 0)
- totals[moive] += prefs[other][moive]*sim
- #计算相似度之和
- simSums.setdefault(moive, 0)
- simSums[moive] += sim
- #建立一个归一化的列表
- rankings = [(total / simSums[moive], moive) for moive,total in totals.items()]
- #排序
- rankings.sort()#升序排列
- rankings.reverse()#颠倒(降序排列)
- return rankings
该函数循环遍历perfs中所有的其他人,每一轮循环中,他都会算出其他人与person的相似度,然后找出所有person没看过的电影,分别计算每部电影相似度*评价值之和与相似度之和,最后做商得出结果,返回一个列出所有person没看过的电影与对应评分的列表。
接下来,我们就可以为Toby推荐电影了:
- >>> recommendations.getRecommendations(recommendations.critics, 'Toby')
- [(3.3477895267131013, 'The Night Listener'), (2.8325499182641614, 'Lady in the Water'), (2.5309807037655645, 'Lust My Luck')]
- >>> recommendations.getRecommendations(recommendations.critics, 'Toby', sim_distance)
- [(3.457128694491423, 'The Night Listener'), (2.778584003814924, 'Lady in the Water'), (2.4224820423619167, 'Lust My Luck')]
- >>>
怎么样?庆祝一下吧,我们已经成了半个电影推荐专家了,现在我们可以建议Toby今晚去看'The Night Listener',当然,选择权还是在Toby自己。而且不知道你们注意到没有,尽管在前面讨论欧几里得算法有其缺陷,但是具体采取哪种方法对于最终的结果并没有太大影响。
现在,基于推荐者的推荐方法我们已经掌握的差不多了。其实没什么难的,我们做的全部工作不过是:1、建立一个涉及人员、物品和评价的字典 2、然后借此来为任何人提供建议。只要有数据,一切似乎都不是问题。
但是,富有经验的程序员马上会提出质疑,咱们这个程序效率太低下了!
这种方法对于一个小型的应用完全是没有问题的,但如果是类似于豆瓣淘宝这种动辄几千万用户的应用来说,将一个用户和其他所有用户进行比较,然后再对每位用户评分过的商品进行比较,你会发现我们的系统已经不给力了。
因此,我们不得不继续讨论另一种在大数据条件下表现更好的方法。
五、基于物品的推荐
现在我们已经十分熟悉如何为指定人员寻找品味相近者,以及如何向其推荐商品的方法。但是,假如我们想了解那些物品是相近的呢?也许我们在网上购物时就曾遇到过这个问题,特别是当购物网站刚起步,还没收集到的足够的用户数据的时候。事实上,像亚马逊一些购物网站也是可以自动为我们列出相近的物品的。那么,在这背后的又是哪些算法呢?
事实上,我们可以通过查看哪些人喜欢某一物品,以及这些人喜欢哪些其他物品来确定物品间的相似度。而如果你想得足够清楚的话,就会发现这与确定人之间的相似度其实是一样的——只需要把字典中的人与物品对调位置。即:
把 {'Lisa Rose':{'Lady in the Water':2.5, 'Snacks on a Plane': 3.5,
'Lust My Luck': 3.0, 'Superman Returns': 3.5……
换成{'Lady in the Water':{'Lisa Rose':2.5, 'Gene Seymour':3.0},
'Snake on a Plane':{'Lisa Rose':3.5, 'Gene Seymour':3.5……
我们就可以利用前面已经写好的方法对所有物品彼此间的相似度进行计算了。
继续将转换函数写入recommendations.py中:
- #将数据转换的方法
- def transprefs(prefs):
- result = {}
- for person in prefs:
- for moive in prefs[person]:
- #将电影和评论人对调
- result.setdefault(moive, {})
- #将物品与人对换
- result[moive][person] = prefs[person][moive]
- return result
试着调用topMatchs方法,我们可以与Superman Returns最相近的电影:
- >>> moives = recommendations.transprefs(recommendations.critics)
- >>> recommendations.topMatchs(moives, 'Superman Returns')
- [(0.6579516949597695, 'You, Me and Dupree'), (0.4879500364742689, 'Lady in the Water'),
- (0.11180339887498941, 'Snacks on a Plane'), (-0.1798471947990544, 'The Night Listener'),
- (-0.42289003161103106, 'Lust My Luck')]
在这里注意,由于皮尔逊相关度的特性,出现了一些相似度为负值的情况,这意味着喜欢'Superman Returns'的人存在着不喜欢'Lust My Luck'的倾向!(意外发现吧)
同样的,和为评价人推荐电影一样,我们可以为某部电影推荐评论人:
- recommendations.getRecommendations(moives,'Lust My Luck')
- [(4.0, 'Michael Phillips'), (3.0, 'Jack Matthews')]
或许有时候将物品与人对调并不会得出有意义的结论,但是大多情况下,还是有利于我们做出有意义的比较。为了向不同的个体推荐商品,在线零售商可能会收集用户购买记录,将商品与人对调——正如我们前面所做的——可以帮助零售商找到这些商品的潜在用户,这有助于在清仓处理某些商品时定向投放促销广告,规划广告投入。
六、那种方法更合适
既然基于用户的方法和基于物品的方法都能得出推荐结果,那么那种方法更好呢?两种方法表面看起来差不多,其实仔细分析的话,两种方法在实际操作中还是有差别的。
我们或许没有考虑到一点,那就是物品和用户的区别。一般来讲,对于一个购物网站:
1、商品数通常少于用户数
2、每种商品购买者的数量通常远大于每个购买者购买商品的数量
3、有的用户甚至不买或者只买几样商品,而一种商品通常有一定数量的人购买
4、商品数增长速度通常小于用户增长速度
所以,对于同样的相似度算法,一般会有:
1、商品间比较次数远小于用户间比较次数
2、商品可比较的空间维度更多,得到的相似度结果更可靠
3、基于商品的数据储存量小于基于用户的数据储存量(不买商品的用户远少于没被买过的商品)
4、基于商品的相似度列表不必及时更新,大处理量的运算任务可以转移到网站流量压力小时运行,
而对于基于用户的列表则需在新用户购买商品时的及时更新,而在计算中间又会有新用户加入
考虑到购物网站的情况,我们其实更倾向于选择基于商品的方法。
七、问题的拓展
以上的两种推荐方法,我们称为基于用户的物品的协同过滤和当然。
其实,两种方法各有利弊,如基于物品的方法也有存在冷启动(必须有物品的历史数据)等问题,具体的选取主要看情景:基于用户的协同过滤更适合亚马逊淘宝等购物网站,而基于物品的协同过滤更适合微博人人等社交网站——对于这些网站,物品比用户更多,更新也更频繁。
真正的实际操作中,任何单一的算法都不会得到最优的结果,常常需要多种方法的结合,我们称为组合推荐,而我们代码中的函数也远远不能满足大数据条件下的运算需求,为了降低运算复杂度,常常采用聚类算法将数据进行预处理,由于鄙人水平所限无法继续提供代码,后面补上。
最后给感兴趣的同志提供一些参考资料:
1、《集体智慧编程》第二章——提供推荐
http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/index.html
3、推荐系统的常用算法概述 :
http://www.cnblogs.com/luchen927/archive/2012/02/01/2325360.html
4、百度百科——推荐系统 :