关于协同过滤的一个最经典的例子就是看电影,有时候不知道哪一部电影是我们喜欢的或者评分比较高的,那么通常的做法就是问问周围的朋友,看看最近有什么好的电影推荐。在问的时候,都习惯于问跟自己口味差不 多的朋友,这就是协同过滤的核心思想。
协同过滤是在海量数据中挖掘出小部分与你品味类似的用户,在协同过滤中,这些用户成为邻居,然后根据他们喜欢的东西组织成一个排序的目录推荐给你。所以就有如下两个核心问题
(1)如何确定一个用户是否与你有相似的品味?
(2)如何将邻居们的喜好组织成一个排序目录?
协同过滤算法的出现标志着推荐系统的产生,协同过滤算法包括基于用户和基于物品的协同过滤算法。
更详细的计算用户相似度的方法请参考这篇博客:
https://www.cnblogs.com/heaad/archive/2011/03/08/1977733.html
要实现协同过滤,需要进行如下几个步骤
1)收集用户偏好
2)找到相似的用户或者物品
3)计算并推荐
1:基于用户的协同过滤算法
基于用户的协同过滤算法是通过用户的历史行为数据发现用户对商品或内容的喜欢(如商品购买,收藏,内容评论或分享),并对这些喜好进行度量和打分。根据不同用户对相同商品或内容的态度和偏好程度计算用户之间的关系。在有相同喜好的用户间进行商品推荐。简单的说就是如果A,B两个用户都购买了x,y,z三本图书,并且给出了5星的好评。那么A和B就属于同一类用户。可以将A看过的图书w也推荐给用户B。
算法实现流程分析:
(1):计算用户的相似度
以余弦相似度为例
下面我拿这个图举例
计算用户的相似度,例如A,B为
同理
但是这样计算的效率是低的,因为我们需要计算每一对用户之间的相似度,事实上,很多用户相互之间并没有对同样的物品产生过行为,所以很多时候当分子为0的时候没有必要再去计算分母,所以这里可以优化:即首先计算出|N(u) 并 N(v)| != 0 的用户对(u,v),然后对这种情况计算分母以得到两个用户的相似度。
针对此优化,需要2步:
(1)建立物品到用户的倒查表T,表示该物品被哪些用户产生过行为;
(2)根据倒查表T,建立用户相似度矩阵W:在T中,对于每一个物品i,设其对应的用户为j,k,在W中,更新相应的元素值,w[j][k]=w[j][k]+1,w[k][j]=w[k][j]+1,以此类推,扫描完倒查表T中的所有物品后,就可以得到最终的用户相似度矩阵W,这里的W是余弦相似度中的分子部分,然后将W除以分母可以得到最终的用户兴趣相似度。
得到用户的相似度后,便可以进行下一步了
(2):给用户推荐兴趣最相近的k个用户所喜欢的物品
公式如下:
其中,p(u,i)表示用户u对物品i的感兴趣程度,S(u,k)表示和用户u兴趣最接近的K个用户,N(i)表示对物品i有过行为的用户集合,Wuv表示用户u和用户v的兴趣相似度,Rvi表示用户v对物品i的兴趣(这里简化,所有的Rvi都等于1)。
根据UserCF算法,可以算出,用户A对物品c、e的兴趣是
2:基于物品的协同过滤算法
基于物品的协同过滤算法与基于用户的协同过滤算法很像,将商品和用户互换。通过计算不同用户对不同物品的评分获得物品间的关系。基于物品间的关系对用户进行相似物品的推荐。这里的评分代表用户对商品的态度和偏好。简单来说就是如果用户A同时购买了商品1和商品2,那么说明商品1和商品2的相关度较高。当用户B也购买了商品1时,可以推断他也有购买商品2的需求。
算法流程分析:
基于物品的协同过滤算法主要分为两步:
1.计算物品之间的相似度;
2.根据物品的相似度和用户的历史行为给用户生成推荐列表
计算物品之间的相似度
物品i和j的相似度计算公式:
分母|N(i)|是喜欢物品i的用户数,而分子 |N(i)&N(j)| 是同时喜欢物品i和物品j的用户数。因此,上述公式可以理解为喜欢物品i的用户中有多少比例的用户也喜欢物品j。
改进1:上述公式虽然看起来很有道理,但是却存在一个问题。如果物品j很热门,很多人都喜欢,那么Wij就会很大,接近1。因此,该公式会造成任何物品都会和热门的物品有很大的相似度,这对于致力于挖掘长尾信息的推荐系统来说显然不是一个好的特性。为了避免推荐出热门的物品,可以用下面的公式:
改进2:需要惩罚用户的活跃度。若用户活跃度比较低,只买了有限的几本书,那么这几本书很有可能在一个或者两个兴趣范围内,对计算物品相似度比较有用,但是如果说一书店卖家趁着打折把亚马逊90%的书都买了然后赚差价,那么该用户的行为对计算物品相似度就没什么作用,因为90%的书肯定会覆盖很多范围,故应该惩罚用户的活跃度。
改进3:物品相似度的归一化。归一化不仅仅能提高推荐的准确度,还可以提高推荐的覆盖率和多样性。比如亚马逊上,用户的兴趣爱好肯定是分成几类的,很少说爱好集中在一类。假设有两类A和B,A类之间的相似度为0.5, B类之间的相似度为0.8,A和B之间的相似度为0.2, 当用户买了5本A类的书和5本B类的书后,我们要给用户来推荐书,如果按照之前的方法,最后按照相似度排序,那么推荐的应该都会是B类物品,就算B类中排名比较低,但照样比A类要高阿,所以应该根据类别进行相似度的归一化,这样一来A的相似度为1,B的相似度也为1,这样的话排序后的推荐A,B类商品都有,就大大提高了准确度,覆盖率和多样性。
和UserCF类似,我们可以建立一张用户-物品的倒排表(对每个用户建立一个包含他喜欢的物品的列表),这样每次去计算一个用户有过行为的那些物品间的相似度,能够保证计算的相似度都是有用的,而不用花大的计算量在那些0上面(肯定是个稀疏矩阵)
建立相似度矩阵:
C[i][j]记录了同时喜欢物品i和物品j的用户数,这样我们就可以得到物品之间的相似度矩阵W。
在得到物品之间的相似度后,进入第二步。
根据物品的相似度和用户的历史行为给用户生成推荐列表
ItemCF通过如下公式计算用户u对一个物品j的兴趣:
其中,Puj表示用户u对物品j的兴趣,N(u)表示用户喜欢的物品集合(i是该用户喜欢的某一个物品),S(j,k)表示和物品j最相似的K个物品集合(j是这个集合中的某一个物品),Wji表示物品j和物品i的相似度,rui表示用户u对物品i的兴趣(对于隐反馈数据集,如果用户u对物品i有过行为,rui都等于1)。
该公式的含义是:和用户历史上感兴趣的物品越相似的物品,越有可能在用户的推荐列表中获得比较高的排名。
from operator import *
import math
#例子中的数据相当于是一个用户字典{A:(a,b,d),B:(a,c),C:(b,e),D:(c,d,e)}
#我们这样存储原始输入数据
dic={'A':('a','b','d'),'B':('a','c'),'C':('b','e'),'D':('c','d','e')}#简单粗暴,记得加''
#计算用户兴趣相似度
def Usersim(dicc):
#把用户-商品字典转成商品-用户字典(如图中箭头指示那样)
item_user=dict()
for u,items in dicc.items():
for i in items:#文中的例子是不带评分的,所以用的是元组而不是嵌套字典。
if i not in item_user.keys():
item_user[i]=set()#i键所对应的值是一个集合(不重复)。
item_user[i].add(u)#向集合中添加用户。
C=dict()#感觉用数组更好一些,真实数据集是数字编号,但这里是字符,这边还用字典。
N=dict()
for item,users in item_user.items():
for u in users:
if u not in N.keys():
N[u]=0 #书中没有这一步,但是字典没有初始值不可以直接相加吧
N[u]+=1 #每个商品下用户出现一次就加一次,就是计算每个用户一共购买的商品个数。
#但是这个值也可以从最开始的用户表中获得。
#比如: for u in dic.keys():
# N[u]=len(dic[u])
for v in users:
if u==v:
continue
if (u,v) not in C.keys():#同上,没有初始值不能+=
C[u,v]=0
C[u,v]+=1 #这里我不清楚书中是不是用的嵌套字典,感觉有点迷糊。所以我这样用的字典。
#到这里倒排阵就建立好了,下面是计算相似度。
W=dict()
for co_user,cuv in C.items():
W[co_user]=cuv / math.sqrt(N[co_user[0]]*N[co_user[1]])
return W
def Recommend(user,dicc,W2,K):
rvi=1 #这里都是1,实际中可能每个用户就不一样了。就像每个人都喜欢beautiful girl,但有的喜欢可爱的多一些,有的喜欢御姐多一些。
rank=dict()
related_user=[]
interacted_items=dicc[user]
for co_user,item in W2.items():
if co_user[0]==user:
related_user.append((co_user[1],item))#先建立一个和待推荐用户兴趣相关的所有的用户列表。
for v,wuv in sorted(related_user,key=itemgetter(1),reverse=True)[0:K]:
#找到K个相关用户以及对应兴趣相似度,按兴趣相似度从大到小排列。itemgetter要导包。
for i in dicc[v]:
if i in interacted_items:
continue #书中少了continue这一步吧?
if i not in rank.keys():#如果不写要报错,是不是有更好的方法?
rank[i]=0
rank[i]+=wuv*rvi
return rank
if __name__=='__main__':
W3=Usersim(dic)
Last_Rank=Recommend('A',dic,W3,2)
print(Last_Rank)
from math import sqrt
import operator
import numpy as np
#1.构建用户-->物品倒排
def LoadData(basis_data):
data = {}
for line in basis_data:
user,score,item = line.split(",")
data.setdefault(user,{})
data[user][item] = score
print("物品倒排:\n",data)
return data
#2.构建物品与物品的共(同)现矩阵
def similarity(one_data):
#构造物品的共现矩阵
N = {} #喜欢物品i的总人数
C = {} #喜欢物品i也喜欢物品j的人数
for user,item in one_data.items():
for i,score in item.items():
N.setdefault(i,0)
N[i] += 1
C.setdefault(i,{})
for j,scores in item.items():
if j not in i:
C[i].setdefault(j,0)
C[i][j] += 1
print("构造的共现矩阵为:\n{}\n{}".format(N,C))
#计算物品与物品的相似矩阵
W = {}
for i,item in C.items():
W.setdefault(i,{})
for j,item2 in item.items():
W[i].setdefault(j,0)
W[i][j] = C[i][j]/sqrt(N[i]*N[j])
print("构造的相似矩阵为:\n",W)
return W
#3.根据用户的历史记录,给用户推荐物品
def recommandList(data,W,user,k,N):
rank={};
for i,score in data[user].items():#获得用户user历史记录,如A用户的历史记录为{'a': '1', 'b': '1', 'd': '1'}
for j,w in sorted(W[i].items(),key=operator.itemgetter(1),reverse=True)[0:k]:#获得与物品i相似的k个物品
if j not in data[user].keys():#该相似的物品不在用户user的记录里
rank.setdefault(j,0)
rank[j]+=float(score) * w
sort_data = sorted(rank.items(),key=operator.itemgetter(1),reverse=True)[0:N]
print("推荐为:\n",sort_data)
return sort_data
#主函数
if __name__=='__main__':
#A死侍 B钢铁侠 C美国队长 D黑豹 E蜘蛛侠
#1代表喜欢
user_item_data = ['A,1,a', 'A,1,b', 'A,1,d', 'B,1,b', 'B,1,c', 'B,1,e', 'C,1,c', 'C,1,d', 'D,1,b', 'D,1,c', 'D,1,d','E,1,a', 'E,1,d']
data = LoadData(user_item_data)
W = similarity(data)
#为用户A推荐2部电影
recommandList(data,W,"A",3,2)