MO

发现意外,创造可能

MO Blog


【专栏01】 推荐系统(三)基于物品的协同过滤算法

专栏1-推荐系统(三)基于物品的协同过滤算法

基于物品的协同过滤算法给用户推荐那些和他们之前喜欢过物品相似的物品,比如用户购买过篮球那系统很有可能对其推荐球衣。

基于物品的协同过滤算法主要分为两步: 1.计算物品之间的相似度 2.根据物品的相似度和用户的历史行为给用户生成推荐列表

比如我们可以定义物品的相似度:

其中,分母是喜欢物品i的用户数,分子是同时喜欢物品i和物品j的用户数。

当然,如果物品j非常热门,那么Wij就接近于1,即任何物品都和热门的物品有很高的相似度,所以我们可以使用下面的公式

在设计算法的时候首先建立用户-物品字典,然后对于每个用户,将其物品列表中的物品在共现矩阵对应位置加1。

举一个简单的例子, 用户1:a,b,d 用户2:b,c,e 用户3:c,d 用户4:b,c,d 用户5:a,d

那么共线矩阵为

则ItemCF计算用户u对物品j的兴趣为:

其中N(u)是用户喜欢的物品集合,S(j,k)是和物品j最相似的K个物品的集合,wji是物品j和i的相似度,rui是用户u对物品i的兴趣。

以下为具体代码,这里用movie-lens的数据集,链接如下 https://grouplens.org/datasets/movielens/ 中的ml-latest-small.zip

# coding = utf-8
# 基于项目的协同过滤推荐算法实现
import random
import math
from operator import itemgetter

class ItemBasedCF():

    def __init__(self):
        
        # 找到相似的20部电影,为目标用户推荐10部电影
        self.n_sim_movie = 20
        self.n_rec_movie = 10

        # 将数据集划分为训练集和测试集
        self.trainSet = {}
        self.testSet = {}

        # 用户相似度矩阵
        self.movie_sim_matrix = {}
        self.movie_popular = {}
        self.movie_count = 0

    # 读文件得到“用户-电影”数据
    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

    # 读文件,返回文件的每一行
    def load_file(self, filename):
        with open(filename, 'r') as f:
            for i, line in enumerate(f):
                if i == 0:  # 去掉文件第一行的title
                    continue
                yield line.strip('\r\n')


    # 计算电影之间的相似度
    def calc_movie_sim(self):
        for user, movies in self.trainSet.items():
            for movie in movies:
                if movie not in self.movie_popular:
                    self.movie_popular[movie] = 0
                self.movie_popular[movie] += 1

        self.movie_count = len(self.movie_popular)

        for user, movies in self.trainSet.items():
            for m1 in movies:
                for m2 in movies:
                    if m1 == m2:
                        continue
                    self.movie_sim_matrix.setdefault(m1, {})
                    self.movie_sim_matrix[m1].setdefault(m2, 0)
                    self.movie_sim_matrix[m1][m2] += 1

        # 计算电影之间的相似性
        print("Calculating movie similarity matrix ...")
        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])


    # 针对目标用户U,找到K部相似的电影,并推荐其N部电影
    def recommend(self, user):
        K = self.n_sim_movie
        N = self.n_rec_movie
        rank = {}
        watched_movies = self.trainSet[user]

        for movie, rating in watched_movies.items():
            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]


    # 产生推荐并通过准确率、召回率和覆盖率进行评估
    def evaluate(self):
        N = self.n_rec_movie
        hit = 0
        rec_count = 0
        test_count = 0
        all_rec_movies = set()

        for i, user in enumerate(self.trainSet):
            test_moives = self.testSet.get(user, {})
            rec_movies = self.recommend(user)
            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('precisioin=%.4f\trecall=%.4f\tcoverage=%.4f' % (precision, recall, coverage))


if __name__ == '__main__':
    rating_file = '你的文件路径'
    itemCF = ItemBasedCF()
    itemCF.get_dataset(rating_file)
    itemCF.calc_movie_sim()
    itemCF.evaluate()

本文参考链接 https://github.com/xingzhexiaozhu/MovieRecommendation

在计算用户活跃度时,活跃用户对物品相似度的贡献应该不高于不活跃的用户,这是因为一个用户可能是狂热电影爱好者,他观看了大量的电影,电影之间并无明显的相似程度。这里我们可以使用导数进行修正。

在计算用户相似度矩阵时,可以对其归一化:

一般来说物品总是属于某一类,如果存在一个热门的类,其中又有一个热门的物品,如果不进行归一化,则该物品很有可能有一个很大的权重,归一化可以提高推荐的多样性。

本篇介绍了itemCF的算法原理,下篇会介绍其同门师兄弟userCF,以及师兄弟的比较。期待大家的持续关注!

最近的文章

【技术博客40】基于alexnet网络的垃圾分类

40-基于AlexNet网络的垃圾分类AlexNetAlexNet模型来源于论文-ImageNet Classification with Deep Convolutional Neural Networks,作者Alex Krizhevsky,Ilya Sutskever,Geoffrey E.Hinton.AlexNet在ImageNet LSVRC-2012比赛中,达到最低的15.3%的Top-5错误率,比第二名低10.8个百分点。网络结构AlexNet包含八层,前五层是卷积层,最后...…

继续阅读
更早的文章

【专栏02】激活函数(一)浅谈激活函数以及其发展

专栏2-激活函数(一)浅谈激活函数以及其发展激活函数是神经网络的相当重要的一部分,在神经网络的发展史上,各种激活函数也是一个研究的方向。我们在学习中,往往没有思考过——为什么用这个函数以及它们是从何而来?生物神经网络曾给予了人工神经网络相当多的启发。如上图,来自树突信号不断累积,如若信号强度超过一个特定阈值,则向轴突继续传递信号。如若未超过,则该信号被神经元“杀死”,无法继续传播。在人工神经网络之中,激活函数有着异曲同工之妙。试想,当我们学习了一些新的东西之后,一些神经元会产生不同的输出信...…

继续阅读