python实现itemCF and userCF

本文介绍了协同过滤推荐系统中的两种核心算法:基于用户的协同过滤(UserCF)和基于物品的协同过滤(ItemCF)。UserCF通过寻找兴趣相似的用户来推荐物品,而ItemCF则通过物品间的相似度来进行推荐。文中还提供了这两种算法的Python实现,并对其性能进行了对比。

http://my.oschina.net/zhangjiawen/blog/185625

1基于用户的协同过滤算法:

基于用户的协同过滤算法是推荐系统中最古老的的算法,可以说是这个算法的诞生标志了推荐系统的诞生。该算法在1992年被提出,并应用于邮件过滤系统,1994年被GroupLens用于新闻过滤。

在一个在线个性化推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的而用户A没有接触过的物品推荐给A。这种方法称为基于用户的协同过滤算法。

给定用户u和用户v,令N(u)表示用户u曾经有过正反馈的物品集合,通过余弦相似度计算用户的相似度。由于很多用户相互之间并没有对同样的物品产生过行为,即clip_image002,因此可以先计算clip_image002[4]的用户对(u,v)。为此,可以首先建立物品到用户的倒查表,对于每个物品保存对该物品产生过行为的用户列表。令稀疏矩阵clip_image006,假设用户u和用户v同时属于倒查表中K个物品对应的用户列表,就有clip_image008(即用户u和v对相同物品产生正反馈的物品数),从而可以扫描倒查表中每个物品对应的用户列表,将用户列表中的两两用户对应的clip_image010加1,最终就可以获得所有用户之间不为0的clip_image010[1](也就是余弦相似度的分子)。

得到用户之间的兴趣相似度之后,基于用户的协同过滤算法(User Based Collaborative Filering)会给用户推荐和他兴趣最相似的K个用户喜欢的物品。如下公式度量了UserCF算法中用户u对物品i的感兴趣程度:

clip_image002[9]

其中,S(u,k)包含和用户u兴趣相似度最接近的k个用户集合,N(i)是对物品i有过行为的用户集合,clip_image015是用户u和用户v的兴趣相似度,clip_image017代表用户v对物品i的兴趣,因为使用的是单一行为的隐反馈数据,因此clip_image019为1。

根据以上思路,使用Python实现UserCF算法的代码如下:

import random
002import math
003class UserBasedCF:
004    def __init__(self,datafile = None):
005        self.datafile = datafile
006        self.readData()
007        self.splitData(3,47)
008         
009    def readData(self,datafile = None):
010    """
011    read the data from the data file which is a data set
012    """
013        self.datafile = datafile or self.datafile
014        self.data = []
015    for line in open(self.datafile):
016        userid,itemid,record,_ = line.split()
017        self.data.append((userid,itemid,int(record)))
018 
019    def splitData(self,k,seed,data=None,M = 8):
020        """
021        split the data set
022        testdata is a test data set
023        traindata is a train set 
024        test data set / train data set is 1:M-1
025        """
026        self.testdata = {}
027        self.traindata = {}
028        data = data or self.data
029        random.seed(seed)
030        for user,item, record in self.data:
031            if random.randint(0,M) == k:
032                self.testdata.setdefault(user,{})
033                self.testdata[user][item] = record
034            else:
035                self.traindata.setdefault(user,{})
036                self.traindata[user][item] = record
037     
038    def userSimilarityBest(self,train = None):
039    """
040    the other method of getting user similarity which is better than above
041    you can get the method on page 46
042    In this experiment,we use this method
043    """
044        train = train or self.traindata
045        self.userSimBest = dict()
046        item_users = dict()
047        for u,item in train.items():
048            for in item.keys():
049                item_users.setdefault(i,set())
050                item_users[i].add(u)
051        user_item_count = dict()
052        count = dict()
053        for item,users in item_users.items():
054            for in users:
055                user_item_count.setdefault(u,0)
056                user_item_count[u] += 1
057        for in users:
058            if == v:continue
059        count.setdefault(u,{})
060        count[u].setdefault(v,0)
061        count[u][v] += 1
062        for u ,related_users in count.items():
063            self.userSimBest.setdefault(u,dict())
064            for v, cuv in related_users.items():
065                self.userSimBest[u][v] = cuv / math.sqrt(user_item_count[u] * user_item_count[v] * 1.0)
066     
067    def recommend(self,user,train = None,k = 8,nitem = 40):
068        train = train or self.traindata
069        rank = dict()
070        interacted_items = train.get(user,{})
071        for v ,wuv in sorted(self.userSimBest[user].items(),key = lambda x : x[1],reverse = True)[0:k]:
072            for i , rvi in train[v].items():
073                if in interacted_items:
074                    continue
075                rank.setdefault(i,0)
076                rank[i] += wuv
077            return dict(sorted(rank.items(),key = lambda x :x[1],reverse = True)[0:nitem])
078 
079    def recallAndPrecision(self,train = None,test = None,k = 8,nitem = 10):
080    """
081    Get the recall and precision, the method you want to know is listed 
082    in the page 43
083    """
084    train = train or self.traindata
085    test = test or self.testdata
086    hit = 0
087    recall = 0
088    precision = 0
089    for user in train.keys():
090        tu = test.get(user,{})
091    rank = self.recommend(user, train = train,k = k,nitem = nitem)
092    for item,_ in rank.items():
093        if item in tu:
094            hit += 1
095    recall += len(tu)
096    precision += nitem
097    return (hit / (recall * 1.0),hit / (precision * 1.0))
098     
099    def coverage(self,train = None,test = None,k = 8,nitem = 10):
100        train = train or self.traindata
101        test = test or self.testdata
102        recommend_items = set()
103        all_items = set()
104        for user in train.keys():
105            for item in train[user].keys():
106                all_items.add(item)
107        rank = self.recommend(user, train, k = k, nitem = nitem)
108        for item,_ in rank.items():
109            recommend_items.add(item)
110        return len(recommend_items) / (len(all_items) * 1.0)
111     
112    def popularity(self,train = None,test = None,k = 8,nitem = 10):
113    """
114    Get the popularity
115    the algorithm on page 44
116    """
117        train = train or self.traindata
118        test = test or self.testdata
119        item_popularity = dict()
120        for user ,items in train.items():
121            for item in items.keys():
122                item_popularity.setdefault(item,0)
123                item_popularity[item] += 1
124        ret = 0
125        = 0
126        for user in train.keys():
127            rank = self.recommend(user, train, k = k, nitem = nitem)
128        for item ,_ in rank.items():
129            ret += math.log(1+item_popularity[item])
130        += 1
131        return ret / (n * 1.0)
132 
133    def testUserBasedCF():
134        cf = UserBasedCF('u.data')
135        cf.userSimilarityBest()
136        print "%3s%20s%20s%20s%20s" % ('K',"recall",'precision','coverage','popularity')
137        for in [5,10,20,40,80,160]:
138            recall,precision = cf.recallAndPrecision( k = k)
139            coverage = cf.coverage(k = k)
140            popularity = cf.popularity(k = k)
141            print "%3d%19.3f%%%19.3f%%%19.3f%%%20.3f" % (k,recall * 100,precision * 100,coverage * 100,popularity)
142 
143if __name__ == "__main__":
144testUserBasedCF()

2 基于物品的协同过滤算法:

基于物品的协同过滤算法(Item-Based Collaborative Filtering)是目前业界应用最多的算法,亚马逊、Netflix、Hulu、YouTube都采用该算法作为其基础推荐算法。

基于用户的协同过滤算法有一些缺点:随着网站的用户数目越来越大,计算用户兴趣相似度矩阵将越来越困难,其运算时间复杂度和空间复杂度的增长和用户数的增长近似平方关心。并且,基于用户的协同过滤算法很难对推荐结果做出解释。因此亚马逊提出了基于物品的协同过滤算法。

基于物品的协同过滤算法给用户推荐那些和他们之前喜欢的物品相似的物品。不过ItemCF算法并不利用物品的内容属性计算物品之间的相似度,它主要通过分析用户的行为记录计算用户之间的相似度,也就是说物品A和物品B具有很大的相似度是因为喜欢物品A的用户大都也喜欢物品B(这一点也是基于物品的协同过滤算法和基于内容的推荐算法最主要的区别)。同时,基于物品的协同过滤算法可以利用用户的历史行为给推荐结果提供推荐解释,用于解释的物品都是用户之前喜欢的或者购买的物品。

“Customers Who Bought This Item Also Bought”(亚马逊显示相关物品推荐时的标题),从这句话的定义出发,利用以下公式定义物品之间的相似度:

clip_image021

其中N(i)是喜欢物品i的用户数,分子clip_image023是同时喜欢物品i和物品j的用户数。这个公式惩罚了物品j的权重,减轻了热门物品会和很多物品相似的可能性(这样wij的值会很大,接近于1)。这个公式说明两个物品产生相似度是因为它们共同被很多用户喜欢,也就是说每个用户都可以通过他们的历史兴趣列表给物品“贡献”相似度。

和UserCF算法类似,用ItemCF算法计算物品相似度时也可以首先建立用户-物品倒排表,即对每个用户建立一个包含他喜欢的物品的列表,然后对于每个用户,将他物品列表中的物品两两在共现矩阵C中加1,最终就可以得到所有物品之间不为0的clip_image025,也就是公式中的分子。

在得到物品之间的相似度后,ItemCF通过如下公式计算用户u对一个物品i的兴趣:

clip_image027

其中,N(u)是用户喜欢的物品集合,S(i,k)是和物品i最相似的k个物品的集合,clip_image029是物品j和物品i的相似度,clip_image031是用户u对物品i的兴趣,对于隐反馈数据集,如果用户u对物品i有过行为,即可令clip_image033为1。

根据以上思路,使用Python实现ItemCF算法的代码如下:

001import math
002import random
003class ItemBasedCF:
004    def __init__(self, datafile = None):
005        self.datafile = datafile
006        self.readData()
007        self.splitData(3,47)
008         
009    def readData(self,datafile = None):
010        self.datafile = datafile or self.datafile
011        self.data = []
012        file = open(self.datafile,'r')
013        for line in file.readlines()[0:100*1000]:
014            userid, itemid, record,_ = line.split()
015            self.data.append((userid,itemid,int(record)))
016             
017    def splitData(self,k,seed,data=None,M = 8):
018        self.testdata = {}
019        self.traindata = {}
020        data = data or self.data
021        random.seed(seed)
022        for user,item,record in self.data:
023            if random.randint(0,7== k:
024                self.testdata.setdefault(item,{})
025                self.testdata[item][user] = record
026            else:
027                self.traindata.setdefault(item,{})
028                self.traindata[item][user] = record
029                 
030def ItemSimilarity(self, train = None):
031    train = train or self.traindata
032    self.itemSim = dict()
033    #user_items = dict()
034    item_user_count = dict() #item_user_count{item: likeCount} the number of users who like the item
035    count = dict() #count{i:{j:value}} the number of users who both like item i and j
036    for user, item in train.items(): #initialize the user_items{user: items}
037        for in item.keys():
038            item_user_count.setdefault(i,0)
039            item_user_count[i] += 1
040            for in item.keys():
041                if == j:
042                    continue
043        count.setdefault(i,{})
044        count[i].setdefault(j,0)
045        count[i][j] += 1
046     for i, related_items in count.items():
047        self.itemSim.setdefault(i,dict())
048        for j, cuv in related_items.items():
049            self.itemSim[i].setdefault(j,0)
050            self.itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j] * 1.0)
051             
052def recommend(self,user,train = None, k = 8, nitem = 40):
053    train = train or self.traindata
054    rank = dict()
055    ru = train.get(user,{})
056    for i,pi in ru.items():
057        for j,wj in sorted(self.itemSim[i].items(), key = lambda x:x[1], reverse = True)[0:k]:
058            if in ru:
059                continue
060        rank.setdefault(j,0)
061        rank[j] += wj
062    #print dict(sorted(rank.items(), key = lambda x:x[1], reverse = True)[0:nitem])
063    return dict(sorted(rank.items(), key = lambda x:x[1], reverse = True)[0:nitem])
064     
065    def recallAndPrecision(self,train = None,test = None,k = 8,nitem = 10):
066    train = train or self.traindata
067    test = test or self.testdata
068    hit = 0
069    recall = 0
070    precision = 0
071    for user in train.keys():
072        tu = test.get(user,{})
073    rank = self.recommend(user,train = train,k = k,nitem = nitem)
074    for item,_ in rank.items():
075        if item in tu:
076            hit += 1
077            recall += len(tu)
078            precision += nitem
079    return (hit / (recall * 1.0),hit / (precision * 1.0))
080     
081    def coverage(self,train = None,test = None,k = 8,nitem = 10):
082        train = train or self.traindata
083        test = test or self.testdata
084        recommend_items = set()
085        all_items = set()
086        for user in train.keys():
087            for item in train[user].keys():
088                all_items.add(item)
089        rank = self.recommend(user, train, k = k, nitem = nitem)
090        for item,_ in rank.items():
091            recommend_items.add(item)
092        return len(recommend_items) / (len(all_items) * 1.0)
093     
094    def popularity(self,train = None,test = None,k = 8,nitem = 10):
095    """
096    Get the popularity
097    the algorithm on page 44
098    """
099        train = train or self.traindata
100        test = test or self.testdata
101        item_popularity = dict()
102        for user ,items in train.items():
103            for item in items.keys():
104                item_popularity.setdefault(item,0)
105                item_popularity[item] += 1
106        ret = 0
107        = 0
108        for user in train.keys():
109            rank = self.recommend(user, train, k = k, nitem = nitem)
110        for item ,_ in rank.items():
111            ret += math.log(1+item_popularity[item])
112        += 1
113        return ret / (n * 1.0)
114         
115    def testRecommend():
116        ubcf = ItemBasedCF('u.data')
117        ubcf.readData()
118        ubcf.splitData(4,100)
119        ubcf.ItemSimilarity()
120        user = "345"
121        rank = ubcf.recommend(user,k = 3)
122        for i,rvi in rank.items():
123            items = ubcf.testdata.get(user,{})
124            record = items.get(i,0)
125            print "%5s: %.4f--%.4f" %(i,rvi,record)
126             
127    def testItemBasedCF():
128        cf = ItemBasedCF('u.data')
129        cf.ItemSimilarity()
130        print "%3s%20s%20s%20s%20s" % ('K',"recall",'precision','coverage','popularity')
131        for in [5,10,20,40,80,160]:
132            recall,precision = cf.recallAndPrecision( k = k)
133        coverage = cf.coverage(k = k)
134        popularity = cf.popularity(k = k)
135        print "%3d%19.3f%%%19.3f%%%19.3f%%%20.3f" % (k,recall * 100,precision * 100,coverage * 100,popularity)
136         
137if __name__ == "__main__":
138testItemBasedCF()

3 UserCF和ItemCF的综合比较:

UserCF给用户推荐那些和他有共同兴趣爱好的用户喜欢的物品,而ItemCF给用户推荐那些和他之前喜欢的物品类似的物品。从这个原理可以看到,UserCF的推荐结果着重于反映和用户兴趣相似的小群体的热点,而ItemCF的推荐结果着重于维系用户的历史兴趣。UserCF的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF的推荐更加个性化,反映了用户自己的兴趣传承。同时,从技术上来说,UserCF需要维护一个用户相似度的矩阵,而ItemCF需要维护一个物品相似度矩阵。从存储的角度来说,如果用户很多,那么维护用户兴趣相似度矩阵需要很大的空间,同理,如果物品很多,维护物品相似度矩阵代价较大。

对于UserCF和ItemCF,我们采用http://www.grouplens.org/node/73 的数据集进行测试,使用准确率/召回率、覆盖率和流行度对实验结果进行评测。

对用户u推荐N个物品R(u),令用户u在测试集上喜欢的物品集合为T(u),则:

clip_image035

clip_image037

召回率描述有多少比例的用户-物品评分记录包含在最终的推荐列表中,而准确率描述最终的推荐列表中有多少比例是发生过的用户-物品评分记录。

clip_image039

覆盖率表示最终的推荐列表中包含多大比例的物品,如果所有的物品都被推荐给至少一个用户,那么覆盖率就是100%。

最后还需要评测推荐的新颖度,这里用推荐列表中物品的平均流行度度量推荐结果的新颖度,如果推荐出的物品都很热门,说明推荐的新颖度较低,否则说明推荐结果比较新颖。

clip_image041

图1 UserCF实验结果

clip_image043

图2 ItemCF实验结果

对于以上UserCF和ItemCF的实验结果可以看出,推荐系统的精度指标(准确率和召回率)并不和参数k成线性关系。推荐结果的精度对k也不是特别敏感,只要选在一定的区域内,就可以获得不错的精度。

对于覆盖率而言,k越大则推荐结果的覆盖率越低,覆盖率的降低是因为流行度的增加,随着流行度增加,推荐算法越来越倾向于推荐热门的物品,这是因为k决定了推荐算法在做推荐时参考多少和你兴趣相似的其他用户的兴趣,如果k越大,参考的人或者物品越多,结果就越来越趋近于全局热门的物品。

转载于:https://www.cnblogs.com/DjangoBlog/p/3594280.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值