采用 GroupLens 提供的 MovieLens 数据集

数据集下载链接:​​https://grouplens.org/datasets/movielens/​

基于用户的协同过滤算法_相似度


采用此版本的数据集:该数据集包含 6000 多用户对 4000 多部电影的 100 万条评分。

使用TopN推荐方法

评测指标

基于用户的协同过滤算法_相似度_02


基于用户的协同过滤算法_相似度_03


基于用户的协同过滤算法_相似度_04


最后,我们还需要评测推荐的新颖度,这里用推荐列表中物品的平均流行度度量推荐结果的

新颖度。如果推荐出的物品都很热门,说明推荐的新颖度较低,否则说明推荐结果比较新颖。

基于用户的协同过滤算法

基于用户的协同过滤算法主要包括两个步骤。

(1) 找到和目标用户兴趣相似的用户集合。

(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。

基于用户的协同过滤算法_lua_05


例子如下:

基于用户的协同过滤算法_数据集_06


之前一直看不懂{a,b,d}||{a,c}为什么是等于6,看完别人的代码才知道这是len(A_movie)*len(B_movie),就是A看过的电影的部数乘以B看过的电影的部数

基于用户的协同过滤算法_lua_07

用户相似度计算的改进

基于用户的协同过滤算法_lua_08


经过一天的挣扎,终于把大神的代码给看懂、注释完了:

import sys
import random
import math
import os
from operator import itemgetter
from collections import defaultdict

random.seed(0) #设置好随机种子,即相同的随机种子seed


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

def __init__(self):
self.trainset = {} #训练数据集
self.testset = {} #测试数据集
self.n_sim_user = 20 #兴趣最近的20个用户
self.n_rec_movie = 10 #系统推荐的10部电影
self.user_sim_mat = {} #用户兴趣相似度矩阵
self.movie_popular = {} #电影的欢迎系数
self.movie_count = 0 #电影的数量
print ('Similar user number = %d' % self.n_sim_user)
print ('recommended movie number = %d' % self.n_rec_movie)

def generate_dataset(self, filename, pivot=0.7):
trainset_len = 0 #训练集的大小
testset_len = 0 #测试集的大小
for line in loadfile(filename): #遍历文件的每一行
user, movie, rating, _ = line.split('::') #切割文本,第一个是用户号。第二个是电影号,第三个是评分
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')
print ('train set = %s' % trainset_len)
print ('test set = %s' % testset_len)

def calc_user_sim(self): #计算用户之间的兴趣相似度
print ('building movie-users inverse table...')
movie2users = dict()

for user, movies in self.trainset.items():
for movie in movies: #遍历每一部电影
if movie not in movie2users:
movie2users[movie] = set() #每部电影的观众的集合
movie2users[movie].add(user) #将该观众加入到该电影的观众集合中
if movie not in self.movie_popular: #如果该电影部长电影流行度数组
self.movie_popular[movie] = 0 #将该电影的流行度初始化为0
self.movie_popular[movie] += 1 #每部电影的观影人数加一
print ('build movie-users inverse table succ')

# save the total movie number, which will be used in evaluation
self.movie_count = len(movie2users) #获得电影的部数
print ('total movie number = %d' % self.movie_count)

# count co-rated items between users
usersim_mat = self.user_sim_mat #用户之间的兴趣相似度矩阵
print ('building user co-rated movies matrix...')

for movie, users in movie2users.items(): #循环每一个键值对,即 for key,values in xxx.items()
for u in users: #u、v观众是否在同一部电影的观众集合里面
usersim_mat.setdefault(u, defaultdict(int))
for v in users:
if u == v:
continue
usersim_mat[u][v] += 1 #如果在同一部电影的观众里面,则兴趣点加一
print ('build user co-rated movies matrix succ')

# calculate similarity matrix
print ('calculating user similarity matrix...')
simfactor_count = 0

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

print ('calculate user similarity matrix(similarity factor) succ')
print ('Total similarity factor number = %d' % simfactor_count)

def recommend(self, user):
''' Find K similar users and recommend N movies. 找到兴趣最近的前20个用户,从中找到最适合的前10部电影'''
K = self.n_sim_user #前面给出是20
N = self.n_rec_movie #前面给出是10
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]:
#排序,找出兴趣相似度最高的前20个用户
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
#返回最好的N部电影
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...')
N = self.n_rec_movie #n_rec_movie在前面已经给出是10
# 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): #i为下标,user为训练集的内容
test_movies = self.testset.get(user, {})
rec_movies = self.recommend(user) #此处的recommend是该函数的前一个函数,即系统推荐的最适合的10部电影
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))


def loadfile(filename): #加载文件
''' load a file, return a generator. '''
fp = open(filename, 'r')
for i, line in enumerate(fp): #循环遍历文件的每一行
yield line.strip('\r\n') #返回文件的一行内容,yield可以当做return
fp.close()
print ('load %s succ' % filename)


if __name__ == '__main__':
ratingfile = os.path.join('ml-1m', 'ratings.dat')
usercf = UserBasedCF() #类的实例化
usercf.generate_dataset(ratingfile)
usercf.calc_user_sim()
usercf.evaluate()