电影推荐系统
目录
电影推荐系统 1
本次课程作业在 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 sqrt
import os
TOPN = 10 # 设置每个用户相似度最高的前TOPN个用户
AMOUNT = 5 # 设置前TOPN个相似用户中每个用户给当前用户推荐的电影数量
# 读取训练集数据文件
filePath = './data/u1.base'
users = [] # 保存所有用户,不重复list类型
userWatchedMovie = {} # 保存所有用户看过的所有电影,字典嵌套字典类型
movieUser = {} # 保存用户与用户之间共同看过的电影,字典嵌套字典嵌套list
with 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 # 如果两个人没有看过同一部电影,则相似度为0
continue
else:
# 下面开始进行相似度的计算,皮尔森相关系数
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 = 0
if count > 20: # 20是自己指定的
factor = 1.0
else:
if count < 0:
factor = 0
else:
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]) - avgUserA
tempB = float(userWatchedMovie[b][m]) - avgUserB
# print(tempA)
numerator += tempA * tempB
denominatorA += pow(tempA, 2) * 1.0
denominatorB += 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:
break
else:
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 = 0
recommendedMovies[oneUser].setdefault(simUser, {})
for movie in userWatchedMovie[simUser].keys():
if number >= AMOUNT: # 每个人推荐数量为number部的电影数
break
if 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.0
sumSim = 0.0
for i in movieUser[oneUser][simUser]:
sumOne += userWatchedMovie[oneUser].get(i)
sumSim += userWatchedMovie[simUser].get(i)
sumSim += userWatchedMovie[simUser].get(movie)
avgOneUser = sumOne / length
avgSimUser = sumSim / (length + 1)
predictionRating = avgOneUser + (userWatchedMovie[simUser][movie] - avgSimUser)
recommendedMovies[oneUser][simUser][movie] = predictionRating #这是一个需要计算的值
number += 1
else:
# oneMovieList.append(movieUser[simUser][oneUser])
# simUserMovieList.append(movieUser[simUser][oneUser])
length = len(movieUser[simUser][oneUser])
# simUserMovieList.append(movie)
sumOne = 0
sumSim = 0
for i in movieUser[simUser][oneUser]:
sumOne += userWatchedMovie[oneUser].get(i)
sumSim += userWatchedMovie[simUser].get(i)
sumSim += userWatchedMovie[simUser].get(movie)
avgOneUser = sumOne / length
avgSimUser = sumSim / (length + 1)
predictionRating = avgOneUser + (userWatchedMovie[simUser][movie] - avgSimUser)
recommendedMovies[oneUser][simUser][movie] = predictionRating #这是一个需要计算的值
number += 1
else:
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()