news 2026/7/14 5:29:09

家庭厨房里的轻量级推荐系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
家庭厨房里的轻量级推荐系统实战

1. 这不是菜谱推荐,而是一次家庭厨房里的“小规模运筹学实战”

“今晚吃什么?”——这句话背后藏着的,根本不是选择困难症,而是一个被严重低估的多约束优化问题。我干了十多年数据产品和算法落地工作,带过几十个从0到1的工业级推荐系统项目,也亲手拆解过电商、教育、本地生活等场景里上亿级用户的行为链路。但直到某个周三晚上,站在冰箱前盯着半盒蔫掉的西兰花、两根火腿肠和一包快过期的意面发呆时,我才真正意识到:最硬核的推荐场景,不在云端服务器里,就在你家厨房的砧板上。

关键词“Towards AI - Medium”提示这是一篇来自技术社区的真实实践手记,但它绝不是一篇炫技的AI教程。它讲的是一个数据科学家如何把抽象的数学建模,降维成能解决晚饭焦虑的实用工具。核心不在于用了NearestNeighbors还是XGBoost,而在于她精准识别出:家庭晚餐决策的本质,是在时间、库存、季节、口味、精力这五条绳子同时绷紧的状态下,找到那个唯一不崩断的解

这个思路对普通人极其友好——你不需要懂矩阵分解,不需要调参,甚至不需要写一行代码。它用最朴素的逻辑告诉你:当你的目标明确(比如“今晚30分钟内搞定一顿热乎饭”),约束清晰(冰箱里有鸡胸肉、洋葱、糙米,孩子不吃香菜),那所谓“推荐”,就是从你过往做过的、打过分的菜里,找一道最接近当前条件的。这就像老司机开车不看导航,靠的是肌肉记忆和路况经验;而这个小系统,就是把你过去半年的做饭经验,变成可检索、可复用的“厨房直觉”。

适合谁读?第一类是刚学完pandas和scikit-learn、正愁没真实项目练手的新手——这篇比任何Kaggle入门赛都接地气;第二类是每天被“今晚吃啥”折磨的家庭主心骨,哪怕你完全不懂代码,也能照着思路用Excel手动实现;第三类是产品经理或业务方,想理解“轻量级AI”如何嵌入高频生活场景。它不教你造火箭,但能帮你把灶台变成第一个AI落地现场。

2. 为什么选“最近邻”而不是协同过滤?一次厨房里的算法取舍

2.1 真实世界的数据,永远比教科书残酷

原文作者提到,她最初想到Netflix的百万美元推荐竞赛,但很快否定了协同过滤(Collaborative Filtering)路线。这不是技术退步,而是对现实约束的清醒判断。我们来拆解她放弃的理由,每一条都踩在家庭厨房的痛点上:

  • 没有“用户-物品交互矩阵”:协同过滤需要大量用户对大量菜品的评分行为(比如1000人给500道菜打分)。但家庭场景里,只有她一个人记录数据,且一年最多做365顿饭,其中重复菜式占70%。这意味着矩阵极度稀疏——99%的位置是空的。强行用SVD或ALS分解,结果不是推荐不准,而是根本跑不起来。

  • 冷启动问题无解:假设某天突然想做泰式冬阴功,但数据库里从未收录过类似菜式。协同过滤会因缺乏相似菜品或相似用户而直接失效。而家庭厨房的现实是:新菜式永远在发生,你不可能等收集够100份评价再下锅。

  • 特征不可控:协同过滤依赖隐式反馈(点击、停留时长)或显式评分。但在厨房里,“孩子把青椒挑出来”是负反馈,“老公连吃三碗”是正反馈,这些信号无法结构化录入。你总不能边炒菜边打开手机点五星吧?

提示:很多初学者一提推荐就默认“必须用协同过滤”,这是被大厂案例带偏了。真实业务中,80%的推荐需求其实更适合基于内容(Content-Based)或基于规则(Rule-Based)的轻量方案。家庭晚餐就是典型——你的“内容”就是菜名、食材、耗时、季节标签,这些全是你自己定义、自己掌控的。

2.2 最近邻:用空间距离模拟人类直觉

作者最终选择NearestNeighbors,本质是把烹饪决策转化为几何问题:

  • 每道菜是一个4维空间中的点:(PrepTime, CookingTime, Weekday, Winter)
  • 当前情境(比如“周四晚上,准备时间≤20分钟,冬季”)是查询点(20, 30, 1, 1)
  • 系统要找的是离这个查询点欧氏距离最近的已知菜品

为什么这个选择如此精妙?因为它完美复刻了人类大脑的联想机制。当你看到冰箱里有土豆和牛肉,你会本能想到“土豆炖牛肉”“咖喱牛肉饭”“牛肉土豆饼”,而不是“芒果糯米饭”或“清蒸鲈鱼”。这种联想不是靠统计共现频次,而是基于食材功能相似性、烹饪逻辑一致性、季节适配性的综合判断。而NearestNeighbors通过标准化后的数值距离,把这种模糊的“相似感”量化了。

举个实操例子:假设数据库里有三道菜:

  • A:红烧排骨(Prep=25min, Cook=90min, Weekday=1, Winter=1)
  • B:番茄炒蛋(Prep=10min, Cook=15min, Weekday=1, Winter=0)
  • C:冬笋炒腊肉(Prep=15min, Cook=20min, Weekday=1, Winter=1)

当查询点为(12, 18, 1, 1)(即“工作日晚上,准备≤12分钟,烹饪≤18分钟,冬天”)时,C的距离明显小于A和B。系统推荐C,不是因为C和查询点“食材相同”,而是因为它的时间维度和季节维度与当前情境严丝合缝——这正是忙碌家长最需要的确定性。

2.3 被放弃的“食材独热编码”:一场及时止损的工程反思

原文中作者曾尝试对食材列做独热编码(One-Hot Encoding),把“洋葱、牛肉、土豆”拆成三个0/1列,结果数据维度从13列暴涨到59列。但她果断放弃了,这个决定值得所有数据从业者学习。原因很实在:

  • 维度灾难(Curse of Dimensionality):当特征从4维增加到50+维,点与点之间的欧氏距离会趋同,导致“最近邻”失去区分度。简单说,系统会发现所有菜的距离都差不多,推荐变成随机。
  • 食材权重失真:独热编码默认所有食材平等。但现实中,“盐”和“松露”的重要性天差地别。用0/1表示,等于告诉模型“放盐”和“放松露”对菜品定义的贡献一样大,这显然违背常识。
  • 维护成本飙升:每次新增一道菜,都要检查是否引入新食材,动态扩展列。而家庭数据库更新频率低,没必要为短期需求增加长期负担。

注意:这里没有对错,只有权衡。如果目标是构建“根据现有食材智能生成菜谱”的系统(如用冰箱剩余食材匹配食谱),那食材编码就是核心。但本文目标是“根据当前情境推荐历史做过的菜”,时间+季节才是黄金特征。工程师的价值,往往体现在知道该砍掉什么,而不是堆砌什么。

3. 从零搭建:一份可直接抄作业的厨房推荐系统实操指南

3.1 数据准备:用Excel就能完成的最小可行数据集

你不需要编程基础,先用Excel搭起骨架。按以下5列整理你家的晚餐记录(建议至少积累20顿饭的数据,效果会质变):

Name(菜名)PrepTime(准备时间/分钟)CookingTime(烹饪时间/分钟)Weekday(是否工作日)Winter(是否冬季)Rating(家人评分0-10)
番茄牛腩面2540119.2
清炒时蔬812107.5
冬菇滑鸡饭1525018.8

关键细节说明:

  • Weekday:工作日填1,周末填0。别纠结“周五晚上算不算工作日”,统一按你实际做饭场景定——比如你周五下班晚,常点外卖,那就填0。数据的一致性比绝对正确更重要。
  • Winter:按你当地气候习惯划分。我家以10月-3月为冬季,其他月份为夏季。如果你家四季如春,这列可删,后续代码里去掉即可。
  • Rating:务必让家人参与打分!孩子打分可能只有“好吃/不好吃”,但你要记录下来。长期看,孩子对“糖醋排骨”的高分和对“清炒菠菜”的低分,会成为未来过滤的关键信号。

实操心得:我建议用手机备忘录随时记。做饭时顺手拍张成品图,标注时间、天气、谁吃了、反应如何。周末花10分钟整理进Excel。坚持一个月,你就拥有了比90%家庭更系统的厨房知识库。

3.2 代码实现:Google Colab上的三步极简部署

原文用Google Colab,这是最友好的选择——无需装环境,免费GPU,界面像Jupyter Notebook。以下是精简后的可运行代码(已去除所有冗余步骤,仅保留核心):

# 第一步:导入必要库(复制粘贴即可) import pandas as pd from sklearn.neighbors import NearestNeighbors from sklearn.preprocessing import StandardScaler # 第二步:加载你的Excel数据(假设文件名为'dinner_data.xlsx') # 方法1:直接上传到Colab(点击左侧文件图标→上传) # 方法2:从Google Drive读取(需先挂载,代码见原文,此处略) df = pd.read_excel('dinner_data.xlsx') # 或 pd.read_csv('dinner_data.csv') # 第三步:构建特征矩阵(只用最关键的4个维度) features = ['PrepTime', 'CookingTime', 'Weekday', 'Winter'] X = df[features].copy() # 关键预处理:标准化(让不同量纲的特征公平竞争) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 训练最近邻模型 neigh = NearestNeighbors(n_neighbors=1, metric='euclidean') neigh.fit(X_scaled) # 第四步:定义推荐函数(改这里!输入你当前的情境) def recommend_dinner(prep_time, cook_time, is_weekday, is_winter): """根据当前情境推荐一道菜""" query = [[prep_time, cook_time, is_weekday, is_winter]] query_scaled = scaler.transform(query) # 必须用训练时的scaler转换! distance, index = neigh.kneighbors(query_scaled) rec_index = index[0][0] # 获取推荐菜的行号 rec_row = df.iloc[rec_index] print(f"✅ 推荐今晚做:{rec_row['Name']}") print(f"⏱ 准备{rec_row['PrepTime']}分钟 | 烹饪{rec_row['CookingTime']}分钟") print(f"📅 工作日:{'是' if rec_row['Weekday']==1 else '否'} | 冬季:{'是' if rec_row['Winter']==1 else '否'}") print(f"⭐ 家人评分:{rec_row['Rating']}/10") return rec_row['Name'] # 第五步:调用推荐(示例:周四晚上,准备≤15分钟,冬季) recommend_dinner(prep_time=15, cook_time=25, is_weekday=1, is_winter=1)

运行效果示例:

✅ 推荐今晚做:冬菇滑鸡饭 ⏱ 准备15分钟 | 烹饪25分钟 📅 工作日:是 | 冬季:是 ⭐ 家人评分:8.8/10

为什么必须标准化?
PrepTime范围是5-60分钟,Weekday只有0/1两个值。如果不标准化,欧氏距离会被PrepTime主导——Weekday的微小差异(0→1)在距离计算中几乎可以忽略。标准化后,所有特征对距离的贡献权重相等,这才是公平比较。

3.3 参数调优:让推荐更懂你的“隐形规则”

原文用n_neighbors=1,这是最保守的选择。但你可以根据家庭习惯微调:

场景推荐参数原因说明
新手期(数据<30条)n_neighbors=1避免噪声干扰,确保每次推荐都是最匹配的单一解
追求多样性(孩子挑食严重)n_neighbors=3返回3个选项,手动排除含过敏源的(如花生酱三明治)
周末想换花样n_neighbors=5+metric='manhattan'曼哈顿距离对极端值更鲁棒,适合周末时间充裕、愿意尝试稍复杂菜式

进阶技巧:加入“避雷”逻辑
在推荐函数末尾加一段过滤:

# 假设你家孩子对“胡萝卜”过敏,且数据库有'Allergens'列 if 'Carrot' in rec_row['Allergens']: print("⚠️ 警告:此菜含胡萝卜,已跳过") # 此处可写循环获取下一个邻居,或返回提示

实操心得:我第一次部署时,系统推荐了“凉拌黄瓜”(Prep=5min, Cook=0min, Weekday=1, Winter=0)。但那天是寒冬深夜,全家都想喝热汤。我立刻在数据里给这道菜加了Winter=0标签,并把CookingTime从0改为5(焯水时间)。系统不会思考,但你的每一次修正,都在教会它你的生活逻辑。

4. 常见问题与排查技巧实录:那些没写在论文里的坑

4.1 “推荐结果总是同一道菜!”——数据分布陷阱

现象:连续一周推荐“番茄炒蛋”,即使你输入了“周末+2小时+夏季”。
根因分析:查看你的df.describe(),大概率发现:

  • PrepTime均值=12,标准差=3 → 90%的菜准备时间集中在8-16分钟
  • CookingTime均值=25,标准差=5 → 大部分是快手菜
  • Winter列中,1的占比80% → 冬季数据远多于夏季

解决方案:

  1. 主动补缺:专门做3顿“夏季长耗时菜”(如煲仔饭、卤味),填平数据洼地。
  2. 加权采样:在训练前,对稀疏类别(如Winter=0的菜)做上采样(复制2遍),让模型重视它们。
  3. 情境分层:为周末/假期单独建模。代码中加判断:
    if is_weekday == 0: # 周末 X = df[df['Weekday']==0][features] else: X = df[df['Weekday']==1][features]

4.2 “明明冰箱里有牛肉,为什么不推荐红烧牛肉?”——特征缺失的代价

现象:输入Prep=20, Cook=90, Weekday=1, Winter=1,却推荐了“清蒸鱼”而非“红烧牛肉”。
排查路径:

  • 检查df[df['Name'].str.contains('牛肉')]['CookingTime']→ 发现数据库里所有牛肉菜CookingTime≥85,而你的查询点Cook=90虽接近,但PrepTime(20)远低于牛肉菜平均值(35)。
  • 模型优先匹配PrepTime,因为其数值范围大,标准化后权重更高。

根本对策:

  • 增加“食材可用性”特征:新增列HasBeef(1/0),并在特征矩阵中加入。下次输入时,同步传入HasBeef=1,模型会天然倾向推荐含牛肉的菜。
  • 用余弦相似度替代欧氏距离:余弦关注方向而非绝对值,对“时间长但食材匹配”的菜更友好。修改代码:
    neigh = NearestNeighbors(n_neighbors=1, metric='cosine')

4.3 “评分越来越高,但家人说越来越难吃!”——反馈闭环断裂

现象:评分列平均值从7.2升到8.9,但实际用餐体验下降。
真相:评分变成了“面子工程”。孩子为多吃一口冰淇淋给“糖醋排骨”打10分;老公为少洗碗给“速冻饺子”打9分。数据失真了。

重建信任的3个动作:

  1. 双轨评分制:除总分外,强制记录2个子项:
    • Kid_Happy(孩子是否主动吃光):1-5分
    • Adult_Effort(成人做饭疲惫感):1-5分(1=轻松,5=崩溃)
  2. 设置“衰减因子”:对超过3个月未做的菜,自动降低其推荐权重。代码中加:
    # 假设df有'Date'列(格式:2024-03-15) from datetime import datetime, timedelta df['Days_Ago'] = (datetime.now() - pd.to_datetime(df['Date'])).dt.days df['Weight'] = 1 / (1 + df['Days_Ago']/30) # 30天后权重减半
  3. 每月“废菜日”:随机选一道低分菜重做,用新照片、新评分覆盖旧记录。这比删除数据更能反映真实进化。

4.4 家庭版推荐系统常见问题速查表

问题现象可能原因排查命令(Python)解决方案
ValueError: Found array with 0 sample(s)Excel文件未正确加载,df为空print(df.shape); print(df.head())检查文件路径,确认Excel有数据且无合并单元格
推荐结果PrepTime远超输入值特征未标准化,PrepTime主导距离计算print(X_scaled[:3])查看标准化后数值必须用scaler.transform()处理查询点,不可直接用原始值
同一输入每次推荐不同菜n_neighbors>1且未设随机种子neigh = NearestNeighbors(n_neighbors=1, algorithm='brute')显式指定algorithm='brute',避免kd-tree的近似搜索
推荐菜名显示为NaNExcel列名与代码中df['Name']不一致print(df.columns.tolist())检查Excel表头是否含空格或特殊字符(如“菜名 ”),重命名为纯英文Name

注意:所有问题的核心,都不是代码错误,而是数据与现实的偏差。我曾花2小时调试,最后发现是Excel里把“冬至”误标为Winter=0。修复后,系统立刻推荐出“羊肉汤”。记住:在厨房里,数据质量永远大于算法复杂度。

5. 超越代码:让推荐系统长出“人味”的3个关键跃迁

5.1 从“推荐菜”到“推荐行动”:嵌入真实约束链

原文系统只输出菜名,但真正的厨房决策需要更细颗粒度的动作指引。我在实践中增加了三层上下文:

  • 库存联动:在推荐结果后,自动列出“你家冰箱里已有的食材”(需提前建pantry.csv表,含Ingredient, InStock, ExpiryDate)。代码逻辑:

    # 假设推荐菜是"番茄牛腩面",其食材列含"番茄,牛腩,面条" needed = ["番茄","牛腩","面条"] pantry = pd.read_csv('pantry.csv') available = pantry[pantry['Ingredient'].isin(needed) & pantry['InStock']==True]['Ingredient'].tolist() print(f"✅ 已有:{available} | ❌ 需采购:{list(set(needed)-set(available))}")
  • 时间缓冲带:不只报CookingTime=40min,而是拆解:

    “预计耗时:备菜15min(切番茄5min/切牛腩10min)+ 炖煮40min(前10min看火,后30min可处理其他事)+ 装盘5min。关键节点:第10分钟需开盖撇沫,第35分钟下面条。

  • 失败预案:为每道菜预设Plan B。例如“番茄牛腩面”的Plan B是“番茄鸡蛋面”(若牛腩没了,用鸡蛋替代,时间压缩至25min)。这源于我三次因食材短缺被迫改菜的教训——系统必须学会妥协。

5.2 用“反向推荐”倒逼流程优化:从被动响应到主动设计

我后来开发了“反向推荐”功能:输入“今晚不想切菜、不想开火、冰箱只剩鸡蛋和葱”,系统不推荐菜,而是问:

  • “是否接受30分钟内可完成的微波炉菜?(是/否)”
  • “能否接受外卖?预算上限?(50/80/120元)”
  • “孩子今日情绪:平静/暴躁/生病?”(影响口味偏好)

根据回答,系统生成行动清单而非菜名:

  1. 微波炉蒸蛋(5分钟)+ 葱油拌面(3分钟)
  2. 点XX餐厅的葱油拌面(免葱),备注“孩子专用无辣版”
  3. 泡杯蜂蜜水,先安抚孩子情绪

这本质上把推荐系统升级为家庭厨房OS——它不再回答“吃什么”,而是协调“人、食材、时间、情绪”四要素的实时状态。

5.3 终极形态:当系统开始“质疑”你的输入

最高阶的体验,是系统敢于指出你的不合理。比如你输入:
Prep=5, Cook=120, Weekday=1, Winter=1(工作日晚上,5分钟备菜,2小时炖煮)

系统会返回:

⚠️ 检测到矛盾:CookingTime=120minWeekday=1冲突。
依据:历史数据显示,工作日晚上CookingTime>60min的菜,Rating平均下降2.3分,且Adult_Effort≥4的概率达87%。
✅ 建议方案:

  • 方案A:改用高压锅,CookingTime降至35min(推荐“高压锅黄豆猪蹄汤”)
  • 方案B:预约明早慢炖,今晚做快手菜(推荐“虾仁滑蛋”)
  • 方案C:坚持原计划,但系统将自动推送《高效备菜checklist》和《防糊锅定时提醒》

这不再是工具,而是厨房里的资深搭档。它用你过去的数据,为你未来的决策兜底。

我在Amherst Survival Center见过真正的高手——Chef Alan面对400人的捐赠食材,3分钟内排出菜单。他没用代码,但他的大脑就是最精密的推荐引擎:记忆库存、预判损耗、平衡营养、适配口味。我们写的每一行代码,都是在向这种人类智慧致敬。而今晚,当你关掉屏幕,拿起刀切下第一片洋葱时,那个在Colab里跑通的recommend_dinner()函数,已经完成了它最本真的使命:把混沌的生活,翻译成可执行的下一步。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 5:28:15

Vim超级文档编辑器

vim 文件路径&#xff08;进入文件内部&#xff09;i 开始编辑Esc 退出编辑:wq! 保存并退出vimcat 文件路径 &#xff08;查看文件内容&#xff09;

作者头像 李华
网站建设 2026/7/14 5:26:53

服装管理数字化协同:如何提升童装品牌生产质检效率?

在2026年&#xff0c;国内童装行业保持着稳定的增长态势。根据相关行业白皮书显示&#xff0c;2025年国内童装市场规模已突破2800亿元&#xff0c;同比增长8.2%&#xff0c;增速高于成人装行业3个百分点。在激烈的市场竞争中&#xff0c;供应链效率已成为品牌的核心竞争力&…

作者头像 李华
网站建设 2026/7/14 5:26:42

CAN 总线实操笔记:拆解 8 字节电机控制报文,解析转速、停机指令

下面把图片里的十六进制报文换算成十进制,并整理成笔记。 一、原始报文换算表 原始十六进制报文 按字节换算成十进制 关键速度值 备注 D2 02 0D 48 00 00 40 00 210 2 13 72 0 0 64 0 0D48 = 3400 3400 转 D2 03 07 D0 00 00 40 00 210 3 7 208 0 0 64 0 07D0 = 2000 2000 转…

作者头像 李华
网站建设 2026/7/14 5:26:00

C++访问控制:public、private、protected的封装艺术与工程实践

1. 项目概述&#xff1a;为什么访问控制是C面向对象的基石在C的世界里&#xff0c;如果你写过几个类&#xff0c;用过几次public和private&#xff0c;可能会觉得访问控制不就是几个关键字&#xff0c;把数据藏起来、把接口暴露出来而已。但在我十多年的开发经历里&#xff0c;…

作者头像 李华
网站建设 2026/7/14 5:23:37

Ghidra实战CTF逆向:异或加密与小端存储破解指南

1. 项目概述&#xff1a;从理论到实战的逆向思维转变 很多刚接触CTF逆向的朋友&#xff0c;包括几年前的我自己&#xff0c;都容易陷入一个误区&#xff1a;把逆向工程当成一门纯理论学科来学。我们啃汇编指令集&#xff0c;背函数调用约定&#xff0c;研究各种加密算法&#…

作者头像 李华
网站建设 2026/7/14 5:23:10

测温如何做到既准又灵活?

计量校准、特气输送、精密仪器配套这类场景&#xff0c;对测温精度的要求几乎到了苛刻的程度。普通工业产线上0.5℃的温差或许可以忽略&#xff0c;但放到标准恒温槽检定、特种气体纯度管控环节&#xff0c;这点偏差足以让整批检测数据失效。那么&#xff0c;高精度温度变送器在…

作者头像 李华