简介面向机器学习与知识工程学习者这是一份饮食推荐系统的完整项目资源包。项目基于用户饮食数据构建推荐模型涵盖从数据清洗、特征分析到推荐算法实现的主要流程适合作为课程设计、毕业设计或推荐系统入门开发的参考。资源共包含6个文件类型覆盖2个CSV数据文件、1个Python推荐算法脚本、1个Jupyter Notebook交互式分析文档、1份PDF课程资料及1个说明文档其中CSV文件提供食物成分与营养分布数据Python脚本封装了推荐核心逻辑Notebook则展示从数据探索到模型评估的完整过程整体压缩包约429KB结构紧凑、便于按需查阅。目前已有217人学习说明其内容具备一定参考价值。通过该资源读者可以获得完整的项目代码与数据学习如何将机器学习方法应用于饮食推荐场景理解知识库专家系统与推荐功能结合的实现思路并可直接调整数据与算法以适配自己的实验需求。1. 饮食推荐系统这个 zip值不值得你花一晚上跑通很多人以为推荐系统是电商和短视频平台的专利其实「使用机器学习算法的饮食推荐系统」恰恰是机器学习最接地气的落地场景录入你的身高体重和口味偏好系统告诉你今天该吃什么、哪道菜的搭配更适合你。我见过不少同学从网上下载这类打包好的 Jupyter Notebook 项目结果一晚上全耗在装环境和解压上连数据长什么样都没看到最后只能把代码截图交作业。这个方向本身不难——核心就是数据清洗、特征工程加相似度计算跑通它相当于把推荐系统的完整套路走了一遍。这篇笔记适合两类人要交算法汇报、课程设计的学生和想判断饮食推荐能不能真正上线的工程师。全文从解压 zip 开始一步步讲到你拿它做验收或二次开发。2. 拿到 zip 第一步解压、配环境、把第一个 notebook 跑起来这类项目压缩包一般长一个样子一个.ipynb文件、一份 CSV 或 Excel 格式的数据文件再加一个requirements.txt或者README。先把这三样东西找齐再动手配环境能省掉后面一大半的排错时间。我习惯先把压缩包内容列出来看一遍而不是直接双击解压——很多坑在解压前就能看出来。2.1 解压 zip 前先做三件事文件校验、目录预览、编码确认在 Linux 服务器上解压这类 zip 包最常用的是unzip但我一般会先用unzip -l看一眼包结构。这一步能确认包里到底有没有数据文件、有没有 readme以及目录是不是嵌套了一层才到 notebook 本体。# 先看压缩包里有几个文件、路径是什么不解压也能预览 unzip -l food_recommend_system.zip # 解压到单独目录避免文件散落一地 mkdir -p ./food_reco unzip food_recommend_system.zip -d ./food_reco-l是 list 模式只列出压缩包内容不解压-d指定解压目标目录。我坚持解压到独立目录的原因是notebook 项目经常会用相对路径读取data/下的文件直接右键解压到桌面再移动文件夹路径一变代码里pd.read_csv()立刻报错。这也是这类下载项目最常见的翻车点。如果unzip报错提示文件损坏、或者解出来的文件不全先别急着删除重下。常见情况是打包工具非标准导致的伪加密问题——文件头里加密标志位是 1但内容根本没加密7-Zip 能打开、普通 unzip 却要密码。这种情况优先换工具Windows 上用 7-Zip 打开后全选拖出来即可别去折腾什么密码破解。2.2 Python 环境与 Jupyter Notebook 网页版最小安装顺序环境这块是新手最头疼的我见过太多人把包往全局环境里装装完 pip 列表乱成一锅粥。我的做法是每个项目开一个虚拟环境这个习惯能救命。如果你机器上还没装 Python建议直接装 Anaconda 或者 Miniconda自带 conda 命令创建环境最省事已经有 Python 3.8 的用自带的 venv 就够了。# 创建独立虚拟环境避免污染全局 Python python3 -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate # 安装 Jupyter Notebook 和常用数据科学依赖 python -m pip install --upgrade pip python -m pip install jupyter pandas numpy scikit-learn matplotlib seaborn这里要特别说明一下为什么用python -m pip而不是直接pip很多机器上同时存在 Python 2 和 Python 3或者有多个 Python 版本直接敲pip装的包很可能进了一个你意想不到的解释器最后import报错。用python -m pip能确保装到当前激活的虚拟环境里。装完以后启动 Jupyter Notebook默认会在浏览器打开一个网页版操作界面点开.ipynb就能写代码跑实验。jupyter notebook如果你的服务器是多用户环境可以指定端口启动避免冲突jupyter notebook --port8889 --no-browser。初学者如果看到网页版一直在 “connecting” 状态大概率是没装ipykernel这个问题的解法放到第 5 章专门讲。2.3 安装依赖有 requirements.txt 和没有是两种玩法打开解压后的目录先看有没有requirements.txt。这个文件是项目的依赖清单有它就按文件批量装没有就只装核心库别把网上教程里提到的包全部装一遍版本冲突会让你怀疑人生。# 有 requirements.txt 的直接用 -r 批量安装 python -m pip install -r requirements.txt # 没有的话装这六个就够跑 80% 的饮食推荐 demo python -m pip install pandas numpy scikit-learn matplotlib seaborn jupyter注意一点scikit-learn的安装包名和导入包名不一样安装写pip install scikit-learn代码里import sklearn少一个连字符就装出问题来。如果 notebook 跑起来以后报ImportError: xxx has no attribute yyy八成是包版本太老或太新用pip list看一下版本号再针对性升降级。这类小项目一般不用锁死版本能用就行。2.4 一键验证 notebook 能否完整跑通nbconvert 的诡异用法拿到手的 notebook 是在作者的环境里写的他用的 pandas 版本、sklearn 版本跟你不一定一样手动在网页版里一个个 cell 连着点太慢。我习惯先用命令行把整个 notebook 自动执行一遍跑不通的报错会直接打在输出里这一步能快速暴露环境问题。# 复制一份再执行避免原文件被改动后没法回头 cp food_recommend.ipynb food_recommend_backup.ipynb # nbconvert 会按顺序执行所有代码块生成一个新的 .ipynb jupyter nbconvert --to notebook --execute food_recommend.ipynb --output executed_food.ipynb--to notebook表示输出格式还是 notebook 文件--execute是让 jupyter 从头到尾执行--output指定结果文件名。执行成功后用jupyter notebook打开executed_food.ipynb直接看每个 cell 下方的输出数据不用自己重新跑一遍。如果中间某个 cell 报错报错信息里会告诉你具体是哪一个 cell 挂了——这比在网页版里手动排查快得多也是我每次拿到别人项目后的第一道检查工序。3. 算法选型饮食推荐里的三种机器学习落地方案打开 notebook 你很快会发现核心代码其实就是几套经典机器学习算法的组合。饮食推荐场景里最常见的是三条路线基于内容的过滤、协同过滤、以及矩阵分解或分类模型兜底。新手最容易犯的错误是一上来就堆深度学习在小数据集上效果未必比一个余弦相似度好而且还没法跟人讲清楚为什么这样推荐。这一章把三种方案的原理、代码和选型理由拆开讲。3.1 基于内容的过滤把菜品特征向量化用余弦相似度找“像”的菜基于内容的思路最直观给每道菜建一份“画像”包含热量、蛋白质、脂肪、碳水、菜系等特征然后算出菜与菜之间的相似度。用户如果吃过某几道菜就把这些菜的画像合并成一份用户画像再去匹配画像最接近的未推荐菜品。这种方案不依赖用户之间的行为冷启动时也能用。import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.metrics.pairwise import cosine_similarity # 每行是一道菜列是营养特征索引是菜名 food pd.read_csv(food_features.csv, index_colfood_name) X food[[calories, protein, fat, carbs]].values # 标准化热量数值大蛋白质数值小不处理相似度会被大数主导 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 计算菜与菜之间的余弦相似度矩阵 sim cosine_similarity(X_scaled) # 查某道菜最像的前 5 道 target 鸡胸肉沙拉 idx list(food.index).index(target) top sim[idx].argsort()[-6:-1][::-1] print([food.index[i] for i in top])cosine_similarity按行计算两两之间的余弦值结果范围在[-1, 1]越接近 1 表示两道菜在营养结构上越像。argsort()[-6:-1][::-1]是取相似度最高的前 5 个索引并倒序排列这样打印出来就是从最相似往下排。要换推荐对象把target变量改成任意菜名即可。这里最容易忽略的是标准化这一步。热量动辄几百千卡蛋白质只有几十克量纲差太多不标准化的话相似度基本被热量这一个特征控制其他营养维度全废了。谁要是发现相似度结果看起来很怪先检查是不是漏了StandardScaler这种问题很像玄学其实就是数据预处理没做。3.2 协同过滤从用户行为矩阵里找偏好相似的人协同过滤的思路换了个方向不分析菜本身长什么样而是看用户行为。用户都对哪些菜打过高分口味相似的人吃的菜大概率你也喜欢。这种方案贯彻了“人以群分”的思想是推荐系统里的常青树。具体做法是把评分数据整理成一个用户 × 菜品的矩阵行是用户、列是菜品、值是评分然后算用户之间的相似度。最经典的相似度度量是皮尔逊相关系数它有天然的去中心化效果两个用户一个普遍打高分、一个普遍打低分只要偏好顺序一致相似度依然很高。import pandas as pd import numpy as np # 三列user_id / food_id / rating ratings pd.read_csv(user_ratings.csv) # 透视成用户-菜品矩阵缺失位置是 NaN matrix ratings.pivot_table(indexuser_id, columnsfood_id, valuesrating) # 转置后按列求皮尔逊相关系数得到用户与用户的相似度矩阵 user_sim matrix.T.corr() # 预测目标用户对某道菜的评分取最相似 K 个用户的评分做加权平均 target_user u_001 food_id f_042 sim_users user_sim[target_user].drop(labels[target_user]).sort_values(ascendingFalse).head(10) # 过滤掉没吃过这道菜的用户用相似度做权重 valid matrix.loc[sim_users.index, food_id].dropna() pred np.average(valid, weightssim_users.loc[valid.index]) print(f预测用户 {target_user} 对 {food_id} 的评分为 {pred:.2f})pivot_table会把没评过分的格子填成 NaNmatrix.T.corr()这一步转置后每列是一个用户的行向量corr()自动按列计算皮尔逊相关系数。注意weights参数传的是相似度相似的人说话分量更重。还要留一个心眼dropna()以后如果剩下的有效用户太少比如只剩一两个这个预测值可信度很低实际项目中至少要有 5 个相似用户都吃过这道菜才算数。协同过滤最大的坑是数据稀疏。饮食类 app 里用户一天最多记录几顿饭评分矩阵里 99% 的位置都是空相似度算出来虚低。我一般会在算相似度之前先过滤掉评分少于 5 条的用户剩下的人算出来的结果才有点参考价值。3.3 矩阵分解与分类兜底评分缺失和冷启动时的替代思路协同过滤在矩阵稀疏时表现不佳另一个思路是用矩阵分解把用户-菜品矩阵压缩成低维隐向量。每个用户和每道菜都用一个几十维的向量表示向量的内积就是预测评分。这样做的好处是能挖掘出表面上没有交集的用户之间潜在的相似口味。from sklearn.decomposition import TruncatedSVD from sklearn.impute import SimpleImputer from sklearn.metrics.pairwise import cosine_similarity # SVD 不接受 NaN先用均值填充兜底 filled_matrix SimpleImputer(strategymean).fit_transform(matrix.values) # 压缩成 8 维隐向量这个维度一般取 5~20 svd TruncatedSVD(n_components8) item_latent svd.fit_transform(filled_matrix.T) # 在隐向量空间里重新计算菜品相似度 item_sim_svd cosine_similarity(item_latent) print(隐向量相似度矩阵维度:, item_sim_svd.shape)TruncatedSVD和 PCA 不同它不先做中心化适合直接处理稀疏矩阵。n_components是隐向量维度取值太小丢信息、太大容易过拟合小数据集上 8 左右是个不错的起点。均值填充只是临时方案它会把稀疏矩阵变成稠密矩阵内存开销变大但胜在简单demo 阶段够用。除了矩阵分解还有一类情况要用分类模型兜底zip 里的数据根本没有任何用户评分只有用户画像和菜品画像。这时候推荐问题变成了二分类——这道菜对这个用户推还是不推。逻辑回归是通用性最强的选择特征解释性也好。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X features # 用户年龄、BMI、口味类别、菜品营养值 y labels # 是否满意0 或 1 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf LogisticRegression(max_iter500, C1.0) clf.fit(X_train, y_train) print(测试集准确率:, clf.score(X_test, y_test))max_iter500是给梯度下降足够的迭代次数防止收敛警告C是正则化强度的倒数默认1.0一般不用动stratifyy保证训练集和测试集里正负样本比例一致避免测试集里全是“不推荐”导致准确率虚高。这类模型的缺点是只能输出 0 或 1不能直接排序但用来做冷启动兜底是合格的。4. 数据与特征工程饮食推荐系统里真正花时间的部分初学者拿到 zip 往往直奔算法代码但这类小项目真正调优的空间几乎全在数据质量和特征工程上。算法选得再花哨数据是脏的结果全是垃圾。我自己的经验是项目时间分配上数据处理和特征工程至少要占六成模型训练反而是最省心的一步。4.1 数据从哪来常见 CSV 字段与数据质量检查这类饮食推荐项目的数据通常有两张表一张是菜品特征表字段包括菜名、热量、蛋白质、脂肪、碳水、菜系另一张是用户行为表字段是用户 ID、菜品 ID、评分或是否食用。拿到数据第一件事不是写算法而是把数据读进来做三个基础检查看规模、看缺失、看分布。import pandas as pd # 读取两份核心数据Windows 环境编码不对就改 gbk food pd.read_csv(food_features.csv, encodingutf-8) ratings pd.read_csv(user_ratings.csv, encodingutf-8) print(菜品表规模:, food.shape) print(行为表规模:, ratings.shape) print(food.head()) # 缺失值检查NaN 太多会影响相似度计算 print(food.isnull().sum()) # 评分分布是否均匀集中在一个值说明数据没有区分度 print(ratings[rating].value_counts())shape一眼看出数据量级几百道菜的数据算是小样本几万道菜就要考虑内存优化了。isnull().sum()检查缺失值如果某些列缺失比例超过 30%建议直接删列而不是填充否则噪声比信号还大。value_counts()查评分分布如果 90% 的评分都是同一个值说明用户的反馈没有区分度再强的模型也推不出差异化结果。数据可视化这一步也很值得花十分钟用 seaborn 画个评分分布图或者菜系分布图比盯着 DataFrame 更直观地看出问题。比如评分数值集中在 3 分和 5 分、4 分几乎没有那这个数据源本身就可能有问题得回头重新审视采集逻辑。4.2 特征工程把口味、菜品类别和营养值变成机器能算的数算法只能吃数字所以文本型特征必须转换。这个环节的常见错误是把所有文本特征都塞进LabelEncoder完全不区分类别到底有没有顺序关系。菜系这种无序类别编码成 0、1、2 会带来大小关系模型会误以为“川菜”大于“粤菜”这完全是误导必须用 One-Hot。而“清淡、中等、重口”这种本身带有强弱顺序的LabelEncoder反而合适。from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import StandardScaler # 菜系是无序类别one-hot 展开成多个 0/1 列 cuisine_oh pd.get_dummies(food[cuisine], prefixcuisine) # 口味程度是有序类别转成数字保留顺序关系 taste_le LabelEncoder() food[taste_level] taste_le.fit_transform(food[taste]) # 注意转换后打印看一下对应关系确认顺序符合直觉 print(dict(zip(taste_le.classes_, taste_le.transform(taste_le.classes_)))) # 营养列数值差异大标准化统一量纲 scaler StandardScaler() food[[calories, protein, fat, carbs]] scaler.fit_transform( food[[calories, protein, fat, carbs]] )pd.get_dummies把菜系字段拆成若干 0/1 列prefix参数给列名加前缀防混淆。LabelEncoder用之前我习惯把映射关系打印出来看一眼防止编码顺序跟直觉相反。标准化这一步做完food这个 DataFrame 里所有列就都是数值了可以直接喂给 3.1 的cosine_similarity。还有一个地方要提醒特征列拼接到一起时务必确认索引对齐。从get_dummies得到的新 DataFrame 和原表索引顺序要一致最稳妥的做法是pd.concat([food, cuisine_oh], axis1)之前先reset_index(dropTrue)否则行错位会带来极其隐蔽的错误。4.3 训练测试划分与效果评估别只用准确率很多人在这个环节翻车翻得毫无自觉。做推荐系统时如果用train_test_split随机切分数据同一个用户的评分会同时出现在训练集和测试集里模型等于见过答案再考试分数虚高得吓人一上真实场景立刻暴露。正确的做法是按用户划分保证同一个用户的数据只落在某一侧。from sklearn.model_selection import train_test_split # 按用户划分而不是按评分记录划分 users pd.unique(ratings[user_id]) train_users, test_users train_test_split( users, test_size0.2, random_state42 ) train ratings[ratings[user_id].isin(train_users)] test ratings[ratings[user_id].isin(test_users)] print(f训练集出现用户数: {train[user_id].nunique()}) print(f测试集出现用户数: {test[user_id].nunique()})test_size0.2表示拿 20% 的用户做验证random_state固定随机种子保证每次跑结果一致方便对比调参效果。评估指标的选择也要说清楚推荐系统常用的三个指标针对不同目标指标适用场景说明RMSE评分预测类任务对预测误差敏感0.5 以下算不错PrecisionK推荐列表排序看前 10 条里用户真正吃/点赞的比例Coverage推荐多样性推荐结果覆盖多少种菜品覆盖低说明算法偏科对饮食推荐来说用户真正关心的是推荐列表里有没有他想吃的菜而不是预测评分和真实评分差了多少所以我个人更看重PrecisionK。Feature 层面如果用了分类模型提升准确率还得多看正样本召回率。5. 避坑指南跑饮食推荐系统最容易翻车的五个现场这部分内容是我见过最多的血泪经验汇总。环境问题和数据问题占了这类项目排错时间的一大半模型跑不出理想效果反而是少数。每一条我都按“现象 → 原因 → 解决”写清楚直接对号入座就行。5.1 Jupyter Notebook 内核连不上网页版一直卡在 “connecting”现象点开 notebook 文件后代码块右侧一直显示 “connecting”点运行没反应。原因Jupyter Notebook 的网页版只是前端界面实际执行代码的是一个叫 kernel 的后端进程。项目是用某个 Python 环境创建的但启动 notebook 时没有为当前环境安装ipykernel前后端对不上号。解决在激活的虚拟环境里安装并注册内核然后重启 notebook。python -m pip install ipykernel python -m ipykernel install --useripykernel install --user的作用是把当前环境注册成 Jupyter 可选的内核。注册完在 notebook 网页版里点击菜单栏的 “Kernel → Change Kernel”选对刚才注册的 Python 环境再重新运行就可以了。5.2 zip 解压报错提示伪加密或找不到 EOCD现象解压到一半报错 “unsupported” 或者 “could not find EOCD”文件释放出来只有几个其他找不着了。原因网上流传的 zip 包经常被二次打包打包器不标准或者文件头的加密标志位被改动过。伪加密的意思是标志位写的是加密但内容实际上没有任何加密zipfile 模块就被这个假标志卡住了。解决先用unzip -l看能否列出内容列不出来就换 Python 的 zipfile 模块尝试容错性比命令行 unzip 更好。python -m zipfile -e food_recommend_system.zip ./food_reco-e参数把 zip 解压到指定目录。如果 Python 的 zipfile 能正常解压但命令行 unzip 不行就是典型的非标准打包问题。Windows 上我更推荐用 7-Zip 打开能看到内容就直接全选复制出来不跟命令行斗气。5.3 pandas 读中文 CSV 报 UnicodeDecodeError现象pd.read_csv(food_features.csv)直接红字UnicodeDecodeError这一行代码都跑不过去。原因CSV 文件不是默认的 UTF-8 编码。Windows 上 Excel 另存的 CSV 大概率是 GBK 或 GB18030 编码pandas 默认用 UTF-8 解码当然炸锅。解决读取时指定编码gb18030是 GBK 的超集容错率更高。food pd.read_csv(food_features.csv, encodinggb18030)如果不想每次读都写编码参数可以把文件统一转成 UTF-8 存一份副本后续所有脚本都读这份新文件。写文件时也用encodingutf-8避免在别人电脑上二次报错。这个小习惯能省掉大量重复排错。5.4 相似度矩阵全 NaN或者控制台报 RuntimeWarning现象cosine_similarity算出来的矩阵全是 NaN或者运行过程弹出RuntimeWarning: invalid value encountered。原因特征矩阵里包含 NaN相似度计算时 NaN 传染或者标准化后某些列标准差为 0——比如所有菜的卡路里数值都一模一样除零产生了 NaN。解决先查缺失值再做预处理。用中位数填充对异常值不敏感比均值更稳。from sklearn.impute import SimpleImputer imputer SimpleImputer(strategymedian) X_clean imputer.fit_transform(X) print(填充后剩余 NaN:, pd.DataFrame(X_clean).isnull().sum().sum())如果填充以后还是 NaN就检查是不是某列是常数列。常数列的方差为 0标准化时除以 0这种情况直接删掉这一列。5.5 notebook 跑一半内存炸了内核直接重启现象代码跑到相似度计算时卡住不动过一会儿 jupyter 页面提示 “Kernel Restarting”前面算好的结果全没了。原因代码里用了双层 for 循环计算 N x N 相似度矩阵。N 是几千道菜的时候循环次数是千万级Python 的循环效率又低内存直接被撑爆。解决能用向量化实现就不要手写循环。scipy.spatial.distance.pdist只存上三角矩阵内存占用比完整矩阵少一半。from scipy.spatial.distance import pdist, squareform # pdist 返回压缩后的距离向量只存储上三角 dist_compact pdist(X_scaled, metriccosine) # squareform 还原成完整方阵方便按索引取结果 dist_matrix squareform(dist_compact)pdist的metric参数可以换成euclidean、correlation等按场景调整。如果数据量再大一个量级连pdist都撑不住就用分批计算或者上稀疏矩阵方案。6. 从能跑到能用推荐效果的验证方法与两个进阶改进把 notebook 跑通只是第一步离“能用”还差一个系统评估和两个数据策略上的补救。这一章讲的不是我常用的炫技操作而是所有推荐系统上线前都必须补的功课。6.1 离线验证做扎实RMSE、PrecisionK 与表格对比离线评估最关键的一点不要用随机切分按时间切。用户在第一周的行为是历史第二周的行为是验证这样才能模拟真实场景。如果手里的数据没有时间戳才退而求其次按用户切分。指标计算方式合格线参考RMSEsqrt(平均评分误差平方)0.5~0.8 说明预测可用PrecisionK推荐前 K 个中命中用户实际食用的比例0.1 以上就算有信号Coverage有推荐记录的菜品数 / 总菜品数低于 50% 需要扩充推荐池这三种指标要在同一批测试用户上同时计算只报准确率没有说服力。很多模型推荐来推荐去都是那几道高热量菜覆盖率数字一拉出来就不及格。6.2 冷启动补救给新用户和新菜品各留一条退路纯协同过滤最怕的新用户没有行为记录、新菜品没有评分这时候模型一片空白。既然你拿到的是饮食推荐项目就一定能用上这个策略新用户直接走第 3 章讲的基于内容的推荐让他先选几个喜欢的口味标签把标签转成特征向量去匹配菜品而新上架的菜品先计算它和已上线菜品的相似度挂在最相似已上线菜品的详情页里等评分攒够了再进入协同过滤池。这个策略不用改模型只需要调整数据的接入顺序落地成本最低收益却几乎所有推荐系统都能复用。在真实项目里我也走过弯路当时把协同过滤的矩阵分解维度调了一整天测试集分数提升不到 1%后来才反应过来模型根本不是瓶颈新用户根本没有历史记录。最后做事的顺序变成先接住冷启动再优化模型。这条经验救了我很多次希望你跑完这个饮食推荐项目以后也能记得先补这一层再研究那些花哨参数。希望帮到你。本文还有配套的精品资源点击获取