简介基于协同过滤算法的美食推荐系统毕业论文docx格式是本资源唯一文件面向计算机专业学生、推荐系统研发者及需完成类似课题的读者。文档采用Python语言结合Django框架与MySQL数据库完整覆盖从绪论、需求分析、总体设计、功能模块实现到软件测试的论文框架算法部分重点阐述基于用户和基于物品的协同过滤策略并讨论数据实时更新、隐私保护等优化方向。文中管理员端与用户端功能划分明确涵盖美食分类、特色美食、购买记录及个人中心等模块。资源共1个docx文档大小约6.61MB内含中文摘要、英文摘要、目录与正文各章节可直接参考其章节布局、图表绘制和实现思路。目前已有128人学习可作为毕业设计写作模板或系统开发蓝本帮助读者掌握协同过滤落地美食推荐场景的完整流程。1. 一份能直接复现的美食推荐毕设协同过滤不是玄学是相似度矩阵这份资源最值钱的地方不在那几十个页面也不在管理员的增删改查而是藏在后台的那张相似度矩阵。它把个性化推荐完整串起来了Python Django MySQL 搭台评分、购买记录、浏览历史当原料协同过滤算法计算用户之间、美食之间的相似度最后把“你可能想吃”的结果顶到首页。对正在选毕设题目的学生它是一份能把完整开发过程讲清楚的模板对想在企业系统里加推荐模块的从业者它是把推荐公式落到工程的最小可复现样本。前台推荐、个人中心、后台管理、系统测试流程齐全照着改就能跑。2. 协同过滤选型用户相似还是物品相似先看数据长什么样论文里同时写了基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF但目录里还罗列了 Hadoop、Scrapy 这些与核心逻辑无关的技术。实际开发时先别被这种技术堆料带偏推荐模块真正依赖的只有一张评分表和两个相似度公式。本章先把选型逻辑讲清楚什么情况下用 UserCF什么情况下用 ItemCF公式各有哪几个变种最后落到一段能直接跑出相似度矩阵的 Python 代码。2.1 两种协同过滤的适用边界先看数据规模再决定协同过滤的核心假设是过去口味相似的人未来口味也相似。UserCF 先找“和自己像的人”再把这些人喜欢过的、而自己没吃过的美食推荐出来ItemCF 反过来先找“和自己吃过的菜相似的菜”再推荐过去。区别在于相似度计算的方向一个是用户行之间的比较一个是物品列之间的比较。选哪个不是看哪个算法新而是看数据长什么样。毕设系统里用户量一般只有几十到几百个但美食条目可以持续扩充这种场景用 UserCF 更容易出效果新加一道菜不需要重算用户相似度只要它被少数几个高相似用户吃过就能进入推荐列表。反过来如果是一个用户量大、菜品相对稳定的平台ItemCF 更合适因为物品相似度矩阵比用户相似度矩阵小得多算一次能服务所有用户。做个简单判断你的数据表里是“用户数少于物品数”还是“物品数少于用户数”前者偏 UserCF后者偏 ItemCF。论文里首页做的是用户维度的“推荐美食”走的就是 UserCF 路线。实际复现时先把这条主线定下来后面所有表结构和视图都围绕它展开。2.2 相似度公式怎么选余弦、皮尔逊与修正余弦确定算法路线后下一个坑是相似度公式。最常见的三个公式各有脾气。余弦相似度计算的是两个向量方向上的接近程度适合评分都是正数的场景但有个隐患它把“没评分”和“打了低分”都当成同一个值处理所以做之前要把缺失值想清楚。皮尔逊相关系数在余弦的基础上减掉了用户自己的平均分等于先把每个人打分的手松手紧抹平再比口味差异适合评分数据存在系统性偏差的场景。修正余弦则在物品维度减均值常配合 ItemCF 使用。我用一段代码演示最基础的余弦相似度计算先跑通链路再谈优化。这段代码在本地装好 pandas 和 numpy 就能执行。import numpy as np import pandas as pd def build_user_similarity(ratings_df): 根据评分表计算用户之间的相似度矩阵 参数: ratings_df: DataFrame必须包含 user_id、food_id、rating 三列 例如: user_id1, food_id101, rating5 返回: 相似度矩阵行和列都是 user_id值为 [-1, 1] 的相似度 # 透视成 用户 x 美食 的评分矩阵未评分的格子是 NaN pivot ratings_df.pivot_table( indexuser_id, columnsfood_id, valuesrating ) # 暴力填 0先跑通推荐链路 pivot pivot.fillna(0) # 余弦相似度 A·B / (|A| * |B|) # 矩阵乘法一步算出所有用户对的内积 dot pivot pivot.T # 每个用户的向量模长 norm np.linalg.norm(pivot.values, axis1) norm_matrix np.outer(norm, norm) # 加 1e-9 防止除零 sim dot / (norm_matrix 1e-9) return sim这段代码是整个推荐系统的心脏。pivot_table 把评分记录变成二维矩阵行是用户列是美食值是评分。pivot pivot.T是线性代数里的常规操作一次算出所有用户两两之间的内积。norm 那两行计算每个用户评分向量的长度norm_matrix 再把长度扩展成两两组合的矩阵。1e-9是防止某个用户向量全 0 时除零报错。有一个细节你复现时一定会遇到fillna(0) 把“没吃过”和“打了 0 分”混在一起了。评分数据都是正数时它的意思是“没吃过的菜当成 0 参与计算”这会让两个只吃过同一种菜的陌生用户被错误地拉近距离。这是余弦相似度在稀疏评分矩阵下的通病第 4 章专门讲怎么治。2.3 从相似度到评分预测一条完整计算链路算完相似度离“推荐”还差最后一步预测目标用户对某道没吃过的菜的评分然后按预测分数从高到低取 Top-N。论文里的口径是“自动将高相似度用户喜爱的美食推荐给目标用户”落到代码就是加权平均。预测公式是目标用户对美食 i 的预测评分等于他自己的平均分加上所有相似用户对该美食评分偏差的加权和权重就是相似度。写成代码比写成公式更直观。def predict_rating(user_id, food_id, pivot_df, sim_matrix, k10): 预测用户对某道菜的评分取前 k 个相似用户做加权平均 参数: user_id: 目标用户ID food_id: 待预测的美食ID pivot_df: 用户x美食的评分矩阵NaN 表示未吃过 sim_matrix: build_user_similarity 算出的相似度矩阵 k: 只取相似度最高的 k 个用户防止全量计算 返回: 预测评分值 user_mean pivot_df.loc[user_id].mean() # 按相似度降序排除自己取前 k 个用户 sim_series sim_matrix.loc[user_id].drop(indexuser_id).sort_values(ascendingFalse) top_users sim_series.head(k) weighted_sum 0.0 sim_sum 0.0 for neighbor_id, sim_val in top_users.items(): # 邻居吃没吃过这道菜没吃过就没法提供参考 neighbor_rating pivot_df.loc[neighbor_id, food_id] if not np.isnan(neighbor_rating): neighbor_mean pivot_df.loc[neighbor_id].mean() # 用 邻居评分偏差 代替 原始评分抵消打分习惯差异 weighted_sum sim_val * (neighbor_rating - neighbor_mean) sim_sum abs(sim_val) if sim_sum 0: return user_mean # 兜底没有邻居吃过这道菜返回用户自己的平均分 return user_mean weighted_sum / sim_sum注意这段代码里我没有对 pivot_df 做 fillna(0)因为预测阶段必须区分“没吃过”和“打了 0 分”NaN 才能表示缺失。函数开头取 user_mean 是中心化处理先记录目标用户的平均分最后再加回来。遍历 top_users 时只有邻居真正吃过这道菜才参与计算避免把缺失值当成权重拉低预测结果。sim_sum 0的兜底分支是必须写的否则冷启动用户进来推荐列表直接崩给你看。到这一步协同过滤的完整链路已经走通评分表 → 透视矩阵 → 用户相似度 → 加权预测 → Top-N 排序。第 3 章把这些代码搬进 Django 工程把表结构建出来系统才算真正落地。3. Django 工程落地五张数据表与推荐视图的完整实现算法代码在 Jupyter 里跑通只是第一步把它塞进 Django 工程才算落地。本章处理三件事数据模型怎么建、用户端和管理员端各有哪些功能、推荐结果怎么通过视图顶到首页。论文里管理员端有用户管理、美食分类、特色美食、购买记录监控用户端有首页推荐、特色美食展示、美食资讯和个人中心这些功能最终都要落到五张核心表和若干张辅助表上。3.1 数据模型设计五张表搞定用户端和管理员端这五张核心表是用户表、美食分类表、美食表、评分表和购买记录表。用户表可以直接继承 Django 自带的 AbstractUser省去自己处理密码哈希和会话的麻烦分类表与美食表是一对多关系评分表是用户表和美食表的多对多中间表同时记录评分值和评分时间购买记录表同理记录用户买过哪道菜。个人中心里的收藏和浏览历史属于辅助表实现方式相同。下面是我复现时用的 models.py 核心部分字段命名尽量贴近论文语义。from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): 用户表继承 Django 自带用户扩展头像和昵称 nickname models.CharField(max_length32, blankTrue) avatar models.ImageField(upload_toavatar/, blankTrue) class Meta: db_table sys_user class FoodCategory(models.Model): 美食分类川菜、粤菜、甜品等 name models.CharField(max_length32, uniqueTrue) sort_order models.IntegerField(default0) # 控制前台展示顺序 class Meta: db_table food_category class Food(models.Model): 美食表一道菜的完整信息 name models.CharField(max_length64) category models.ForeignKey(FoodCategory, on_deletemodels.CASCADE) price models.DecimalField(max_digits6, decimal_places2) image models.ImageField(upload_tofood/, blankTrue) description models.TextField(blankTrue) avg_rating models.FloatField(default0.0) # 用于首页热门榜兜底 sales_count models.IntegerField(default0) # 用于热门排序 class Meta: db_table food class Rating(models.Model): 评分表用户对美食的打分协同过滤的原料 user models.ForeignKey(User, on_deletemodels.CASCADE) food models.ForeignKey(Food, on_deletemodels.CASCADE) score models.IntegerField(choices[(i, i) for i in range(1, 6)]) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table food_rating unique_together (user, food) # 一个用户对同一道菜只保留一条评分 class PurchaseRecord(models.Model): 购买记录表记录下单行为admin 端监控用 user models.ForeignKey(User, on_deletemodels.CASCADE) food models.ForeignKey(Food, on_deletemodels.CASCADE) quantity models.IntegerField(default1) total_price models.DecimalField(max_digits8, decimal_places2) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table purchase_record几个设计决策值得说明。unique_together在 Rating 表上很重要它保证一个用户对同一道菜只能有一个评分否则协同过滤的透视矩阵会出现重复索引。avg_rating和sales_count看起来和算法无关但它们是冷启动阶段的兜底排序字段后面避坑章会反复用到。db_table统一加前缀避免 Django 默认的表名太长。美食资讯表我没有放进来因为它的结构与推荐算法无关就是标题、正文、发布时间三个字段按 Django 教程标准做法写就行。3.2 用户端与管理端功能拆解一个系统两套操作面功能拆解直接决定 URL 路由怎么设计。用户端走前台核心是浏览和推荐管理员端走后台核心是数据维护。两张表列清楚路由和视图就好分了。端功能模块关键操作用户端系统首页查看推荐美食列表用户端特色美食按分类浏览全部美食用户端美食资讯查看管理员发布的资讯文章用户端个人中心修改密码、查看购买记录、我的收藏、浏览历史管理员端系统首页数据概览管理员端用户管理查看、编辑、禁用用户管理员端美食分类管理新增、修改、删除分类管理员端特色美食管理新增、修改、删除美食管理员端购买记录查看所有用户的下单记录管理员端系统管理发布资讯、维护轮播图等管理员端的核心是美食管理和分类管理这两个功能直接决定前台能展示什么菜。建议把 Django Admin 自定义一下就能覆盖大半需求不需要单独写页面而用户端推荐首页适合自己写模板因为要看效果调试算法。3.3 推荐视图实现ORM 查到相似用户再把 Top-N 顶到首页推荐首页的视图是整个系统的核心环节。它的流程是取当前登录用户 → 找出相似度最高的 N 个用户 → 收集这些用户评过分而当前用户没吃过的美食 → 用第 2 章的预测函数打分 → 按分数排序返回。为了不在每次请求时全表扫描评分数据我在视图里加了缓存这是毕设系统里很容易被忽略的优化。from django.core.cache import cache from .models import Rating, Food from .algorithms import build_user_similarity, predict_rating import pandas as pd def recommend_view(request): 首页推荐视图UserCF 推荐 热门榜兜底 user request.user if not user.is_authenticated: # 未登录用户不做个性化直接给热门榜 hot_foods Food.objects.order_by(-sales_count)[:8] return render(request, index.html, {foods: hot_foods}) # 评分数据缓存半小时半小时内的新评分不会立刻生效 cache_key rating_matrix pivot_df cache.get(cache_key) if pivot_df is None: ratings Rating.objects.all().values(user_id, food_id, score) df pd.DataFrame(list(ratings)) pivot_df df.pivot_table(indexuser_id, columnsfood_id, valuesscore) cache.set(cache_key, pivot_df, 1800) # 从数据库实时取当前用户的已评分列表 rated_food_ids set( Rating.objects.filter(useruser).values_list(food_id, flatTrue) ) sim_matrix build_user_similarity(pivot_df.reset_index().melt( id_varsuser_id, var_namefood_id, value_namescore ).dropna()) # 取前 k 个相似用户 knn sim_matrix.loc[user.id].drop(indexuser.id).sort_values( ascendingFalse ).head(10) # 收集候选美食相似用户吃过 且 当前用户没吃过的 candidate_ids [] for neighbor_id in knn.index: neighbor_rated Rating.objects.filter(user_idneighbor_id).exclude( food_id__inrated_food_ids ).values_list(food_id, flatTrue) candidate_ids.extend(neighbor_rated) # 去重 预测评分 按分数倒序 scored [] for food_id in set(candidate_ids): pred predict_rating(user.id, food_id, pivot_df, sim_matrix, k10) scored.append((pred, food_id)) scored.sort(reverseTrue) recommend_ids [food_id for _, food_id in scored[:8]] # 推荐不足 8 个时用热门榜补齐冷启动兜底 if len(recommend_ids) 8: hot_ids list(Food.objects.exclude( id__inrecommend_ids ).order_by(-sales_count).values_list(id, flatTrue)[:8 - len(recommend_ids)]) recommend_ids hot_ids foods Food.objects.filter(id__inrecommend_ids) return render(request, index.html, {foods: foods})这里的请求链比较长过一遍先从缓存取评分透视表缓存没有就从 Rating 表重建半小时过期意味着新评分不会实时影响推荐结果但对毕设演示足够。rated_food_ids从数据库实时查是防止把用户吃过的菜再推荐回去。candidate_ids 用列表而不是集合收集保留数量信息后面 set 去重。最后的热门榜补齐是关键兜底推荐结果不足 8 个时系统不会白屏而是退化为热门推荐。settings.py 里要配 MySQL注意别用默认的 SQLite 完事论文里明确写了 MySQL而且购买记录、用户管理等模块在高并发演示时 SQLite 容易锁库。DATABASES 配置里填库名、用户名、密码再把pip install pymysql装好在项目__init__.py里补两行pymysql.install_as_MySQLdb()这套路做过 Django 的人都知道。4. 协同过滤实战避坑冷启动、稀疏矩阵与评分偏差的排查记录这一章的坑全部是实际跑系统时踩出来的。数据量小的时候算法看起来一切正常一旦你注册一个新用户、清理一次评分表或者把用户量扩到几百问题就接踵而至。每一条都按“现象 → 原因 → 解决”来写方便你对号入座。4.1 现象新用户首页推荐列表全是空白注册一个新账号登录首页推荐区域空荡荡只显示“暂无推荐”。原因是显然的新用户没有任何评分记录透视矩阵里他那一行全是 NaN相似度计算出来全是 0predict_rating 的兜底分支返回他自己的平均分而新用户没有平均分于是返回 NaN渲染到页面上就是空白。解决思路分两层。第一层是马上做热门榜兜底也就是上一章视图代码里最后那几行推荐不足 8 条就用sales_count或avg_rating排序补齐。第二层是引导用户产生评分注册时让用户挑几道喜欢的菜打个分或者把热门菜的“想尝一尝”按钮做成一键评分尽快积累第一桶评分数据。论文里虽然没写这个注册向导但它是所有推荐系统冷启动的标准解法照着加上不会错。4.2 现象fillna(0) 之后推荐结果出现离奇相似系统刚上线数据稀疏时只有十几个用户、二十几道菜我拿余弦相似度算出来两个几乎没共同评分的用户相似度竟然高达 0.9。原因是 fillna(0) 把大量“没吃过”的格子当成了双方都打了 0 分两个用户的向量里同时出现几十个 0方向自然被拉近。这在数学上叫“零填充带来的虚假相似”。解决方法是把相似度计算从余弦换成皮尔逊或者在建透视矩阵后只保留共同评分的维度。皮尔逊公式在减均值时已经把缺失值排除在外是评分场景下的默认选择。如果你执意用余弦那至少要改成计算两个用户相似度时只取两人都评过分的那几列而不是全向量比对。我之前项目里也偷懒直接填 0后来发现用户 A 吃过的 5 道菜和用户 B 吃过的菜品完全没交集相似度却是 0.82典型的翻车现场。4.3 现象评分偏差让“手松”的用户成了所有人的近邻模拟数据时我故意设了两种用户一种逢菜必给 5 分一种严格打分、好吃的才给 4 分。结果余弦相似度把前者推成了所有人的 Top-1 近邻因为他的向量里全是高分和谁的向量都“夹角很小”。这就是评分偏差评分标准不同直接比原始分数没有意义。皮尔逊相关系数天然解决这个问题因为每个用户先减了自己的平均分手松手紧的差异被抹平剩下的才是口味偏好。第 2 章代码里predict_rating用的就是“中心化”思路neighbor_rating - neighbor_mean就是在做这件事。注意相似度计算和预测环节要同时改成中心化版本只改一头效果照样会歪。4.4 现象数据量过百推荐接口响应从毫秒变成秒级系统里注册用户到 200 个、评分记录到 3000 条之后首页推荐接口加载时间从几十毫秒涨到两三秒。原因很简单每次刷新都要全量重建用户相似度矩阵透视矩阵是 200 维乘 200 维再加上模型和前端的序列化开销接口自然慢。解决方向有两个。最有效的是把相似度矩阵做成离线任务每天或每小时算一次存到缓存里在线请求只查表不计算这个方案在第 5 章细讲。另一个是近邻截断算相似度时只保留每个用户 Top-50 的近邻其余直接置零矩阵会从稠密变成稀疏计算量大幅下降。毕设阶段建议两个都做答辩时说到性能优化也有素材。5. 进阶技巧相似度矩阵离线化把“每次现算”改成“查表”在线推荐接口最大的性能瓶颈就是实时重建相似度矩阵。我的做法是把它改造成两段式离线任务负责算矩阵在线请求负责查表。离线任务每天凌晨跑一次计算全部用户或物品的相似度按“用户 ID → 近邻用户列表”的格式存到缓存里在线推荐直接读缓存按用户 ID 取近邻再查候选美食。这样推荐接口的耗时从秒级降到百毫秒以内而且评分数据的更新每小时或每天生效一次对美食推荐这种非实时场景完全够用。在此基础上推荐效果需要验证不能拍脑袋说“推荐结果挺好”。常用的验证方法是把评分数据按时间切一刀前 80% 当训练集、后 20% 当测试集用训练集算相似度对测试集里的“用户-美食”对预测评分取每用户 Top-10看命中了多少真实评分。命中率用 Precision10 表达预测的 10 道菜里有几道是测试集里用户真实吃过的。代码很简单但能让你调参时有据可依。def evaluate_precision(train_df, test_df, k10): 离线评估推荐质量 在训练集上建模对测试集里的每个用户取 Top-k 推荐 统计测试集真实吃过的菜有多少条命中推荐列表。 参数: train_df: 训练集评分 DataFrame test_df: 测试集评分 DataFrame sim_matrix build_user_similarity(train_df) hit_count 0 total_pred 0 for user_id in test_df[user_id].unique(): # 预测该用户在测试集里每道菜的分排序取 Top-k pred_scores [] for food_id in test_df[test_df[user_id] user_id][food_id]: score predict_rating(user_id, food_id, train_df.pivot_table( indexuser_id, columnsfood_id, valuesscore ), sim_matrix) pred_scores.append((score, food_id)) pred_scores.sort(reverseTrue) top_k_foods set(food_id for _, food_id in pred_scores[:k]) actually_eaten set( test_df[test_df[user_id] user_id][food_id] ) hit_count len(top_k_foods actually_eaten) total_pred k return hit_count / total_pred评估函数返回的是 PrecisionK数值越高说明推荐越贴近真实偏好。调参时可以用它来对比 k 取 5、10、20 的差异看相似度阈值设多少合适。从那以后我每次复现这类协同过滤系统都强制走一遍完整流程先构造评分数据再离线算相似度矩阵然后评估命中率最后才接 Django 视图。这套流程看着多其实半小时能跑完但能挡掉前面讲的四类坑。如果你正在复现这套美食推荐系统建议也把算法部分独立成一个包模型、相似度、预测、评估分开写后面换 ItemCF 或者加深度学习模型时不用动视图层。希望帮到你。本文还有配套的精品资源点击获取