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数据集及完整代码地址

你可能感兴趣的:(推荐系统)