提供推荐

根据群体偏好来为人们提供推荐。

收集偏好

嵌套的字典表示一个人的偏好。

相似度评估值

欧几里德距离

以经过人们一致评价德物品为坐标轴,然后将参与的人绘制在图上,考察他们之间的距离。

函数pow(n,2)对某数求平方,并使用sqrt函数求平方根。

1
2
3
sqrt(pow(4.5-4,2)+pow(1-2,2))

1.118033988749895

上述可计算距离值,偏好相似的人,距离越短。需要一个函数计算偏好相似的情况下给出越大的值,可以将函数值加1(避免被0整除),并取其倒数。

1
2
1/(1+sqrt(pow(4.5-4,2)+pow(1-2,2)))
0.4721359549995794

这个函数总是返回0到1之间的值,返回1则表示两人具有一样的偏好。

相似度函数

1
2
3
4
5
6
7
8
9
for item in critics['Lisa Rose']:
print item

Lady in the Water
Snakes on a Plane
Just My Luck
Superman Returns
The Night Listener
You, Me and Dupree

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# -*- coding:utf-8 -*-  

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}}

from math import sqrt

#返回一个有关person1和person2的基于距离的相识度评价
# 欧几里得算法
def sim_distance(prefs,person1,person2):
# prefs 字典
# 得到shared_items 的列表
si = {}
for item in prefs[person1]:
if item in prefs[person2]:
si[item] = 1

# 如果两者没有共同之处,则返回 0
if len(si) == 0:
return 0

# 计算所有差值的平方和
sum_of_squares = sum([pow(prefs[person1][item]-prefs[person2][item],2) for item in prefs[person1] if item in prefs[person2]])

return 1 / (1+sqrt(sum_of_squares))

计算两个人的相似度。

1
2
3
import recommendations
recommendations.sim_distance(recommendations.critics,'Lisa Rose','Gene Seymour')
0.29429805508554946

皮尔逊相关度评价

判断两组数据与某一条直线拟合程度的相似度。

如果两位评价者对所有的评分情况都相同,那么这条直线将成为对角线。

首先会找出两位评论者都曾评价过的物品,然后计算两者的评分总和与平方和,并求得评分的乘积和。

1
2
3
4
5
6
7
8
>>> sum(1,2,3,4)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>
TypeError: sum expected at most 2 arguments, got 4
>>> sum([1,2,3,4])
10

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#皮尔逊算法
#计算 p1和p2的相关系数
def sim_person(prefs,p1,p2):
# 得到双方都评价过的物品列表
si = {}
for item in prefs[p1]:
if item in prefs[p2]:
si[item] = 1

# 得到列表元素的个数
n = len(si)

#如果两者没有相同之处,则返回 1
if n == 0:
return 1

#对所有偏好求和
sum1 = sum([prefs[p1][it] for it in si])
sum2 = sum([prefs[p2][it] for it in si])

#求平方和
sum1Sq = sum([pow(prefs[p1][it],2) for it in si])
sum2Sq = sum([pow(prefs[p2][it],2) for it in si])

#求乘积和
pSum = sum(prefs[p1][it]*prefs[p2][it] for it in si)

#计算皮尔逊评价值
num = pSum - (sum1*sum2/n)
den = sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
if den == 0:
return 0

r = num/den
return r

计算:

1
2
3
>>> import recommendations
>>> recommendations.sim_person(recommendations.critics,'Lisa Rose','Gene Seymour')
0.39605901719066977

寻找与自己有相似品味的影评者。以下的函数中的一个参数为可选的相似性参数,该参数指向一个实际的算法函数。

Python 列表推导式

1
2
3
4
5
6
7
8
9
#从反映偏好的字典中返回最匹配者。
#返回结果中的个数和相似度函数均为可选参数
def topMatches(prefs,person,n=5,similarity=sim_person):
scores = [(similarity(prefs,person,other),other) for other in prefs if other!=person]

#对列表进行排序,评价值最高者排在前面
scores.sort()
scores.reverse()
return scores[0:n]

计算:

1
2
3
>>> import recommendations
>>> recommendations.topMatches(recommendations.critics,'Toby',n=3)
[(0.9912407071619299, 'Lisa Rose'), (0.9244734516419049, 'Mick LaSalle'), (0.8934051474415647, 'Claudia Puig')]

推荐物品

通过一个加权的评价值为影片打分,评论者的评分结果因此而形成了先后的排名。

首先取得所有其他评论者的评价结果,借此得到相似度后,在乘以他们为每部影片所给的评价值。

Sum(相似度*评价值)/sum(相似度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#推荐物品
#利用所有他人评价值的加权平均,为某人提供建议
def getRecommendations(prefs,person,similarity=sim_person):
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:
# 相似度 * 评价值
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim
#相似度之和
simSums.setdefault(item,0)
simSums[item]+=sim

#建立一个归一化的列表
rankings = [(total/simSums[item],item) for item,total in totals.items()]

#返回经过排序的列表
rankings.sort()
rankings.reverse()
return rankings

计算

1
2
3
>>> import recommendations
>>> recommendations.getRecommendations(recommendations.critics,'Toby')
[(3.3477895267131013, 'The Night Listener'), (2.8325499182641614, 'Lady in the Water'), (2.5309807037655645, 'Just My Luck')]

匹配商品

那些商品是彼此相近的。

可以通过哪些人喜欢某一特定物品,以及这些人喜欢哪些其他物品决定相似度。这和之前决定人和人之间相似度的方法是一样的—只需将人员和物品对换即可。

现在以电影作为键,评论者和评分为值。

1
2
3
4
5
6
7
8
9
10
#字典转换。键值转换
def transformPrefs(prefs):
result = {}
for person in prefs:
for item in prefs[person]:
result.setdefault(item,{})

#将物品和人员对调
result[item][person] = prefs[person][item]
return result

结果

1
2
3
4
5
6
>>> 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')]
>>> print movies
{'Lady in the Water': {'Lisa Rose': 2.5, 'Jack Matthews': 3.0, 'Michael Phillips': 2.5, 'Gene Seymour': 3.0, 'Mick LaSalle': 3.0}, 'Snakes on a Plane': {'Jack Matthews': 4.0, 'Mick LaSalle': 4.0, 'Claudia Puig': 3.5, 'Lisa Rose': 3.5, 'Toby': 4.5, 'Gene Seymour': 3.5, 'Michael Phillips': 3.0}, 'Just My Luck': {'Claudia Puig': 3.0, 'Lisa Rose': 3.0, 'Gene Seymour': 1.5, 'Mick LaSalle': 2.0}, 'Superman Returns': {'Jack Matthews': 5.0, 'Mick LaSalle': 3.0, 'Claudia Puig': 4.0, 'Lisa Rose': 3.5, 'Toby': 4.0, 'Gene Seymour': 5.0, 'Michael Phillips': 3.5}, 'The Night Listener': {'Jack Matthews': 3.0, 'Mick LaSalle': 3.0, 'Claudia Puig': 4.5, 'Lisa Rose': 3.0, 'Gene Seymour': 3.0, 'Michael Phillips': 4.0}, 'You, Me and Dupree': {'Jack Matthews': 3.5, 'Mick LaSalle': 2.0, 'Claudia Puig': 2.5, 'Lisa Rose': 2.5, 'Toby': 1.0, 'Gene Seymour': 3.5}}

基于物品的过滤

对于一个大型的网站,将一个用户和其他用户进行比较,然后对每位用户评过分的商品进行比较,其速度太慢。之前是采用基于用户的协作过滤,还有一种基于物品的协作过滤。在拥有大量数据集的情况下,基于物品的协作过滤能够得出更好的结论,且允许将大量计算任务预先执行,从而给需要推荐的用户更快得到想要的结果。

思路,为每件物品预先计算好最为相近的其他物品。为某位用户提供推荐时,查看他曾经评价过的物品,从中选出排位靠前者,再构造一个加权列表,其中包含了与这些选中物品最为相近的其他物品。物品的比较不会向用户间的比较那么频繁变化。

构建物品比较数据集

构建一个包含相近物品的完整数据集。构建完后,可以重复使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def calculateSimilarItems(prefs,n=10):
#建议字典,以给出与这些物品最为相近的其他物品
result = {}

#以物品为中心对偏好矩阵实施倒置处理
itemPrefs = transformPrefs(prefs)
c = 0
for item in itemPrefs:
#根据大数据集更新状态变量
c+=1
if c%100==0:
print "%d / %d" % (c,len(itemPrefs))

#寻找最为相近的物品
scores = topMatches(itemPrefs,item,n=n,similarity=sim_distance)
result[item]=scores
return result

该函数首先利用此前定义过的transformPrefs函数,对反映评价值的字典进行倒置处理,从而得到有关物品及其用户评价情况的列表。程序又循环遍历每项物品,并将转换的字典传入topMatches函数中,求得最为先进的物品及其相似度评价值。最后建立了一个包含物品及其最相近物品列表的字典。

1
2
3
4
5
6
7
>>> import recommendations
>>> itemsim = recommendations.calculateSimilarItems(recommendations.critics)
>>> itemsim
{'Lady in the Water': [(0.4494897427831781, 'You, Me and Dupree'), (0.38742588672279304, 'The Night Listener'), (0.3483314773547883, 'Snakes on a Plane'), (0.3483314773547883, 'Just My Luck'), (0.2402530733520421, 'Superman Returns')],
'Snakes on a Plane': [(0.3483314773547883, 'Lady in the Water'), (0.32037724101704074, 'The Night Listener'), (0.3090169943749474, 'Superman Returns'), (0.2553967929896867, 'Just My Luck'), (0.1886378647726465, 'You, Me and Dupree')],
'Just My Luck': [(0.3483314773547883, 'Lady in the Water'), (0.32037724101704074, 'You, Me and Dupree'), (0.2989350844248255, 'The Night Listener'), (0.2553967929896867, 'Snakes on a Plane'), (0.20799159651347807, 'Superman Returns')],
'Superman Returns': [(0.3090169943749474, 'Snakes on a Plane'), (0.252650308587072, 'The Night Listener'), (0.2402530733520421, 'Lady in the Water'), (0.20799159651347807, 'Just My Luck'), (0.1918253663634734, 'You, Me and Dupree')], 'You, Me and Dupree': [(0.4494897427831781, 'Lady in the Water'), (0.32037724101704074, 'Just My Luck'), (0.29429805508554946, 'The Night Listener'), (0.1918253663634734, 'Superman Returns'), (0.1886378647726465, 'Snakes on a Plane')], 'The Night Listener': [(0.38742588672279304, 'Lady in the Water'), (0.32037724101704074, 'Snakes on a Plane'), (0.2989350844248255, 'Just My Luck'), (0.29429805508554946, 'You, Me and Dupree'), (0.252650308587072, 'Superman Returns')]}

获得推荐

取得用户评价过的所有商品,找出其相近物品,并根据相似度来进行加权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def getRecommendedItems(prefs,itemMatch,user):
userRatings = prefs[user]
scores = {}
totalSim = {}


#循环遍历由当前用户评分的物品
for (item,rating) in userRatings.items():
#循环遍历与当前物品相近的物品
for(similarity,item2) in itemMatch[item]:
#如果该用户已经对当前物品做过评价,则将其忽略
if item2 in userRatings: continue

#评价值与相似度的加权之和
scores.setdefault(item2,0)
scores[item2]+= similarity*rating

#全部相似度之和
totalSim.setdefault(item2,0)
totalSim[item2]+= similarity

#将每个合计值除以加权和,求出平均值
rankings = [(score/totalSim[item],item) for item,score in scores.items()]

#按最高值到最低值的顺序,返回平均值
rankings.sort()
rankings.reverse()
return rankings