当前位置:   article > 正文

基于Python的电影推荐系统

基于python的电影推荐系统

电影推荐系统
目录
电影推荐系统 1

  1. 数据描述 1
  2. 变量说明 1
  3. 程序介绍 2

本次课程作业在 small-movielens 数据集的基础上,对用户、电影、评分数据进行了处理,然后根据 Pearson 相关系数计算出用户与其他用户之间的相似度,根据相似度进行推荐和预测评分,最后再根据数据集中的测试数据,计算该推荐系统的MAE等。

  1. 数据描述
    数据集来源于 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 部不同电影进行了评分。数据集如下图所示。
    在这里插入图片描述

  2. 变量说明
    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()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/531484
推荐阅读
相关标签
  

闽ICP备14008679号