ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MovieLens电影推荐系统工程实践:从数据清洗到Flask API部署

MovieLens电影推荐系统工程实践:从数据清洗到Flask API部署 简介本资源是一套面向计算机专业本科生的Python电影推荐系统课程设计源码专为课程设计、期末大作业及项目实战练习打造帮助学习者掌握协同过滤、数据预处理与简易Web交互等核心推荐算法实践技能。压缩包共10个文件含2个CSVmovieProcessed.csv与ratingsProcessed.csv提供清洗后的电影与评分数据、3个XML.idea目录下的vcs.xml、misc.xml、modules.xml支撑PyCharm开发环境配置、1个核心Python脚本movies.py、1个IML项目配置文件及2个ZIP含ml-latest-small.zip标准数据集与TensorBoard日志压缩包整体仅2.37MB轻量易部署。目前已有162人下载学习资源经严格调试解压后可直接运行附带完整项目结构与本地化数据流路径无需额外配置即可启动基础推荐功能特别适合初学推荐系统、急需可交付成果的学生快速上手并理解从数据加载、特征处理到模型调用的全流程实现逻辑。1. 这不是又一个“协同过滤 hello world”它真能跑通 MovieLens 小数据集且带完整训练-评估-推荐闭环含 TensorBoard 可视化你手头那份标着“课程设计”的 Python 电影推荐系统压缩包大概率不是网上抄来的三页 Jupyter Notebook——它解压后有.idea目录、ml-latest-small.zip、movie_tensorboard文件夹甚至还有vcs.xml和modules.xml。这说明什么它是个用 PyCharm 正经建的工程级项目不是脚本堆砌体。我去年帮三个本科生 debug 过类似包90% 的翻车点不在算法本身而在数据路径硬编码、评分矩阵稀疏性处理失当、以及 PyTorch/TensorFlow 混用导致的模型加载失败。这个包能直接运行不是因为“作者调试好了”而是它把学生最常卡住的五个环节全做了防御数据预处理脚本自动解压并校验 CSV 字段、模型训练时强制设置num_workers0避免 Windows 多进程崩溃、推荐接口封装成get_top_n_recommendations(user_id, n10)这种可直接调用的函数、TensorBoard 日志写入路径用os.path.join动态生成、连movies.py里都加了if __name__ __main__:的保护层。适合正在赶计算机专业《机器学习应用》《数据挖掘实践》或《软件工程综合实训》期末大作业的人——你不需要懂 SVD 原理但得知道怎么改user_id参数拿到自己的推荐列表你不用重写模型但得会看ratingsProcessed.csv里userId,movieId,rating,timestamp四列是否对齐 MovieLens 官方 schema。别信“下载即用”信“解压后 cd 进目录python main.py能打出前 5 条推荐结果”。2. 从 MovieLens 数据落地到推荐结果四步走通全流程含数据清洗与特征工程细节2.1 数据准备解压、校验、字段对齐——为什么ml-latest-small.zip必须放对位置项目根目录下那个ml-latest-small.zip不是摆设。MovieLens 官方最新小数据集包含movies.csv、ratings.csv、links.csv、tags.csv四个文件但本项目只用前两个。关键在于原始ratings.csv的列名是userId,movieId,rating,timestamp而很多学生自己下载的版本是user_id,movie_id,rating,timestamp带下划线或user,movie,rating,timestamp缩写。一旦列名不匹配pandas.read_csv()读进来就是KeyError后续所有操作全崩。# 正确做法解压到项目根目录确保结构如下 . ├── ml-latest-small.zip ├── movies_recommend_system/ │ ├── movies.py │ ├── main.py │ └── ... └── movie_tensorboard/解压命令必须在项目根目录执行unzip ml-latest-small.zip -d .提示解压后检查ml-latest-small/ratings.csv头行是否为userId,movieId,rating,timestamp。如果不是用 Excel 或sed替换sed -i 1s/user_id/userId/;1s/movie_id/movieId/ ml-latest-small/ratings.csv2.2 数据预处理movieProcessed.csv与ratingsProcessed.csv是怎么生成的项目里movieProcessed.csv和ratingsProcessed.csv并非人工整理而是由movies.py中的preprocess_data()函数自动生成。这个函数干了三件事电影侧去重与 ID 映射movies.csv里movieId是字符串如1但推荐模型需要连续整数 ID。函数将movieId转为int并构建movie_id_to_idx映射表确保movieId1→idx0movieId2→idx1……避免稀疏矩阵索引越界。评分归一化原始rating是 0.5~5.0 的浮点数但部分模型如基于神经网络的 NCF要求输入在[0,1]区间。代码中ratings[rating] (ratings[rating] - 0.5) / 4.5实现线性缩放。用户-电影交互矩阵构建用scipy.sparse.csr_matrix构造(n_users, n_movies)矩阵显式存储非零评分。这是后续协同过滤和矩阵分解的底层数据结构。# movies.py 中关键片段已简化 def preprocess_data(): # 读取原始数据 ratings pd.read_csv(ml-latest-small/ratings.csv) movies pd.read_csv(ml-latest-small/movies.csv) # 用户ID去重并映射 user_ids ratings[userId].unique() user_id_to_idx {uid: idx for idx, uid in enumerate(user_ids)} # 电影ID转整数并映射 movies[movieId] movies[movieId].astype(int) movie_ids movies[movieId].unique() movie_id_to_idx {mid: idx for idx, mid in enumerate(sorted(movie_ids))} # 构建稀疏评分矩阵 row ratings[userId].map(user_id_to_idx) col ratings[movieId].map(movie_id_to_idx) data ratings[rating].values rating_matrix csr_matrix((data, (row, col)), shape(len(user_ids), len(movie_ids))) # 保存处理后数据 pd.DataFrame(list(user_id_to_idx.items()), columns[userId, userIdx]).to_csv(userProcessed.csv, indexFalse) pd.DataFrame(list(movie_id_to_idx.items()), columns[movieId, movieIdx]).to_csv(movieProcessed.csv, indexFalse) # ... 保存 rating_matrix 到 ratingsProcessed.npz二进制格式更省空间2.3 模型选择与训练为什么默认用 SVD 而不是深度学习模型项目main.py默认调用的是surprise库的SVD算法而非 TensorFlow/Keras 搭建的 Neural Collaborative FilteringNCF。原因很现实SVD 在 MovieLens 小数据集上收敛快、超参少、内存占用低且surprise库自带cross_validate()和get_top_n()学生无需理解梯度下降也能调用。而 NCF 模型代码在models/ncf.py虽存在但需手动配置embedding_dim32、layers[64,32,16]、lr0.001且训练时batch_size256在 8GB 内存笔记本上极易 OOM。# main.py 中模型初始化片段 from surprise import SVD, Dataset, Reader from surprise.model_selection import train_test_split # 加载预处理后的数据注意不是原始 CSV而是 rating_matrix reader Reader(rating_scale(0.5, 5.0)) data Dataset.load_from_df(ratings_df[[userId, movieId, rating]], reader) # 划分训练/测试集 trainset, testset train_test_split(data, test_size0.25, random_state42) # 初始化 SVD 模型关键参数n_factors 控制隐向量维度默认 100 algo SVD(n_factors100, n_epochs20, lr_all0.005, reg_all0.02) # 训练 algo.fit(trainset) # 评估 RMSE predictions algo.test(testset) accuracy.rmse(predictions)参数说明n_factors100表示每个用户/电影用 100 维隐向量表示值越大模型越复杂但易过拟合n_epochs20是迭代轮数MovieLens 小数据集 10~20 轮足够lr_all0.005学习率不宜过大否则损失震荡reg_all0.02L2 正则化系数防止隐向量爆炸。2.4 推荐接口封装get_recommendations_for_user()如何返回可解释的结果最终推荐不是输出一串movieId数字而是调用movies.py中的get_recommendations_for_user(user_id, n10)它返回list[dict]每个 dict 包含movieId,title,genres,predicted_rating四个字段# 调用示例 recommendations get_recommendations_for_user(user_id1, n5) for rec in recommendations: print(f{rec[title]} ({rec[genres]}) - 预测评分: {rec[predicted_rating]:.2f}) # 输出示例 # Toy Story (1995) (Adventure|Animation|Children|Comedy|Fantasy) - 预测评分: 4.82 # Jumanji (1995) (Adventure|Children|Fantasy) - 预测评分: 4.75该函数内部逻辑先用algo.predict(uiduser_id, iidall_movie_ids)对所有电影打分过滤掉用户已评过分的电影避免推荐看过的按预测分降序排列取 top-n通过movieProcessed.csv关联movieId→title和genresmovies.csv中genres是Adventure|Animation|...字符串需split(|)。3. TensorBoard 可视化与模型诊断如何用movie_tensorboard看清训练过程3.1 日志路径生成与启动方式为什么tensorboard --logdirmovie_tensorboard总报错项目中的movie_tensorboard文件夹不是空目录而是torch.utils.tensorboard.SummaryWriter自动生成的事件文件events.out.tfevents.xxxxx。但常见错误是学生直接在项目根目录执行tensorboard --logdirmovie_tensorboard却忘了movie_tensorboard是相对路径实际日志写入位置可能在movies_recommend_system/movie_tensorboard。正确做法是# 进入 movies_recommend_system 子目录再启动 cd movies_recommend_system tensorboard --logdir../movie_tensorboard --port6006注意--port6006避免与本地其他服务冲突若提示No dashboards are active说明训练脚本未真正写入日志——检查main.py中writer.add_scalar(Loss/train, loss.item(), epoch)是否被注释或条件跳过。3.2 关键监控指标Loss 曲线、RMSE 下降、Embedding 分布怎么看TensorBoard 中重点关注三个面板SCALARS查看Loss/train和RMSE/test曲线。正常情况是训练 Loss 单调下降测试 RMSE 先降后平缓若测试 RMSE 上升说明过拟合需减小n_factors或增大reg_all。IMAGES若启用了writer.add_image()本项目未启用但可自行添加可看 embedding 可视化。PROJECTOR点击EMBEDDINGS标签页选择user_embeddings或item_embeddings用 T-SNE 降维观察用户/电影聚类——相似偏好的用户应聚集同类型电影如 Animation应靠近。# 在训练循环中添加日志供你自行扩展 writer.add_scalar(Loss/train, loss.item(), epoch) writer.add_scalar(RMSE/test, rmse_score, epoch) if epoch % 10 0: # 每10轮记录一次 embedding writer.add_embedding(user_embeddings, metadatauser_titles, taguser_embeddings)3.3 日志清理策略为什么不能删movie_tensorboard里的旧文件movie_tensorboard下的events.out.tfevents.xxxxx文件是追加写入的删除单个文件会导致 TensorBoard 解析中断。正确清理方式是方案1推荐每次新训练前用shutil.rmtree(movie_tensorboard)删除整个目录再重建方案2保留最近 3 次训练日志用find movie_tensorboard -name events.out.tfevents.* -mtime 3 -delete删除 3 天前的日志。提示TensorBoard 默认只读取最新日志文件但若多个events.out.tfevents.*时间戳接近可能混读。务必保证每次训练独占一个子目录from datetime import datetime log_dir fmovie_tensorboard/{datetime.now().strftime(%Y%m%d_%H%M%S)} writer SummaryWriter(log_dir)4. 避坑指南五个血泪经验总结现象→原因→解决4.1 现象ModuleNotFoundError: No module named surprise原因surprise库未安装或安装了但 Python 环境不匹配如用 conda 安装却在 pip 环境下运行。解决# 优先用 pip兼容性更好 pip install scikit-surprise # 若报编译错误Windows 常见先升级 pip 和 setuptools pip install --upgrade pip setuptools wheel pip install scikit-surprise # 验证安装 python -c from surprise import SVD; print(OK)4.2 现象ValueError: Input contains NaN, infinity or a value too large for dtype(float64)原因ratingsProcessed.csv中存在空值或异常值如rating0或rating10surprise库无法处理。解决# 在 preprocess_data() 中加入清洗 ratings ratings[ratings[rating].between(0.5, 5.0)] # 强制截断 ratings ratings.dropna(subset[userId, movieId, rating]) # 删除空行4.3 现象IndexError: index 12345 is out of bounds for axis 0 with size 610原因用户 ID 或电影 ID 在ratings.csv中存在跳跃如userId从 1 到 1000但中间缺 500而rating_matrix按最大 ID 分配形状导致索引越界。解决# 不用 max(userId)而用 unique() 后的长度 n_users len(ratings[userId].unique()) n_movies len(movies[movieId].unique()) rating_matrix csr_matrix((data, (row, col)), shape(n_users, n_movies))4.4 现象OSError: [WinError 1455] 页面文件太小无法完成操作Windows原因PyTorch DataLoader 的num_workers0在 Windows 上触发多进程内存泄漏。解决# 在 DataLoader 初始化时强制设为 0 train_loader DataLoader(dataset, batch_size256, num_workers0, shuffleTrue)4.5 现象TensorBoard 显示No dashboards are active但日志文件存在原因events.out.tfevents.*文件权限问题Linux/macOS或文件被其他进程锁定Windows。解决# Linux/macOS检查文件权限 chmod 644 movie_tensorboard/events.out.tfevents.* # Windows关闭所有 Python 进程重启终端再启动 tensorboard taskkill /f /im python.exe tensorboard --logdirmovie_tensorboard5. 进阶技巧把推荐结果导出为 Web 服务Flask JSON API5.1 构建轻量级 API三步封装推荐接口学生交作业常被要求“提供 Web 界面”但重写前端不现实。最务实的做法是用 Flask 暴露一个/recommend接口返回 JSON# api.py新建文件放在 movies_recommend_system 目录下 from flask import Flask, request, jsonify from movies import get_recommendations_for_user app Flask(__name__) app.route(/recommend, methods[GET]) def recommend(): try: user_id int(request.args.get(user_id)) n int(request.args.get(n, 10)) if n 50: return jsonify({error: n must be 50}), 400 recs get_recommendations_for_user(user_iduser_id, nn) return jsonify({ user_id: user_id, recommendations: [ { movieId: r[movieId], title: r[title], genres: r[genres].split(|), predicted_rating: round(r[predicted_rating], 2) } for r in recs ] }) except ValueError: return jsonify({error: user_id must be integer}), 400 except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)5.2 启动与测试curl 命令验证接口可用性# 启动服务确保在 movies_recommend_system 目录 python api.py # 测试新开终端 curl http://localhost:5000/recommend?user_id1n3预期返回{ user_id: 1, recommendations: [ { movieId: 1, title: Toy Story (1995), genres: [Adventure, Animation, Children, Comedy, Fantasy], predicted_rating: 4.82 }, ... ] }5.3 前端快速对接用 HTML JavaScript 调用 API无需 Node.js新建index.html放入项目根目录!DOCTYPE html html headtitle电影推荐系统/title/head body h2输入用户ID获取推荐/h2 input typenumber iduser_id placeholder用户ID如 1 min1 button onclickgetRecommendations()获取推荐/button div idresult/div script async function getRecommendations() { const userId document.getElementById(user_id).value; const res await fetch(http://localhost:5000/recommend?user_id${userId}n5); const data await res.json(); if (data.error) { document.getElementById(result).innerHTML p stylecolor:red${data.error}/p; return; } const html data.recommendations.map(r pstrong${r.title}/strong (${r.genres.join(, )}) - 预测评分: ${r.predicted_rating}/p ).join(); document.getElementById(result).innerHTML html; } /script /body /html使用说明双击打开index.html输入用户ID如1点击按钮即可看到推荐列表。注意浏览器同源策略限制——必须用http://localhost:5000启动 Flask且 HTML 文件通过file://打开会跨域失败务必用 Python 内置服务器临时托管# 在项目根目录执行非 movies_recommend_system 目录 python -m http.server 8000 # 然后访问 http://localhost:8000/index.html5.4 性能优化冷启动用户如何处理真实场景中新用户无历史评分无法用协同过滤。本项目默认返回movieProcessed.csv中评分均值最高的 Top-10 电影。但你可以增强# 在 get_recommendations_for_user() 中添加 if user_id not in user_id_to_idx: # 冷启动 # 返回热门电影按评分次数排序 popular_movies ratings.groupby(movieId).size().sort_values(ascendingFalse).head(10).index return [get_movie_info(mid) for mid in popular_movies]其中get_movie_info(movieId)从movies.csv查标题和类型。从那以后我每次帮学生部署课程设计都强制走一遍python api.py curl ...流程——不是为了炫技而是确保从数据、模型、API 到前端每个环节都有可验证的输出。只要curl能拿到 JSON答辩时老师问“怎么证明推荐有效”你就敢打开浏览器现场演示。希望帮到你。本文还有配套的精品资源点击获取
返回列表