基于用户的协同过滤算法(user-based Collaborative Filtering)

欣灌费研仍在继续,不能返校只好在家做毕设,毕设的内容是利用地泼雷妞技术优化推荐算法。

悲剧的是地泼雷妞不会,推荐系统算法也不懂,包含推荐系统的东西倒是用过不少(某宝,某东,某音)。

只好从最基础的开始学,先学推荐算法。

打开Microsoft Edge,搜索推荐系统总结,得到该文章《推荐系统干货总结》,如获至宝。

文章中推荐了一本书《推荐系统实践》,我决定先从这本书开始。

电子书链接:https://pan.baidu.com/s/12BPDnGPe7jgXlqTXgT8i1g ,提取码:mnve 

本文是我的学习笔记,对应此书2.4.1小节,

原理就不介绍了,直接上数据集和代码。

不懂原理的,先看这个 基于用户的协同过滤推荐算法原理和实现

下面进入正题


数据集

采用GroupLens提供的MovieLens数据集。 MovieLens数据集有3个不同的版本,书中选用中等大小的数据集。该数据集包含6000多用户对4000多部电影的100万条评分。该数据集是一个评分数据集,用户可以给电影评5个不同等级的分数(1~5分)。

数据集来源   https://grouplens.org/datasets/movielens/1m/

你要是实在特别懒的话,这里有百度网盘链接,https://pan.baidu.com/s/1QaOW5_1quMRDsBobD6CIsw ,提取码:o1rt 

数据集中包含4个文件,movies.dat,users.dat,ratings.dat,README

movies.dat

1::Toy Story (1995)::Animation|Children's|Comedy
2::Jumanji (1995)::Adventure|Children's|Fantasy
3::Grumpier Old Men (1995)::Comedy|Romance
4::Waiting to Exhale (1995)::Comedy|Drama
5::Father of the Bride Part II (1995)::Comedy
6::Heat (1995)::Action|Crime|Thriller
7::Sabrina (1995)::Comedy|Romance
8::Tom and Huck (1995)::Adventure|Children's
9::Sudden Death (1995)::Action
10::GoldenEye (1995)::Action|Adventure|Thriller  
....
....

每行数据由三部分构成 MovieID::Title::Genres (Genres可能有多个)

users.dat

1::F::1::10::48067
2::M::56::16::70072
3::M::25::15::55117
4::M::45::7::02460
5::M::25::20::55455
6::F::50::9::55117
7::M::35::1::06810
8::M::25::12::11413
9::M::25::17::61614
10::F::35::1::95370
....
....

每行数据由四部分构成 UserID::Gender::Age::Occupation::Zip-code

 

ratings.dat

1::1193::5::978300760
1::661::3::978302109
1::914::3::978301968
1::3408::4::978300275
1::2355::5::978824291
1::1197::3::978302268
1::1287::5::978302039
1::2804::5::978300719
1::594::4::978302268
1::919::4::978301368
....
....

每行数据由四部分构成 UserID::MovieID::Rating::Timestamp 

看到这,你可能还对数据内容即格式有疑问,实际上我这个算法是最基本的算法,只需知道有多少部电影,有多少个用户,每个用户对那些电影评过分即可,想深入了解数据的自己去看README文档,看懂了告诉我Zip-code是什么意思

Code

代码来源:https://github.com/Lockvictor/MovieLens-RecSys

Lockvictor 大哥写了一个类 class UserBasedCF()来实现算法,类中包括这几个函数

def __init__(self)					初始化变量
def loadfile(filename)					读取文件到一个生成器里面
def generate_dataset(self, filename, pivot=0.7)		得到训练集,测试集,及其他参数
def calc_user_sim(self)					计算用户相似度
def recommend(self, user)				给用户做出推荐列表
def evaluate(self)					评价算法

来看一下我认为这段代码中比较值得注意的几个点

def __init__(self)

    def __init__(self):

        self.trainset = {}

        self.testset = {}

        self.n_sim_user = 20

        self.n_rec_movie = 10

        self.user_sim_mat = {}

        self.movie_popular = {}

        self.movie_count = 0

        print('Similar user number = %d' % self.n_sim_user, file=sys.stderr)

        print('recommended movie number = %d' %

              self.n_rec_movie, file=sys.stderr)

这个初始化函数中很多变量采用了字典类型的数据结构,对字典不熟悉的点这里,Python 字典(Dictionary)

print()函数中 file=sys.stderr 的意思是输出的字体为红色,红色字体本意是报错用的,不知道作者咋想的。

 

def loadfile(filename)

 def loadfile(filename):

        ''' load a file, return a generator. '''

        fp = open(filename, 'r')

        for i, line in enumerate(fp):

            yield line.strip('\r\n')

            if i % 100000 == 0:
                print('loading %s(%s)' % (filename, i), file=sys.stderr)

        fp.close()

        print('load %s succ' % filename, file=sys.stderr)

注意代码中的yield,yield 的作用就是把loadfile()函数变成一个 generator,这个generator中存放了所有行的数据。

深入了解yield点这里,Python yield 使用浅析

 

def generate_dataset(self, filename, pivot=0.7)

这段代码太长,不复制了。

参数pivot=0.7意思是随机切分数据集时,训练集70%,测试集30%

调用这个函数之后,可以得到 

self.trainset = {user1:{movie1:rating, movie2:rating ...} user2:{...}...}	训练集 

self.testset = {user1:{movie1:rating, movie2:rating ...} user2:{...}...}	测试集

两层的字典,第一层为用户ID,第二层中KEY为用户看过的电影ID,VALUE为用户对这部电影的评分。

 

def calc_user_sim(self)

调用这个函数之后,可以得到

movie2users 		传说中的倒查表
self.user_sim_mat   	用户相似度矩阵
self.movie_popular	每个电影的流行度
self.movie_count 	训练集中电影总数

这四个变量如何得到的详见代码,不可能看不懂的

def recommend(self, user)

函数返回值 return sorted(rank.items(), key=itemgetter(1), reverse=True)[0:N]

这个函数是什么意思呢,听我解释,它干了这么个事

举个例子,对于测试集用的用户A,由相似度矩阵,找到与其相似度最高的前20个用户,

然后找出这20个用户看过而A没看过的电影 movie1,movie2,movie3,movie1,.....

注意到前面出现了movie1出现了两次,因为这20个用户中有人看过相同的电影,把相同的电影对应的用户相似度相加

得到{movie:similarity_factor...},然后找到similarity_factor排名前10的推荐给A用户。

认真看代码能看懂。

def evaluate(self)

这个函数涉及到了召回率和准确率

precision = hit / (1.0 * rec_count) 

recall = hit / (1.0 * test_count)

hit 是测试集中用户的评分列表里有,给用户的推荐列表里有,而测试集中用户的评分列表里没有的电影总数。

就是你推荐了用户没有看过的又 喜欢的电影总数。

所以举个例子,准确率就是,你推荐了100个,hit 了10个,那准确率就是10%

而召回率就是,你理论上可以推荐100个让用户 hit 的电影,而你只推荐了这100个里面的20个,召回率就是20%

其他的指标覆盖率等等,详见代码。

下面附上完整代码

# -*- coding: utf-8 -*-
'''
Created on 2015-06-22
@author: Lockvictor

'''

import sys
import random
import math
import os

from operator import itemgetter

random.seed(0)


class UserBasedCF(object):
    ''' TopN recommendation - User Based Collaborative Filtering '''

    def __init__(self):

        self.trainset = {}

        self.testset = {}

        self.n_sim_user = 20

        self.n_rec_movie = 10

        self.user_sim_mat = {}

        self.movie_popular = {}

        self.movie_count = 0

        print('Similar user number = %d' % self.n_sim_user, file=sys.stderr)

        print('recommended movie number = %d' %

              self.n_rec_movie, file=sys.stderr)

    @staticmethod
    def loadfile(filename):

        ''' load a file, return a generator. '''

        fp = open(filename, 'r')

        for i, line in enumerate(fp):

            yield line.strip('\r\n')

            if i % 100000 == 0:
                print('loading %s(%s)' % (filename, i), file=sys.stderr)

        fp.close()

        print('load %s succ' % filename, file=sys.stderr)

    def generate_dataset(self, filename, pivot=0.7):

        ''' load rating data and split it to training set and test set '''

        trainset_len = 0

        testset_len = 0

        for line in self.loadfile(filename):

            user, movie, rating, _ = line.split('::')

            # split the data by pivot

            if random.random() < pivot:

                self.trainset.setdefault(user, {})

                self.trainset[user][movie] = int(rating)

                trainset_len += 1

            else:

                self.testset.setdefault(user, {})

                self.testset[user][movie] = int(rating)

                testset_len += 1

        print('split training set and test set succ', file=sys.stderr)

        print('train set = %s' % trainset_len, file=sys.stderr)

        print('test set = %s' % testset_len, file=sys.stderr)

    def calc_user_sim(self):

        ''' calculate user similarity matrix '''

        # build inverse table for item-users

        # key=movieID, value=list of userIDs who have seen this movie

        print('building movie-users inverse table...', file=sys.stderr)

        movie2users = dict()

        for user, movies in self.trainset.items():
            print(user,movies)
            for movie in movies:

                # inverse table for item-users

                if movie not in movie2users:
                    movie2users[movie] = set()

                movie2users[movie].add(user)

                # count item popularity at the same time

                if movie not in self.movie_popular:
                    self.movie_popular[movie] = 0

                self.movie_popular[movie] += 1

        print('build movie-users inverse table succ', file=sys.stderr)

        # save the total movie number, which will be used in evaluation

        self.movie_count = len(movie2users)

        print('total movie number = %d' % self.movie_count, file=sys.stderr)

        # count co-rated items between users

        usersim_mat = self.user_sim_mat

        print('building user co-rated movies matrix...', file=sys.stderr)

        for movie, users in movie2users.items():

            for u in users:

                for v in users:

                    if u == v:
                        continue

                    usersim_mat.setdefault(u, {})

                    usersim_mat[u].setdefault(v, 0)

                    usersim_mat[u][v] += 1

        print('build user co-rated movies matrix succ', file=sys.stderr)

        # calculate similarity matrix

        print('calculating user similarity matrix...', file=sys.stderr)

        simfactor_count = 0

        PRINT_STEP = 2000000

        for u, related_users in usersim_mat.items():

            for v, count in related_users.items():

                usersim_mat[u][v] = count / math.sqrt(

                    len(self.trainset[u]) * len(self.trainset[v]))

                simfactor_count += 1

                if simfactor_count % PRINT_STEP == 0:
                    print('calculating user similarity factor(%d)' %

                          simfactor_count, file=sys.stderr)

        print('calculate user similarity matrix(similarity factor) succ',

              file=sys.stderr)

        print('Total similarity factor number = %d' %

              simfactor_count, file=sys.stderr)

    def recommend(self, user):

        ''' Find K similar users and recommend N movies. '''

        K = self.n_sim_user

        N = self.n_rec_movie

        rank = dict()

        watched_movies = self.trainset[user]

        for similar_user, similarity_factor in sorted(self.user_sim_mat[user].items(),

                                                      key=itemgetter(1), reverse=True)[0:K]:

            for movie in self.trainset[similar_user]:

                if movie in watched_movies:
                    continue

                # predict the user's "interest" for each movie

                rank.setdefault(movie, 0)

                rank[movie] += similarity_factor

        # return the N best movies

        return sorted(rank.items(), key=itemgetter(1), reverse=True)[0:N]

    def evaluate(self):

        ''' print evaluation result: precision, recall, coverage and popularity '''

        print('Evaluation start...', file=sys.stderr)

        N = self.n_rec_movie

        #  varables for precision and recall

        hit = 0

        rec_count = 0

        test_count = 0

        # varables for coverage

        all_rec_movies = set()

        # varables for popularity

        popular_sum = 0

        for i, user in enumerate(self.trainset):

            if i % 500 == 0:
                print('recommended for %d users' % i, file=sys.stderr)

            test_movies = self.testset.get(user, {})

            rec_movies = self.recommend(user)

            for movie, _ in rec_movies:

                if movie in test_movies:
                    hit += 1

                all_rec_movies.add(movie)

                popular_sum += math.log(1 + self.movie_popular[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)

        popularity = popular_sum / (1.0 * rec_count)

        print('precision=%.4f\trecall=%.4f\tcoverage=%.4f\tpopularity=%.4f' %

              (precision, recall, coverage, popularity), file=sys.stderr)


if __name__ == '__main__':
    ratingfile = os.path.join('ml-1m', 'ratings.dat')

    usercf = UserBasedCF()

    usercf.generate_dataset(ratingfile)

    usercf.calc_user_sim()

    usercf.evaluate()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值