ARTICLE DETAIL

资讯详情

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

电影推荐系统毕设:协同过滤与Django实战指南

电影推荐系统毕设:协同过滤与Django实战指南 简介这份资源是一套面向高校计算机专业毕业设计的电影推荐系统完整项目采用Python3.7、Django2.2.1与MySQL/SQLite搭建核心算法同时实现了基于用户和基于物品的协同过滤适合需要完成推荐系统课题或学习协同过滤落地的中高级开发者参考。压缩包共83个文件约8.41MB以28个py源码文件为主体辅以16个html模板、4个csv数据集、1个sqlite3数据库、1个pdf论文及readme文档覆盖模型、视图、表单、爬虫与数据填充脚本等模块结构清晰便于按功能检索。项目基于movielens数据集附带豆瓣爬虫、论文与数据库文件可在线预览效果已有10607人学习下载。读者可从中获得完整的推荐算法实现思路、Django工程组织方式、数据预处理与爬虫脚本以及论文与数据库配套材料适合作为毕业设计参考或协同过滤入门实践。1. 电影推荐系统毕设从协同过滤到 Django 落地的完整路径很多计算机毕业设计选题里电影推荐系统是出现频率最高的那一类。原因很直接数据集公开、算法有理论深度、前后端能串起来、答辩时演示效果直观。但真正动手做的时候大部分人会卡在三个地方——协同过滤的相似度矩阵到底怎么算、Python 3.7 和 Django 2.2.1 这套老版本环境怎么配、MySQL 里用户评分数据怎么组织才能让推荐跑得动。这个标题对应的就是一套完整的、能跑通的、适合毕业设计答辩的技术方案。它不追求工业级推荐引擎的复杂度而是用最小可用的架构把「用户-物品-评分」这条链路走通让你在两周内有一个能演示、能讲清楚原理、能应对老师提问的系统。适合正在做计算机毕业设计、需要一套完整推荐系统方案的同学也适合想理解协同过滤工程落地的 Python 开发者。2. 协同过滤的核心逻辑为什么选 ItemCF 而不是 UserCF2.1 用户-based 和物品-based 的本质区别协同过滤分两条路UserCF 和 ItemCF。UserCF 的思路是「找到和你口味相似的人把他们喜欢的电影推荐给你」ItemCF 的思路是「找到和你喜欢的电影相似的电影推荐给你」。听起来差不多但在电影推荐场景下ItemCF 明显更合适。原因在于电影数据的特性电影数量相对稳定用户数量会持续增长。UserCF 需要维护用户相似度矩阵用户越多矩阵越大计算越慢而 ItemCF 维护的是电影相似度矩阵电影数量不会爆炸式增长矩阵规模可控。另外ItemCF 的可解释性更强——你可以直接告诉用户「因为你喜欢《星际穿越》所以推荐《盗梦空间》」这种推荐理由在答辩演示时非常好用。从实现角度看ItemCF 的核心就两步计算物品之间的相似度然后根据用户历史评分加权预测未评分物品的分数。相似度用余弦相似度或皮尔逊相关系数都行毕设场景下余弦相似度足够。2.2 相似度矩阵的计算与评分预测先看数据组织。MySQL 里至少需要三张表用户表、电影表、评分表。评分表是核心结构大概是 user_id、movie_id、rating、timestamp。数据量建议控制在 10 万条评分以内否则本地 MySQL 跑相似度矩阵会非常慢。CREATE TABLE rating ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, movie_id INT NOT NULL, rating FLOAT NOT NULL, timestamp BIGINT, INDEX idx_user (user_id), INDEX idx_movie (movie_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表的设计要点user_id 和 movie_id 都建索引因为后续查询「某用户的所有评分」和「某电影的所有评分」是高频操作。rating 用 FLOAT 而不是 INT因为有些数据集是 0.5 分制的。timestamp 字段可选但如果要做时间衰减加权就需要它。接下来是相似度计算。用 Python 实现 ItemCF 的核心逻辑import numpy as np from collections import defaultdict def load_ratings(): 从 MySQL 加载评分数据返回 user-item 矩阵的稀疏表示 # 实际项目中用 pymysql 或 Django ORM 查询 # 这里用字典模拟{user_id: {movie_id: rating}} ratings defaultdict(dict) # 假设从数据库读出的每一行是 (user_id, movie_id, rating) for user_id, movie_id, rating in db_query(): ratings[user_id][movie_id] rating return ratings def item_similarity(ratings): 计算物品之间的余弦相似度矩阵 # 转置{movie_id: {user_id: rating}} item_users defaultdict(dict) for user_id, items in ratings.items(): for movie_id, rating in items.items(): item_users[movie_id][user_id] rating # 计算共现矩阵和模长 item_count defaultdict(int) co_occur defaultdict(int) for movie_id, users in item_users.items(): item_count[movie_id] len(users) for u1 in users: for u2 in users: if u1 ! u2: co_occur[(movie_id, u2)] 1 # 余弦相似度 共现次数 / sqrt(物品1用户数 * 物品2用户数) similarity defaultdict(dict) for (m1, m2), count in co_occur.items(): similarity[m1][m2] count / np.sqrt(item_count[m1] * item_count[m2]) return similarity def recommend(user_id, ratings, similarity, top_n10): 给指定用户推荐 top_n 部电影 user_ratings ratings[user_id] scores defaultdict(float) for movie_id, rating in user_ratings.items(): for similar_movie, sim in similarity.get(movie_id, {}).items(): if similar_movie not in user_ratings: scores[similar_movie] sim * rating # 按分数排序取前 N 个 return sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_n]这段代码的逻辑说明load_ratings把数据库里的评分读成嵌套字典外层 key 是用户内层 key 是电影。item_similarity先转置成物品-用户视角然后统计每对电影被多少共同用户评过分除以模长得到余弦相似度。recommend遍历用户看过的每部电影找到相似电影用相似度乘以用户评分累加得到预测分。参数方面top_n控制推荐数量毕设演示一般 10 到 20 部就够。相似度阈值可以加一个过滤比如只保留相似度大于 0.1 的电影对避免长尾噪声。如果数据量超过 10 万条评分建议先用surprise库或者自己写增量更新不要每次全量重算。注意协同过滤有冷启动问题。新用户没有评分历史ItemCF 无法推荐。毕设答辩时老师大概率会问这个准备一个兜底方案——热门电影排行榜按平均评分和评分人数加权排序。3. Django 2.2.1 项目搭建从零到推荐接口跑通3.1 环境配置与项目初始化Python 3.7 Django 2.2.1 这套组合现在看是有点老但胜在稳定网上资料多出问题容易搜到答案。安装步骤不复杂但有几个坑要提前避开。# 创建虚拟环境强烈建议不要装到全局 python3.7 -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装核心依赖 pip install django2.2.1 pip install pymysql pip install numpy # 创建项目和应用 django-admin startproject movie_rec cd movie_rec python manage.py startapp recommendDjango 2.2.1 默认用 MySQLdb 连接 MySQL但 Python 3 下 MySQLdb 安装麻烦所以用 pymysql 替代。在movie_rec/__init__.py里加两行import pymysql pymysql.install_as_MySQLdb()然后在settings.py里配置数据库DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_rec, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }这里有个高频翻车点Django 2.2.1 对 MySQL 8.0 的认证插件有兼容问题。如果连接时报RuntimeError: cryptography is required装一个pip install cryptography就行。如果报django.db.utils.OperationalError: (2002, Cant connect to local MySQL server)检查 MySQL 服务是否启动、端口是否被占用。3.2 模型设计与数据导入Django 的 ORM 让建表变得简单但推荐系统的表结构要考虑查询效率。在recommend/models.py里定义from django.db import models class User(models.Model): name models.CharField(max_length50) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table user class Movie(models.Model): title models.CharField(max_length200) genres models.CharField(max_length200) # 逗号分隔的类型 year models.IntegerField(nullTrue, blankTrue) class Meta: db_table movie class Rating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) movie models.ForeignKey(Movie, on_deletemodels.CASCADE) rating models.FloatField() timestamp models.BigIntegerField(nullTrue, blankTrue) class Meta: db_table rating indexes [ models.Index(fields[user, movie]), models.Index(fields[movie]), ]模型定义完执行python manage.py makemigrations和python manage.py migrate建表。数据导入建议用 management command 写脚本不要手动往数据库塞。MovieLens 数据集是标准选择ml-latest-small 版本有 10 万条评分、9000 部电影、600 个用户规模刚好。# recommend/management/commands/import_data.py import csv from django.core.management.base import BaseCommand from recommend.models import User, Movie, Rating class Command(BaseCommand): help 从 CSV 导入 MovieLens 数据 def handle(self, *args, **options): # 导入电影 with open(movies.csv, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: Movie.objects.get_or_create( idint(row[movieId]), defaults{title: row[title], genres: row[genres]} ) # 导入评分批量创建提升性能 batch [] with open(ratings.csv, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: batch.append(Rating( user_idint(row[userId]), movie_idint(row[movieId]), ratingfloat(row[rating]), timestampint(row[timestamp]) )) if len(batch) 1000: Rating.objects.bulk_create(batch) batch [] if batch: Rating.objects.bulk_create(batch) self.stdout.write(self.style.SUCCESS(数据导入完成))bulk_create是关键优化逐条插入 10 万条数据要几分钟批量插入几秒就完事。注意get_or_create在电影表上用避免重复导入报错。3.3 推荐接口与前端展示推荐逻辑写成 Django 的 view对外暴露 JSON 接口。在recommend/views.py里import json from django.http import JsonResponse from django.views.decorators.http import require_GET from .models import Rating, Movie from .cf import item_similarity, recommend # 前面写的协同过滤模块 require_GET def get_recommendations(request, user_id): # 从数据库加载该用户的评分 user_ratings { r.movie_id: r.rating for r in Rating.objects.filter(user_iduser_id) } if not user_ratings: # 冷启动返回热门电影 hot Movie.objects.order_by(-rating_count)[:10] return JsonResponse({type: hot, movies: [m.title for m in hot]}) # 加载全量评分构建相似度矩阵生产环境应缓存 all_ratings {} for r in Rating.objects.all().values(user_id, movie_id, rating): all_ratings.setdefault(r[user_id], {})[r[movie_id]] r[rating] sim item_similarity(all_ratings) recs recommend(user_id, all_ratings, sim, top_n10) # 查电影标题 movie_ids [m_id for m_id, _ in recs] movies Movie.objects.filter(id__inmovie_ids) movie_map {m.id: m.title for m in movies} result [ {movie_id: m_id, title: movie_map.get(m_id, 未知), score: round(score, 3)} for m_id, score in recs ] return JsonResponse({type: cf, movies: result})这个 view 的逻辑先查用户评分没有就走热门兜底有的话加载全量数据算相似度调推荐函数最后把电影 ID 映射成标题返回 JSON。前端用简单的 HTML 表格或者 Vue 都行毕设演示不需要太花哨。URL 配置在movie_rec/urls.pyfrom django.urls import path from recommend import views urlpatterns [ path(api/recommend/int:user_id/, views.get_recommendations), ]启动python manage.py runserver访问http://127.0.0.1:8000/api/recommend/1/就能看到推荐结果。提示每次请求都全量计算相似度矩阵会非常慢。毕设场景下可以在启动时算一次缓存到内存或者用 Django 的 cache 框架存 Redis。如果数据量小直接在 view 里算也能忍但答辩演示前记得预热。4. 避坑与排查那些让毕设卡三天的典型问题4.1 MySQL 连接报错 2002 和 1045现象运行python manage.py migrate时报(2002, Cant connect to local MySQL server through socket /tmp/mysql.sock)。原因通常是 MySQL 服务没启动或者 socket 文件路径不对。解决先systemctl start mysql或service mysql start启动服务然后确认my.cnf里的 socket 路径和 Django 配置一致。如果报 1045 错误是用户名密码不对检查settings.py里的 USER 和 PASSWORDMySQL 8.0 还要确认用户有没有远程访问权限。4.2 Django 2.2.1 与 MySQL 8.0 的认证兼容现象连接时报django.db.utils.OperationalError: (2059, Authentication plugin caching_sha2_password cannot be loaded)。原因是 MySQL 8.0 默认用 caching_sha2_password 插件而 Django 2.2.1 用的 mysqlclient 版本不支持。解决要么改 MySQL 用户认证方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY password;要么升级 mysqlclient 并安装 cryptography。毕设环境建议直接用第一种改完FLUSH PRIVILEGES生效。4.3 相似度矩阵内存溢出现象数据量到 10 万条评分时Python 进程内存飙升到几个 G甚至被系统 kill。原因是item_similarity里的双重循环在物品数量大时产生大量中间字典。解决限制参与计算的物品数量只对评分次数超过阈值的电影算相似度或者用 numpy 的稀疏矩阵替代字典。毕设数据量下把电影限制在评分人数最多的 2000 部以内内存和速度都能接受。4.4 推荐结果全是已经看过的电影现象接口返回的推荐列表里混入了用户已经评过分的电影。原因是recommend函数里过滤条件写错了或者相似度矩阵的 key 类型不匹配int 和 str 混用。解决在推荐循环里加if similar_movie not in user_ratings判断同时确保数据库查出来的 movie_id 和相似度矩阵里的 key 都是 int 类型。Django ORM 查出来默认是 int但如果你手动转成 str 就会出问题。4.5 中文电影名乱码现象前端展示的电影标题是问号或者乱码。原因是 MySQL 数据库、表、连接三处的字符集不一致。解决建库时指定CREATE DATABASE movie_rec CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Django 的settings.py里 OPTIONS 加charset: utf8mb4HTML 页面加meta charsetutf-8。三处都对了就不会乱码。5. 让推荐效果更好的三个调优技巧5.1 用时间衰减给近期评分更高权重用户三年前给《阿甘正传》打了 5 分和昨天刚打的 5 分意义完全不同。在评分预测时加一个时间衰减因子import math from datetime import datetime def time_weight(timestamp, half_life_days365): 时间衰减权重半衰期默认一年 days (datetime.now().timestamp() - timestamp) / 86400 return math.exp(-days / half_life_days)在recommend函数里把sim * rating改成sim * rating * time_weight(ts)。这个改动很小但答辩时能讲出「考虑了用户兴趣漂移」是个加分项。半衰期参数根据数据集时间跨度调MovieLens 数据跨度几年365 天比较合适。5.2 用流行度惩罚避免热门电影霸榜ItemCF 有个天然缺陷热门电影因为被评次数多和任何电影都有较高相似度导致推荐结果全是大众片。解决办法是在相似度计算时除以流行度的对数def item_similarity_with_penalty(ratings, penalty0.5): # ... 前面的共现统计不变 ... for (m1, m2), count in co_occur.items(): # 流行度惩罚热门物品相似度打折 pop_penalty item_count[m2] ** penalty similarity[m1][m2] count / (np.sqrt(item_count[m1] * item_count[m2]) * pop_penalty) return similaritypenalty参数控制惩罚力度0 就是不惩罚1 是强惩罚。毕设场景下 0.3 到 0.5 之间效果比较均衡。这个技巧来自推荐系统经典论文答辩时能体现你读过文献。5.3 离线评估用准确率和召回率验证效果光有推荐结果不够老师会问「你怎么知道推荐得准」。做一个简单的离线评估把每个用户的评分按时间排序最后 20% 作为测试集前 80% 作为训练集。对测试集里的电影看推荐列表命中多少。def evaluate(ratings, similarity, k10, test_ratio0.2): precisions, recalls [], [] for user_id, items in ratings.items(): # 按时间排序切分简化版随机切分 sorted_items sorted(items.items(), keylambda x: x[1]) n_test max(1, int(len(sorted_items) * test_ratio)) test_items dict(sorted_items[-n_test:]) train_items dict(sorted_items[:-n_test]) recs recommend(user_id, {user_id: train_items}, similarity, top_nk) rec_ids set(m_id for m_id, _ in recs) hit len(rec_ids set(test_items.keys())) precisions.append(hit / k) recalls.append(hit / len(test_items)) return sum(precisions) / len(precisions), sum(recalls) / len(recalls)跑出来准确率大概在 0.1 到 0.2 之间召回率 0.05 到 0.15 之间这是 ItemCF 在 MovieLens 上的正常水平。把这个数字写进论文的实验章节比空谈「推荐效果良好」有说服力得多。5.4 答辩演示前的检查清单演示前把这几件事过一遍确认 MySQL 服务开机自启避免答辩现场连不上数据库把相似度矩阵预计算好存成 pickle 文件启动时直接加载省去现场计算的几十秒准备一个没有评分历史的新用户 ID演示冷启动兜底逻辑把推荐接口的响应时间打印出来如果超过 2 秒就加缓存。我自己的习惯是答辩前一晚用另一台电脑完整跑一遍部署流程把遇到的每个报错记下来提前解决。这套方案不算复杂但细节多踩过一次坑就记住了。希望帮到你。本文还有配套的精品资源点击获取
返回列表