首先给出一组数据:
critics={'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5, 'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5, 'The Night Listener': 3.0}, 'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5, 'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0, 'You, Me and Dupree': 3.5}, 'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0, 'Superman Returns': 3.5, 'The Night Listener': 4.0}, 'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0, 'The Night Listener': 4.5, 'Superman Returns': 4.0, 'You, Me and Dupree': 2.5}, 'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0, 'You, Me and Dupree': 2.0}, 'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5}, 'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}} 怎样寻找相近用户呢,也就是将每个人与其他人进行对比,通过相似度评价值来进行衡量。
介绍两种相似度评价体系有:欧几里得距离和皮尔逊相关度
欧几里得距离也就是我们平时学习的距离。
比如我们对于Toby有'Toby': 'Snakes on a Plane':4.5,'You, Me and Dupree':1.0
对于LaSalle有:Mick LaSalle': 'Snakes on a Plane': 4.0, 'You, Me and Dupree': 2.0
这样我们就可以大概计算在两个属性情况下的距离。
>>> from math import sqrt >>> sqrt(pow(4.5-4,2)+pow(1-2,2)) 1.118033988749895
这样就可以计算出距离,偏好越相似的人,计算出来的距离就越小。另外我们也可以给出倒数,这样偏好越相似的人值就越大,不过考虑到倒数之后可能分母为0,这样在分母上加1,于是有如下:
>>> 1/(1+sqrt(pow(4.5-4,2)+pow(1-2,2)))0.4721359549995794
这样我们给出一个完整的计算相似度的函数:
# Returns a distance-based similarity score for person1 and person2def sim_distance(prefs,person1,person2):
si={}for item in prefs[person1]:if item in prefs[person2]:
si[item]=1if len(si)==0: return 0sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2)for item in prefs[person1] if item in prefs[person2]])
return 1/(1+sum_of_squares)
运行结果:
>>> import recommendations
>>> recommendations.sim_distance(recommendations.critics,'Lisa Rose','Gene Seymour')0.14814814814814814除了用欧几里得距离之外,我们可以用一个更复杂一点的方法来判断人们兴趣的相似度,用皮尔逊相关系数
用皮尔逊方法,可以解决一个叫“夸大分值”的情况,就是当一个人的评分总是比较高的时候,可能跟另外一个评分给都的比较低的人有同样的喜好。
皮尔斯相关系数给出的计算比较特别,我们从下面的过程中看出:
def sim_pearson(prefs,p1,p2):
si={}for item in prefs[p1]:if item in prefs[p2]: si[item]=1
n=len(si)if n==0: return 0sum1=sum([prefs[p1][it] for it in si])sum2=sum([prefs[p2][it] for it in si])# Sum up the squaressum1Sq=sum([pow(prefs[p1][it],2) for it in si])sum2Sq=sum([pow(prefs[p2][it],2) for it in si])# Sum up the productspSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])# Calculate Pearson scorenum=pSum-(sum1*sum2/n)den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))if den==0: return 0r=num/denreturn r
运行结果:
>>> import recommendations
>>> print recommendations.sim_pearson(recommendations.critics,'Lisa Rose','Gene Seymour')
0.396059017191
上面介绍了两种度量方法,但实际上还有很多其他的方法,比如Jaccard系数或曼哈顿距离。
现在我们可以对两个人进行比较,那下面我们就可以编写函数根据某个人对其他人进行计算,找到最接近的匹配结果了。
def topMatches(prefs,person,n=5,similarity=sim_pearson):
scores=[(similarity(prefs,person,other),other)for other in prefs if other!=person]
scores.sort()scores.reverse()return scores[0:n]
n是返回个数,运行
>>> import recommendations
>>> recommendations.topMatches(recommendations.critics,'Toby',n=3)[(0.9912407071619299, 'Lisa Rose'), (0.9244734516419049, 'Mick LaSalle'), (0.8934051474415647, 'Claudia Puig')]这样对于Toby可以参考Lisa Rise所撰写的评论。
不过我们的目的是为了推荐物品,目前可以找到相近的评论者,不过这个会出现一定的问题,比如对于一些影片还没有做评论,或者有些人对影片随意评价。
为了解决上述问题,我们对评价值进行加权。
加权就通过前面算法的相关系数,这样相关性越大,给定的权重也就越大,这样对于一个影片的推荐,我们通过计算所有人的评价值乘以相关系数的和 除以 相关系数的和,如上图total表示所有评价值乘以相关系数的和 sim.sum表示相关系数的和,如果没有评价则不加入。
上述过程代码如下:
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 item in prefs[other]:if item not in prefs[person] or prefs[person][item]==0:
# Similarity * Scoretotals.setdefault(item,0)totals[item]+=prefs[other][item]*sim# Sum of similaritiessimSums.setdefault(item,0)simSums[item]+=simrankings=[(total/simSums[item],item) for item,total in totals.items()]rankings.sort()rankings.reverse()return rankings
运行结果:
>>> import recommendations
>>> recommendations.getRecommendations(recommendations.critics,'Toby')[(3.3477895267131013, 'The Night Listener'), (2.8325499182641614, 'Lady in the Water'), (2.5309807037655645, 'Just My Luck')]
现在我们知道了如何根据一个人寻找相近品味的人,以及如何推荐商品。
现在有这么一个问题,我们想了解一个商品跟哪些商品相近。
对于这个问题,我们可以通过查看哪些人喜欢某一个物品,以及这些人喜欢的其他物品,这样我们将数据反一下就可以用之前的方法了。
对调方法:
def transformPrefs(prefs):
result={}for person in prefs:for item in prefs[person]:result.setdefault(item,{})result[item][person]=prefs[person][item]return result
这样我们看了一下匹配结果:
>>> import recommendations
>>> movies=recommendations.transformPrefs(recommendations.critics)>>> recommendations.topMatches(movies,'Superman Returns')[(0.6579516949597695, 'You, Me and Dupree'), (0.4879500364742689, 'Lady in the Water'), (0.11180339887498941, 'Snakes on a Plane'), (-0.1798471947990544, 'The Night Listener'), (-0.42289003161103106, 'Just My Luck')]
而且根据匹配结果也可以看到相关评论人。
>>> recommendations.getRecommendations(movies,'Just My Luck') [(4.0, 'Michael Phillips'), (3.0, 'Jack Matthews')]
下面就给出了一个真实的案例 构建一个基于del.icio.us的链接推荐系统