多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于Python协同过滤的电影推荐系统毕业设计实战指南

基于Python协同过滤的电影推荐系统毕业设计实战指南 简介这份资源是面向计算机相关专业毕业设计场景的完整项目包主题为基于Python与协同过滤算法的电影推荐系统适合需要完成毕设、课程设计或自学推荐算法与Web开发的学生参考。项目采用Django框架搭配MySQL数据库区分管理员与用户两种角色管理员可管理用户、电影分类、电影信息、评分与系统配置用户端则支持注册登录、浏览电影与评分推荐协同过滤算法负责生成个性化推荐结果。压缩包共689个文件约21.77MB包含38个py源码、33个vue前端组件、20个html页面、2个sql建库脚本以及svg、js、css、png等前端静态资源另附万字论文文档与mp4视频演示并配有安装、运行、构建等bat脚本方便本地部署。已有73人学习下载评审分达95分以上源码经本地编译调试可运行能帮助读者快速理解推荐系统从数据建模到前后端联调的完整实现路径。1. 从零手搓一个电影推荐系统协同过滤到底能不能扛住毕业设计很多同学做毕业设计时第一反应是找个现成模板改改界面结果答辩时被老师一句“你这推荐结果怎么来的”问得哑口无言。基于 Python 和协同过滤算法的电影推荐系统核心不在界面多花哨而在于你能不能把“用户-物品-评分”这三者之间的关系讲清楚并且用代码跑出一条可解释的推荐链路。它解决的是信息过载下的个性化排序问题适合有一定 Python 基础、想用真实数据集完成从数据处理到算法落地全流程的本科生。你不需要深度学习框架一台普通笔记本就能跑通但前提是理解相似度计算和评分预测的数学直觉否则调参就是玄学。2. 协同过滤的两种路线UserCF 和 ItemCF 到底选哪个2.1 从“人以群分”到“物以类聚”的直觉协同过滤的本质是利用群体行为数据做推荐。UserCF 的思路是找到和你观影口味相似的一批人把他们喜欢但你没看过的电影推给你。ItemCF 的思路是找到和你之前看过的电影相似的片子直接推给你。两者没有绝对优劣但适用场景不同。UserCF 更依赖用户活跃度适合用户量远小于物品量的场景比如新闻推荐ItemCF 更稳定因为电影的数量和特征相对固定用户评分矩阵虽然稀疏但物品之间的相似度可以离线算好线上响应快。对于电影推荐系统我一般会优先选 ItemCF原因是电影库更新频率低相似度矩阵可以定期重算而且解释性强——“因为你看了《星际穿越》所以推荐《盗梦空间》”这种理由用户一看就懂。2.2 相似度计算的三种武器余弦、皮尔逊、调整余弦选好路线后下一步是算相似度。余弦相似度只看向量方向忽略评分尺度差异皮尔逊相关系数会减去用户平均分能消除“有人习惯打高分、有人习惯打低分”的偏差调整余弦则在物品维度上减去物品平均分适合物品评分偏差大的场景。在 MovieLens 数据集上我实测下来皮尔逊在 UserCF 上表现更稳而 ItemCF 用调整余弦效果更好。下面是一个用 Python 计算物品间调整余弦相似度的核心片段import numpy as np def adjusted_cosine_similarity(ratings_matrix): ratings_matrix: shape (n_items, n_users), 缺失值为0 返回物品间相似度矩阵 # 计算每个物品的平均评分仅考虑有评分的用户 item_means np.true_divide( ratings_matrix.sum(axis1), (ratings_matrix ! 0).sum(axis1), where(ratings_matrix ! 0).sum(axis1) ! 0 ) # 中心化每个评分减去该物品平均分 centered ratings_matrix - item_means[:, np.newaxis] centered[ratings_matrix 0] 0 # 缺失值保持为0 # 计算余弦相似度 norm np.linalg.norm(centered, axis1, keepdimsTrue) norm[norm 0] 1e-9 # 防止除零 normalized centered / norm sim_matrix normalized normalized.T return sim_matrix这段代码的关键在于centered矩阵的构造只对已有评分做中心化缺失值置零后不参与相似度计算。item_means的计算用了np.true_divide的where参数避免除零警告。最后归一化后做矩阵乘法得到物品间相似度。参数方面ratings_matrix的维度是物品数乘以用户数MovieLens 100K 数据集大约是 1682 部电影、943 个用户矩阵规模完全在内存里跑得动。2.3 评分预测与 Top-N 推荐生成有了相似度矩阵预测用户 u 对物品 i 的评分公式是取 i 最相似的 K 个物品用它们的相似度加权平均用户对它们的评分。K 是个关键参数太小则推荐不稳定太大则引入噪声。我一般从 10 开始试在验证集上看 RMSE 和 RecallN 的平衡。下面是对指定用户生成 Top-N 推荐的代码def recommend_for_user(user_id, ratings_matrix, sim_matrix, top_k20, n_rec10): user_id: 用户索引 ratings_matrix: (n_items, n_users) sim_matrix: (n_items, n_items) top_k: 选取最相似的K个物品 n_rec: 推荐数量 user_ratings ratings_matrix[:, user_id] already_rated np.where(user_ratings 0)[0] scores [] for i in range(ratings_matrix.shape[0]): if user_ratings[i] 0: continue # 跳过已评分物品 # 找到与物品i最相似的top_k个物品 sim_scores sim_matrix[i] top_k_idx np.argsort(sim_scores)[-top_k:] # 加权平均预测评分 numerator 0.0 denominator 0.0 for j in top_k_idx: if user_ratings[j] 0: numerator sim_scores[j] * user_ratings[j] denominator abs(sim_scores[j]) if denominator 0: pred numerator / denominator scores.append((i, pred)) scores.sort(keylambda x: x[1], reverseTrue) return scores[:n_rec]逻辑说明先拿到目标用户的评分向量遍历所有未评分的物品对每个物品找最相似的 K 个邻居用邻居的相似度加权邻居评分得到预测分。注意denominator用了绝对值因为相似度可能为负。参数top_k控制邻居数量n_rec控制返回条数。实际部署时会把物品相似度矩阵离线算好存成文件线上只做查表和加权求和响应时间能压到毫秒级。3. 用 MovieLens 数据集跑通完整流程从加载到评估3.1 数据加载与稀疏矩阵构建MovieLens 数据集是推荐系统入门的标准选择常见的是 ml-latest-small包含 10 万条评分、600 个用户、9000 部电影。数据格式是 CSV三列userId、movieId、rating。加载后需要构建用户-物品评分矩阵。直接用 NumPy 二维数组会浪费大量内存因为稀疏度超过 95%。我一般用 scipy.sparse 的 csr_matrix但为了协同过滤计算方便转成稠密矩阵在 10 万条规模下也才 9000×600约 540 万个浮点数内存占用 40MB 左右完全可接受。import pandas as pd import numpy as np # 加载评分数据 ratings pd.read_csv(ml-latest-small/ratings.csv) # 构建索引映射 user_ids ratings[userId].unique() movie_ids ratings[movieId].unique() user_to_idx {uid: i for i, uid in enumerate(user_ids)} movie_to_idx {mid: i for i, mid in enumerate(movie_ids)} # 构建评分矩阵 (n_movies, n_users) n_movies len(movie_ids) n_users len(user_ids) rating_matrix np.zeros((n_movies, n_users)) for row in ratings.itertuples(): rating_matrix[movie_to_idx[row.movieId], user_to_idx[row.userId]] row.rating print(f矩阵形状: {rating_matrix.shape}, 稀疏度: {1 - np.count_nonzero(rating_matrix) / rating_matrix.size:.4f})这段代码先读 CSV建立用户和电影的索引映射然后填充矩阵。注意矩阵方向是电影×用户因为 ItemCF 需要按物品维度算相似度。稀疏度打印出来通常在 0.94 到 0.98 之间说明数据非常稀疏这也是推荐系统面临的核心挑战之一。3.2 训练集与测试集划分的两种策略评估推荐系统不能随便切分数据。常见做法有两种随机划分和留一法。随机划分是按比例如 8:2随机选评分作为测试集适合评估评分预测的 RMSE。留一法是每个用户保留最后一个评分作为测试其余作训练适合评估 Top-N 推荐的命中率。我一般两个都做用随机划分看 RMSE用留一法看 Recall10。注意不能按时间顺序切分后还用未来数据训练那会造成信息泄露答辩时被问到就翻车了。from sklearn.model_selection import train_test_split # 随机划分 train_data, test_data train_test_split(ratings, test_size0.2, random_state42) # 重建训练矩阵 train_matrix np.zeros((n_movies, n_users)) for row in train_data.itertuples(): train_matrix[movie_to_idx[row.movieId], user_to_idx[row.userId]] row.ratingrandom_state42保证每次划分一致方便复现。测试集不参与相似度计算和评分预测只用来算误差。3.3 评估指标RMSE、RecallN 和覆盖率RMSE 衡量评分预测准不准公式是预测分和真实分之差的均方根。RecallN 衡量 Top-N 推荐里命中了多少测试集里的物品。覆盖率衡量推荐系统能覆盖多少比例的电影避免总是推那几部热门片。这三个指标要一起看RMSE 低但 Recall 也低说明模型只会预测平均分Recall 高但覆盖率低说明推荐结果同质化严重。在 MovieLens 上调好的 ItemCF 通常能做到 RMSE 0.85 左右Recall10 约 0.12覆盖率 0.3 上下。参数 K 从 10 增加到 50Recall 会先升后降拐点通常在 20 到 30 之间。4. 避坑指南协同过滤在毕业设计里最容易翻车的五个点4.1 冷启动问题新用户和新电影怎么推现象新注册用户没有任何评分ItemCF 算不出相似物品新上架电影没有评分也进不了推荐列表。原因协同过滤完全依赖历史行为数据没有行为就没有向量。解决用热门榜单兜底新用户先推评分人数最多的前 20 部电影新电影则用内容特征类型、导演、演员做基于内容的推荐等积累到一定评分后再切回协同过滤。毕业设计里至少要把兜底逻辑写出来否则答辩时老师一问“新用户怎么办”就卡住了。4.2 数据稀疏导致的相似度失真现象两个用户只共同评过一部电影且都打了 5 分算出来相似度是 1.0但这是偶然重合不是真实口味相似。原因余弦相似度在共同评分物品极少时会虚高。解决设置共同评分阈值比如共同评分少于 5 部电影的用户对不参与相似度计算或者用显著性加权相似度乘以min(共同评分数, 阈值) / 阈值。我在代码里一般加一行判断if len(common_items) 5: continue。4.3 热门物品偏置为什么推荐结果总是那几部现象不管给谁推荐Top-10 里总有《肖申克的救赎》《教父》这些经典片。原因这些电影评分人数多、平均分高在加权平均里天然占优。解决对物品热度做惩罚预测分除以log(1 物品评分人数)或者用 TF-IDF 思想降低热门物品的权重。另一个做法是限制每个导演或类型在推荐列表里的出现次数强制多样性。4.4 矩阵维度爆炸内存不够怎么办现象用完整 MovieLens 20M 数据集时矩阵变成 27000×138000稠密矩阵需要 30GB 内存直接 MemoryError。原因NumPy 稠密矩阵不压缩存储。解决改用 scipy.sparse 的 csr_matrix或者用 Surprise 库的 SVD 做矩阵分解降维。如果坚持用 ItemCF可以只保留评分次数超过阈值的用户和电影把矩阵裁到可处理规模。毕业设计用 ml-latest-small 就够了别贪大。4.5 评估时的数据泄露训练集里混入了测试集信息现象RMSE 低到 0.3看起来完美但线上推荐一塌糊涂。原因计算物品相似度时用了全量数据测试集的评分信息泄露到了相似度矩阵里。解决严格在训练集上算相似度矩阵测试集只用于预测和评估。代码里就是先划分再构建train_matrix所有相似度计算只传train_matrix。这个坑非常隐蔽但答辩老师如果懂行一眼就能看出来。5. 进阶技巧用 Surprise 库快速对比 SVD 和 KNN以及一个验证习惯5.1 用 Surprise 做基线对比省去手写评估代码手写协同过滤能帮你理解原理但做对比实验时Surprise 库更省事。它内置了 KNNBasic、SVD、NMF 等算法还封装了交叉验证和 RMSE 计算。下面是用 Surprise 跑 ItemCF 和 SVD 对比的代码from surprise import Dataset, Reader, KNNBasic, SVD from surprise.model_selection import cross_validate # 加载数据 reader Reader(rating_scale(0.5, 5.0)) data Dataset.load_from_df(ratings[[userId, movieId, rating]], reader) # ItemCF sim_options {name: cosine, user_based: False} algo_knn KNNBasic(sim_optionssim_options) results_knn cross_validate(algo_knn, data, measures[RMSE, MAE], cv5, verboseTrue) # SVD algo_svd SVD(n_factors50, n_epochs20, random_state42) results_svd cross_validate(algo_svd, data, measures[RMSE, MAE], cv5, verboseTrue)sim_options里user_basedFalse表示 ItemCF。SVD 的n_factors是隐因子维度50 到 100 之间比较合适n_epochs是迭代次数20 次通常收敛。跑完 5 折交叉验证后你会看到 SVD 的 RMSE 通常比 KNN 低 0.05 到 0.1但 KNN 的解释性更强。毕业设计里可以把两者都写上用表格对比显得工作量大且有理有据。5.2 一个验证习惯用 A/B 测试思维看离线指标离线指标好不代表线上效果好。我养成的习惯是每次调完参数除了看 RMSE还要随机抽 10 个用户人工看他们的推荐列表是否合理。比如一个只看动画片的用户推荐列表里如果出现恐怖片说明相似度计算有问题。这个习惯帮我抓过好几次隐蔽的 bug比如索引映射错位导致推荐结果完全随机。另外我会把推荐结果和热门榜单做对比如果重合度超过 80%说明模型没学到个性化信息需要检查相似度矩阵是不是没算对。5.3 论文里怎么把协同过滤讲出深度万字论文里协同过滤部分不要只贴公式。我一般会加一节“相似度度量对推荐多样性的影响”用实验数据说明余弦相似度倾向于推荐同类型电影皮尔逊相关系数能引入更多跨类型推荐调整余弦在两者之间取得平衡。再配一张不同 K 值下 Recall 和 Coverage 的折线图论文的实证感就出来了。最后把冷启动兜底策略和热门惩罚写成“工程优化”小节答辩时老师会觉得你不仅会调库还考虑到了实际部署。做毕业设计最怕的就是代码跑通但讲不出所以然。我的习惯是每写一个函数就在注释里用一句话说清“输入是什么、输出是什么、为什么这么算”。这样写论文时直接翻译注释就是技术描述省去大量回忆时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表