python实现基于物品的协同过滤(ItemCF)电影推荐算法

最近,因为导师项目需要,花了几天时间学习了项亮的《推荐系统实践》,并用python实现了书上的Item Collaborative Filtering即基于物品的协同过滤算法,发现很多博客在算法的代码的实现上说得很笼统,而且项亮的书中关于协同过滤的代码实现写得又很零碎,故写此文总结。

什么是协同过滤

协同过滤是推荐系统中最经典和常用的算法,其核心思想就是它的名字:利用所有用户的历史行为数据,用户通过不断地和网站互动,使推荐列表能够不断过滤掉用户不感兴趣的物品,从而越来越满足需求。

协同过滤分为两大类:

  1. 基于用户的协同过滤(UserCF):当要给目标用户进行推荐时,先找出与他相似的其他用户,再从那些用户喜欢的项目中找出目标用户没有看过的项目推荐给他。
  2. 基于项目的协同过滤(ItemCF):先找出目标用户历史观看列表里与之相似的其他项目,再对这些相似项目进行排序并生成最后的推荐列表。

评分预测还是TopN推荐?

评分预测和TopN推荐是推荐系统两种衡量预测准确度的指标,大多数的推荐研究都是基于评分预测来展开的,主要是因为从事这方面早期研究的组织GroupLens主要就是针对电影评分进行的,而且Netflix等推荐算法的大赛也主要是在解决评分预测问题,然而,评分高就是好的推荐吗?当然不是,有时候用户给出好的评分并不代表他最想看它或者购买它。

部分代码实现

  1. 记载数据集并划分训练集与测试集
    def get_dataset(self, filename, pivot=0.75):

        trainSet_len = 0
        testSet_len = 0
        for line in self.load_file(filename):
            user, movie, rating, timestamp = line.split(',')
            if(random.random() < pivot):
                self.trainSet.setdefault(user, {})
                self.trainSet[user][movie] = rating
                trainSet_len += 1
            else:
                self.testSet.setdefault(user, {})
                self.testSet[user][movie] = rating
                testSet_len += 1
        print('划分训练集与测试集成功!')
        print('训练集长 = %s' % trainSet_len)
        print('测试集长 = %s' % testSet_len)

这里的pivot(枢轴)因子将数据集以3:1分成了训练集与数据集。

  1. 计算电影之间的相似度并生成相似矩阵
    def calc_movie_sim(self):
        #建立movies_popular字典
        for user, movies in self.trainSet.items():
            for movie in movies:
            #若该movie没在movies_popular字典中,则把其插入字典并赋值为0,否则+1,最终的movie_popular字典键为电影名,值为所有用户总的观看数
                if movie not in self.movie_popular:
                    self.movie_popular[movie] = 0
                else:
                    self.movie_popular[movie] += 1
        self.movie_count = len(self.movie_popular)
        print("训练集中电影总数 = %d" % self.movie_count)
    
        for user, movies in self.trainSet.items():
            for m1 in movies:
                for m2 in movies:
                    if m1 == m2:
                        continue
                    #下面三步的作用是:分别将每个用户看过的每一部电影与其他所有电影的联系值置1,若之后又有用户同时看了两部电影, 则+1
                    self.movie_sim_matrix.setdefault(m1, {})
                    self.movie_sim_matrix[m1].setdefault(m2, 0)
                    self.movie_sim_matrix[m1][m2] += 1    
        print("建立电影的相似矩阵成功!")
        # print("矩阵进行相似计算前movieId=1的一行为:")
        # print(self.movie_sim_matrix['1'])  

        # 计算电影之间的相似性
        for m1, related_movies in self.movie_sim_matrix.items():
            for m2, count in related_movies.items():
                # 注意0向量的处理,即某电影的用户数为0
                if self.movie_popular[m1] == 0 or self.movie_popular[m2] == 0:
                    self.movie_sim_matrix[m1][m2] = 0
                else:
                    self.movie_sim_matrix[m1][m2] = count / math.sqrt(self.movie_popular[m1] * self.movie_popular[m2])
        print('计算电影的相似矩阵成功!')
       # print("电影相似矩阵中movieId=736的一行:")
       # print(self.movie_sim_matrix['736'])  
  1. 生成推荐列表
def recommend(self, user):
        K = int(self.n_sim_movie)
        N = int(self.n_rec_movie)
        rank = {}
        watched_movies = self.trainSet[user]
        for movie, rating in watched_movies.items():
            #对目标用户每一部看过的电影,从相似电影矩阵中取与这部电影关联值最大的前K部电影,若这K部电影用户之前没有看过,则把它加入rank字典中,其键为movieid名,其值(即推荐度)为w(相似电影矩阵的值)与rating(用户给出的每部电影的评分)的乘积
            for related_movie, w in sorted(self.movie_sim_matrix[movie].items(), key=itemgetter(1), reverse=True)[:K]:
                if related_movie in watched_movies:
                    continue
                rank.setdefault(related_movie, 0)
                #计算推荐度
                rank[related_movie] += w * float(rating)
        return sorted(rank.items(), key=itemgetter(1), reverse=True)[:N]

4.评估算法

    def evaluate(self):
        N = int(self.n_rec_movie)
        reuserN = input("请输入参与评估的用户数量:(总用户数为457)")
        reuserN = int(reuserN)
        # 准确率和召回率
        hit = 0
        rec_count = 0
        test_count = 0
        # 覆盖率
        all_rec_movies = set()
        for user,m in list(self.trainSet.items())[:reuserN]:
            test_moives = self.testSet.get(user, {})
            rec_movies = self.recommend(user)
            print("用户 %s 的电影推荐列表为:" % user)
            self.precommend(rec_movies)
            #注意,这里的w与上面recommend的w不要一样,上面的w是指计算出的相似电影矩阵的权值,而这里是这推荐字典rank对应的推荐度
            for movie, w in rec_movies:
                if movie in test_moives:
                    hit += 1
                all_rec_movies.add(movie)
            rec_count += N
            test_count += len(test_moives)

        precision = hit / (1.0 * rec_count)
        recall = hit / (1.0 * test_count)
        coverage = len(all_rec_movies) / (1.0 * self.movie_count)
        print('准确率=%.4f\t召回率=%.4f\t覆盖率=%.4f' % (precision, recall, coverage))

最后,附上ItemCF数据集及完整代码地址

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容