每年到毕业季,总有一批人对着“基于XXX的推荐系统”这种题目发愁。名著推荐、电影推荐、音乐推荐……本质套路相通,但真正能把它讲清楚、做成一个能跑通全流程的源码包,还能过答辩的项目,其实并不多。我手头这个“基于django+深度学习的经典名著推荐系统”,算是一个很典型的综合性毕设题目。它把Web开发、数据分析、算法模型三块硬伤全占了,但又不像看起来那么难啃。这篇文章我就从项目拆解、算法实现、Django集成到常见掉坑点,完整过一遍这套系统的设计思路和实操细节,给正准备做同类题目的同学一份可以直接“抄作业”的参考。
先说清楚这个系统到底是干什么的:它以经典名著为数据对象,前端是Django渲染的网站,用户在页面上注册登录、浏览图书、打分评价、收藏想看,系统后台根据用户的这些行为数据,用协同过滤和深度学习模型给用户推荐可能喜欢的书。整个项目不是那种只有一个登录页面的“假毕设”,它包含完整的数据表设计、推荐算法训练脚本、Django业务代码、管理后台,以及一套能答辩讲明白的文档逻辑。
1. 项目整体设计与技术选型思路
1.1 为什么这个题目选了Django而不是其他框架
推荐系统类的毕设,可选的Web框架其实不少:Spring Boot、Flask、FastAPI、Django都有人用。但Django在这个场景里有几个天然优势,我实际做下来体会很深。
第一,Django自带Admin后台。毕设最怕的是“管理系统”四个字——老师一定会问你怎么管理数据。Django的admin配合ORM,你只要把数据表模型一写,后台自动生成增删改查界面,不用自己写一堆管理页面。名著信息、用户评分、收藏记录的管理都能直接搞定,省下的时间全都可以砸在推荐算法上。
第二,ORM写起来快,而且对答辩友好。学生普遍不会写特别复杂的SQL,Django的ORM把对象操作翻译成SQL,写起来像是在操作Python列表,答辩时也容易解释清楚数据流向。
第三,Django是“全家桶”结构,自带用户认证体系。注册、登录、会话管理这些需求,在Django里都是现成的组件,尤其自带的User模型配合扩展Profile表,能解决多数字段需求。用Spring Boot当然也行,但对Python技术栈的毕设来说,Django的学习曲线更平缓,出活更快。
1.2 深度学习在推荐系统里到底负责什么
这是很多同学最容易懵的地方。深度学习和推荐系统结合,听起来很高大上,但你要在毕设里落地,得分清楚它解决的具体问题。
我推荐的做法是:传统协同过滤做主链路,深度学习模型做辅助召回或打分预测,两条腿走路。
- 协同过滤负责“解释得通”的推荐:你和某个用户都喜欢《百年孤独》,那系统就把那个用户还喜欢的《霍乱时期的爱情》推荐给你。逻辑简单,答辩好讲。
- 深度学习负责“学得更深”的预测:把用户的历史行为序列、图书的文本描述特征都丢进一个神经网络,模型自己学出用户和书之间的复杂关系,输出一个预测评分。
在这个项目里,我用了一个轻量级的深度矩阵分解模型(Deep Matrix Factorization),它的思路比传统MF多了一层神经网络,能捕捉一些非线性关系。模型结构不复杂,但足以在论文里写出“基于深度学习的推荐模型”这样的关键词,也足以满足毕设的算法要求。
1.3 经典名著数据集从哪里来
做推荐系统,数据是命根子。电影推荐有大把公开数据集,但中文经典名著这块儿没有特别现成的标准数据集。我当时采用的是“公开书单+豆瓣/自建信息”组合的方式。
一个比较实用的做法是:先手动整理一个名著书单,比如《红楼梦》《三国演义》《老人与海》《巴黎圣母院》这类,大概100到200本,每本书记录书名、作者、分类、简介、出版年份。然后让用户行为数据“从零开始积累”——在系统里内置一批模拟用户评分数据,用脚本来生成。
很多同学担心“数据量不够深度学习”,我提醒一句:毕设场景里,数据量不需要达到工业级。几百个用户、上千条评分记录,配合脚本扩充数据,已经足够跑通整个流程。重点是流程完整,而不是模型效果要超过淘宝。
生成模拟数据时要注意分布合理,不能所有用户都打5分,要有高分、低分、没看过的情况,这样协同过滤才有区分度。我用的是正态分布加随机漂移的方式,配合一部分冷门书目,模拟真实场景的稀疏矩阵。
2. 推荐系统核心算法拆解与选型
2.1 基于协同过滤的主推荐链路
协同过滤最基础的两种:UserCF和ItemCF。我做的是这两种都实现,然后按场景切换。
UserCF的核心步骤:先建用户-物品评分矩阵,然后计算用户之间的相似度,找到当前用户的K个最近邻用户,把这K个用户喜欢的、且当前用户没看过的物品按得分推荐出来。相似度计算通常用余弦相似度或皮尔逊相关系数。
ItemCF则反过来,先算物品之间的相似度,再根据用户的历史行为推荐相似物品。毕设答辩时老师经常问“为什么这个场景用ItemCF更适合”,标准答案是因为用户数量比图书数量大得多,计算物品相似度的代价更小,而且新用户的冷启动可以通过历史评分快速兜底。
我在实际实现里是这样写的:
import numpy as np from sklearn.metrics.pairwise import cosine_similarity # rating_matrix: 形状为 (用户数, 图书数) 的稀疏填充矩阵 # 缺失值用0填充,表示该用户没读过这本书 user_sim_matrix = cosine_similarity(rating_matrix) item_sim_matrix = cosine_similarity(rating_matrix.T) def recommend_by_itemcf(user_id, top_n=10): # 用户已打分的图书索引和分数 rated = np.nonzero(rating_matrix[user_id]) scores = {} for item_id, score in zip(rated[0], rating_matrix[user_id][rated]): # 找相似物品 similar_items = np.argsort(-item_sim_matrix[item_id])[:20] for sim_item in similar_items: if sim_item in scores: scores[sim_item] += score * item_sim_matrix[item_id][sim_item] else: scores[sim_item] = score * item_sim_matrix[item_id][sim_item] # 过滤已读内容 for r in rated[0]: scores.pop(r, None) return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n]注意:上面这段是思路示意,真实工程里要考虑稀疏矩阵存储,直接用二维数组几百个用户没问题,但如果模拟用户上万,建议用scipy.sparse来存矩阵,否则内存会炸。
2.2 基于深度学习的评分预测模型
深度学习的部分,我实现了一个深度矩阵分解模型。核心结构是:用户ID经Embedding得到用户向量,图书ID经Embedding得到物品向量,两个向量拼接或点积后,送入全连接层,最后输出一个预测分数。
import torch import torch.nn as nn class DeepMF(nn.Module): def __init__(self, num_users, num_items, embed_dim=64): super().__init__() self.user_emb = nn.Embedding(num_users, embed_dim) self.item_emb = nn.Embedding(num_items, embed_dim) self.fc = nn.Sequential( nn.Linear(embed_dim * 2, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1) ) def forward(self, user_ids, item_ids): u = self.user_emb(user_ids) i = self.item_emb(item_ids) concat = torch.cat([u, i], dim=1) return self.fc(concat).squeeze()训练的时候我踩过一个坑:Embedding维度设得太大,导致模型在毕设机器上训练明显变慢。后来我意识到,几百本书、几百个用户的数据规模,根本不需要大embedding,64维已经完全够用,训练一轮也就几秒钟。很多同学一上来就抄工业界的128、256维,结果设备跑不动,还以为是代码问题。
模型的输入是用户-图书对,标签是真实评分。训练用MSE损失,优化器选Adam,学习率0.001,批次大小256。要记得把数据划分成训练集和验证集,避免过拟合。L2正则和Dropout我都加了,Dropout对防止过拟合的作用更直观。
2.3 模型增强:内容特征做补充
光靠用户评分做深度学习,有点像盲人摸象。我后来在模型里加了一路内容特征:把每本名著简介做文本向量化(用TF-IDF或预训练的Word2Vec),拼到Embedding后面,让模型在训练时能看到“书是什么内容”。
这一步最大的作用不是提升离线指标,而是让答辩时能理直气壮地说“我们融合了用户行为特征和内容特征,属于混合推荐”。老师的追问套路基本就是:你的模型只用ID特征,那新书没评分怎么办?答案是内容特征可以从简介文本里拿,同时解决一部分冷启动问题。
实现上用jieba分词,去掉停用词,再用Word2Vec或TF-IDF把每本书的简介转成固定维度向量。TF-IDF实现更简单,不需要额外训练词向量模型,我建议毕设先用TF-IDF兜底,有时间再升级到预训练词向量。
3. Django工程结构与数据表设计
3.1 Django项目初始化与环境准备
我建议Python用3.10左右稳定版本,Django用3.2 LTS,PyTorch用CPU版就够。不要一上来装GPU版PyTorch,毕设机器大概率没有NVIDIA显卡,装了反而报错。
python -m venv venv source venv/bin/activate # Windows是 venv\Scripts\activate pip install django==3.2.* torch --index-url https://download.pytorch.org/whl/cpu pip install numpy pandas scikit-learn jieba scipy django-admin startproject bookrec python manage.py startapp books python manage.py startapp users python manage.py startapp recommend我习惯拆三个app:books管图书和评分,users管用户,recommend管推荐逻辑。这样结构清晰,答辩时对着架构图也好讲。
settings.py里要注册app,配置数据库为MySQL或SQLite。毕设我建议先用SQLite,零配置,文件即库,省去MySQL安装的麻烦。如果老师要求用MySQL,再改DATABASES配置,Django迁移命令通用,切换成本很低。
3.2 核心数据表建模
数据模型是整套系统的基础。我设计了5张核心表:
from django.db import models from django.contrib.auth.models import User class Book(models.Model): title = models.CharField(max_length=200) author = models.CharField(max_length=100) category = models.CharField(max_length=50) description = models.TextField() cover_url = models.URLField(blank=True) pub_date = models.DateField(null=True, blank=True) class Rating(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) score = models.IntegerField() # 1-5分 created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'book') class Favorite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True)Rating表用unique_together防止同一用户对同一本书重复打分,这个约束在实际场景很重要,否则生成模拟数据和用户真实评分时,会出现重复记录导致协同过滤矩阵脏掉。
这里我吃了不少亏:一开始没加唯一约束,生成模拟评分脚本跑了两遍,用户就出现了多条相同打分记录,推荐结果直接崩了。后来不仅加了约束,生成脚本里也做了先判断再更新的逻辑。
3.3 模拟数据的批量生成
毕设项目必须要有足够的数据支撑,否则推荐系统空空如也。写一个management command在Django里跑:
from django.core.management.base import BaseCommand from books.models import Book, Rating from django.contrib.auth.models import User import random, numpy as np class Command(BaseCommand): def handle(self, *args, **options): users = list(User.objects.all()) books = list(Book.objects.all()) for user in users: # 每个用户只对一部分书打分 rated_count = random.randint(10, len(books) // 2) sample_books = random.sample(books, rated_count) for book in sample_books: # 分数偏正态分布 score = int(np.clip(np.random.normal(3.5, 1.2), 1, 5)) Rating.objects.update_or_create( user=user, book=book, defaults={'score': score} )这个command的好处是数据可复现,答辩演示前跑一遍,环境换台电脑也能重新生成。很多同学直接把模拟数据导成JSON再灌进数据库,一旦删库重建就傻眼。用management command做数据初始化,才是正规做法。
4. 推荐模块的完整实现链路
4.1 离线计算与在线推荐分离
推荐系统如果不做分层,每次请求都实时算相似度矩阵,系统会卡成PPT。我在项目里采用“离线计算+在线读取”的策略:
- 离线脚本定期计算图书相似度矩阵和模型参数,计算结果保存到数据库表或缓存文件。
- 在线展示时,Django视图直接从缓存里取TopN结果,不重复计算。
具体做法:写一个更新任务,先跑协同过滤和深度学习模型,把每个用户的推荐结果存到Redis或数据库表。因为我用了SQLite,就直接建了一张UserRecommendation表,存用户ID和推荐图书列表的JSON字符串。展示时一句话查询搞定。
# recommend/views.py import json from django.shortcuts import render from recommend.models import UserRecommendation from books.models import Book def my_recommend(request): user = request.user rec, _ = UserRecommendation.objects.get_or_create( user=user, defaults={'rec_list': json.dumps([])} ) book_ids = json.loads(rec.rec_list) books = Book.objects.filter(id__in=book_ids) return render(request, 'recommend_list.html', {'books': books})这样做的优点是线上响应速度快,一个页面基本就一两次数据库查询。答辩时还能用“我们将计算密集任务离线处理,在线只做查询”来体现工程思维。
4.2 训练脚本与Django的集成方式
深度学习的训练脚本我不建议直接写在views.py里,太重,而且每次请求都重新加载模型代价太大。我把训练逻辑放在recommend目录下的一个train.py中,用独立进程跑。跑完后把模型权重保存成文件,Django启动时通过一个单例加载模型。
# recommend/inference.py import torch import os from recommend.models import DeepMF _model = None def get_model(): global _model if _model is None: _model = DeepMF(num_users, num_items) _model.load_state_dict( torch.load('recommend/model_weights.pt', map_location='cpu') ) _model.eval() return _model这种懒加载模式保证模型只在第一次请求时载入内存,后续请求复用同一份模型对象,不会造成重复加载导致内存持续上涨。CPU机器上PyTorch推理速度很快,单次前向传播在毫秒级,完全扛得住毕设演示。
4.3 用户端页面与交互功能
用户端需要的功能很明确:注册登录、图书列表、图书详情、评分、收藏、我的推荐。我用的模板是Django自带模板语言,配合Bootstrap做样式,没有再上Vue,因为毕设要控制复杂度。
推荐页面我做了三块内容:猜你喜欢(深度学习模型预测TopN)、相似读者还读过(ItemCF结果)、热门名著榜(简单按评分人数排序)。三个Tab放同一页面,效果非常丰满,评委老师一眼就能看出系统的推荐不是单一路径。
图书详情页要记得展示平均评分和评价人数,以及“推荐类似书目”的入口。不少同学忽视详情页的推荐位,其实这个位置是提升项目完整度的关键细节,也顺势实现了“基于物品的协同过滤在页面上的自然呈现”。
4.4 Django Admin后台管理
Django自带的admin是我最喜欢的模块。把Book和Rating注册进去:
# books/admin.py from django.contrib import admin from books.models import Book, Rating @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'category') search_fields = ('title', 'author') list_filter = ('category',)有搜索、有筛选、有列表展示,管理员的日常操作就全覆盖了。答辩演示时打开后台页,展示一本书的增删改查过程,评分的分布情况,老师对“管理模块”这部分基本直接放过。
5. 实际操作中踩过的坑与排查技巧
5.1 用户冷启动问题怎么处理
新注册用户没有评分行为,协同过滤算不出相似用户,深度学习模型也没法预测评分。这是推荐系统最经典的冷启动问题,答辩必问。
我的方案是分层兜底:新用户先推热门榜——按全局平均分和评分人数加权排序,保证推荐出来的书是大众认可度高的。用户有了一条评分记录后,立即改用ItemCF,根据他评过的那本书去推相似书。等评分超过10条,再切换到深度学习模型。
这个“三段式”策略代码实现不难,核心是先判断用户行为数量落在哪个区间。关键在于答辩时能讲清楚,为什么冷启动阶段用热门兜底而不是直接上模型。
5.2 相似度计算出来的结果很怪
有一段时间推荐结果里频繁出现“架空”的情况:用户打了《红楼梦》高分,推荐列表里却出现了毫不相关的书。查下来是文本特征的问题。
我用TF-IDF算书的内容相似度时,简介分词的质量直接决定结果质量。名著简介里大量出现“小说”“作者”“讲述”等通用词,这些词的TF-IDF权重被拉低还好,可一旦没清干净停用词,就会出现干扰。解决方式是扩充停用词表,针对文学类文本加上“本书”“作者”“故事”“讲述”这类词,再跑一遍效果立刻正常。
5.3 Django加载PyTorch模型时内存一直涨
这是一个非常隐蔽的坑。Django开发服务器默认单进程,但多线程处理请求。如果每次请求都无脑加载模型,内存在几次请求后就疯涨。
后来我改成模块级单例缓存,并在模型推理时包上torch.no_grad(),把梯度计算彻底关掉,隔离了中间变量对内存的占用。还有就是推理之前要把模型切换到eval模式,忘记调eval()会导致Dropout和BatchNorm的行为不一致,预测结果完全不对。
5.4 大数据量下推荐响应变慢
模拟数据量上到几千用户、几万评分之后,实时计算相似矩阵的做法彻底不可行。我优化了两次:
第一次是把相似度矩阵预计算并存储,推荐时只查表。第二次是给数据库表加索引,尤其是Rating表的user_id和book_id字段。Django的ORM默认不会为外键以外的普通字段建索引,手动加db_index=True之后,查询速度肉眼可见地提升。
另外要给每个用户只存Top20推荐结果,而不是全量推荐列表。一页只展示10-20本书,存多了浪费空间不说,数据刷新时也拖慢脚本。
6. 二次开发与答辩展示建议
6.1 答辩前必须准备的演示路径
很多同学代码能跑,但演示时手忙脚乱。我每次带人做这类项目,都会先设计好一条“演示故事线”:
- 先展示系统首页和热门推荐,让评委对项目有直观印象。
- 演示注册一个新账号,登录后进入冷启动状态,看到的推荐是热门书。
- 给一两本书打分,刷新再看,推荐列表发生变化,引导出“协同过滤算法生效”。
- 打开管理后台,展示用户、图书、评分数据的管理能力。
- 最后跑一次训练脚本,展示评分预测曲线,引出深度学习部分。
这条路径走下来,评委基本能对你的项目技术点一目了然。别一上来就讲代码,先讲流程,再深入细节,效果最好。
6.2 项目还能怎么扩展
如果时间有余,有几个低成本的扩展方向:
- 把SQLite换成MySQL/PostgreSQL,在论文里写“支持大规模数据的存储方案”。
- 加一个简单的推荐理由解释模块,比如“因为你看过《三国演义》”,在XGBoost/协同过滤时代这叫可解释性。
- 把TF-IDF升级成BERT或者Sentence-BERT做文本Embedding,深度学习含量直接翻倍。
- 增加定时任务,用Celery或APScheduler周期性重算推荐结果,让系统看起来更“自动”。
这些方向挑一两个做,就能让项目的技术纵深比同题目的其他毕设明显高出一截。
6.3 一条龙定制服务时的代码组织规范
我是建议拿到源码后先按README跑通,再对照系统架构图去读代码,不要上来就乱改。很多同学看别人代码时最大的问题是没有全局观,一进来扎进某个视图里拔不出来。正确顺序是:看数据表 -> 看路由 -> 看视图 -> 看模板 -> 看推荐算法模块,这样才不会被细节淹没。
源码包里一定要有完整的requirements.txt、README、数据库初始化脚本和答辩演示脚本。因为这类项目经常要换机器演示,依赖不齐是最常见的翻车原因。
这套系统本质上是“一本正经的算法外壳 + 踏实的产品闭环”:Django保证了系统的完整性和工程感,协同过滤保证了基础推荐逻辑的可解释性,深度学习让项目有了技术亮点,而模拟数据预处理和离线/在线分离让系统在毕设体量下表现稳定。做这类题目的核心经验是:不要贪多,把一条完整链路从数据处理到模型推理再到页面展示跑通,远比堆砌十个模型更有价值。我见过太多人花了大量时间调参,最后连完整演示都跑不出来的案例。所以,先把主干流程走通,再去打磨细节,这才是毕设项目的正确打开方式。