`

Programming Collective Intelligence读书笔记三 推荐系统(续)

阅读更多
根据前面的两个相似度的函数,我们可以计算和你相同电影的口味的top N了:
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')

分享到:
评论
1 楼 fuliang 2008-12-28  
ruby1.8对函数作为参数的支持还是不够好,不像python可以很直观的像传递函数名字一样传递函数对象:def top_matches(prefs,person,n=5,similarity=sim_pearson) 
然后调用的时候和普通的调用完全一样
我们这里使用了字符串,然后在eval一下,看起来比较丑陋了。这里当然可以使用Poc对象和lambda表达式,但我们有希望sim_pearson能够像普通的函数一样调用而不是使用call。一种更直接的方法是给Symbol添加to_proc方法:
def to_proc
   proc { |obj, *args| obj.send(self, *args) }
end
然后就可以像python同样的用法了:
def top_matches(prefs,person,n=5,similarity= :sim_pearson)
事实上最近发布的ruby1.9内置了这个方法,这使得我们更加简洁:
result = names.map {|name| name.upcase}
现在可以这么做了:
result = names.map(&:upcase)

相关推荐

Global site tag (gtag.js) - Google Analytics