☰
Django+深度学习驱动的经典名著推荐系统设计与实现
2026/10/8 4:21:34 网站建设 项目流程

1. 项目全貌与核心痛点

这个项目的标题信息量其实很大:“基于django+深度学习的经典名著推荐系统”。拆开看,django是Web框架,深度学习是推荐算法的实现手段,经典名著则是垂直领域的数据对象。很多人第一眼会觉得这是“用深度学习做一个网站”,但真想动手做的时候才会发现,难点根本不在“网站”,而在“推荐”两个字上。

1.1 毕设选这个题目的实际价值

我见过太多人毕设选“XX管理系统”“XX平台”这类题目,技术栈写出来全是增删改查,答辩时老师一问“你的创新点在哪里”,场面就会非常尴尬。而这个题目天然带有三个亮点:一是django保证了系统的完整性和工程规范性,二是深度学习给了算法层面的讨论空间,三是经典名著这个垂直场景让推荐逻辑可以围绕“内容本身”展开,而不是像电商推荐那样过度依赖用户行为数据。

换句话说,这个题目的兼容性非常强。你既可以把它做成“基于用户协同过滤的简单推荐”,也可以进一步升级成“物品向量化+相似度计算”的深度推荐链路,还可以往“NLP文本向量化”“知识图谱”方向扩展。毕设答辩时,老师问的每一个问题,这个题目基本都有对应的技术点可以回答。

1.2 系统整体架构与推荐流程拆解

整个系统从数据流向上看是下面这条链路:

原始书籍数据(书名、作者、分类、简介、评分信息)→ 数据清洗与格式化 → 构建用户-物品交互矩阵 → 深度学习模型训练得到物品向量(或用户向量)→ 相似度计算生成推荐候选集 → django后端通过REST API输出推荐结果 → 前端渲染展示 → 用户反馈回写数据库 → 周期性重训模型。

这套链路里最核心的一个设计决策是:推荐系统的效果不取决于你用了多复杂的模型,而取决于你把数据组织得有多干净。很多新手上来就想着用Transformer、用BERT,结果数据只有几十本书、几百条评分,模型根本学不到任何有效信号。我的建议是:第一版先用“物品嵌入 + 余弦相似度”把链路跑通,后面再逐步升级模型复杂度。

还有一个容易被忽略的点,就是“经典名著”这个垂直领域带来的天然优势:书籍的描述文本是现成的,可以用于内容向量化。这就让“冷启动”问题有了一个非常合理的解释路径——新书没有评分数据时,可以用描述文本的语义向量去匹配同类书籍。这一条在论文里能写出很大一段,也是评委老师很买账的创新点。

1.3 技术栈选型的合理性分析

选django而不是Spring Boot、Flask或FastAPI,主要有三个层面的考虑。

第一,从毕设答辩的角度,django自带Admin后台、ORM、模板引擎、Form组件,能完整覆盖“用户管理、书籍管理、推荐记录管理”这类基础功能,不需要自己额外拼装。而且django的MTV架构非常清晰地展示了“数据-逻辑-展示”三层分离,写进论文里结构会非常工整。

第二,从Python生态角度,django与深度学习模型之间的衔接是最顺滑的。训练好的模型用torch.save保存成文件后,django的views.py里可以直接torch.load加载推理,中间不需要跨语言调用。相比之下,如果后端用Java,你可能还得写一套模型服务用HTTP接口暴露出去,工程复杂度一下就上去了。

第三,从部署角度,django项目可以一条命令跑起来,SQLite起步、后续换MySQL也不费劲,对于毕设演示环境来说足够轻量。同样的逻辑,你答辩时现场演示不会希望后端启动花五分钟、或者因为某个中间件没装好直接白屏。

2. 数据准备与推荐算法链路设计

2.1 经典名著数据的采集与清洗策略

做推荐系统,80%的时间都花在数据上。这里说的“数据”不只是从某个开源平台爬下来的几百本书信息,还包括用户行为数据的模拟与构造。

对于经典名著这个垂直领域,数据来源非常明确:公开的书目信息网站、豆瓣读书的评分和标签、开源的书评数据集。但有一条非常重要的红线——不要为了毕设去大规模爬取别人的数据,量小可以手动标注,量大就找一个已经开源的公开数据集来用。我见过有同学用Scrapy去爬某大型图书网站,爬到一半IP被封,最后数据还是不够,反而误了进度。

数据清洗层面我总结出三条经验:

一是字段标准化。作者名、出版社名、出版年份、ISBN这些字段往往存在大量缺失和不一致,比如同一本书在不同的收录渠道里作者写法不同——“周树人”和“鲁迅”指向同一个人,这种问题要去重合并。用pandas做归一化处理时建议先统一数据类型、再处理缺失值、最后做去重,顺序反了的话数据很容易越洗越乱。

二是构造用户行为数据。如果只有书籍信息而没有用户评分记录,推荐系统就没有东西可以训练。这时候要么去找到一个公开的书籍评分数据集与名著字段对齐,要么自己构造一个合理的模拟用户行为矩阵。自己构造时一定不要纯随机生成,要让每个用户集中在一两个分类方向上(比如有人只读科幻,有人偏向古典文学),同时混入少量跨界阅读记录,这样训练出来的向量才有人类行为的分布特征。

三是文本字段的清洗。书籍简介里经常有特殊字符、乱码、URL残留,做embedding之前需要先做基础清洗。中文场景还要考虑分词,像“红楼梦”这种专有名词不能直接被jieba拆成“红楼”和“梦”,必须加载自定义词典。这个细节很琐碎,但直接影响后面内容向量的质量。

2.2 协同过滤与深度学习结合的推荐链路

推荐系统业界有一条共识:协同过滤是地基,深度学习是把地基上的特征做得更精细的砖瓦。这个项目里我采取的方案是二者结合,叠加互相补充。

具体实现上分为两条线。

第一条线是“基于物品的协同过滤”。计算逻辑是:如果用户A同时喜欢《百年孤独》和《霍乱时期的爱情》,那《霍乱时期的爱情》就和《百年孤独》存在相关性;当用户B只喜欢《百年孤独》时,系统就可以把《霍乱时期的爱情》推荐给他。这条线的特点是计算简单、可解释性强,用户看到推荐结果时容易理解“为什么推荐这本书”。

第二条线是“深度学习向量化匹配”。用Word2Vec的思路,把用户看作“上下文”,把名著看作“中心词”,从用户-书籍共现关系里训练出每本书的向量表示。训练完成后,每一本书都映射成一个固定维度的向量(比如64维或128维),两个向量的余弦相似度就是书籍之间的语义关联度。这条线的优势是可以捕捉到协同过滤发现不了的隐式关联——比如两本书在用户行为上从未共现过,但是它们周围的书相似,向量空间里也会被拉近。

两条线最终通过加权融合得到最终的推荐分数:

最终分数 = α × 协同过滤相似度 + (1 - α) × 向量余弦相似度

α一般取0.5到0.7之间,具体取值要用小批次的用户反馈去调。如果系统刚上线没有任何反馈数据,就把α调低,让内容匹配占据主导;当用户行为数据积累多了,再逐步调高协同过滤的权重。

2.3 为什么这里选择了深度学习而不是传统机器学习

很多同学会问一个问题:“我直接用协同过滤就能做推荐,为什么还要引入深度学习?这不是为了用而用吗?”

这个问题如果答不好,答辩现场会很难受。我的理解是这样的:传统协同过滤解决的是“用户喜欢什么”的问题,它依赖的是评分矩阵;而深度学习解决的是“用户为什么喜欢”的问题,它可以从文本语义、内容特质、跨域行为里提取出低维稠密向量,让泛化能力上一个台阶。

举个例子。用户只读了一本《三体》,传统协同过滤只能给他推荐“喜欢《三体》的人还喜欢的书”,这个结果背后依赖的是《三体》与其他书籍的共性评分;而向量化方法可以把《三体》的“科幻”“宏大叙事”“物理学”“刘慈欣”这些隐特征编码到向量空间里,就算另一本书《球状闪电》从来没有和《三体》在同一用户下共现过,只要向量空间里它们语义相近,系统也能给出推荐。

所以在这个项目里,深度学习的定位不是替代协同过滤,而是补足传统方法在“稀疏数据”和“冷启动”上的短板。毕业论文里的说法可以是:“基于双通道特征融合的混合推荐方法”,既包含行为协同信号,又包含语义内容信号。

3. 从零到一实现推荐模型训练

3.1 环境准备与项目结构初始化

项目开始之前,先把环境理顺。我用的是Python 3.9版本,django 3.2(django 4.x之后有些指令发生了变化,但核心用法一致),深度学习框架用PyTorch CPU版就够了,训练数据量在这个量级完全跑得动。

我把项目结构按下面的方式组织:

recommend_system/ ├── manage.py ├── recommendations/ │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── wsgi.py ├── books/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── admin.py │ ├── migrations/ │ └── services/ │ ├── __init__.py │ ├── recommender.py │ ├── item2vec_trainer.py │ ├── collaborative_filtering.py │ └── data_preprocess.py ├── templates/ │ ├── base.html │ ├── index.html │ ├── book_detail.html │ └── recommend.html ├── static/ │ ├── css/ │ ├── js/ │ └── images/ ├── datasets/ │ ├── raw_books.csv │ ├── cleaned_books.csv │ └── user_behavior.csv └── model_weights/ ├── book_vectors.npy └── book_id_map.json

关键的思路是把推荐相关的算法代码从django的views.py中剥离开,独立放到services目录下。这样有两个好处:一是训练代码和执行代码分离,改模型的时候不用动Web逻辑;二是views.py只负责“拿到用户请求→调推荐服务→返回推荐结果”,代码清晰度很高,答辩讲解的时候也容易给老师讲清楚每一层在做什么。

3.2 用户行为数据模拟与交互矩阵构建

如果没有真实用户行为数据,可以自己构造一个仿真数据集。但这里有个分寸要拿捏好:完全随机的数据训练不出有价值的模型,因为真实用户的阅读偏好是有结构的。

我构造数据集时用了分层的策略:

  • 将书籍按标签分为“中国古典文学”“外国文学”“科幻”“历史传记”“哲学社科”“武侠小说”六个大类。
  • 每个用户被分配1到2个主导兴趣大类,在以这些类别为中心的书籍范围内生成高评分记录。
  • 每个用户有一定概率(我设的是15%)跨到其他类别随机生成低评分记录,模拟人类偶尔尝鲜的行为。
  • 评分范围设置为1到5分整数,单用户交互书目数控制在15到60条之间,总体稀疏度控制在85%左右,贴近真实环境。

用pandas写的话核心代码长这样:

import pandas as pd import numpy as np from random import choice, randint, uniform, sample user_ids = range(1, 201) book_ids = list(range(1, 501)) book_category_map = {b_id: f"cat_{b_id % 6}" for b_id in book_ids} records = [] for uid in user_ids: n_ratings = randint(15, 60) main_cats = sample([f"cat_{i}" for i in range(6)], k=randint(1, 2)) chosen_books = [] for _ in range(n_ratings): if uniform(0, 1) < 0.85: cat = choice(main_cats) else: cat = choice([f"cat_{i}" for i in range(6)]) candidates = [bid for bid in book_ids if book_category_map[bid] == cat and bid not in chosen_books] if not candidates: continue bid = choice(candidates) chosen_books.append(bid) score = int(np.random.normal(loc=4.0, scale=0.8)) if cat in main_cats else int(np.random.normal(loc=2.2, scale=0.8)) score = max(1, min(5, score)) records.append([uid, bid, score]) df = pd.DataFrame(records, columns=["user_id", "book_id", "rating"]) df.to_csv("datasets/user_behavior.csv", index=False)

注意生成评分时不要写死一个固定分数,用正态分布去模拟真实分数的“随机波动”,否则后面算相似度时所有点都会挤在一起,模型很难拉开距离。

3.3 Item2Vec模型训练与相似度计算

把用户行为数据整理成“用户-书籍”的序列对,然后用类似Word2Vec的Skip-Gram思路训练物品向量。核心逻辑是:同一个用户交互过的书,在行为语义上是互相关联的,可以放在同一个窗口里互相预测。

我这里用Gensim来跑,它封装了Word2Vec实现,毕设规模的数据完全够用,而且代码量比用PyTorch手写小一个量级:

from gensim.models import Word2Vec from gensim.models.word2vec import LineSentence # 构建训练语料:每个用户交互过的书籍id序列作为一条"句子" def build_corpus(df): grouped = df.groupby("user_id")["book_id"].apply(list) corpus = [[str(bid) for bid in books] for books in grouped] return corpus corpus = build_corpus(df) model = Word2Vec( sentences=corpus, vector_size=128, window=5, min_count=1, epochs=20, sg=1, # 1代表Skip-gram,0代表CBOW negative=10, # 负采样数量 workers=4 ) # 保存书籍向量与ID映射表 book_vectors = {str(bid): model.wv[str(bid)] for bid in book_ids} import numpy as np np.save("model_weights/book_vectors.npy", np.array([book_vectors[str(bid)] for bid in book_ids]))

这里有几个参数值得解释。embedding维度我选了128,不要用太小的维度,否则名著之间的差异表达不出来;但也不要太大,因为训练数据量就几千条交互记录,维度太高容易过拟合。窗口大小取5,意思是在同一个用户的行为序列里,最多互相视为“上下文”的书籍跨度是5本。负采样取10,能让训练时正负样本更均衡。

训练完成后,计算两本书的相似度就是一次向量点乘的事:

def cosine_sim(v1, v2): return float(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))) def recommend_by_vector(book_id, top_k=10): v1 = book_vectors[str(book_id)] scores = [] for bid in book_ids: if bid == book_id: continue v2 = book_vectors[str(bid)] scores.append((bid, cosine_sim(v1, v2))) scores.sort(key=lambda x: x[1], reverse=True) return [bid for bid, _ in scores[:top_k]]

实测下来,在这个数据规模下训练速度非常快,普通笔记本CPU也就是几十秒的事。真正花时间的反而是调参数和看效果。

3.4 模型效果评估的关键指标

毕设答辩时老师一定会问“你的推荐系统效果怎么衡量”,这个问题必须提前准备,不能只说“感觉推荐得还挺准的”。

第一层是离线评估指标。把用户行为数据按8:2划分训练集和测试集,在训练集上建模,用测试集里的“用户-书籍”对来判断推荐结果是否命中。常用指标是准确率Precision@K和召回率Recall@K:

def evaluate_precision_recall(recommend_func, test_df, top_k=10): hit = 0 total = 0 for _, row in test_df.iterrows(): uid, bid = int(row["user_id"]), int(row["book_id"]) recs = recommend_func(uid, top_k=top_k) if bid in recs: hit += 1 total += 1 return hit / total # 精确率:推荐列表里有多少是用户真正喜欢的 # 召回率:用户真正喜欢的书里有几个出现在了推荐列表

第二层是推荐多样性评估。虽然经典名著有天然的分类维度,但推荐结果如果永远局限在同一分类里,用户会觉得无聊。可以统计每次推荐的类别覆盖数,交叉熵越高多样性越好。这一条在论文里也能写成一节“推荐结果多样性分析”。

第三层是人工评测榜单。models训练完后,人工选几本代表性书目看推荐结果是否合理。比如输入《百年孤独》,看看返回结果里有没有《霍乱时期的爱情》《族长的秋天》这些同作者作品,有没有《红楼梦》这类同文学地位的作品,有没有马尔克斯风格相近的拉美文学作品。如果自动推荐结果里出现风格完全不对的书,先回来排查数据清洗环节。

4. django后端核心模块实现

4.1 数据模型设计

django的ORM是MTV架构里M层的关键所在。我的表设计如下:

from django.db import models from django.contrib.auth.models import User class Book(models.Model): title = models.CharField(max_length=200, verbose_name="书名") author = models.CharField(max_length=100, verbose_name="作者") publisher = models.CharField(max_length=100, blank=True, verbose_name="出版社") publish_date = models.DateField(null=True, blank=True, verbose_name="出版日期") isbn = models.CharField(max_length=20, blank=True, verbose_name="ISBN") category = models.CharField(max_length=50, verbose_name="分类") summary = models.TextField(verbose_name="内容简介") cover_url = models.URLField(blank=True, verbose_name="封面链接") rating = models.FloatField(default=0.0, verbose_name="平均评分") vector_id = models.IntegerField(default=0, verbose_name="向量ID") class Meta: db_table = "book" class UserRating(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") book = models.ForeignKey(Book, on_delete=models.CASCADE, verbose_name="书目") score = models.IntegerField(verbose_name="评分") created_at = models.DateTimeField(auto_now_add=True, verbose_name="评分时间") class Meta: db_table = "user_rating" unique_together = (("user", "book"),) class RecommendLog(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") book = models.ForeignKey(Book, on_delete=models.CASCADE, verbose_name="书目") reason = models.CharField(max_length=100, verbose_name="推荐理由") is_clicked = models.BooleanField(default=False, verbose_name="是否点击") created_at = models.DateTimeField(auto_now_add=True, verbose_name="推荐时间") class Meta: db_table = "recommend_log"

这里三个表的分工很清晰。Book存书籍全量信息,UserRating存用户行为数据,RecommendLog用来记录“给用户推荐了什么、用户点没点”。RecommendLog这个表看起来不起眼,但它的价值在于:有了点击反馈数据,你能在后续论文里写“基于用户反馈的推荐效果优化”,还能把它作为一个数据源去动态调整α权重,可扩展性非常强。

SQLite在毕设场景下完全够用,但如果你打算把真实用户行为数据往里头灌,建议还是切换成MySQL。django的settings.py里改一行配置即可,ORM代码不用动。

4.2 推荐服务接口的封装与调用

推荐算法不能散落在各个视图函数里重复调用,我把它们封装成一个统一的服务类:

import numpy as np from .collaborative_filtering import cf_recommend from .item2vec_trainer import vector_recommend class RecommenderService: def __init__(self, alpha=0.6, top_k=10): self.alpha = alpha self.top_k = top_k self.book_vectors = np.load("model_weights/book_vectors.npy") self.book_id_map = json.load(open("model_weights/book_id_map.json")) def mix_recommend(self, user_id, top_k=None): k = top_k or self.top_k cf_results = cf_recommend(user_id, top_k=20) vec_results = vector_recommend(user_id, top_k=20) combined = {} for bid, score in cf_results.items(): combined[bid] = combined.get(bid, 0) + self.alpha * score for bid, score in vec_results.items(): combined[bid] = combined.get(bid, 0) + (1 - self.alpha) * score ranked = sorted(combined.items(), key=lambda x: x[1], reverse=True) return [bid for bid, _ in ranked[:k]]

这个混合接口是整个后端最核心的一层。它保证了Web端不需要关心推荐细节,每次执行推荐只是new一个服务、调一个方法、拿到结果列表,然后查数据库取完整的书目信息返回给前端。

django视图层的代码就很轻了:

from django.shortcuts import render from .models import Book from .services.recommender import RecommenderService service = RecommenderService() def recommend_view(request): user = request.user if not user.is_authenticated: return render(request, "login.html", {"error": "请先登录"}) rec_ids = service.mix_recommend(user.id, top_k=10) rec_books = Book.objects.filter(id__in=rec_ids) book_map = {b.id: b for b in rec_books} # 保持推荐顺序 sorted_books = [book_map[rid] for rid in rec_ids if rid in book_map] return render(request, "recommend.html", {"books": sorted_books})

一个小细节:查数据库时如果直接Book.objects.filter(id__in=rec_ids),返回的书目顺序可能和rec_ids的顺序不一致,因为SQL的IN子句不保证排序。所以我在视图里手动用id做了一次映射,确保前端展示的顺序和推荐算法的排序保持一致。

4.3 登录注册与用户反馈模块实现

django自带的认证系统非常成熟,不建议自己手写密码加密逻辑。直接用内置的User模型,配合LoginView、LogoutView、RegistrationView就可以完成基础登录注册,给模板里放上对应的form表单即可。

这里我想多说一点用户反馈模块的设计思路。推荐系统做完不是终点,能够“用起来”才算真正闭环。我给项目加了两个反馈入口:第一个是推荐结果页上的“不喜欢”按钮,用户点击后这条记录的is_clicked字段会被置为True,同时UserRating表里自动生成一条低分记录(默认2分);第二个是书籍详情页的评分入口,用户可以给任何一本书打分。

有了这两类feedback数据之后,系统就能做一个小功能叫作“实时修正”:当用户对某本书点了不喜欢,推荐服务在计算时会把这本书及其高度相似书籍的分数临时压到最低。这个功能虽然逻辑简单,但答辩时演示效果很好——“老师你看,我点掉了一本我不喜欢的书,推荐列表当场就变了”,这种即时反馈给人的技术感非常强。

后端实现用户可以这样写,把反馈处理串起来:

from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .models import UserRating, RecommendLog, Book @csrf_exempt def feedback_api(request): if request.method == "POST": user = request.user if not user.is_authenticated: return JsonResponse({"success": False, "msg": "未登录"}, status=401) book_id = int(request.POST.get("book_id")) action = request.POST.get("action") book = Book.objects.get(id=book_id) if action == "dislike": UserRating.objects.update_or_create( user=user, book=book, defaults={"score": 2} ) elif action == "rate": score = int(request.POST.get("score")) UserRating.objects.update_or_create( user=user, book=book, defaults={"score": score} ) return JsonResponse({"success": True, "msg": "已提交"}) return JsonResponse({"success": False, "msg": "请求方式错误"}, status=405)

4.4 前端页面与交互设计

前端在毕设里其实不需要多花哨,但至少要能展示出三个关键页面:首页(推荐展示)、书籍详情页、个人中心。

首页是重点。布局上我建议做成“搜索栏 + 分类筛选 + 推荐卡片流”三块。推荐卡片流加载数据用django模板渲染即可,不需要单独引Vue或React(引了反而增加答辩复杂度)。每张卡片展示封面、书名、作者、评分和一句简介,点击后进入书籍详情页。

书籍详情页要安排四个模块:书籍基本信息、内容简介、同作者作品、推荐相似书籍。相似书籍区域可以直接调用推荐服务的向量匹配接口,展示5到8本相关书籍卡片。

个人中心页展示用户的评分历史和点击过的推荐记录,让用户感觉系统“记住了自己”。建议在这里做一个很加分的细节——显示“你的读书偏好”标签云,从用户评分历史里统计最高频的三个分类标签,展示成语义化标签。

前端调后端时,如果不涉及文件上传和大数据量交互,用django的模板标签就够了。只有异步刷新推荐结果(比如点击“换一批”)时才需要写一个fetch请求调接口:

async function refreshRecommendations() { const res = await fetch('/api/recommend/refresh/'); const data = await res.json(); // 更新推荐卡片列表 }

这里有个实操心得:django模板里直接写fetch请求,返回的数据格式建议用JSON定义一个统一的格式,比如:

{ "success": true, "books": [ {"id": 1, "title": "百年孤独", "author": "加西亚·马尔克斯", "reason": "与你喜欢的《霍乱时期的爱情》同作者"} ] }

reason字段是推荐解释,这个细节非常加分。给每条推荐配上一句话的解释,比如“与你读过的《红楼梦》同属中国古典文学”“与《三体》相似度最高的科幻作品”,不仅美观,而且直接把推荐系统的“可解释性”这一个科研热点带出来了。答辩时老师看到这个,通常都会追问两句,你就能顺势讲出“基于物品匹配的可解释推荐”这个技术方案。

4.5 Admin后台与数据管理功能

django内置的Admin后台是毕设里的一个实用宝库,几乎不需要额外写代码就可以管理所有数据表。在admin.py里注册模型:

from django.contrib import admin from .models import Book, UserRating, RecommendLog @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ("id", "title", "author", "category", "rating") search_fields = ("title", "author") list_filter = ("category",) admin.site.register(UserRating) admin.site.register(RecommendLog)

用Admin后台做数据录入和管理非常方便,答辩前如果要临时修正某本书的信息或者加一本新书,不必去数据库里敲SQL语句,直接在后台点鼠标就完成。同时我也把Admin作为“管理员入口”写在论文里,属于“系统权限管理”模块,一举两得。

5. 常见问题与排查技巧实录

5.1 django ORM查询性能的几种典型问题

第一个常见问题是N+1查询。页面要显示10本推荐书籍,每本书要查作者、分类、评分,如果代码写的是Book.objects.all()然后在模板里循环访问book.author.name,django会对每本书单独发起一次SQL查询。10本书就是11条SQL,页面肉眼可见地变慢。

解决办法是用select_related或prefetch_related:

books = Book.objects.select_related("author").prefetch_related("tags").filter(id__in=rec_ids)

select_related适用于ForeignKey和OneToOneField,会用一条JOIN把关联数据拉出来;prefetch_related适用于ManyToManyField和反向关联,会用额外一次查询把所有关联数据批量加载。毕设数据量小的时候效果不明显,但这一条写进论文里,能体现你考虑过系统性能问题。

第二个常见问题是django执行查询与删除对象的语法混淆。新手经常在views.py里写出类似Book.objects.delete(id=1)这样的代码,但django的规范写法是先取对象再删除:

book = Book.objects.get(id=1) book.delete()

或者用QuerySet的删除方法:

Book.objects.filter(id=1).delete()

前者删单条,后者可以批量删。删除前建议先确认外键关联,如果UserRating表里还引用着这本书,直接删Book会导致外键约束报错。

第三个问题是大数据量分页缺失。如果你的Book表数据量上了几千条,Admin后台或前端列表没有分页的话,页面加载会非常卡。django自带Paginator类:

from django.core.paginator import Paginator books_all = Book.objects.all().order_by("-rating") paginator = Paginator(books_all, 12) page_obj = paginator.get_page(request.GET.get("page"))

模板里渲染时加上页码导航即可。这个问题在Qt等桌面端也有类似场景(比如大表格体量卡顿优化),本质逻辑都是“数据量大了就必须分页或按需加载”,可以写成通用经验。

5.2 推荐冷启动问题的应对方案

冷启动是推荐系统所有项目都逃不过的话题。在这个项目里,冷启动有两个层面。

第一个层面是新用户的冷启动。用户第一次登录,没有任何评分记录,协同过滤这时候没法用。我的处理方式是:给新用户展示“热门书籍榜”和“分类默认推荐”——按评分热度排序拉一批高分名著,再按“每类抽取精品”的方式混排出一个初始推荐列表。等用户有了第一次评分或点击行为后再切换到个性化推荐。

第二个层面是新书的冷启动。新录入的书没有任何用户行为数据,向量也训练不到。这时候用内容语义匹配来兜底:对书籍简介做jieba分词、去停用词后,把文本用TF-IDF或预训练的中文句子向量转换成语义向量,与已有书籍的语义向量做相似度计算。虽然精度肯定不如行为协同过滤高,但是至少新书有机会被推荐出去。

这两条应对策略务必写进论文的“系统优化”章节,它们都是非常有辨识度的实际问题,能够体现你的项目不是停留在“调通”阶段,而是真的考虑过落地。

5.3 深度学习代码调试与训练过程问题

训练过程中最常踩的坑是ID映射不一致。训练脚本和django服务里分别加载了不同的book_id映射表,导致前端拿到推荐ID,去数据库查这本书时发现查到了错误的书。排查方法很简单,训练时保存一份book_id_map.json,服务启动时加载同一份文件,两边严格比对id。

第二个坑是训练语料过短导致向量质量差。Word2Vec是统计模型,要求语料达到一定规模才能学到稳定分布。如果只有几十条交互记录,模型训练出来每本书的向量都会挤在一起,相似度区分度极差。解决方法是把负样本数调大一些(比如negative=15),或者把用户行为序列里重复出现的书籍按次数加权采样,这样能略微缓解数据稀疏的影响。

第三个坑是模型文件加载路径问题。django开发环境跑python manage.py runserver时,当前工作目录是manage.py所在目录,所以使用相对路径加载模型文件没问题。但如果你用django的settings.py里的BASE_DIR做路径拼接,更稳妥的方法是:

import os from django.conf import settings model_path = os.path.join(settings.BASE_DIR, "model_weights", "book_vectors.npy")

这个细节看起来小,但是部署到服务器或者拿到别人电脑上演示时最容易出问题,提前用绝对路径拼接能少踩一个坑。

5.4 整套系统答辩前的完整检查清单

我自己带过的学生在答辩前翻车的情况,总结下来主要是下面几条,每次答辩前都过一遍:

  • 检查数据库迁移是否最新:python manage.py migrate,确保所有模型变更都同步到了数据库。
  • 检查超级管理员账号是否可用:python manage.py createsuperuser,因为Admin后台演示一定会用到。
  • 检查模型权重文件是否是最新训练的版本:训练完模型后一定要同步刷新model_weights目录,不要出现代码里引用的文件还是上一版的情况。
  • 检查前端静态文件是否正常加载:如果用了STATICFILES_DIRS,确认路径配置和文件命名一致。
  • 检查推荐接口是否有基础数据可显示:如果用户是空的,推荐列表也会是空的,提前准备好一个演示用的测试账号并注入评分记录。
  • 检查核心演示流程至少走两遍:注册新用户→完善评分→查看推荐列表→点击书籍详情→查看反馈效果。

6. 交付物结构与一条龙定制经验

6.1 毕业论文文档的框架建议

毕设源码通常要配套毕业论文,论文框架建议按下面的结构走,和答辩讲解的逻辑保持一致:

第一章绪论写研究背景、国内外研究现状、研究意义。第二章写需求分析,把“经典名著推荐”的业务问题转成“算法问题”。第三章写系统总体设计,放架构图、技术栈选型说明、数据库设计表。第四章写推荐算法的设计与实现,这是核心章节,重点讲协同过滤与深度学习结合的链路、参数选择、效果对比实验。第五章写系统功能实现与测试,展示页面截图、核心代码片段、功能测试用例。第六章写总结与展望。

论文里建议放两组对比实验:一组是“纯协同过滤 vs 协同过滤+深度学习”的Precision@10和Recall@10对比表,数据不用很漂亮,但趋势要合理——混合模型在大多数指标上优于单独模型;另一组是不同embedding维度下的推荐效果对比,用来说明你做参数调优的过程,这一组数据非常能体现工作量。

6.2 源码交付前需要做的三件事

第一,写好README。里面必须包含:项目环境依赖清单(requirements.txt)、运行启动步骤、测试账号说明、数据文件放置路径、模型文件下载/训练方式。这份文档要详细到“一个从没接触过这个项目的人,照着走能10分钟内跑起来”的程度。

第二,清除所有绝对路径和本机专用配置。比如settings.py里的SECRET_KEY要改掉、数据库路径不要写死、上传文件的路径用BASE_DIR拼接。不要让老师打开你的代码发现里面写的是C:\Users\xxx\Desktop这种个人路径。

第三,写一份“答辩讲解稿”式的技术文档。按“系统架构→数据流向→推荐算法→实现细节→效果评估”五段来写,控制在10分钟的口述量。这不算论文内容,但对你自己答辩帮助极大,因为临近答辩最容易语无伦次,有一份讲解稿在手上,紧张的时候照着思路顺一遍能稳住节奏。

6.3 一条龙定制服务中的常见沟通误区

现在很多毕设项目都会提到“一条龙定制”,这个环节最考验沟通能力。我在实际沟通中发现几个高频误区:

第一个是需求变更不及时同步。同学一开始说“只要经典名著推荐”,做了两周后突然说“想加入悬疑小说数据”,如果没有及时沟通,代码结构可能已经定型,临时加数据源会耗费大量精力。正确做法是项目开始时就把数据源、推荐范围、功能边界写清楚,任何变更第一时间更新文档。

第二个是重代码轻讲解。很多同学源码拿到手能跑起来,但答辩讲不清楚、老师一问“你这里为什么用Word2Vec而不是BERT”就卡壳。所以我交付项目时一定会安排一次“代码走读+模拟提问”梳理,把模型选型、数据组织、系统设计的每个逻辑讲透。这也是为什么我认为源码、文档、代码讲解三者缺一不可。

第三个是忽略运行环境的差异。有些代码在Win11上跑得好好的,换到Win10或者Mac上就各种报错,最常见的就是Python版本不一致、MySQL安装失败、Redis没启动。所以交付时我一定把requirements.txt列完整,并且给出“Windows和Mac两套环境运行说明”,顺手把常见报错的处理方法贴在文档末尾。

6.4 对这个项目的可扩展方向思考

把这套系统做完之后,我心里其实很清楚:它只是一个“起点版本”。往后面可以扩展的方向非常多。

第一个扩展方向是引入BERT语义向量替换Word2Vec。用预训练的中文BERT模型对每本书的简介做语义编码,生成768维文本向量,替代基于行为序列训练的128维物品向量。这个升级能显著提升内容匹配的精度,但是需要额外的GPU资源,本地跑会慢很多。

第二个扩展方向是加入知识图谱。经典名著之间的关联不只是行为共现,还包括“同一作者”“同一时代”“同一文学流派”“书中人物关联”等结构化关系。把这些关系建成图谱,用图神经网络或TransE嵌入做推荐,能极大提升推荐结果的丰富度和可解释性。

第三个扩展方向是做成多端联动的应用。目前只有Web端,可以加上微信小程序端或移动端。django后端换成一个纯REST API,前端用uni-app或Flutter开发,复用同一套推荐服务,工作量主要在客户端,后端逻辑完全不需要重写。

现在回看这个项目,最让我有成就感的不是模型跑到多少精度,而是把“数据清洗→算法训练→Web展示→反馈闭环”这条链路串完整了。很多人做毕设只盯着“模型准不准”,忽略了推荐系统是一个系统工程,任何一环断掉都会影响整体体验。这也是我在文章开头强调数据处理、中间强调推荐链结构化设计、最后强调反馈逻辑的原因——如果你能把学生的思路从“我会用框架”带到“我会设计系统”,这台毕设才算真正做明白了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询