本文为《推荐系统实践-项亮》中,关于2.4.1 基于用户的协同过滤算法的程序实现python
源码下载:《推荐系统实践》 程序实现 —— 2.4.1 基于用户的协同过滤算法web
公式介绍:
书中选择了以下两种公式,每一种均可以用于计算两个用户的兴趣类似度。算法
用一个图片,介绍这个公式:
因此,O越大,两我的共同喜欢的物品越多,两我的的兴趣爱好越类似。svg
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})
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,可参考其介绍。 #一、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'}
#二、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)
执行结果ui
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}
#三、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)
执行结果.net
{'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}
#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)
执行结果:code
输出rank[]: {'c': 0.7415816237971964, 'e': 0.7415816237971964}
Python中的math.log()函数默认以math.e为底数。[5]
经测试xml
>>> import math >>> math.log(math.e) 1.0
[1] Python的二维字典(two-dimension dictionary)定义与实现方法
[2]《推荐系统实践》项亮
[3]《Python基础教程 (第三版)》
[4]《流畅的Python》Luciano Ramalho
[5] math不写底数时,底数究竟是多少?-知乎