推荐系统实践:基于用户的协同过滤算法原理及实现(含改进算法)

总体思路:

1.利用余弦相似度对两两用户计算相似度

   1.1 建立物品-用户倒排表

         左半部分为训练数据格式,ABCD等是用户,abc等是对应用户喜欢的物品

         右半部分物品-用户倒排表,如对于物品a,喜欢它的有用户A和B

       

   1.2 建立用户相似度矩阵

         利用物品-用户倒排表,构建用户相似度矩阵,其中的值,如 matrix[A][B]表示用户A和用户B共同喜欢的电影的数量。

        

   1.3 计算用户相似度

         遍历用户相似度矩阵中所有的两两用户,根据两两用户共同喜欢的电影的数量,计算用户相似度

         计算用户相似度的公式如下:

         其中_{}W_{uv}表示用户u与v的相似度,作为matrix[u][v]的值,

         N(u)表示用户u增有过正反馈的物品集合,N(u)表示用户u增有过正反馈的物品集合。

            

例如:

          

          或使用改进的用户相似度计算公式:

          该公式惩罚了用户u和v共同喜欢的物品中热门物品对他们相似度的影响,以图书为例,如果两个用户都曾经买过《新华字典》,这丝毫不能说明他们的兴趣相似,因为绝大多数中国人小时候都买过《新华字典》。但如果两个用户都买过《数据挖掘导论》,那可以认为他们的兴趣比较相似,因为只有研究数据挖掘的人才会买这本书。换句话说,两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。

           i表示用户u和用户v都有过正反馈的物品集合,N(i)表示对物品i有过正反馈的用户数

           即分子部分表示   “用户u和v有正反馈的物品数”

           

2. 针对目标用户u,找到其最相似的K个用户,产生N个推荐

   K表示与用户u兴趣相似的用户个数,N表示为用户u推荐的物品数

   首先,对用户u,在用户相似度中找到与其相似度最高的K个用户

   利用如下的公式计算用户u对物品i的感兴趣程度p(u, i)

   其中,S(u, k)包含和用户u兴趣最接近的K个用户,N(i)是对物品i有过行为的用户集合,w_{uv}是用户u和用户v的相似度,_{}r_{vi}表示用户v对i的兴趣,这里使用的是单一行为的隐反馈,即对物品有过用户行为即使_{}r_{vi}=1。

   该公式的意义在于:对于与用户u相似度接近的K个用户,遍历他们有过正反馈的物品,计算出用户u对每一个物品的感兴趣程度。

   

    然后根据感兴趣程度由高到低确定N个推荐给用户u的物品。

3. 评测指标

        将用户行为数据按照均匀分布随机划分为M份(如取M=8),挑选一份作为测试集,将剩下的M-1份作为训练集。为防止评测指标不是过拟合的结果,共进行M次实验,每次都使用不同的测试集。然后将M次实验测出的评测指标的平均值作为最终的评测指标。

   3.1 召回率

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

         召回率描述有多少比例的用户-物品评分记录包含在最终的推荐列表中。

         

   3.2 准确率

         准确率描述最终的推荐列表中有多少比例是发生过的用户-物品评分记录

         

   3.3 覆盖率

         覆盖率反映了推荐算法发掘长尾的能力,覆盖率越高,说明推荐算法越能够将长尾中的物品推荐给用户。

         分子部分表示实验中所有被推荐给用户的物品数目(集合去重),分母表示数据集中所有物品的数目

         

4. 实验部分

    采用GroupLens提供的MovieLens数据集,http://www.grouplens.org/node/73   。本章使用中等大小的数据集,包含6000多用户对4000多部电影的100万条评分。该数据集是一个评分数据集,用户可以给电影评1-5分5个不同的等级。本文着重研究隐反馈数据集中TopN推荐问题,因此忽略了数据集中的评分记录。也就是说,TopN推荐的任务是预测用户会不会对某电影评分,而不是预测用户在准备对某部电影评分的前提下会给电影评多少分。

    代码如下:

# coding = utf-8

# 基于用户的协同过滤推荐算法实现
import random
import math
from operator import itemgetter


class UserBasedCF():

    ''''''''''初始化相关参数'''
    def __init__(self):
        # 找到与目标用户兴趣相似的20个用户,为其推荐10部电影
        self.n_sim_user = 80
        self.n_rec_movie = 10

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

        # 用户相似度矩阵
        self.user_sim_matrix = {}   #使用典中典模拟矩阵
        self.movie_count = 0
        self.fenzi = {}

        print('Similar user number = %d' % self.n_sim_user)
        print('Recommneded movie number = %d' % self.n_rec_movie)


    '''读文件得到“用户-电影”数据'''
    def get_dataset(self, filename, pivot=0.875):
        trainSet_len = 0
        testSet_len = 0
        random.seed()
        for line in self.load_file(filename):
            user, movie, rating, timestamp = line.split('::')
            if random.random() < pivot: #训练集的数量约为75%
                self.trainSet.setdefault(user, {})  #相当于trainSet.get(user),若该键不存在,则设trainSet[user] = {},典中典

                #键中键:形如{'1': {'1287': '2.0', '1953': '4.0', '2105': '4.0'}, '2': {'10': '4.0', '62': '3.0'}}
                #用户1看了id为1287的电影,打分2.0
                self.trainSet[user][movie] = rating
                trainSet_len += 1
            else:   #测试集数量约为25%
                self.testSet.setdefault(user, {})
                self.testSet[user][movie] = rating
                testSet_len += 1
            # if random.randint(0, 7) == 1:     #书上的方法,M=8,一份为测试集,剩下为训练集,未注释的是github上方法,效果差不多
            #     self.testSet.setdefault(user, {})
            #     self.testSet[user][movie] = rating
            #     testSet_len += 1
            # else:
            #     self.trainSet.setdefault(user, {})  #相当于trainSet.get(user),若该键不存在,则设trainSet[user] = {},典中典
            #
            #     #键中键:形如{'1': {'1287': '2.0', '1953': '4.0', '2105': '4.0'}, '2': {'10': '4.0', '62': '3.0'}}
            #     self.trainSet[user][movie] = rating
            #     trainSet_len += 1

        print('Split trainingSet and testSet success!')
        print('TrainSet = %s' % trainSet_len)
        print('TestSet = %s' % testSet_len)



    '''读文件,返回文件的每一行'''
    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')
        print('Load %s success!' % filename)


    '''计算用户之间的相似度(改进版 User-IIF)'''
    def improved_userSimilarity(self):
        #构建“电影-用户”倒排索引
        print("Building movie-user table...")
        movie_uesr = {}

        for user, movies in self.trainSet.items():
            for movie in movies:
                if movie not in movie_uesr:
                    movie_uesr[movie] = set()
                movie_uesr[movie].add(user)
        print("Build movie-user table success!")

        #建立用户相似度矩阵
        self.movie_count = len(movie_uesr)
        print('Total movie number = %d' % self.movie_count)

        print('Build user co-rated movies matrix...')
        for movie, users in movie_uesr.items():
            for u in users:
                for v in users:
                    if u == v:
                        continue
                    # self.fenzi.setdefault(u, {})
                    # self.fenzi[u].setdefault(v, 0)
                    # self.fenzi[u][v] += 1 / math.log(1 + len(users))
                    self.user_sim_matrix.setdefault(u, {})
                    self.user_sim_matrix[u].setdefault(v, 0)
                    self.user_sim_matrix[u][v] += 1 / math.log(1 + len(users))
        print('Build user co-rated movies matrix success!')

        # 计算相似性
        print('Calculating user similarity matrix ...')
        for u, related_users in self.user_sim_matrix.items():
            for v, count in related_users.items():  # count表示用户u和v都看过电影数
                # 计算用户相似度
                self.user_sim_matrix[u][v] = count / math.sqrt(len(self.trainSet[u]) * len(self.trainSet[v]))
        print('Calculate user similarity matrix success!')




    '''计算用户之间的相似度  UserCF'''
    def calc_user_sim(self):
        # 构建“电影-用户”倒排索引
        # key = movieID, value = list of userIDs who have seen this movie
        print('Building movie-user table ...')
        movie_user = {} #电影-用户倒排索引表
        # .items()方法,该方法将返回所有键值对,并将其保存在一个元组列表(列表中的元素为元组)中:
        #对于键中键,外层已是元组列表,里层还是字典的形式:[('1', {'1129': '2.0'}), ('2', {'39': '5.0', '110': '4.0'})]
        for user, movies in self.trainSet.items():  #对每一个用户和他看过的电影们
            for movie in movies:    #对该用户看过的每一步电影
                if movie not in movie_user:
                    movie_user[movie] = set()   #字典中的集合,索引为电影,里层集合内容为用户们
                movie_user[movie].add(user)
        print('Build movie-user table success!')
        print(movie_user)

        self.movie_count = len(movie_user)  #电影-用户倒排索引表的长度
        print('Total movie number = %d' % self.movie_count)

        print('Build user co-rated movies matrix ...')  #建立用户相似度矩阵W
        for movie, users in movie_user.items(): #取出键值对,每一部电影movie对应着观看过它的一群users
            for u in users: #对该电影的每一种用户组合u-v
                for v in users:
                    if u == v:
                        continue
                    self.user_sim_matrix.setdefault(u, {})
                    self.user_sim_matrix[u].setdefault(v, 0)
                    self.user_sim_matrix[u][v] += 1
        print('Build user co-rated movies matrix success!')

        # 计算相似性
        print('Calculating user similarity matrix ...')
        for u, related_users in self.user_sim_matrix.items():
            for v, count in related_users.items():  #count表示用户u和v都看过电影数
                #计算用户相似度
                self.user_sim_matrix[u][v] = count / math.sqrt(len(self.trainSet[u]) * len(self.trainSet[v]))
        print('Calculate user similarity matrix success!')


    '''针对目标用户U,找到其最相似的K个用户,产生N个推荐'''
    def recommend(self, user):
        K = self.n_sim_user     #与目标用户兴趣相似的用户个数
        N = self.n_rec_movie    #为其推荐电影数
        rank = {}
        watched_movies = self.trainSet[user]

        # v=similar user, wuv=similar factor 相似度
        for v, wuv in sorted(self.user_sim_matrix[user].items(), key=itemgetter(1), reverse=True)[0:K]: #按维度1(用户相似度)降序排列,取前K个
            for movie, rvi in self.trainSet[v].items():  #遍历v看过的电影,及v对该电影的兴趣程度

                if movie in watched_movies: #如果user已看过这部电影,跳过
                    continue
                rank.setdefault(movie, 0)
                rank[movie] += float(wuv) * float(rvi)         #计算user对电影movie的感兴趣程度
        return sorted(rank.items(), key=itemgetter(1), reverse=True)[0:N]   #返回user感兴趣程度最高的N部电影


    '''产生推荐并通过准确率、召回率和覆盖率进行评估'''
    def evaluate(self):
        print("Evaluation start ...")
        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_movies = self.testSet.get(user, {})    #拿出典中典,即user看过的电影及对它们的打分
            rec_movies = self.recommend(user)   #给user推荐的topN部电影及计算出的user对它们感兴趣的程度
            # print(rec_movies)
            for movie, w in rec_movies: #查看推荐的电影是否在测试集上喜欢的电影中出现,如果是,就+1
                if movie in test_movies:
                    hit += 1
                all_rec_movies.add(movie)   #被推荐的电影的集合(用于计算覆盖率)
            rec_count += N  #推荐给所有训练集中用户的电影数的加和
            test_count += len(test_movies)  #所有的用户看过的电影的加和

        precision = hit / (1.0 * rec_count)
        recall = hit / (1.0 * test_count)
        coverage = len(all_rec_movies) / (1.0 * self.movie_count)   #movie_count:电影_用户倒排索引表的长度,即所有电影的数目
        print('precisioin=%.4f\trecall=%.4f\tcoverage=%.4f' % (precision, recall, coverage))


if __name__ == '__main__':
    rating_file = 'ratings2.csv'
    userCF = UserBasedCF()
    userCF.get_dataset(rating_file)
    #userCF.calc_user_sim()
    userCF.improved_userSimilarity()
    userCF.evaluate()

结果如下:

               

猜你喜欢

转载自blog.csdn.net/m0_37917271/article/details/82498308
今日推荐