def top_matches(prefs,person,n=5,similarity="sim_pearson") scores = [] #计算相似度 prefs.each_key{|other| scores << eval("[#{similarity}(prefs,person,other),other]") if other != person} #返回相似读最高的top n return scores.sort.reverse[0...n] end
下面我们看看如何推荐你没有看过的电影,我们平时的想法是,如果这部电影
大家评论很好,我们就认为值得我们看,但是你的口味可能和这些评论很高的
的人不同,所以和你口味相似的人评论很高的电影,推荐给你效果会很好。
我们这样虽然一个人对一部电影的评价很高,但是由于他和你的口味不同,那么
这个评价对于你的贡献也不会太多。结合相似度和评价的一种方法是:
相似度与评价的成绩作为这个电影评论的一个贡献,同时为了避免评论的人越多
最终的总分越高,可以用这个公式:
所有人(相似度与评论分的成绩) 之和 / 相似度之和,于是我们可以得到如下
代码:
def get_recommendations(prefs,person,similarity='sim_pearson') totals = {} simSums = {} prefs.each_key do |other| #跳过自己 next if person == other sim = eval("#{similarity}(prefs,person,other)") #去掉similarity为0的人 next if sim <= 0 prefs[other].each_key do |item| if (not prefs[person][item]) or (prefs[person][item] == 0) then #计算相似度和评论的成绩之和 totals[item] = if totals[item] then totals[item] + prefs[other][item] * sim else prefs[other][item] * sim end #相似度之和 simSums[item] = if simSums[item] then simSums[item] + sim else sim end end end end #计算每个电影的符合你口味的程度 rankings = totals.map{|item,total| [total/simSums[item],item]} return rankings.sort.reverse end
如何根据用户的评论来看产品的相似度呢?一种方法是通过看一个人喜欢某个产品,再看看他喜欢
的其他产品,这其实和前面的方法一样,你只需要把people和items交换一下位置。这样我们只需要
对前面的字典做一下转置操作即可:
def transform_prefs(prefs) result = {} prefs.each_key do |person| prefs[person].each_key do |item| result[item] ||= {} result[item][person] = prefs[person][item] end end result end
然后我们就可以向前面的代码一样做top match和recommendation了:
movies = transform_prefs(critics) p top_matches(movies,'Superman Returns') p get_recommendations(movies,'Just My Luck')
二、Item-Based Filtering:
前面介绍的算法被称为user-based collaborative filtering,每次都要计算一下
Customer之间的相似度,伸缩性不够好,一种更好的方法是事先把Item之间相似度
计算出来,然后排序好,保存下来,用户每次请求的时候只需要直接把top N返回给
用户就可以了,这个算法是基于 Items之间的相似性比较与Users之间的比较变化要
少这个事实的。
根据这个思想,我们可以事先把Items之间的相似性计算出来并保存下来,下面就是
算法的实现:
def calculate_similar_items(prefs,n=10) result = {} #把矩阵转置成item-centric的 item_prefs = transform_prefs(prefs) c = 0 item_prefs.each_key do |item| c += 1 printf("[%d / %d]",c,item_prefs.size) if c % 100 == 0 #把与item top n相似的记录下来,并保存在result map中 scores = top_matches(item_prefs,item,n,'sim_distance') result[item] = scores end return result end
现在你可以直接使用我们前面已经保存下来的Item之间的相似度来做推荐了:
def get_recommended_items(prefs,item_match,user) user_ratings = prefs[user] scores = {} total_sim = {} user_ratings.each do |item,rating| #已经计算出来的top n item item_match[item].each do |similarity,item2| #跳过已经rated的item next if user_ratings[item2] #所有rating的 相似度*rating 之和 scores[item2] = if scores[item2] then scores[item2] + similarity * rating else similarity * rating end #所有的相似度之和 total_sim[item2] = if total_sim[item2] then total_sim[item2] + similarity else similarity end end rankings = scores.map{|item,score| [score/total_sim[item],item]} return rankings.sort.reverse end end item_sim = calculate_similar_items(critics) p get_recommended_items(critics,item_sim,'Toby')