ARTICLE DETAIL

资讯详情

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

协同过滤电影推荐系统毕设实战:算法选型、数据库设计与Python实现

协同过滤电影推荐系统毕设实战:算法选型、数据库设计与Python实现 简介基于Python的协同过滤推荐算法电影推荐系统毕业设计项目面向计算机相关专业学生适合毕业设计、课程设计或期末大作业场景。项目已获导师指导答辩评审97分下载后即可运行包含算法实现、前端界面、数据库脚本及论文文档。资源共687个文件压缩包约13.3MB主要类型包括Python源码、Vue组件、JavaScript脚本、CSS/SVG样式、SQL数据库文件、HTML页面、GIF演示动画与图片素材另有doc论文和bat启动脚本目录结构清晰便于按模块阅读和二次开发。目前已有308人学习下载。借助这份资源读者可以看清协同过滤推荐系统的完整链路用户操作通过前端Vue与JavaScript呈现后端Python实现相似度计算与Top-N推荐SQL维护电影与用户数据。对于准备毕设或课设的学生可直接运行使用并参考论文写作对希望深入推荐算法的开发者可在此基础上替换算法或扩展功能。1. 协同过滤推荐系统毕业设计项目的第一道坎也是最后一块拼图如果你在毕业设计里选了“电影推荐系统”大概率会搜到一堆带源码、带数据库、带论文的压缩包。这个标题的价值不在于那几十兆的 zip 文件本身而在于它把一个完整的工程链路压缩成了一个可复现的模板Python 做主体逻辑、协同过滤算法做推荐核心、SQLite 或 MySQL 做数据层、论文充当设计文档。对正在做毕设的人来说真正要解决的不是“推荐原理看不懂”而是“从算法公式到一个能跑、能演示、能被答辩老师提问的系统中间缺了什么”。本文就从协同过滤的工程落地出发讲清楚三件事基于用户的 UserCF 和基于物品的 ItemCF 在电影场景下到底怎么选、相似度计算和评分预测的每个参数怎么调、历史冷启动和数据稀疏这些坑在哪里。目录结构、代码组织、甚至数据库表怎么建都会给出能直接复现的一套方案。这套东西适合两类人一类是拿这个题目做毕设的学生另一类是刚入门推荐系统、想用最小成本验证算法效果的工程师。2. 协同过滤的数学原理与电影场景选型先决定用哪个公式再写第一行代码协同过滤的核心假设很简单相似的人有相似的品味相似的商品会被同一个人喜欢。放到电影推荐里就是通过用户的历史评分行为计算用户与用户、或电影与电影之间的相似度然后预测某个用户对一部没看过的电影的打分取 Top-N 作为推荐结果。2.1 基于用户的 UserCF找到“与你口味一致”的另一个人UserCF 的思想是你要给用户 A 推荐电影先找到和 A 历史评分最相似的一群用户然后把这些用户看过且评分高的电影中A 没看过的推荐给他。它的关键在于用户相似度矩阵。最常见的相似度计算是余弦相似度和皮尔逊相关系数。余弦相似度的公式是similarity(u, v) sum(r_ui * r_vi) / (sqrt(sum(r_ui^2)) * sqrt(sum(r_vi^2)))其中 r_ui 是用户 u 对电影 i 的评分。如果两个用户共同评分过的电影数量很少但恰好一致直接用原始评分算余弦相似度会偏高因为没考虑用户评分尺度的差异——有人习惯全给高分有人永远在 3 分上下。所以工程上更常用的是皮尔逊相关系数它先把每个用户的评分做均值中心化再计算相关性import numpy as np def pearson_similarity(user_a, user_b, rating_matrix): 计算两个用户的皮尔逊相似度 参数: - user_a, user_b: 用户 id 字符串或数字索引 - rating_matrix: 二维数组行是用户、列是电影值为评分或 0 返回: - 相似度值范围 [-1, 1] # 找到两个用户都评过分电影的索引 common (rating_matrix[user_a] 0) (rating_matrix[user_b] 0) if np.sum(common) 2: return 0.0 r_a rating_matrix[user_a][common] r_b rating_matrix[user_b][common] # 均值中心化 a_mean np.mean(r_a) b_mean np.mean(r_b) numerator np.sum((r_a - a_mean) * (r_b - b_mean)) denominator np.sqrt(np.sum((r_a - a_mean) ** 2) * np.sum((r_b - b_mean) ** 2)) if denominator 0: return 0.0 return numerator / denominator这里有个容易被忽视的细节共同评分电影数量小于 2 时直接返回 0这是为了规避小样本下的偶然相似。皮尔逊系数能有效处理用户评分偏好差异但代价是计算量比余弦大。在电影推荐这种评分数据多为 1-5 分、且每个用户评分数基本在几十到几百的毕设场景里性能完全不是问题优先选皮尔逊。2.2 基于物品的 ItemCF豆瓣“喜欢这部电影的人也喜欢”的工程原型ItemCF 的逻辑是先计算电影与电影的相似度然后根据用户历史评分过的电影去推荐“与这些电影最相似”的其他电影。它不需要实时计算用户全量相似度而是维护一张电影相似度表推荐阶段只需要查表和加权求和。这里有一个关键点电影之间的相似度不是按内容的类型、导演、演员算的而是按“是否被同一群用户共同喜爱”算的。这个叫行为相似度。公式一般是sim(i, j) |N(i) ∩ N(j)| / sqrt(|N(i)| * |N(j)|)N(i) 是给电影 i 评过分的用户集合。这个式子是改进后的余弦相似度分母用乘积开方相当于对热门电影做惩罚避免《肖申克的救赎》这种人人都看过的高频电影和所有电影都相似。在毕设论文里这个公式通常会被写成“基于物品的协同过滤算法”答辩老师大概率会追问为什么分母不用 |N(i) ∩ N(j)| 本身而用乘积答案就是惩罚热门物品。如果直接用交集大小热门电影会霸占相似结果推荐多样性极差。2.3 电影推荐系统里该选 UserCF 还是 ItemCF两套方案的取舍在实际做电影推荐时我的建议是主用 ItemCF理由有三点第一电影数量相对用户数更稳定。一个课程设计网站的注册用户可能只有几百人但电影库可能有几千部用户每多一个UserCF 的在线计算复杂度就涨一次ItemCF 的相似度矩阵却是预计算好的。展示给用户的延迟ItemCF 是 O(1) 级别的查表UserCF 是 O(n) 级别的在线聚合。第二冷启动的恶性程度不同。新用户刚注册时没有任何评分两个算法都失效但 ItemCF 在用户第一次给一部电影打分后立刻能推荐出相近电影感知上“响应更快”。UserCF 至少要积累十几个评分才能找到相似人群。第三论文好讲。ItemCF 的相似度矩阵可以导出成表格放进论文附录答辩演示时可以直接展示“和《盗梦空间》最相似的 5 部电影”可视化效果比“和用户 A 最相似的 5 个用户”直观得多。当然如果你的数据是围绕“好友关系”展开的社交型电影推荐UserCF 更合适。纯算法选型没有绝对对错但项目演示效果和实现成本需要优先考虑。3. 从零搭建电影推荐系统数据库表设计、Python 数据清洗与关键函数实现有了算法选型的结论接下来进入实现阶段。一个能交差的毕设项目至少包含四层数据库存原始数据、Python 读数据并构建评分矩阵、协同过滤算法计算相似度和推荐结果、Web 或命令行层做交互展示。数据库推荐系统源码这三者在这一层已经形成闭环。3.1 数据库表怎么建users、movies、ratings 三张表就够了最常见的做法是用 MySQL 或 SQLite。毕设推荐 SQLite理由很实在免安装、单文件、答辩现场换电脑不用配环境。但论文里一般都会写 MySQL 以体现“企业级”这种差异化展示与课题本身无关建议你自己的认知也要跟上。无论选哪个表结构都一样-- 用户表 CREATE TABLE users ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, gender TEXT, age INTEGER, occupation TEXT ); -- 电影表 CREATE TABLE movies ( movie_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, genres TEXT, -- 使用竖线分隔例如 Action|Sci-Fi release_year INTEGER ); -- 评分表推荐算法的数据源 CREATE TABLE ratings ( user_id INTEGER NOT NULL, movie_id INTEGER NOT NULL, rating REAL NOT NULL CHECK(rating 0 AND rating 5), timestamp INTEGER, PRIMARY KEY (user_id, movie_id), FOREIGN KEY (user_id) REFERENCES users(user_id), FOREIGN KEY (movie_id) REFERENCES movies(movie_id) ); -- 推荐系统最核心的查询按用户取评分 CREATE INDEX idx_ratings_user ON ratings(user_id); -- 物品相似度计算最核心的查询按电影取评分用户 CREATE INDEX idx_ratings_movie ON ratings(movie_id);两个索引务必加上。因为协同过滤算法在计算电影相似度时最频繁的 SQL 是“谁给这部电影评过 4 分以上”没有 idx_ratings_movie 这个索引数据量超过几千条后算法会慢得让人怀疑人生。电影表里的 genres 字段用字符串存而不是连表存是故意省略了第三范式。毕设推荐系统不追求复杂关系模型把类型作为一个标签字段做统计过滤即可答辩时顺势解释“这是为算法读取性能做出的反规范化设计”。3.2 Python 读取数据和评分矩阵的三种姿势最直接的方式是 pandas 读全表import pandas as pd from sqlite3 import connect conn connect(movie.db) def load_all_data(): 一次性加载全部数据毕设数据量没必要做流式处理 users_df pd.read_sql_query(SELECT user_id, username FROM users, conn) movies_df pd.read_sql_query(SELECT movie_id, title, genres FROM movies, conn) ratings_df pd.read_sql_query( SELECT user_id, movie_id, rating FROM ratings, conn ) return users_df, movies_df, ratings_df对于中小型毕设数据集这个方案足够。但如果用了 MovieLens 的 10M 版本或自己爬了更大的片单一次性装载可能让内存吃紧这时可以改成逐批读取或者直接构造 scipy 的稀疏矩阵from scipy.sparse import csr_matrix def build_rating_sparse(ratings_df): 构建用户-电影评分稀疏矩阵 user_ids ratings_df[user_id].astype(category) movie_ids ratings_df[movie_id].astype(category) rows user_ids.cat.codes.values cols movie_ids.cat.codes.values data ratings_df[rating].values matrix csr_matrix((data, (rows, cols))) return matrix, user_ids.cat.categories, movie_ids.cat.categories这里的逻辑是把 user_id 和 movie_id 从原始主键映射到 0 到 N-1 的稠密索引目的是让矩阵的坐标范围连续避免出现稀疏的“空洞”。csr_matrix存储三元组不存在的评分天然是 0但在协同过滤里“0 不代表评分是 0而是代表没评分”这个语义要在后续计算中时刻记住否则均值计算会出错。我一般会直接落一个data_preprocess.py脚本把加载、清洗、稀疏矩阵构建串起来输出成一个 .npy 或 .npz 文件算法模块直接读预处理结果这样跑推荐实验时不用每次都碰数据库。3.3 ItemCF 核心实现相似度矩阵、评分预测与 Top-N 推荐ItemCF 的完整实现可以压缩在三个函数里。第一个函数算电影间相似度第二个函数对单个用户生成推荐候选第三个函数做最终排序。下面是能直接跑的版本数据来自于上面构建的稀疏矩阵。import numpy as np from scipy.sparse import csr_matrix class ItemCFRecommender: def __init__(self, rating_matrix: csr_matrix, movie_count: int): self.rating_matrix rating_matrix self.movie_count movie_count self.item_sim None # 电影相似度矩阵shape (movie_count, movie_count) def compute_item_similarity(self, min_common_users: int 5): 计算电影-电影相似度矩阵 相似度公式: |N(i) ∩ N(j)| / sqrt(|N(i)| * |N(j)|) 其中 N(x) 表示对电影 x 有评分的用户集合 # 每一列非零元素个数就是该电影的用户数量 col_count (self.rating_matrix ! 0).sum(axis0) col_count np.array(col_count).ravel() # 共现矩阵 C[i][j] 同时给 i 和 j 评分的用户数 count_matrix (self.rating_matrix.T self.rating_matrix).toarray() count_matrix np.nan_to_num(count_matrix, nan0.0, posinf0.0, neginf0.0) # 惩罚热门电影的分母 denom np.sqrt(np.outer(col_count, col_count)) denom[denom 0] 1 # 防止除 0 sim_matrix count_matrix / denom # 过滤共同用户数过少的相似对避免噪声干扰 sim_matrix[count_matrix min_common_users] 0 np.fill_diagonal(sim_matrix, 0) # 自己和自己不相干 self.item_sim sim_matrix def recommend(self, user_history, top_n10): 给一个用户推荐电影 参数: - user_history: dict {movie_id: rating}用户已评分的电影及分数 - top_n: 返回前 N 部电影 返回: - list 格式 [(movie_id, score), ...] candidate_score {} for movie_id, rating in user_history.items(): if movie_id self.item_sim.shape[0]: continue # 取相似度向量只保留相似度高的近邻 sim_vector self.item_sim[movie_id] # 按相似度取 Top 50 的近邻 nearest np.argsort(sim_vector)[-50:] for n_movie in nearest: sim sim_vector[n_movie] if sim 0: continue # 如果用户已经看过跳过 if n_movie in user_history: continue candidate_score[n_movie] candidate_score.get(n_movie, 0) rating * sim ranked sorted(candidate_score.items(), keylambda kv: kv[1], reverseTrue) return ranked[:top_n]好几个参数是设计的灵魂。min_common_users5过滤掉了少量用户同时观看导致的虚假相似近邻数取 50控制推荐候选集的规模权重因子rating * sim里rating 是用户真实打的分 1-5 分sim 是 0-1 之间的相似度如果改成只取 sim 不加权冷门电影的排序就会异常靠前。还需要注意rating_matrix.T rating_matrix这行代码用的是稀疏矩阵乘法如果提前.toarray()成稠密矩阵几万部电影会产生几亿元素的数组内存立刻爆掉。4. 从算法到可演示系统Flask 接口封装与冷启动、数据稀疏问题的针对性处理算法跑出推荐列表之后下一步是把它变成毕业设计里的可运行系统。常见做法是写一个轻量级 Flask 后端前端用一个简单的 HTML 页面或模板引擎渲染结果。与其给一堆无法验证的代码不如把整个架构和四个关键接口讲明白。4.1 最小可用的 Flask 推荐接口from flask import Flask, jsonify, request import json, sqlite3 app Flask(__name__) rec ItemCFRecommender.__new__(ItemCFRecommender) # 伪代码示意加载预训练结果 app.route(/api/recommend, methods[GET]) def recommend_api(): user_id request.args.get(user_id, typeint) if not user_id: return jsonify({code: 400, msg: 缺少 user_id}), 400 # 从数据库读用户历史评分 conn sqlite3.connect(movie.db) ratings conn.execute( SELECT movie_id, rating FROM ratings WHERE user_id ?, (user_id,) ).fetchall() # 返回结果转成可 JSON 序列化结构 return jsonify({code: 0, data: {user_id: user_id, recs: ratings}}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)这里最重要的一点是推荐系统模块必须在 app 启动前完成模型加载不能在每次请求里重新算相似度矩阵。ItemCF 的相似度矩阵在训练完成后是静态的实时推荐阶段不应该重复计算。4.2 冷启动的四种解法与毕设论文里的对应章节冷启动分用户冷启动和物品冷启动。用户冷启动的典型场景注册后没有任何评分算法无法推荐。我常用的候选方案有四种按平均分推荐热门电影、按注册时选择的题材偏好做规则过滤、随机推荐几部高口碑冷门片作为探索、上线时给每个新用户预置 10 部电影的评分体验入口。毕设里最简单可控的是第一种SELECT movie_id, title, AVG(rating) AS avg_rating FROM ratings JOIN movies USING(movie_id) GROUP BY movie_id ORDER BY avg_rating DESC LIMIT 10;物品冷启动的处理就是平时看到的新片推荐位。新片子可能连一条评分都没有比冷启动更彻底的是“无行为可用”。此时常见做法是回落到基于内容的匹配用电影类型字段做初筛SELECT movie_id, title FROM movies WHERE genres LIKE %Action% ORDER BY release_year DESC LIMIT 6;这段 SQL 演示了论文里常见的“混合推荐策略”主协同过滤、冷门时段用内容过滤托底。答辩时老师问到冷启动直接把这套组合解释清楚比吹一个大型模型更可信。4.3 数据稀疏与灰度问题评分矩阵稀疏到 98% 以上会怎样MovieLens 这种成熟数据集稀疏度在 95% 左右自己爬下来的数据常常在 99%。稀疏度越高ItemCF 的共现矩阵里大部分元素是 0很多电影因为没有任何用户共同评分而相似度为 0推荐结果退化为 “只推荐那几部热门片”。常规调优手段有三个。第一是调整min_common_users的下限数据稀疏就降低到 2 到 3增加可产生的相似对数量。第二是给评分矩阵做均值填充或奇异值分解降维但这属于矩阵分解领域工作量会变大。第三是更换相似度函数从 Jaccard 换到余弦再换到皮尔逊逐一对比指标这个对比过程正好可以作为论文里的实验章。我推荐一个更实际的策略先查数据分布。统计每个用户评了多少部电影、每部电影有多少个人评过分用直方图找稀疏原因。如果大量用户只评了 1 部电影这些用户对推荐算法贡献极低可以按行业惯例设置“最小评分样本量”比如评分数少于 5 的用户不用于训练。这样训练的评分矩阵从极稀疏变得相对可用。5. 推荐效果评测与论文里的关键数据离线指标、参数实验与相似度可视化评委老师看毕设看到的代码能力是表面能看到评测体系才是亮点。协同过滤的好与坏不能靠“感觉推荐得挺像的”来下结论必须回到指标。5.1 离线评测按时间留出 Train/Test 划分与 PrecisionK最规范的做法是按评分时间划分。用最后一个时间戳的前 80% 做训练集后 20% 做测试集而不是随机划分——随机划分会把“用户在看完电影后打分”这一时间语义打乱。测试集里用户的每个评分都可以视为一个“应该被推荐到的电影”。指标计算用精度和召回率就够了def precision_recall_at_k(recommendations, test_items, k10): 计算 Precisionk 和 Recallk 参数: - recommendations: 算法推荐出的电影 id 列表 - test_items: 用户实际看过的电影 id 集合 hit len(set(recommendations[:k]) test_items) precision hit / k recall hit / len(test_items) if test_items else 0 return precision, recall写论文时至少应该有三组对比不同的相似度计算函数下 Precision5、10、15 的结果不同的近邻数 K 下 Precision10 的折线以及 ItemCF 和 UserCF 在同一份数据上的指标数值。这样 3 个小节的内容就够了而且每一节都能给出一张表格。5.2 相似度的可解释性把算法结果做成人话答辩环节的高频问题之一是“你的推荐结果怎么解释”。ItemCF 的天然优势在于可以倒查“为什么推荐了这部片”。保存相似度矩阵的时候顺便把相似度最高的几对存成 JSON 或数据库表CREATE TABLE movie_similarity ( movie_id_a INTEGER NOT NULL, movie_id_b INTEGER NOT NULL, similarity REAL NOT NULL, PRIMARY KEY (movie_id_a, movie_id_b) ); INSERT INTO movie_similarity (movie_id_a, movie_id_b, similarity) SELECT 1, 288, 0.87;前端展示时直接写“因为你看过《星际穿越》所以向你推荐《盗梦空间》”这种解释话术对答辩演示很有说服力因为它给算法指标添加了人可以理解的意义。5.3 报告里的推荐结果效果验证不只是截图一篇合格的毕设论文最后不能只放一个运行截图。更有效的做法是选 3 到 5 个典型用户把他们的历史评分影片、算法预测评分、Top10 推荐列成一张表。比如一个用户看过《阿甘正传》《肖申克的救赎》系统推荐《绿里奇迹》相似度逻辑一眼可见。这种表格直接放进论文的“系统测试与分析”章节老师扫一眼就能确认算法真的在工作而不是只跑通了一个空壳页面。数据量不算大但足以证明你对推荐系统的理解不是抄来的。6. 把相似度矩阵做成增量更新的页面一个可视化小工具提升完成度拿这种毕设项目最容易被忽略的再加一分的方法是用 Flask 加一个简单的页面展示整张相似度矩阵的局部。改进不大但让整个项目从“代码能跑”变成“结果能看见”。最简单的方式是做一个/explore路由输入 movie_id输出相似度最高的 10 部电影及其相似度数值用 SQL 直接查相似度表app.route(/explore) def explore(): movie_id request.args.get(movie_id, 1, typeint) conn sqlite3.connect(movie.db) rows conn.execute( SELECT b.title, s.similarity FROM movie_similarity s JOIN movies a ON a.movie_id s.movie_id_a JOIN movies b ON b.movie_id s.movie_id_b WHERE s.movie_id_a ? ORDER BY s.similarity DESC LIMIT 10, (movie_id,) ).fetchall() # 组装 HTML 表格并返回 return render_template(explore.html, rowsrows, movie_idmovie_id)这里直接用 SQL 关联查询把相似度矩阵表当作普通数据来读。如果毕设要求数据库存储的是原始评分那么movie_similarity表就是预处理后的“衍生数据”这种情况下论文里可以补一段 ETL 说明把训练脚本的产出物与数据库的关系讲清楚。最后再留一手在compute_item_similarity里把皮尔逊和余弦相似度做成可切换参数这样演示的时候可以现场切换相似度算法实时展示推荐结果变化。答辩时直接用“切换后相似度排名从第 3 变成第 1”来证明参数调整是有实际影响的而不是靠猜。毕设答辩的分数往往不在模型多先进而在你把每一个参数的来历、每一条数据的流向、每一个指标后的人话解释到位。本文还有配套的精品资源点击获取
返回列表