电影推荐系统
目录
电影推荐系统 1
- 数据描述 1
- 变量说明 1
- 程序介绍 2
本次课程作业在 small-movielens 数据集的基础上,对用户、电影、评分数据进行了处理,然后根据 Pearson 相关系数计算出用户与其他用户之间的相似度,根据相似度进行推荐和预测评分,最后再根据数据集中的测试数据,计算该推荐系统的MAE等。
数据描述
数据集来源于 MovieLens|GroupLens 网站。完整的数据集是由 943 个用户对 1682 个项目的 100000 个评分组成。每位用户至少对 20 部电影进行了评分。用户和项从 1 开始连续编号。数据是随机排列的。这是一个以选项卡分隔的:用户id|项id|分级|时间戳 列表。从 1970年1月1日 UTC 开始,时间戳是unix时间戳。在这些数据集的基础上,small-movielens 还包括 u1-u5,ua,ub 等七组测试集 (.base)/训练集 (.test) 数据。其中,u1-u5 是以 8:2 的比例随机生成互斥的训练集和测试集;ua、ub 是按照 9:1 的比例随机生成的互斥的训练集和测试集,不同的是,这两组数据的测试集中每个用户是对正好 10 部不同电影进行了评分。数据集如下图所示。
变量说明
users :保存所有用户,不重复 list 类型 存储结构:[user1, user2,…]
userWatchedMovie :保存所有用户看过的所有电影,字典嵌套字典类型 存储结构:{user1:{movie1:rating1, movie2:rating2, …}, user2:{…},…}
movieUser :保存用户与用户之间共同看过的电影,字典嵌套字典嵌套 list 存储结构:{user1:{user2:[movie1,…],user3:[…],…},user2:{user3:[…],…},…}
userSimilarity :保存用户与用户之间的相似度(皮尔逊相似度) 存储结构:{user1:{user2:sim, user3:sim,…}, user2:{user3:sim, …}, …}
allUserTopNSim :保存每个用户都取前 n=10 个最相似的用户,以及相似度 存储结构:{user1:{user01:sim,user02:sim,…},user2:{user01:sim,…},…}
recommendedMovies :从最相似的用户中推荐,每个相似用户推荐两部,同时计算出预测值并保存在这个变量里 存储结构:{user1:{user01:{movie01:predictionRating,…},user02:[…],…},user2:{user01:[…],…},…}
usersTest :测试集文件中的所有用户 存储结构:同 users
userWatchedMovieTest :测试集文件中所有用户看过的所有电影 存储结构:同 userWatchedMovie
movieAlsoInTest :保存推荐的电影正好也在用户测试数据中看过的那一些电影,以便后面进行 MAE 计算 存储结构:{user1:[movie1,movie2,…],…}
averageRating :保存每个用户对被推荐的电影的预测平均分 存储结构:{user1:{movie01:[count,sumPreRating,averageRating],…},…}
eachUserMAE :保存对每个用户而言计算出的 MAE 存储结构:{user1:MAE,user2:MAE,…}
# -*- coding:utf-8 -*-from math import sqrtimport osTOPN = 10# 设置每个用户相似度最高的前TOPN个用户AMOUNT = 5# 设置前TOPN个相似用户中每个用户给当前用户推荐的电影数量# 读取训练集数据文件filePath = './data/u1.base'users = []# 保存所有用户,不重复list类型userWatchedMovie = {}# 保存所有用户看过的所有电影,字典嵌套字典类型movieUser = {}# 保存用户与用户之间共同看过的电影,字典嵌套字典嵌套listwith open(filePath, 'r') as trainFile:# 计算所有的用户以及用户看过的电影和评分,保存到相关字典变量中for line in trainFile:# type(line)是string类型,其中分离出来的userId等也都是string类型# strip()方法是去除指定首尾字符(userId, movieId, rating, timestamp) = line.strip('\n').split('\t')if userId not in users:users.append(userId)userWatchedMovie.setdefault(userId, {})userWatchedMovie[userId][movieId] = float(rating)# 计算用户与用户共同看过的电影for x in range(len(users)-1):movieUser.setdefault(users[x], {})for y in range(x+1, len(users)):movieUser[users[x]].setdefault(users[y], [])for m in userWatchedMovie[users[x]].keys():if m in userWatchedMovie[users[y]].keys():movieUser[users[x]][users[y]].append(m)# 计算用户与用户之间的相似度,皮尔逊相似度userSimilarity = {}for a in movieUser.keys():userSimilarity.setdefault(a, {})for b in movieUser[a].keys():userSimilarity[a].setdefault(b, 0)if len(movieUser[a][b]) == 0:userSimilarity[a][b] = 0# 如果两个人没有看过同一部电影,则相似度为0continueelse:# 下面开始进行相似度的计算,皮尔森相关系数avgUserA = 0# A用户打分的平均值avgUserB = 0# B用户打分的平均值numerator = 0# Pearson的分子部分denominatorA = 0# Pearson的分母A部分denominatorB = 0# Pearson的分母B部分count = len(movieUser[a][b])# 保存两个用户共同看过的电影的数量# 这里用到了一个召回率的因素,因为在前述的实验过程中,发现最相似的用户# 相似度达到了 1.0 ,感觉有点不正常,比如说用户1和用户9,共同只看过两部电影,但是相似度却等于 1.0# 这个是不太正确的,所以加入了召回率的考量# 加入之后,发现用户1和用户9的相似度从1.0下降到0.19,感觉是比较合理的factor = 0if count > 20:# 20是自己指定的factor = 1.0else:if count < 0:factor = 0else:factor = (-0.0025 * count * count) + (0.1 * count)for movie in movieUser[a][b]:# 对于两个用户都看过的每一部电影,进行计算avgUserA += float(userWatchedMovie[a][movie])avgUserB += float(userWatchedMovie[b][movie])avgUserA = float(avgUserA / count)avgUserB = float(avgUserB / count)# print(avgUserA)# print(avgUserB)for m in movieUser[a][b]:# print(userWatchedMovie[a][m])tempA = float(userWatchedMovie[a][m]) - avgUserAtempB = float(userWatchedMovie[b][m]) - avgUserB# print(tempA)numerator += tempA * tempBdenominatorA += pow(tempA, 2) * 1.0denominatorB += pow(tempB, 2) * 1.0# print(numerate)if denominatorA != 0 and denominatorB != 0:userSimilarity[a][b] = factor * (numerator / (sqrt(denominatorA * denominatorB)))else:userSimilarity[a][b] = 0# 每个用户都取前n个最相似的用户,以便后续进行推荐# singleUserTopNSim = {}allUserTopNSim = {}for currentUserId in users:# 计算当前用户的最相似的前n个用户singleUserSim = {}# 存放单个用户对所有用户的相似度allUserTopNSim.setdefault(currentUserId, {})# 存放所有用户的TopN相似的用户for compareUserId in users:if currentUserId == compareUserId:breakelse:singleUserSim[compareUserId] = userSimilarity[compareUserId][currentUserId]if int(currentUserId) != len(users):singleUserSim.update(userSimilarity[currentUserId])# print(currentUserId, end=' ')# print(singleUserSim)# python中的字典是无序的,但是有时候会根据value值来取得字典中前n个值,# 此处思想是将字典转化成list,经过排序,取得前n个值,再将list转化回字典# 进行排序,取前N个相似的用户,此时singleSortedSim是一个list类型:[(key,value),(key,value),(key,value),...]singleSortedSim = sorted(singleUserSim.items(), key=lambda item: item[1], reverse=True)singleTopN = singleSortedSim[:TOPN]# 取出前N个最相似的值for single in singleTopN:allUserTopNSim[currentUserId][single[0]] = single[1]# 保存当前用户计算出的TopN个相似用户以及相似度# 从最相似的用户中推荐,每个相似用户推荐number部,那么每个用户就能得到推荐的number*10部电影recommendedMovies = {}for oneUser in allUserTopNSim.keys():recommendedMovies.setdefault(oneUser, {})for simUser in allUserTopNSim[oneUser].keys():oneMovieList = []simUserMovieList = []number = 0recommendedMovies[oneUser].setdefault(simUser, {})for movie in userWatchedMovie[simUser].keys():if number >= AMOUNT:# 每个人推荐数量为number部的电影数breakif movie not in userWatchedMovie[oneUser].keys():# and (movie not in recommendedMovies[oneUser]):# 计算预测权值if int(oneUser) < int(simUser):# oneMovieList.append(list(movieUser[oneUser][simUser]))# simUserMovieList.append(list(movieUser[oneUser][simUser]))length = len(movieUser[oneUser][simUser])#simUserMovieList.append(movie)#tupleOne = tuple(oneMovieList)#tupleSim = tuple(simUserMovieList)sumOne = 0.0sumSim = 0.0for i in movieUser[oneUser][simUser]:sumOne += userWatchedMovie[oneUser].get(i)sumSim += userWatchedMovie[simUser].get(i)sumSim += userWatchedMovie[simUser].get(movie)avgOneUser = sumOne / lengthavgSimUser = sumSim / (length + 1)predictionRating = avgOneUser + (userWatchedMovie[simUser][movie] - avgSimUser)recommendedMovies[oneUser][simUser][movie] = predictionRating#这是一个需要计算的值number += 1else:# oneMovieList.append(movieUser[simUser][oneUser])# simUserMovieList.append(movieUser[simUser][oneUser])length = len(movieUser[simUser][oneUser])# simUserMovieList.append(movie)sumOne = 0sumSim = 0for i in movieUser[simUser][oneUser]:sumOne += userWatchedMovie[oneUser].get(i)sumSim += userWatchedMovie[simUser].get(i)sumSim += userWatchedMovie[simUser].get(movie)avgOneUser = sumOne / lengthavgSimUser = sumSim / (length + 1)predictionRating = avgOneUser + (userWatchedMovie[simUser][movie] - avgSimUser)recommendedMovies[oneUser][simUser][movie] = predictionRating#这是一个需要计算的值number += 1else:continue'''# 读取测试集数据文件filePathTest = './data/u1.test'usersTest = []# 保存所有用户,不重复list类型userWatchedMovieTest = {}# 保存所有用户看过的所有电影,字典嵌套字典类型with open(filePathTest, 'r') as testFile:# 计算所有的用户以及用户看过的电影和评分,保存到相关字典变量中for lineTest in testFile:(userIdTest, movieIdTest, ratingTest, timestampTest) = lineTest.strip('\n').split('\t')if userIdTest not in usersTest:usersTest.append(userIdTest)userWatchedMovieTest.setdefault(userIdTest, {})userWatchedMovieTest[userIdTest][movieIdTest] = ratingTest''''''# 要找到在测训练集中用户没有看过,但是在测试集中用户看过并且被推荐过的电影,这样后面好计算MAE(平均绝对误差)movieRecommendedAlsoInTest = {}for user in usersTest:movieRecommendedAlsoInTest.setdefault(user, [])for m in recommendedMovies[user]:if m in userWatchedMovieTest[user].keys():movieRecommendedAlsoInTest[user].append(m)''''''writeFilePath = './data/movieuser.txt'if os.path.exists(writeFilePath):os.remove(writeFilePath)writeFile = open(writeFilePath, 'w')for m in movieUser.keys():for n in movieUser[m].keys():writeFile.writelines([m, '\t', n, '\t', str(movieUser[m][n])])writeFile.write('\n')writeFile.close()'''writeFilePath = './data/userWatchedMovie.txt'if os.path.exists(writeFilePath):os.remove(writeFilePath)writeFile = open(writeFilePath, 'w')for m in userWatchedMovie.keys():for n in userWatchedMovie[m].keys():writeFile.writelines([m, '\t', n, '\t', str(userWatchedMovie[m][n])])writeFile.write('\n')writeFile.close()writeFilePath = './data/allUserTop10Sim.txt'if os.path.exists(writeFilePath):os.remove(writeFilePath)writeFile = open(writeFilePath, 'w')for m in allUserTopNSim.keys():for n in allUserTopNSim[m].keys():writeFile.writelines([m, '\t', n, '\t', str(allUserTopNSim[m][n])])writeFile.write('\n')writeFile.close()writeFilePath = './data/recoMovieWithRating.txt'if os.path.exists(writeFilePath):os.remove(writeFilePath)writeFile = open(writeFilePath, 'w')for m in recommendedMovies.keys():for n in recommendedMovies[m].keys():writeFile.writelines([m, '\t', n, '\t', str(recommendedMovies[m][n])])writeFile.write('\n')writeFile.close()writeFilePath = './data/userSimilarity.txt'if os.path.exists(writeFilePath):os.remove(writeFilePath)writeFile = open(writeFilePath, 'w')for m in userSimilarity.keys():for n in userSimilarity[m].keys():writeFile.writelines([m, '\t', n, '\t', str(userSimilarity[m][n])])writeFile.write('\n')writeFile.close()