本文为《推荐系统实践-项亮》中,关于2.4.1 基于用户的协同过滤算法的程序实现
- 在查询本文中,关于原书某页的算法实现时,可以直接Crtl+F,按格式"PXX"输入原书页码(例:P45)查询。未添加的程序实现会在之后陆续补上。
- 在概览程序思想时,可以将程序中所有的**print()**注释掉。在详细了解程序内容或练习的时候,建议将其中的解释性print()执行一遍 ,查看运行过程,通过中间过程,可以很好的帮助理解。
- 程序中“警”字的注释,表示此处与原书中不同,但是思想一致,只是在python语言的表述上不同。
源码下载:《推荐系统实践》 程序实现 —— 2.4.1 基于用户的协同过滤算法
1 基础算法
公式介绍:
书中选择了如下两种公式,每一种都可以用于计算两个用户的兴趣相似度。
P45Jaccard公式
用一个图片,介绍这个公式:
所以,O越大,两个人共同喜欢的物品越多,两个人的兴趣爱好越相似。
P45 余弦相似度公式
p45 实现余弦相似度,计算两两用户的相似度
import math
W = dict()
train = {'A':{'a','b','d'},'B':{'a','c'},'C':{'b','e'},'D':{'c','d','e'}} #{'a','b','c'}是一个集合
for u in train.keys():
for v in train.keys():
if u == v:
continue
W.update({u:{v:len(train[u]&train[v])}})#计算分子,'&'是 Python中集合的并运算
W[u][v]/=math.sqrt(len(train[u])*len(train[v])*1.0)#分子/分母
for u,v_similarity in W.items():
print(u,'corresponds to',v_similarity)
执行结果
('A', 'corresponds to', {'D': 0.3333333333333333})
('C', 'corresponds to', {'D': 0.4082482904638631})
('B', 'corresponds to', {'D': 0.4082482904638631})
('D', 'corresponds to', {'B': 0.4082482904638631})
p46 建立物品-用户的倒排表(build inverse table for item_users)
train = dict();
train = {'A':{'a':1,'b':1,'d':1},
'B':{'a':1,'c':1},
'C':{'b':1,'e':1},
'D':{'c':1,'d':1,'e':1}
}#此处物品a、b、c、d、e后面对应的值1,是用户对物品感兴趣的程度。比如'A':{'a':1},是用户A对物品a感兴趣的程度。
#按理说感兴趣的程度应该是不同的数值,但此处,因为使用的是单一行为的隐反馈数据,所以所有的都定为1了。这里的感兴趣程度即书中p47页公式(用于计算用户u对物品i的感兴趣程度)中的rvi,可参考其介绍。
#1、build inverse table for item_users:通过用户-物品列表,建立物品-用户倒排表
item_users = dict()#存储物品-用户倒排表
for u,items in train.items():
#print(u,'corresponds to',items)
for i in items.keys():#遍历每一个用户的物品列表。
#print(i)
if i not in item_users:
item_users[i] = set()
item_users[i].add(u)#物品列表中的物品i,有用户u访问过
print('输出item_users[]')
for u,items in item_users.items():
print(u,'corresponds to',items)
print('')
执行结果
A corresponds to {'a': 1, 'b': 1, 'd': 1}
a
b
d
B corresponds to {'a': 1, 'c': 1}
a
c
C corresponds to {'b': 1, 'e': 1}
b
e
D corresponds to {'c': 1, 'd': 1, 'e': 1}
c
d
e
输出item_users[]
a corresponds to {'A', 'B'}
b corresponds to {'A', 'C'}
d corresponds to {'A', 'D'}
c corresponds to {'B', 'D'}
e corresponds to {'D', 'C'}
p46 建立用户相似度矩阵W(calculate co-rated items between users)
#2、calculate co-rated items between users :计算两个用户共同访问过的物品数,建立用户相似度矩阵C,C[u][v]=x,即表示u,v共同访问过的物品有x个。
C = dict()#C[][]是一个嵌套的二维字典,eg:C = {'A':{'C':1,'B':1,'D':1}}
N = dict()#N[]统计用户有过行为的物品数,最终N[A]=3,N[B]=2,N[C]=2,N[D]=3
for i , users in item_users.items():
print(i, 'corresponds to ', users) #示例:('a', 'corresponds to ', set(['A', 'B']))
for u in users:#u遍历一遍物品i的users列表
print(u ,'u in users')
if u not in N.keys():
N[u] = 0
N[u] += 1 #统计用户u有过行为的物品数
for v in users:#v遍历一遍物品i的users列表
print(v ,'v in uers')
if u == v:
continue
#注意:二维“字典”新添一个key-value对时,需要判断key是否已经存在了
if u not in C.keys():
C.update({u:{v:0}})
if v not in C[u].keys():
C[u].update({v:0})
C[u][v] += 1 #用户u、v对同一个物品有过行为。经过u,v两层遍历,C[][]会成为一个对称矩阵。
print(C)
print('\n输出N[]:')
for user,value in N.items():
print(user,'corresponds to',value)
print('\n输出C[][]:')
for u,related_users in C.items():
print(u,'corresponds to',related_users)
执行结果
a corresponds to {'B', 'A'}
B u in users
B v in uers
A v in uers
{'B': {'A': 1}}
A u in users
B v in uers
{'B': {'A': 1}, 'A': {'B': 1}}
A v in uers
b corresponds to {'C', 'A'}
C u in users
C v in uers
A v in uers
{'B': {'A': 1}, 'A': {'B': 1}, 'C': {'A': 1}}
A u in users
C v in uers
{'B': {'A': 1}, 'A': {'B': 1, 'C': 1}, 'C': {'A': 1}}
A v in uers
d corresponds to {'D', 'A'}
D u in users
D v in uers
A v in uers
{'B': {'A': 1}, 'A': {'B': 1, 'C': 1}, 'C': {'A': 1}, 'D': {'A': 1}}
A u in users
D v in uers
{'B': {'A': 1}, 'A': {'B': 1, 'C': 1, 'D': 1}, 'C': {'A': 1}, 'D': {'A': 1}}
A v in uers
c corresponds to {'B', 'D'}
B u in users
B v in uers
D v in uers
{'B': {'A': 1, 'D': 1}, 'A': {'B': 1, 'C': 1, 'D': 1}, 'C': {'A': 1}, 'D': {'A': 1}}
D u in users
B v in uers
{'B': {'A': 1, 'D': 1}, 'A': {'B': 1, 'C': 1, 'D': 1}, 'C': {'A': 1}, 'D': {'A': 1, 'B': 1}}
D v in uers
e corresponds to {'C', 'D'}
C u in users
C v in uers
D v in uers
{'B': {'A': 1, 'D': 1}, 'A': {'B': 1, 'C': 1, 'D': 1}, 'C': {'A': 1, 'D': 1}, 'D': {'A': 1, 'B': 1}}
D u in users
C v in uers
{'B': {'A': 1, 'D': 1}, 'A': {'B': 1, 'C': 1, 'D': 1}, 'C': {'A': 1, 'D': 1}, 'D': {'A': 1, 'B': 1, 'C': 1}}
D v in uers
输出N[]:
B corresponds to 2
A corresponds to 3
C corresponds to 2
D corresponds to 3
输出C[][]:
B corresponds to {'A': 1, 'D': 1}
A corresponds to {'B': 1, 'C': 1, 'D': 1}
C corresponds to {'A': 1, 'D': 1}
D corresponds to {'A': 1, 'B': 1, 'C': 1}
p46 计算最终相似度矩阵W(cal)
#3、calculate final similarity matrix W :计算最终相似度矩阵W
W = dict()
#下面的两层迭代其实即C[u][v]=cuv。
for u,related_users in C.items():#u为一级坐标
for v,cuv in related_users.items():#v为二级坐标,cuv即C[u][v]的值,即|N(u)∩N(v)|
if u not in W.keys():
W.update({u:{v:cuv/math.sqrt(N[u]*N[v])}})
else:
W[u].update({v:cuv/math.sqrt(N[u]*N[v])})
print(W)
for u,related_users in W.items():
print(u,'corresponds to ',related_users)
执行结果
{'B': {'A': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}, 'C': {'A': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}, 'C': {'A': 0.4082482904638631, 'D': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}, 'C': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'D': {'A': 0.3333333333333333}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}, 'C': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'D': {'A': 0.3333333333333333, 'B': 0.4082482904638631}}
{'B': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'A': {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}, 'C': {'A': 0.4082482904638631, 'D': 0.4082482904638631}, 'D': {'A': 0.3333333333333333, 'B': 0.4082482904638631, 'C': 0.4082482904638631}}
输出W[][]:
B corresponds to {'A': 0.4082482904638631, 'D': 0.4082482904638631}
A corresponds to {'B': 0.4082482904638631, 'C': 0.4082482904638631, 'D': 0.3333333333333333}
C corresponds to {'A': 0.4082482904638631, 'D': 0.4082482904638631}
D corresponds to {'A': 0.3333333333333333, 'B': 0.4082482904638631, 'C': 0.4082482904638631}
P47 计算用户user,对,自己没有过行为的物品,的感兴趣的程度
#4 计算用户A对,自己没有过行为的物品,的感兴趣程度
def itemgetter(elem):
return elem[1]
# 给用户A进行推荐,user = 'A'
user = 'A'
K = 3
rank = dict()
interacted_items = train[user]
for v, wuv in sorted(W[user].items(), key=itemgetter, reverse=True)[0:K]:
for i, rvi in train[v].items():
if i in interacted_items:
#即如果物品i,在用户user(此处为A)的,有过行为的列表中,(换句话说,用户user对物品i有过行为),将物品i过滤掉,不计算感兴趣程度。
continue;
else:
if i not in rank.keys():
rank.update({i:0})
rank[i] += wuv*rvi
# 得到结果rank
print('输出rank[]:')
print(rank)
执行结果:
输出rank[]:
{'c': 0.7415816237971964, 'e': 0.7415816237971964}
2 用户相似度计算的改进
Python中的math.log()函数默认以math.e为底数。[5]
经测试
>>> import math
>>> math.log(math.e)
1.0
参考文献
[1] Python的二维字典(two-dimension dictionary)定义与实现方法
[2]《推荐系统实践》项亮
[3]《Python基础教程 (第三版)》
[4]《流畅的Python》Luciano Ramalho
[5] math不写底数时,底数到底是多少?-知乎