Programming Collective Intelligence 推荐系统 读书笔记二

  这章主要讲了如何作推荐,如今推荐最经常使用的几种算法:Collaborative Filtering、Cluster Models、Search-Based Methods、Item-to-Item Collaborative Filtering.前两种是经过找类似的Customer,后两种经过找类似的Item.论文Amazon.com Recommendations Item-to-Item Collaborative Filtering 对这几种算法都有介绍。这章主要提了Collaborative Filtering和tem-to-Item Collaborative Filtering。 Collaborative Filtering:经过搜索大量的Customer数据集来找到那一小撮和你口味类似的。书中举了一个电影评论的例子,每一个人都对一些电影进行评等级,经过这些数据来找到和你口味类似的人,以及对你没有看过的电影作推荐,并以这个例子演示了如何作推荐。

准备数据:(本笔记的代码使用ruby实现,python代码的实现见原书)java

Ruby代码  复制代码
  1. critics={  
  2.     'Lisa Rose' => {'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.5,  
  3.     'Just My Luck' => 3.0, 'Superman Returns' => 3.5, 'You, Me and Dupree' => 2.5,  
  4.     'The Night Listener' => 3.0},  
  5.   
  6.     'Gene Seymour' => {'Lady in the Water' => 3.0, 'Snakes on a Plane' => 3.5,  
  7.     'Just My Luck' => 1.5, 'Superman Returns' => 5.0, 'The Night Listener'=> 3.0,  
  8.     'You, Me and Dupree' => 3.5},  
  9.   
  10.     'Michael Phillips' => {'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.0,  
  11.     'Superman Returns' => 3.5, 'The Night Listener' => 4.0},  
  12.   
  13.     'Claudia Puig' => {'Snakes on a Plane' => 3.5, 'Just My Luck' => 3.0,  
  14.     'The Night Listener' => 4.5, 'Superman Returns' => 4.0,  
  15.     'You, Me and Dupree' => 2.5},  
  16.   
  17.     'Mick LaSalle'=> {'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0,  
  18.     'Just My Luck' => 2.0, 'Superman Returns' => 3.0, 'The Night Listener' => 3.0,  
  19.     'You, Me and Dupree' => 2.0},  
  20.   
  21.     'Jack Matthews'=> {'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0,  
  22.     'The Night Listener'=> 3.0, 'Superman Returns'=> 5.0, 'You, Me and Dupree' => 3.5},  
  23.   
  24.     'Toby' => {'Snakes on a Plane' =>4.5,'You, Me and Dupree' =>1.0,'Superman Returns' => 4.0}  
  25. }  

 

定义类似度:python

欧拉距离:web

 

 

 

代码实现:算法

 

Ruby代码  复制代码
  1. def sim_distance(prefs,person1,person2)  
  2.     si = {}  
  3.     prefs[person1].each_key do |item|  
  4.         si[item] = 1 if prefs[person2][item]  
  5.     end  
  6.       
  7.     return 0 if si.empty?  
  8.       
  9.     sum_of_squares = si.keys.inject(0) do |sum,item|  
  10.         sum + (prefs[person1][item] - prefs[person2][item]) ** 2  
  11.     end  
  12.     
  13.     return 1 / (1 + sum_of_squares)  
  14. end  

 

 

 Pearson Correlation Score:ruby

 

代码实现:函数

 

Ruby代码  复制代码
  1. def sim_pearson(prefs,person1,person2)  
  2.     si = {}  
  3.     prefs[person1].each_key do |item|  
  4.         si[item] = 1 if prefs[person2][item]  
  5.     end  
  6.     
  7.     return 0 if si.empty?  
  8.     
  9.     sum1 = si.keys.inject(0){|sum,item| sum + prefs[person1][item]}  
  10.     sum2 = si.keys.inject(0){|sum,item| sum + prefs[person2][item]}  
  11.       
  12.     sum1Sq = si.keys.inject(0){|sum,item| sum + prefs[person1][item] ** 2}  
  13.     sum2Sq = si.keys.inject(0){|sum,item| sum + prefs[person2][item] ** 2}  
  14.       
  15.     pSum = si.keys.inject(0){|sum,item| sum + prefs[person1][item] * prefs[person2][item]}  
  16.     num = pSum - (sum1 * sum2 / si.size)  
  17.     den = Math.sqrt((sum1Sq - sum1 ** 2 / si.size) * (sum2Sq - sum2 ** 2 / si.size))  
  18.     return (if den == 0 then 0 else num/den end)  
  19. end  

 根据前面的两个类似度的函数,咱们能够计算和你相同电影的口味的top N了: ui

Ruby代码  复制代码
  1. def top_matches(prefs,person,n=5,similarity="sim_pearson")  
  2.     scores = []  
  3.     #计算类似度  
  4.     prefs.each_key{|other|  scores << eval("[#{similarity}(prefs,person,other),other]")  if other != person}  
  5.     #返回类似读最高的人  
  6.     return scores.sort.reverse[0...n]  
  7. end  

下面咱们看看如何推荐你没有看过的电影,咱们平时的想法是,若是这部电影 
你们评论很好,咱们就认为值得咱们看,可是你的口味可能和这些评论很高的 
的人不一样,因此和你口味类似的人评论很高的电影,推荐给你效果会很好。 
咱们这样虽然一我的对一部电影的评价很高,可是因为他和你的口味不一样,那么 
这个评价对于你的贡献也不会太多。结合类似度和评价的一种方法是: 
类似度与评价的成绩做为这个电影评论的一个贡献,同时为了不评论的人越多 
最终的总分越高,能够用这个公式: 
全部人(类似度与评论分的成绩) 之和 / 类似度之和,因而咱们能够获得以下 
代码: 
Ruby代码  复制代码
  1. def get_recommendations(prefs,person,similarity='sim_pearson')  
  2.     totals = {}  
  3.     simSums = {}  
  4.     prefs.each_key do |other|  
  5.         #跳过本身  
  6.         next if person == other  
  7.         sim = eval("#{similarity}(prefs,person,other)")  
  8.         #去掉similarity为0的人  
  9.         next if sim <= 0  
  10.           
  11.         prefs[other].each_key do |item|  
  12.             if (not prefs[person][item]) or (prefs[person][item] == 0) then  
  13.                 #计算类似度和评论的成绩之和  
  14.                 totals[item] = if totals[item] then   
  15.                                   totals[item] + prefs[other][item] * sim   
  16.                                 else   
  17.                                   prefs[other][item] * sim  
  18.                                 end  
  19.                 #类似度之和  
  20.                 simSums[item] = if simSums[item] then  
  21.                                     simSums[item] + sim  
  22.                                 else  
  23.                                     sim  
  24.                                 end  
  25.             end  
  26.         end  
  27.     end  
  28.     #计算每一个电影的符合你口味的程度  
  29.     rankings = totals.map{|item,total| [total/simSums[item],item]}  
  30.     return rankings.sort.reverse  
  31. end  

如何根据用户的评论来看产品的类似度呢?一种方法是经过看一我的喜欢某个产品,再看看他喜欢 
的其余产品,这其实和前面的方法同样,你只须要把people和items交换一下位置。这样咱们只须要 
对前面的字典作一下转置操做便可: 
Ruby代码  复制代码
  1. def transform_prefs(prefs)  
  2.     result = {}  
  3.     prefs.each_key do |person|  
  4.         prefs[person].each_key do |item|  
  5.             result[item] = {} if not result[item]  
  6.             result[item][person] = prefs[person][item]  
  7.         end  
  8.     end  
  9.     result  
  10. end  

而后咱们就能够向前面的代码同样作top match和recommendation了: 
Ruby代码  复制代码
  1. movies = transform_prefs(critics)  
  2. p top_matches(movies,'Superman Returns')  
  3. p get_recommendations(movies,'Just My Luck')  

2、Item-Based Filtering: 
前面介绍的算法被称为user-based collaborative filtering,每次都要计算一下 
Customer之间的类似度,伸缩性不够好,一种更好的方法是事先把Item之间类似度 
计算出来,而后排序好,保存下来,用户每次请求的时候只须要直接把top N返回给 
用户就能够了,这个算法是基于 Items之间的类似性比较与Users之间的比较变化要 
少这个事实的。 
根据这个思想,咱们能够事先把Items之间的类似性计算出来并保存下来,下面就是 
算法的实现: 
Ruby代码  复制代码
  1. def calculate_similar_items(prefs,n=10)  
  2.     result = {}  
  3.     #把矩阵转置成item-centric的  
  4.     item_prefs = transform_prefs(prefs)  
  5.     c = 0  
  6.     item_prefs.each_key do |item|  
  7.         c += 1  
  8.         printf("[%d / %d]",c,item_prefs.size) if c % 100 == 0  
  9.         #把与item top n类似的记录下来,并保存在result map中  
  10.         scores = top_matches(item_prefs,item,n,'sim_distance')  
  11.         result[item] = scores  
  12.     end  
  13.     return result  
  14. end  

如今你能够直接使用咱们前面已经保存下来的Item之间的类似度来作推荐了: 
Ruby代码  复制代码
  1. def get_recommended_items(prefs,item_match,user)  
  2.     user_ratings = prefs[user]  
  3.     scores = {}  
  4.     total_sim = {}  
  5.       
  6.     user_ratings.each do |item,rating|  
  7.         #已经计算出来的top n item  
  8.         item_match[item].each do |similarity,item2|  
  9.             #跳过已经rated的item  
  10.             next if user_ratings[item2]  
  11.             #全部rating的 类似度*rating 之和  
  12.             scores[item2] = if scores[item2] then  
  13.                                 scores[item2] + similarity * rating  
  14.                             else  
  15.                                 similarity * rating  
  16.                             end  
  17.             #全部的类似度之和  
  18.             total_sim[item2] = if total_sim[item2] then  
  19.                                 total_sim[item2] + similarity  
  20.                               else  
  21.                                 similarity  
  22.                               end  
  23.         end  
  24.         rankings = scores.map{|item,score| [score/total_sim[item],item]}  
  25.         return rankings.sort.reverse  
  26.     end  
  27. end  
  28. item_sim = calculate_similar_items(critics)  
  29. p get_recommended_items(critics,item_sim,'Toby')