推荐系统之协同过滤

本文介绍协同过滤推荐算法原理及其实现,包括基于用户和基于物品的两种协同过滤方法,并给出具体实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值