简介本资源为基于Python与协同过滤算法的电影推荐系统毕业设计全套资料面向计算机相关专业学生及需要完成课程设计或论文的开发者。项目采用Django框架与MySQL数据库开发包含管理员与用户两种角色管理员可管理用户、电影分类、电影信息、评分及系统设置用户可注册登录并获取个性化推荐适合作为中等难度的实战学习案例。压缩包共690个文件约21.92MB涵盖38个py源码、33个vue前端组件、20个html页面、2个sql脚本及配套css、js、svg等静态资源另附万字论文、视频演示、数据库与说明文档源码均经本地编译调试可运行。目前已有66人学习下载。读者可获得完整可运行的源码工程、论文写作参考、演示视频与数据库文件便于快速理解协同过滤推荐流程、Django项目结构与前后端交互方式也可用于二次开发或答辩准备。1. 电影推荐系统为什么值得用协同过滤从零搭一遍如果你手头正好有一份「基于 Python 和协同过滤算法的电影推荐系统」的完整资料包含源码、论文、演示、数据库和文档那它最大的价值不是拿来交作业而是拿来吃透推荐系统最核心的一条链路用户看过什么、评分多少、和谁相似、该推什么。这条链路在电商、短视频、音乐平台里几乎一模一样只是数据换了张皮。协同过滤Collaborative Filtering不依赖电影内容本身只靠用户行为矩阵就能跑出推荐这也是它十几年不过时的原因。适合谁刚学完 Python 基础语法、想找一个能跑通又能讲清楚的项目练手的人也适合已经会写爬虫、会做数据分析与可视化但没系统做过推荐逻辑的从业者。下面我按「数据怎么进、相似度怎么算、推荐怎么出、坑在哪」把整套方案拆开讲你照着能复现也能判断这套东西值不值得投入时间。2. 数据层MovieLens 数据集怎么读、怎么切、怎么存进数据库2.1 为什么选 MovieLens 而不是自己爬很多人第一反应是用 Python 爬虫去抓豆瓣或 IMDB 的评分我一般不建议在这个项目里这么干。爬虫拿到的数据字段残缺、用户 ID 不稳定、评分时间戳经常丢清洗成本远高于算法本身。MovieLens 是推荐系统领域最常用的公开数据集结构干净ratings 文件就是标准的「用户-电影-评分-时间戳」四列直接对应协同过滤需要的用户物品矩阵。常见做法是下载 ml-latest-small 这个规模十万条左右评分几千个用户和电影单机跑相似度矩阵不会爆内存也足够演示出推荐效果。数据集里通常有 ratings.csv、movies.csv、links.csv、tags.csv 四个文件核心只用前两个ratings 提供行为movies 提供电影标题用于最后展示推荐结果。2.2 用 pandas 读数据并做最小清洗import pandas as pd # 读取评分和电影信息注意编码MovieLens 的 movies.csv 含特殊字符 ratings pd.read_csv(ml-latest-small/ratings.csv) movies pd.read_csv(ml-latest-small/movies.csv) # 只保留必要列减小内存占用 ratings ratings[[userId, movieId, rating]] movies movies[[movieId, title, genres]] # 去掉评分次数过少的用户和电影缓解冷启动和矩阵稀疏 user_counts ratings[userId].value_counts() movie_counts ratings[movieId].value_counts() ratings ratings[ratings[userId].isin(user_counts[user_counts 20].index)] ratings ratings[ratings[movieId].isin(movie_counts[movie_counts 20].index)] print(ratings.shape, movies.shape)这段代码的逻辑是先读进来再做两步过滤。第一步把评分列裁掉时间戳因为基于用户的协同过滤在基础版本里用不到时间衰减。第二步是关键评分次数少于 20 的用户和电影直接剔除。原因很简单一个只评过 3 部电影的用户算出来的相似度极不稳定会污染整个相似度矩阵同理只有几个人评过的冷门电影也没法参与有效推荐。阈值 20 不是死规定数据量大可以调到 50数据量小调到 10原则是保证每个用户和每部电影都有足够的共现基础。跑完这一步你会看到 ratings 的行数明显下降这是正常的换来的是后面相似度计算的稳定性。2.3 建用户物品矩阵与数据库落库from scipy.sparse import csr_matrix # 把 userId 和 movieId 映射成从 0 开始的连续索引 user_ids ratings[userId].astype(category) movie_ids ratings[movieId].astype(category) # 构建稀疏矩阵行是用户列是电影值是评分 matrix csr_matrix( (ratings[rating], (user_ids.cat.codes, movie_ids.cat.codes)) ) print(matrix.shape, matrix.nnz) # nnz 是非零元素个数即实际评分数这里用 scipy 的稀疏矩阵而不是 numpy 的稠密数组是整套方案能不能跑起来的分水岭。假设有 6000 个用户和 4000 部电影稠密矩阵就是 2400 万个浮点数接近 200MB而实际评分可能只有几万条99% 的位置是空的。稀疏矩阵只存非零值内存占用能降到几 MB 级别。csr_matrix的构造参数里第一个是值第二个是行列索引元组顺序不能反。nnz打印出来就是真实评分数你可以用它和矩阵总容量对比直观感受稀疏程度。落库这一步常见做法是用 SQLite 建三张表users、movies、ratingsratings 表用 (userId, movieId) 做联合主键。SQLite 不需要额外装服务一个文件就能带走配合 Python 的 sqlite3 模块读写都方便适合这种单机演示项目。3. 算法层基于用户和基于物品的协同过滤怎么选、怎么算3.1 两种协同过滤的差别与选型理由协同过滤分两大方向UserCF 找和你口味相似的人把他们喜欢的推给你ItemCF 找你喜欢的电影相似的电影直接推相似的。选哪个不是拍脑袋看的是场景。UserCF 适合用户数远小于物品数的场景比如新闻推荐因为用户兴趣变化快物品更新也快维护用户相似度更划算。电影推荐恰好相反电影数量相对稳定用户评分行为也相对持久ItemCF 更合适而且 ItemCF 的相似度矩阵可以离线算好、定期更新线上响应快。但作为学习项目我建议两个都实现一遍因为它们的代码骨架几乎一样只是矩阵转置的区别跑通两个你才算真正理解「相似度」在推荐里扮演什么角色。下面先讲相似度计算再分别给实现。3.2 余弦相似度与皮尔逊相似度的参数取舍import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 基于物品的相似度对矩阵转置后按列算 item_sim cosine_similarity(matrix.T) np.fill_diagonal(item_sim, 0) # 自己和自己相似度置 0避免推荐自己 # 基于用户的相似度 user_sim cosine_similarity(matrix) np.fill_diagonal(user_sim, 0)余弦相似度衡量的是两个向量方向的一致性对评分绝对值不敏感适合评分尺度不统一的场景。皮尔逊相似度会先减去各自均值能消除用户打分偏严或偏松的影响但计算量更大而且当两个用户只共同评过一两部电影时皮尔逊的结果波动极大。我的经验是数据稀疏时用余弦数据较稠密且用户评分习惯差异明显时用皮尔逊。fill_diagonal那一步千万别漏否则每部电影最相似的永远是自己推荐结果里会出现「因为你看过这部电影所以推荐这部电影」的玄学现象。相似度矩阵算完后通常还要做一步 Top-K 截断每部电影只保留最相似的 20 到 50 部既降噪又提速。3.3 生成推荐列表的完整函数def recommend_for_user(user_index, matrix, item_sim, movies, top_n10, k20): # 取出该用户已评分的电影索引 rated matrix[user_index].toarray().ravel() rated_idx np.where(rated 0)[0] scores {} for i in rated_idx: # 取第 i 部电影最相似的 k 部 sim_items np.argsort(item_sim[i])[::-1][:k] for j in sim_items: if rated[j] 0: continue # 已经看过的跳过 scores[j] scores.get(j, 0) item_sim[i][j] * rated[i] # 按得分排序取前 top_n ranked sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n] return [(movies.iloc[idx][title], round(score, 3)) for idx, score in ranked]这个函数的逻辑是加权求和用户对某部已看电影的评分乘以这部电影和候选电影的相似度累加到候选电影上。评分越高、相似度越高候选电影得分就越高。参数k控制每部已看电影贡献多少邻居设太大会引入不相关电影拉低精度设太小会漏掉长尾好片20 到 50 是常见区间。top_n是最终推荐条数演示用 10 条足够。注意rated[j] 0这个判断它保证不会把用户已经看过的电影再推一遍这是最基础的过滤少了它推荐结果会非常尴尬。函数返回时把 movieId 换成电影标题因为最终展示给用户看的必须是可读的名字这一步在数据库查询里也可以用 JOIN 完成。4. 避坑与排查协同过滤落地时最容易翻车的五个地方4.1 推荐结果全是热门电影现象是无论给哪个用户推荐出来的都是那几部评分人数最多的片子。原因是相似度矩阵被热门电影主导热门电影和谁都有一点相似度累加后得分天然高。解决办法是在相似度计算前对评分做惩罚或者对推荐结果做多样性重排比如限制同一类型电影最多出现 3 部。更彻底的做法是改用带偏置的矩阵分解但那是另一个话题了。4.2 内存溢出或计算卡死现象是跑到cosine_similarity那一步程序直接卡住或者报 MemoryError。原因是矩阵规模估计不足或者用了稠密数组。解决方法是确认用的是csr_matrix并且在算相似度前先做 Top-K 截断不要一次性算全量相似度。如果电影数超过两万建议改用基于矩阵分解的隐语义模型协同过滤的相似度矩阵在那种规模下确实吃力。4.3 新用户完全没有推荐现象是新注册用户进来推荐列表是空的。原因是协同过滤依赖历史行为没有评分就没法算相似度这就是冷启动。解决办法是准备一个兜底策略新用户先推全局高分电影等他产生几条评分后再切换到协同过滤。这个兜底逻辑在真实系统里是标配不是可选项。4.4 相似度矩阵对角线没置零现象是推荐列表里出现用户刚刚看过的电影。原因就是前面提到的对角线问题自己和自己相似度为 1加权后得分最高。解决办法是在相似度矩阵算完后立刻fill_diagonal(sim, 0)并且在推荐函数里再加一层已看过滤双保险。4.5 数据库中文乱码现象是电影标题存进 SQLite 后变成问号或乱码。原因是建表时没有指定编码或者连接时没设置。解决办法是建库时用 UTF-8Python 的 sqlite3 默认就是 UTF-8问题通常出在读取 CSV 时没指定 encoding。MovieLens 的 movies.csv 用 utf-8 读一般没问题如果报错就试 latin-1读完再转。5. 把推荐结果做成可视化界面与离线评估的实用技巧5.1 用 Flask 搭一个最小可交互界面from flask import Flask, request, jsonify import pickle app Flask(__name__) # 启动时加载预训练好的矩阵和相似度避免每次请求重算 with open(model.pkl, rb) as f: model pickle.load(f) app.route(/recommend) def recommend(): user_id int(request.args.get(user_id, 1)) idx model[user_map].get(user_id) if idx is None: return jsonify({error: 用户不存在}) result recommend_for_user(idx, model[matrix], model[item_sim], model[movies]) return jsonify({user_id: user_id, recommend: result}) if __name__ __main__: app.run(debugTrue)这个接口的逻辑很直接启动时把矩阵、相似度、映射关系一次性加载进内存请求进来只做查表和排序响应时间能控制在毫秒级。user_map是原始 userId 到矩阵行索引的映射字典没有它就没法把外部用户 ID 对应到矩阵里。debugTrue只用于开发正式跑要关掉。前端用一个简单的 HTML 页面发 fetch 请求把返回的 JSON 渲染成列表就行不需要上 React 或 Vue这个项目的重点在算法链路界面够用即可。5.2 离线评估RMSE 和 Top-N 命中率怎么算评估推荐系统不能只看「推出来的电影我觉得好不好」要有量化指标。评分预测用 RMSE把一部分评分藏起来当测试集用模型预测再算误差。Top-N 推荐用命中率Hit Rate看推荐列表里有没有用户实际看过的电影。具体做法是每个用户留出最后 10 条评分做测试剩下的做训练推荐 10 条统计命中比例。RMSE 能到 0.9 以下、命中率能到 0.15 以上对于 MovieLens 小数据集就算正常水平。这两个指标要一起看RMSE 低不代表推荐列表好因为高分电影预测准但未必是用户想看的。5.3 我踩过的最大一个坑最后说一个我自己的血泪教训。第一次做这个项目时我把相似度矩阵和推荐结果都算好了界面也搭完了演示很顺利。但后来换了一批数据重新跑推荐质量突然暴跌。排查了很久才发现问题出在用户和电影的索引映射上换数据后 category 编码顺序变了但缓存文件没重新生成导致矩阵的行列和实际用户电影对不上推荐出来的全是错位的电影。从那以后我养成了一个习惯任何依赖索引映射的中间结果都必须和原始数据绑定版本数据一变就全部重算绝不复用旧缓存。这个习惯后来在好几个项目里都救过我。希望帮到你。本文还有配套的精品资源点击获取
