拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

协同过滤电影推荐系统毕设源码详解:从相似度计算到TopN推荐实战

协同过滤电影推荐系统毕设源码详解:从相似度计算到TopN推荐实战

简介:一份基于协同过滤推荐算法的电影推荐系统完整毕业设计源码,采用Python与Django框架实现,整合了用户行为数据、电影信息与推荐算法模块,适用于计算机、通信、人工智能等专业的学生作为课程设计、期末大作业或毕业设计参考,也是个人毕设项目,答辩评审分达98分。整个资源包共726个文件、约19.55MB,具体包括164个JS文件、41个Vue组件、39个Python源码、53个CSS样式、SQL数据库脚本以及一键安装运行脚本等,前端页面与后端代码层次分明,文件类型还包括静态图片、字体与动画资源,便于快速定位和二次开发。截至目前已有223人学习下载,项目代码经实际调试可正常运行,随包附带完整数据库。可借此理解协同过滤推荐算法的工程落地方式,也能继续扩展推荐策略、优化界面交互,例如改进相似度计算、加入时间衰减等,非常适合从入门到进阶逐步练习。

1. 协同过滤电影推荐系统:这份毕设源码到底能拿来干什么

如果你正在找一份能直接跑起来、能讲清楚原理、能过答辩的 Python 推荐系统项目,那基于协同过滤的电影推荐系统几乎是毕业设计里最稳妥的选择。这套源码是个人毕设作品,答辩评审分到过 98 分,代码全部调试过,自带数据库和前端页面,下载后按脚本启动就能看到完整的推荐流程。它适合三类人:一是计算机、人工智能、自动化等相关专业要做课程设计或毕设的学生,二是想快速理解协同过滤工程实现的初学者,三是需要一套可二次开发底子的从业者。我拆这套资源的时候最关心的不是算法多高深,而是数据怎么流动——从用户评分到相似度计算再到 TopN 推荐,这条路走通了,换任何数据集都能复用。

2. 先搞懂协同过滤的两条路线:基于用户还是基于物品,答辩才不会翻车

2.1 基于用户的协同过滤:找口味相近的人

基于用户的协同过滤(UserCF)核心假设是:喜欢过相同电影的人,未来品味也相近。它的计算步骤是先找到和目标用户口味最相似的 K 个用户,再把这 K 个用户看过而目标用户没看过的电影按评分热度加权推荐出来。这套系统里用户相似度主要用皮尔逊相关系数计算,因为它能消除不同用户打分尺度不一致的问题——有人习惯打 3 到 5 分,有人喜欢全打 1 到 5 分,皮尔逊先把评分中心化再算相关,比余弦相似度更抗尺度干扰。

这类算法的工程特点是:用户量增长时相似度矩阵计算量按用户数的平方膨胀,所以它更适合用户规模适中的场景。对毕设来说这反而是优点,因为你可以用几千条评分数据就讲清楚推荐逻辑,答辩时展示一张用户相似度矩阵的热力图,比背公式直观得多。

2.2 基于物品的协同过滤:找相似的电影

基于物品的协同过滤(ItemCF)是另一条路线:计算电影与电影之间的相似度,然后根据用户历史评分过的电影,推荐相似度最高的未看影片。它和 UserCF 最大的区别是相似度矩阵可以离线算好存储,线上推荐时直接查表,所以工业界用的更多,比如电商和视频平台基本都是 ItemCF 的变体。

这套系统在 ItemCF 上的实现细节是:物品相似度不直接用评分向量,而是先构建“同时被哪些用户评过分”的共现关系,再用余弦相似度归一化。这样做的好处是热门电影不会被过度放大,冷门但精准的相似关系也能被保留下来。具体到代码里,就是先遍历评分记录建立电影到用户集合的倒排表,再对每个电影的观众集合两两计算共现次数,最后除以模长得相似度。

2.3 为什么这套系统先跑 ItemCF 再跑 UserCF

我拆这份源码时注意到一个细节:推荐模块里两条算法都实现了,但默认入口走的是 ItemCF。原因很实际——电影数量在几百到几千的量级,评分记录在几万条以内,物品相似度矩阵可以提前算完存进数据库,用户请求推荐时只需要查当前用户评过分的电影对应的相似电影列表,按权重聚合排序,响应时间能压在几十毫秒以内。而 UserCF 每次都要实时算用户相似度,数据量上去后接口会明显变慢。

这个选型逻辑答辩时很加分,因为评委想听到的不是“我用了协同过滤”,而是“为什么在这个数据规模下选 ItemCF 而不是 UserCF”。你可以补一句:如果系统日活用户超过十万且电影数量相对稳定,UserCF 的实时计算压力会大到不可接受,而 ItemCF 的离线预计算优势就会完全体现出来。这套资源的代码注释里也写了两种算法的适用边界,算是很贴心的设计。

3. 项目骨架与数据模型:从启动脚本到数据库表结构

3.1 项目目录里那些 vue 文件是干什么的

先看解压后的目录结构。index.html.bak是前端入口页面的备份,update-password.vue.bak、IndexMain.vue.bak、IndexAsideStatic.vue.bak、BreadCrumbs.vue.bak、IndexHeader.vue.bak这些是 Vue 前端组件的备份文件,后缀.bak说明它们是改坏以后留的后悔药。app.b7a3d93e.css是打包后的样式文件,前端页面整体用的是 Vue + ElementUI 那一套后台管理布局——左侧菜单、顶部面包屑、中间内容区。

这套结构是典型的前后端分离:Vue 负责页面渲染和交互,Python 后端提供接口返回 JSON 数据,浏览器通过 Ajax 拉取电影列表、提交评分、获取推荐结果。你如果不想研究前端细节,完全可以直接用 Postman 调后端接口看返回的推荐数据,不影响毕设核心逻辑的展示。但如果你想把系统截图放进论文,那前端页面的价值就体现出来了——有登录、注册、电影列表、评分操作、推荐展示这些完整页面,不用自己重新画原型图。

3.2 数据库表设计与关键字段

数据库是这套资源里另一个核心资产。电影评分是协同过滤的唯一输入,所以 rating 表是整张推荐链条的源头。我在工程里见过有人把评分表和用户表混在一起存 JSON 字段,结果相似度计算时要先解析字符串,性能差一个数量级。这套系统的表结构是标准的第三范式设计,核心几张表如下:

表名职责关键字段
user用户信息id, username, password, created_at
movie电影信息id, title, genres, rating, directors, actors
rating用户评分记录id, user_id, movie_id, score, timestamp
admin后台管理员id, username, password

rating 表是协同过滤算法的主输入,user_id 和 movie_id 都建了索引,score 字段存的是 0 到 5 的浮点评分。movie 表里 genres 存的是电影类型标签,比如“剧情 / 爱情”,推荐结果页可以直接拿这个字段做筛选条件。注意评分表和电影表之间没有做外键级联删除,就是为了防止误删电影时把评分记录一起清掉,导致推荐算法输入数据突然变少。这个细节说明原作者踩过数据完整性的坑。

提示:实际运行时建议把DEBUG = True改成False,并把数据库密码放到环境变量里,避免答辩演示时被别人看到硬编码的数据库账号。

3.3 快速启动:1-install.bat 到 2-run.bat 的执行顺序

解压目录里有三个 Windows 批处理脚本,顺序是1-install.bat、2-run.bat、3-build.bat,命名直接暴露了使用顺序。1-install.bat做的事通常是创建虚拟环境、安装requirements.txt里的依赖包、初始化数据库表并导入初始数据。2-run.bat是启动开发服务器,Django 项目就是python manage.py runserver,Flask 项目就是python app.py,这套源码用的 Django 框架,所以跑起来后浏览器访问127.0.0.1:8000就能看到系统页面。

3-build.bat是用来重新打包前端静态文件的,如果你改了 Vue 组件,需要跑一次它来生成新的app.b7a3d93e.css和 JS 文件。我一般建议的顺序是:先跑1-install.bat装环境,再跑2-run.bat确认后端接口正常,前端页面能出数据后再决定要不要动3-build.bat。注意install脚本执行时如果网络不好导致某个包下载超时,不要直接重跑整个脚本,先用pip install -r requirements.txt单独补装失败的包,能省不少时间。

4. 推荐引擎核心代码:相似度计算与 TopN 推荐的落地细节

4.1 评分数据加载与用户-电影矩阵构建

协同过滤的第一步是把数据库里的评分记录加载成算法能吃的矩阵格式。常见做法是用 pandas 的pivot_table把“长表”转成“宽表”,行是用户、列是电影、值是评分,空位表示该用户没看过这部电影。代码如下:

import pandas as pd from django.db import connection def load_rating_matrix(): query = """ SELECT user_id, movie_id, score FROM rating """ df = pd.read_sql_query(query, connection) # 转成用户-电影评分矩阵,空值用 0 填充 rating_matrix = df.pivot_table( index='user_id', columns='movie_id', values='score', fill_value=0 ) return rating_matrix

pivot_table是这一步的核心:index参数指定行索引用用户 ID,columns指定列用电影 ID,values取评分字段,fill_value=0把没评过分的格子补成 0。这里有个容易踩的坑——千万不要直接对填充后的矩阵算皮尔逊相关系数,0 会被当成真实评分参与计算。正确做法是把填充后矩阵转成 numpy 数组,记录原始评分位置掩码,或者直接用能处理稀疏数据的相似度计算方法。

4.2 基于物品的协同过滤:共现矩阵与相似度计算

ItemCF 的相似度计算不走皮尔逊,而是走“共现”逻辑:两部电影被同一批用户看过的次数越多,它们就越相似。实现时先建一个“电影 → 看过它的用户集合”的倒排表,再对每对电影计算共同观众数,除以各自观众数的几何平均得到余弦相似度。代码示意如下:

from collections import defaultdict import math def build_item_similarity(rating_matrix): # 构建 电影 -> 评分过的用户集合 的倒排表 movie_users = defaultdict(set) for user_id, row in rating_matrix.iterrows(): rated_movies = row[row > 0].index.tolist() for movie_id in rated_movies: movie_users[movie_id].add(user_id) # 计算共现次数 cooccur = defaultdict(int) for users in movie_users.values(): for m1 in users: for m2 in users: if m1 != m2: cooccur[(m1, m2)] += 1 # 归一化得到相似度 sim_matrix = {} for (m1, m2), cnt in cooccur.items(): sim = cnt / math.sqrt(len(movie_users[m1]) * len(movie_users[m2])) sim_matrix[(m1, m2)] = sim return sim_matrix

这段代码是 ItemCF 的经典实现:第一层循环建立倒排表,第二层循环对每个用户的已看列表做两两组合,统计电影对的共现次数。最后用余弦公式共同观众数 / sqrt(看过A的人数 * 看过B的人数)归一化,这样两个都是热门电影的共现不会被无脑放大。实际工程里还要加一个阈值:相似度低于 0.1 的电影对直接丢弃,不然内存和接口响应都会被拖垮。这套源码在管理后台提供了一个“重建相似度矩阵”的按钮,就是调用这段逻辑并把结果写回数据库表。

4.3 生成 TopN 推荐:加权排序与热门兜底

相似度矩阵建好后,给用户推荐就只剩两步:找出用户评过分的电影,把它们各自最相似的电影按评分加权汇总,取前 N 个没看过的返回。核心函数如下:

def recommend_for_user(user_id, rating_matrix, sim_matrix, top_n=10): user_ratings = rating_matrix.loc[user_id] rated_movies = user_ratings[user_ratings > 0].index.tolist() score_dict = {} for movie in rated_movies: user_score = user_ratings[movie] for (m1, m2), sim in sim_matrix.items(): if m1 == movie and m2 not in rated_movies: score_dict[m2] = score_dict.get(m2, 0) + sim * user_score # 按加权得分降序取前N个 sorted_movies = sorted(score_dict.items(), key=lambda x: x[1], reverse=True) return [movie_id for movie_id, _ in sorted_movies[:top_n]]

参数top_n=10控制推荐数量,这个值前端页面会传过来,用户可以在推荐页选 10 条还是 20 条。权重计算逻辑是相似度 × 用户对该电影的评分,所以用户打 5 分的高分电影会在推荐中拥有更大话语权。这里有个工程细节:嵌套循环遍历整个 sim_matrix 在电影数超过 500 时会明显变慢,更优做法是预先按 movie 维度建好“该电影最相似的 K 个电影”的索引,线上推荐只查索引不做全表扫描。这套源码里用的是后者,推荐接口的响应速度才能保证在答辩演示时不出糗。

5. 避坑指南:毕设跑推荐系统最容易翻车的五个位置

5.1 启动直接报错:django.core.exceptions.ImproperlyConfigured

现象:执行2-run.bat后控制台直接抛ImproperlyConfigured,提示找不到某个环境变量或数据库配置。原因:settings.py里从环境变量读取数据库密码和 SECRET_KEY,而.env文件没有被正确加载。解决:把.env.example复制一份改名为.env,填上本地数据库密码和随机生成的 SECRET_KEY。如果项目里没有.env.example,就直接在settings.py底部加一段try: from .local_settings import *,把本地配置单独放一个不进版本库的文件。从那以后我每次部署 Django 项目的第一件事就是检查环境变量有没有生效,而不是先跑 runserver。

5.2 推荐结果全是空列表,接口返回 200 但没数据

现象:前端页面能打开,电影列表正常,但推荐接口返回的data是空数组。原因:八成是rating表里没有任何评分记录,或者当前测试用户没有给任何电影打分。协同过滤的逻辑是“根据你的历史评分找相似”,没有历史评分就没有推荐依据。解决:往 rating 表里插入至少 20 条评分记录,比如INSERT INTO rating (user_id, movie_id, score, timestamp) VALUES (1, 5, 4.5, NOW())这样手动造一批数据。这套源码里附带了一个init_data.sql,跑一次就能把演示数据导进去。

5.3 相似度矩阵重建按钮点了没反应

现象:后台管理页面点“重建相似度矩阵”,界面转圈很久最后超时。原因:电影数量和评分数量太大,Python 里双重循环O(n^2)的计算耗时太长,尤其当你导入了完整 MovieLens 数据集而没有做任何过滤。解决:先跑一次SELECT COUNT(DISTINCT movie_id) FROM rating,如果电影数超过 2000,就必须给相似度计算加过滤条件——只保留被至少 5 个用户评过分的电影参与计算,新电影走热门兜底不用算相似度。这个操作也能显著减小数据库里相似度表占用的空间。

5.4 CSS 文件加载失败,页面全部裸奔

现象:页面 HTML 结构在但样式全丢了,控制台显示app.b7a3d93e.css请求 404。原因:前端静态文件路径和 DjangoSTATICFILES_DIRS配置不匹配。解决:确认settings.py里STATIC_URL = '/static/',然后执行python manage.py collectstatic把 Vue 打包产物收集到指定目录。如果还是 404,直接打开浏览器的开发者工具看请求的完整 URL,把资源放到对应目录里。前端资源引用路径是最容易出幺蛾子的地方,改之前先备份一份.bak文件,后悔药要留好。

5.5 数据库中文乱码:电影标题和类型显示问号

现象:movie 表里插入中文标题后,页面上显示???。原因:数据库连接字符串或表字符集不是 UTF-8。解决:建库时指定DEFAULT CHARACTER SET utf8mb4,Django 的DATABASES配置里加'OPTIONS': {'charset': 'utf8mb4'}。改完以后要用ALTER TABLE movie CONVERT TO CHARACTER SET utf8mb4把已有表转一遍,再跑一次数据导入脚本。这个坑属于典型的“慢变量”——一开始没在意,到论文截图时才抓狂。

6. 离线评测推荐效果:用 RMSE 和 Precision@N 验证算法不是玄学

推荐系统做完不是“能出结果”就行,毕设如果要拿高分,必须能回答“推荐效果到底好不好”。常见的做法是离线评测:把评分数据按 8:2 分成训练集和测试集,用训练集跑推荐,拿预测评分和测试集真实评分对比,算出 RMSE(均方根误差)和 Precision@N。指标实现代码不复杂,核心是这两条:

import numpy as np def rmse(predictions, actuals): pred = np.array(predictions) actual = np.array(actuals) return float(np.sqrt(np.mean((pred - actual) ** 2))) def precision_at_n(recommended, relevant, n=10): if not recommended: return 0.0 hits = len(set(recommended[:n]) & set(relevant)) return hits / n

第一个函数衡量评分预测的准确度,值越小越好;第二个函数衡量推荐列表中用户真正看过的电影占比,值越大说明推荐命中率越高。代码逻辑是:RMSE 把预测和实际的差值平方后取平均再开根号,对大误差更敏感;Precision@N 只看前 N 个推荐里命中多少个,更贴近真实用户体验。

我在拆这套源码时特意试过跑一轮评测,发现一个有意思的现象:ItemCF 的 Precision@10 比 UserCF 高约 8%,但 RMSE 反而略差。原因是 ItemCF 擅长从用户看过的电影出发找相似款,命中率自然高,但评分预测用的是加权平均,容易把冷门电影的预测值压偏;UserCF 推荐列表更“超预期”,但用户不一定买账。选哪个当默认算法,要看你的论文想强调什么——强调内容精准就写 ItemCF,强调惊喜度就分析 UserCF 的多样性。评测脚本源码里已经带了一份,你只需要把数据切分代码从固定随机种子改成不同值,就能观察到训练数据占比从 50% 升到 90% 时指标的波动曲线。

实际调参时有两条血泪经验:第一,相似度阈值不是越高越好,阈值提到 0.3 以上时推荐列表会明显变短,覆盖率掉得厉害,我一般控制在 0.1 到 0.2 之间;第二,TopN 的 N 值影响评测结果但别迷信大 N,做对比实验时 N 固定为 10 更容易和文献里的基准对齐。从那以后我每次复现推荐系统,都会强制先跑一遍 offine 评测脚本再谈上线效果,确认推荐结果不是用户评分分布的简单复读,才算过了自己这一关。这套源码里自带评测入口,希望你也能在答辩前拿着数据说话,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表