文章目录
1、协同过滤算法简介
协同过滤就是指利用兴趣相投、拥有共同经验群体的喜好来推荐用户感兴趣的信息。一般分为基于用户的协同过滤和基于物品的协同过滤。
2、协同过滤算法核心
(1)收集用户评分、物品信息集
(2)找到相似的用户或物品
(3)计算并进行推荐
3、基于用户的协同过滤
在一个个性化推荐系统中,当用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,把那些用户喜欢的、而用户A没听说过的物品推荐给A。
3.1步骤:
(1)计算两个用户的兴趣相似度
给定用户u和用户v,令N(v)表示用户v曾经评价过的图书集合,令N(u)表示用户u曾经评价过的图书集合,通过相似度计算用户u和v的兴趣相似度。相似度可以是余弦相似度、皮尔逊相似系数等,本文采用的是余弦相似度,公式入下:
例子:
假设用户A对物品{a,b,d}有过行为,用户B对物品{a,c}有过行为,利用余弦相似度计算用户A与用户B的兴趣相似度:
同理,可计算出用户A和用户C、D的相似度:
对两两用户都利用余弦相似度计算相似度,但是很多用户之间并没有对同样的物品产生过行为,即很多时候 N (u) 与N (v)的交集为0。
解决办法:所以可以先计算 N (u) 与N (v)的交集不为0的用户对(u,v),然后再对这种情况除以分母。建立物品到用户的倒排表,令W[u][v]=N (u) 与N (v)的交集数。
例子:
物品-用户倒排表
一共有4个用户,分别对a,b,c,d,e物品产生行为,左上图是用户对哪些物品产生的行为,右上图是某物品下有哪些用户。对于物品a,将W[A][B] 和W[B][A]加1,对物品b,将 W[A][C] 和W[C][A]加1,以此类推,得到最终的矩阵W,W除以分母就可以得到最终的用户兴趣相似度。
(2)给用户推荐和他兴趣最相似的K个用户喜欢的物品
其中,S(u,K)包含和用户u兴趣最接近的K个用户,N(i)是对图书i打过分的用户集合,Wuv是用户u和用户v的兴趣相似度,Rvi代表用户v对图书i的兴趣度,因为使用的是单一行为的隐反馈数据,所以所有的Rvi=1。
例子:
对A进行推荐,选取K=3,用户A对物品c、e没有产生过行为,因此可以把这两个物品推荐给用户A,所以用户A对物品c、e的兴趣是:
3.2 特点
(1)缺点:随着网站的用户数目越来越大,计算用户兴趣相似度矩阵将越来越困难;很难对推荐结果作出解释。
(2)适用于新闻推荐,基于用户的协同过滤维护的是用户相似性表,在新闻网站,物品的更新速度远快于新用户的加入速度。
4 基于物品的协同过滤
给用户推荐那些和他们之前喜欢的物品相似的物品,主要是根据用户的行为记录物品之间的相似度。
4.1 步骤:
(1)计算物品之间的相似度
给定图书i和图书j,N(i)表示喜欢图书i的用户数,N(j)表示喜欢图书j的用户数,通过余弦相似度计算图书i和图书j的兴趣相似度:
该公式解释为喜欢物品i的用户中有多少比例的用户也喜欢物品j。该公式已经做了优化,避免推荐出热门的物品,惩罚物品j的权重。最左边的每一行代表一个用户感兴趣的物品集合,对每个物品集合,将里面的物品两两加1,分别得到一个矩阵,再将矩阵里的值对应相加,得到最右边物品之间的余弦相似度矩阵
(2)根据物品相似度和用户的历史评分进行推荐
根据用户u历史上对图书的评分,选出相似图书,如下公式计算用户u对图书的兴趣度:
其中,S(j,k)是和物品j最相似的K个物品,Wji是物品j和i的相似度,Rui是用户u对物品i的兴趣,一般取Rui=1
例子:
1)下图表示A喜欢a,b,d;B喜欢a,c,依次类推
2)下图表示一个物品的用户数为:
喜欢a的用户数为2,喜欢b用户数是2,依次类推
3)下图表示同时喜欢两个物品的用户数:
喜欢ab的用户数有1人,喜欢ad的用户数有2人,依次类推
4)物品相似度为:
ab相似度:1/(√2×2)=0.5
ad相似度:2/(√2×2)=1
bc相似度:2/(√2×2)=1
be相似度:1/(√2×2)=0.5
cd相似度:2/(√2×2)=1
如果某个用户对a的兴趣度为1,对b的兴趣度为2,那预测他对c,d的兴趣度为:
c:1×0+2×1=2
d:1×1+2×0=1
4.2 特点
基于物品的协同过滤算法适用于图书、电子商务和电影网站,维护的是物品相似度矩阵。
5 算法实现
数据集是用户对图书的评分,用户名-评分-图书id,获取数据集的链接
5.1 基于用户的协同过滤
相似度采用皮尔逊相关系数,公式如下:
上面的公式除了看起来比较复杂,另一个问题是要获得计算结果必须对数据做多次遍历。好在我们有另外一个公式,能够计算皮尔逊相关系数的近似值:
from math import sqrt
"""
@author : Smilehe_
思路:
1、计算目标用户与相邻用户之间的相似度,根据两用户之间对同一本书的评分进行计算相似度
2、获取目标用户和相邻用户评价过的图书,把相邻用户看过的图书但目标用户未看过的图书推荐给目标用户
3、根据用户1的评分*相似度+用户2的评分*相似度...计算该本图书的权重,然后进行排序,把前n本推荐给目标用户
"""
#加载数据
fp = open("uid_score_bid","r")
#设置users为字典
users = {}
for line in open("uid_score_bid"):
#默认strip()默认删除空格键,split(",")以","切割
#如:用户名,评分 ,图书id切出来是'用户名','评分','图书id'
lines = line.strip().split(",")
if lines[0] not in users:
users[lines[0]]={}
#user={'用户名':{'图书id1':'评分','图书id2':'评分'}.......}
#users['用户名']['图书id']=评分
users[lines[0]][lines[2]]=float(lines[1])
class recommender:
#data:数据集,这里指users
#k:表示最相近的k近邻
#metric:表示使用相似度的方法
#n:表示推荐book的个数
def __init__(self, data, k=3, metric='pearson', n=12):
self.k = k
self.n = n
self.username2id = {}
self.userid2name = {}
self.productid2name = {}
#将距离计算方式保存下来
self.metric = metric
if self.metric == 'pearson':
self.fn = self.pearson
#加下划线表示私有属性,如果data是一个字典类型,则保存下来,否则忽略
if type(data).__name__ =='dict':
self.data = data
def convertProductID2name(self,id):
#通过产品id获取名称
if id in self.productid2name:
return self.productid2name[id]
else:
return id
#计算相似度,用的皮尔逊相关系数计算方法
#得到双方曾经评价过的物品列表,rating1是要查的用户,rating2是相邻用户
#rating={'图书id'='评分'}
def pearson(self,rating1,rating2):
sum_xy = 0
sum_x = 0
sum_y = 0
sum_x2 = 0
sum_y2 = 0
n = 0
#key是图书id
for key in rating1:
if key in rating2:
#n加1表示有共同图书
n +=1
#x,y分别表示评分值
x = rating1[key]
y = rating2[key]
sum_xy += x*y
sum_x += x
sum_y += y
sum_x2 +=pow(x,2)#x平方
sum_y2 +=pow(y,2)#y平方
if n == 0:
return 0
denominator = sqrt(sum_x2-pow(sum_x,2)/n)*sqrt(sum_y2-pow(sum_y,2)/n)
if denominator == 0:
return 0
else:
#返回相关系数
return (sum_xy - (sum_x*sum_y)/n)/denominator
def computeNearestNeighbor(self,username):
#创建一个距离列表
distances = []
#instance是指相邻用户
for instance in self.data:
if instance != username:
#fn=pearson函数,这里在传参数
distance = self.fn(self.data[username],self.data[instance])
#传入相邻用户、距离
distances.append((instance,distance))
#做一个排序
distances.sort(key=lambda artistTuple:artistTuple[1],reverse=True)
return distances
#推荐算法主体函数,参数user是用户名
def recommend(self,user):
#定义一个字典,用来存储推荐的书单和分数
recommendations = {}
#计算user与所有其他用户的相似度,返回一个list
nearest = self.computeNearestNeighbor(user)
#用户评价过的图书data[user]={'图书id':'评分'}
userRatings = self.data[user]
totalDistance = 0.0
#得到最近的k个近邻的总距离
for i in range(self.k):
totalDistance +=nearest[i][1]
if totalDistance == 0.0:
totalDistance = 1.0
#将与user最相近的k个人中user没看过的书推荐给user,这里做了一个分数的计算排名
for i in range(self.k):
#第i个人与user的相似度,转换到[0,1]之间
weight = nearest[i][1]/totalDistance
#得到第i个人的name
name = nearest[i][0]
#得到第i个人看过的书和相应打分
neighborRatings = self.data[name]
#获取没有评价过的图书
for artist in neighborRatings:
if not artist in userRatings:
if artist not in recommendations:
#第i个人的打分*与user的相似度
recommendations[artist] = (neighborRatings[artist]*weight)
else:
recommendations[artist] = (recommendations[artist]+neighborRatings[artist]*weight)
#开始推荐
recommendations = list(recommendations.items())
recommendations = [(self.convertProductID2name(k),v) for (k,v) in recommendations]
#做一个排序
recommendations.sort(key=lambda artistTuple:artistTuple[1],reverse=True)
return recommendations[:self.n],nearest
def adjustrecommend(id):
#bookid_list推荐图书的id,
bookid_list = []
#传入文件里的内容
r = recommender(users)
k,nearuser = r.recommend("%s"%id)
for i in range(len(k)):
bookid_list.append(k[i][0])
return bookid_list,nearuser[:15]
#nearuser[:15]最近邻的15个用户
bookid_list,near_list = adjustrecommend("changanamei")
print ("bookid_list:",bookid_list)
print ("near_list:",near_list)
输出结果如下:
bookid_list: ['1529893', '1039487', '2143732', '10594787', '1039752', '1008357', '24934182', '4067621', '1400705', '2250587', '1119522', '1029791']
near_list: [('73148560', 1.0000000000000002), ('yiminuansheng', 1.0000000000000002), ('68475890', 1.0000000000000002), ('36644067', 1.0000000000000002), ('41779303', 1.0000000000000002), ('49717005', 1.0000000000000002), ('69097692', 1.0000000000000002), ('61724985', 1.0000000000000002), ('127363187', 1.0000000000000002), ('49483518', 1.0000000000000002), ('56964251', 1.0000000000000002), ('68468633', 1.0000000000000002), ('122191787', 1.0000000000000002), ('xiaziwu', 1.0000000000000002), ('68063648', 1.0000000000000002)]
5.2 基于物品的协同过滤
import math
"""
@author : Smilehe_
思路:
1、计算目标用户喜欢的图书与相邻图书之间的相似度,根据两图书之间有多少个共同用户,计算相似度
2、取目标用户历史上对喜欢图书的评分,对其推荐新图书,计算新图书的权重=图书1评分*相似度+图书2评分*相似度
3、根据权重进行排序,推荐图书
"""
class ItemBasedCF:
def __init__(self,train_file):
self.train_file = train_file
self.readData()
def readData(self):
#读取文件,生成用户-物品的评分表和测试集
self.train = dict()
for line in open(self.train_file):
user,score,item = line.strip().split(",")
self.train.setdefault(user,{})
# train={'用户名1':{'图书id1':'评分','图书id2':'评分'}.......}
self.train[user][item] = int(float(score))
def ItemSimilarity(self):
#建立物品-物品的共现矩阵
#用于创建一个字典
C = dict() #物品-物品的共现矩阵
N = dict() #物品被多少个不同用户购买
for user,items in self.train.items():
#这里的keys指的是图书id
for i in items.keys():
N.setdefault(i,0)
#N[i]的值表示一个物品下有多少个用户
N[i] +=1
#物品-物品矩阵
#C={图书id1:{},图书id2:{}......}
C.setdefault(i,{})
for j in items.keys():
if i == j : continue
#C={图书id1:{图书id1:用户数,.....},图书id2:{图书id2:用户数...}}
C[i].setdefault(j,0)
C[i][j]+=1
#计算相似度矩阵
self.W = dict()
for i,related_items in C.items():
#W={图书id1:{}}
self.W.setdefault(i,{})
#related_items={图书id1:用户数,.....},
for j,cij in related_items.items():
self.W[i][j] = cij/(math.sqrt(N[i]*N[j]))
return self.W
#给用户user推荐,前k个相关用户
def Recommend(self,user,K=3,N=10):
rank = dict()
action_item = self.train[user] #用户user产生过行为的item和评分
for item,score in action_item.items():
#self.W[item].items()表示{图书id1:用户数,.....},
for j,wj in sorted(self.W[item].items(),key=lambda x:x[1],reverse=True)[0:K]:
if j in action_item.keys():
continue
rank.setdefault(j,0)
#wj是用户数
rank[j] += score*wj
return dict(sorted(rank.items(),key=lambda x:x[1],reverse=True)[0:N])
#声明一个对象
Item = ItemBasedCF("uid_score_bid")
Item.ItemSimilarity()
recommedDic = Item.Recommend("xiyuweilan")
#一行一行输出键值对
for k,v in recommedDic.items():
print(k,'\t',v)
输出结果如下:
推荐的图书id-得出的权重值
1858513 8.78564169540279
26278687 8.78564169540279
参考资料:
1、《推荐系统实战》–项亮
2、https://blog.youkuaiyun.com/Gamer_gyt/article/details/51346159