尧图精选

健康饮食推荐系统实战:从约束优化到混合推荐算法落地

🕒 发布时间:2026/9/8 8:58:30 📁 来源:尧图网络
我是一个做了不少推荐系统相关项目的开发者看到“人工智能算法智能饮食推荐系统”这类标题时第一反应不是兴奋而是“又一个坑”。因为饮食推荐和电影、商品推荐最大的区别在于它不是一个纯粹的“猜你喜欢”问题而是“在满足健康约束的前提下尽量让你吃得开心”的约束优化问题。用户可能喜欢吃炸鸡但系统不能因为他喜欢就一直推炸鸡这是饮食推荐和娱乐推荐本质上的分岔路。这个项目我从数据清洗到推荐算法落地再到把接口给前端调用完整走了一遍。源码和实现思路都整理在下面了。如果你正在做智能健康、营养管理、餐饮SaaS或者只是想找一个能写进简历的AI落地项目这个系统的设计思路和代码结构应该能给你不少参考。不管你是刚接触机器学习的小白还是想看看推荐系统在垂直领域怎么落地的老手这篇文章都值得你花几分钟读完——起码能帮你少踩几个我踩过的坑。1. 系统整体设计与核心思路拆解1.1 饮食推荐系统的本质从“猜你喜欢”到“约束满足”很多人一听到推荐系统脑子里浮现的就是协同过滤、矩阵分解、深度学习这些东西。但饮食推荐不是这么玩的。你可以想一想在视频平台上用户看了一个搞笑视频系统给他推一百个搞笑视频用户大概率不会生气但在饮食场景里系统给一个糖尿病患者推了含糖量高的甜品哪怕这个甜品是他以前点过的这也不是一次成功的推荐——轻则用户体验差重则是健康事故。所以我在设计这个系统时第一件事就是把问题重新定义了一遍。我把它拆成两层硬约束层过敏原、疾病禁忌、用户明确不吃的食材、热量上限这些是“不能触碰”的红线。推荐结果必须先经过这一层过滤。软偏好层用户平时喜欢什么口味、什么菜系、什么烹饪方式这些是“尽量满足”的加分项。在红线之内用算法去匹配用户的偏好。这套逻辑看起来简单但它是整个系统能不能用的基石。很多失败的健康类推荐项目问题都出在把“偏好匹配”和“健康约束”放在同一个模型里去学结果模型学出来的东西经常在边界情况下给出危险的推荐。我的做法是让算法在规则圈定的安全区里“跳舞”而不是让算法自己去学什么能吃什么不能吃。1.2 系统的信息架构与技术选型这个系统我给它的定位是一个可以独立部署、也可以嵌入到现有健康管理App中的服务。整体分为四个模块模块职责核心输入输出用户画像模块收集并结构化用户的基础信息、健康状态、饮食偏好输入注册信息和历史行为输出结构化画像食材知识库模块维护食材营养成分、过敏原、分类标签、适用人群标记输出标准化的食材/菜品特征向量推荐引擎模块基于画像和知识库规则融合多种算法生成推荐列表输出按“适合度”排序的食材/食谱列表解释与反馈模块为每条推荐生成理由收集用户反馈并回流到画像输出推荐理由文本接收点赞/踩/跳过信号技术栈方面我没有用特别重的东西。数据层用SQLite做本地存储方便演示和单机部署算法层用Python生态sklearn负责向量化和相似度计算pandas做数据清洗服务层用Flask暴露RESTful接口。为什么不上一套Hadoop/Spark那套重装备因为这个场景的数据量、并发量根本到不了那个级别用重型分布式架构反而会把项目的落地成本拉高好几倍对学习者和中小团队来说完全不划算。1.3 知识库与算法的双引擎设计为什么不用纯端到端模型我最想强调的设计决策是这个推荐结果不是某一个模型输出的而是“知识库规则 召回模型 排序模型”三段式流水线协同工作的结果。我见过不少同行一上来就想用深度学习模型端到端解决所有问题但在饮食推荐这个场景里端到端模型有两个致命弱点。第一训练数据稀缺且标注成本极高“这个菜对这位用户是否健康”这种标注需要营养师级别的专业判断不是普通用户随便点个赞就能标出来的第二可解释性几乎为零用户问“你为什么给我推这个”端到端模型给不出答案而饮食推荐偏偏需要极强的信任感。所以我采用混合架构召回阶段基于用户画像里的标签从食材知识库里用文本相似度把候选集从几千个缩小到几十个规则校验阶段用过敏原、疾病禁忌、营养上限这些硬规则把不安全的候选项全部砍掉排序阶段用一个轻量级的机器学习模型我用了随机森林综合“偏好匹配度、营养均衡度、多样性”打分排序解释阶段把打分过程中贡献最大的几个特征转译成用户能看懂的自然语言理由。每一步都有明确的目标和产物出了问题很容易定位。这比把一个黑盒模型丢上去要稳得多。2. 核心数据建模食材知识库与用户画像2.1 食材知识库像搭积木一样搭出“营养元数据”做饮食推荐系统没有一份靠谱的食材营养数据什么算法都是白搭。这一步我不能偷懒整理食材数据占了整个项目差不多三成的时间。我设计了一张核心表foods字段大概是这样的CREATE TABLE foods ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 食材名称 category TEXT NOT NULL, -- 类别主食/肉蛋/蔬菜/水果/奶制品/坚果... calories REAL NOT NULL, -- 每100克热量千卡 protein REAL NOT NULL, -- 每100克蛋白质克 fat REAL NOT NULL, -- 每100克脂肪克 carbs REAL NOT NULL, -- 每100克碳水克 sodium REAL NOT NULL, -- 每100克钠毫克 sugar REAL NOT NULL, -- 每100克糖克 tags TEXT NOT NULL, -- 标签列表用逗号分隔 allergy_info TEXT DEFAULT -- 过敏原标记如 花生,海鲜,乳制品,麸质 );tags字段是推荐算法主要的匹配依据。我会给每个食材打上维度丰富的标签比如鸡胸肉的tags是“高蛋白,低脂,健身,清淡,快手菜”三文鱼的tags是“深海鱼,富含Omega-3,优质脂肪,刺身,煎烤”。这个字段看起来不起眼但它是后面计算食材-用户相似度的基础我后面会专门讲。那“菜品”这一层怎么处理因为实际推荐给用户的往往不是单一食材而是一道菜。我的做法是增加一张recipes表每道菜由多个食材组成系统先算食材维度的推荐强度再聚合到菜品维度。菜品主要食材能量等级适合标签推荐优先级香煎鸡胸肉沙拉鸡胸肉、生菜、小番茄低卡减脂、健身、高蛋白高三文鱼牛油果饭三文鱼、牛油果、糙米中卡增肌、优质脂肪、健身高红烧肉五花肉、冰糖、酱油高卡本帮菜、下饭低2.2 用户画像不只是填个问卷这么简单用户画像模块负责把“人”建模成机器能理解的结构化数据。我用了一个users表加一个user_preferences表核心字段包括年龄、性别、身高、体重、活动水平用来算基础代谢率、饮食目标减脂/增肌/维持健康、疾病标签糖尿病/高血压/高血脂/痛风/无、过敏原列表、口味偏好清淡/辛辣/酸甜等。这里有一个细节很多人会忽略年龄和性别不只是档案属性它们直接参与热量需求的数学计算。我用了Mifflin-St Jeor公式估算基础代谢率BMR男性BMR 10 × 体重(kg) 6.25 × 身高(cm) - 5 × 年龄(岁) 5 女性BMR 10 × 体重(kg) 6.25 × 身高(cm) - 5 × 年龄(岁) - 161假设一位用户男性28岁身高175cm体重70kg轻体力活动。他的BMR是10×70 6.25×175 - 5×28 5 1663.75千卡。乘上活动系数1.375每日维持热量约2288千卡。如果目标是减脂我就设置热量红线为2288 × 0.85 ≈ 1945千卡。这个数值会作为硬约束用户画像每次更新时都会重新计算一次然后传到推荐引擎里去。在“口味偏好”这个软偏好上我的实现方法是把它转成一个五维向量比如“清淡、辛辣、酸甜、浓郁、鲜香”各有0到1的权重。这个向量会和食材的tags做匹配计算。这套设计的巧妙之处在于它把用户问卷里的模糊回答“我比较喜欢吃辣”转变成了可计算的量化特征“辛辣0.8”后面推荐引擎才能做数学运算。2.3 数据扩充与冷启动的数据基础很多项目的食材数据都只覆盖常见的几十种食材导致推荐结果翻来覆去就那几样用户很快失去兴趣。我在这套系统里内置了一份扩充后的数据集涵盖300多种常见食材和80多道菜谱覆盖中式、西式、日式、东南亚菜系的主流食材。数据来源主要是权威食物成分表的公开数据菜品结构则来自菜谱网站的结构化整理。扩充数据时我踩了一个坑有些食材在不同菜系里的营养数据差异很大。比如“豆腐”有嫩豆腐、老豆腐、豆腐干之分热量和蛋白质能差两倍。如果用同一个数据代表所有豆腐那推荐出来的热量估算就会失真。我的解决办法是在foods表里把这类食材按加工方式拆开记录一道菜里实际用哪种豆腐就引用哪条记录。这属于数据治理层面的经验后面做营养分析时才会体会到它的重要性。3. 推荐算法实现细节与参数调优3.1 基于标签的召回怎么把几千种食材缩小到几十个候选召回阶段是整个推荐管线的第一关。我的目标是在保证“不遗漏可能合适的选项”的前提下尽量缩小候选集规模让后面的排序模型处理起来更轻快。我的做法是把用户画像和食材的标签都转成文本向量然后计算余弦相似度。具体实现不复杂核心代码如下from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import pandas as pd def build_user_query(profile): 把用户画像转成一个用于检索的文本串。 profile是一个dict包含偏好标签、目标、禁忌对应的反向标签等。 preference_tags profile[preference_tags] # 口味偏好 goal_tags profile[goal_tags] # 减脂/增肌对应的标签 positive_tags preference_tags goal_tags return .join(positive_tags) def recall_by_tags(foods_df, user_query, top_k50): 基于TF-IDF向量召回Top K候选食材。 foods_df[tags]是每个食材的标签文本串。 vectorizer TfidfVectorizer(token_patternr[^\s,]) # 把用户query和所有食材的tags放在一起做向量化 corpus [user_query] foods_df[tags].tolist() tfidf_matrix vectorizer.fit_transform(corpus) user_vec tfidf_matrix[0] food_vecs tfidf_matrix[1:] # 计算相似度 sim_scores cosine_similarity(user_vec, food_vecs).flatten() # 取分数最高的top_k个食材索引 top_indices sim_scores.argsort()[::-1][:top_k] return foods_df.iloc[top_indices].copy(), sim_scores[top_indices]我一开始写得更简单直接用词频统计做向量但效果不太好因为有些高频标签比如“家常”会在大部分食材里出现导致区分度低。换成TF-IDF之后这类高频弱区分度标签的权重被自动降下来了相似度匹配明显更靠谱。这里还藏着一个关于标签组合的小细节我把“过敏原对应的禁忌标签”直接用“反向召回”来处理。怎么理解呢比如用户对花生过敏那么在召回阶段就把带有“花生”标签的食材从语料中临时移除而不是等召回后再过滤。提前排除的好处是效率更高而且能避免相似度计算时给候选集带来无形的偏移。当然这只是第一道保险后面规则校验阶段还会再过一遍。3.2 规则校验把“安全”变成一道不可越过的闸门召回阶段之后候选集还是“可能合适”下一步就必须用规则校验把所有不安全的选项筛掉。这一步不靠机器学习就是用确定性的代码把硬约束逐条落地。我写了这样一个规则校验函数def apply_safety_filters(candidates_df, profile, nutrition_limits): 对候选食材执行安全过滤。 返回通过所有规则的候选DataFrame以及过滤原因列表。 safe_df candidates_df.copy() rejection_reasons {} # 规则1排除过敏原相关食材 allergies set(profile[allergies]) for allergen in allergies: mask ~safe_df[allergy_info].str.contains(allergen, naFalse) rejected safe_df[~mask][name].tolist() if rejected: rejection_reasons[f含过敏原:{allergen}] rejected safe_df safe_df[mask] # 规则2排除疾病禁忌食材 disease_restrictions { 糖尿病: [高糖, 高GI], 高血压: [高钠, 腌制], 高血脂: [高脂肪, 油炸], 痛风: [高嘌呤, 海鲜, 动物内脏] } for disease in profile[diseases]: for tag in disease_restrictions.get(disease, []): mask ~safe_df[tags].str.contains(tag, naFalse) rejected safe_df[~mask][name].tolist() if rejected: rejection_reasons[f{disease}禁忌:{tag}] rejected safe_df safe_df[mask] # 规则3估算每道菜的总热量并执行热量上限约束 # 这里按每道菜可食部分120克估算实际可按菜谱精确计算 safe_df[est_calories] safe_df[calories] * 1.2 over_calorie_mask safe_df[est_calories] nutrition_limits[max_calories_per_meal] rejected_high_cal safe_df[~over_calorie_mask][name].tolist() if rejected_high_cal: rejection_reasons[超过单餐热量上限] rejected_high_cal safe_df safe_df[over_calorie_mask] return safe_df, rejection_reasons补充说明一下规则不是一劳永逸写死了就行的。比如“糖尿病”患者要限糖但“糖”在营养学里有“添加糖”和“天然果糖”的区别一根香蕉和一杯奶茶对血糖的影响完全是两回事。所以我在疾病禁忌标签里做了细分糖尿病约束主要针对“高糖”和“高GI”而不是对所有含糖的都一刀切。这个细节决定了系统会不会被用户吐槽“连水果都不让我吃”。3.3 排序模型随机森林如何融合多维度特征通过安全过滤的候选集已经可以推荐了但如果单纯靠标签相似度排序结果会缺少“多样性”和“营养均衡性”——极端情况下可能推荐五道口味几乎一样的菜。所以排序阶段我训练了一个轻量级的随机森林模型把“偏好匹配度、营养均衡度、多样性、用户历史反馈”融合成一个综合得分。特征工程是这里的关键。我给每个候选食材生成以下特征向量特征名含义计算方式pref_score偏好匹配度食材tags与用户偏好向量的余弦相似度protein_density蛋白质密度100克蛋白质 / 100克热量fat_ratio脂肪供能比脂肪热量 / 总热量carb_density碳水密度碳水克数 / 100克fiber_score膳食纤维得分富含膳食纤维记为1否则为0variety_penalty多样性惩罚与该用户已选/已推荐食材的相似度hist_like_rate历史好评率用户对该类食材历史正向反馈比例这些特征里pref_score负责“像不像用户爱吃的”protein_density和fat_ratio负责“营养结构合不合理”variety_penalty负责“这次别老推荐同类食材”hist_like_rate则把用户的行为反馈融进来。模型训练我并没有一上来就追求高精度因为饮食推荐的“真实反馈数据”很难大批量获取。我采用的是离线构造训练样本 线上A/B修正的策略。先按规则给一批合成样本打标规则认为合适的标1不合适的标0训练一个baseline模型上线后通过用户对推荐结果的“采纳/忽略/踩”信号做增量微调。随机森林的好处是特征重要度一目了然我能清楚看到哪些特征在生效哪些特征只是摆设。实测下来pref_score和protein_density的贡献度最高variety_penalty在候选集较大的时候也很重要。3.4 协同过滤的补充作用当行为数据多起来之后系统运行一段时间后就会积累“用户-食材(菜品)”的隐式反馈矩阵比如用户浏览了哪些食材、点了哪些菜品、忽略过哪些推荐。这时候可以用协同过滤做一些补充推荐。我用的是User-Based协同过滤的简化版计算当前用户与其它用户的画像相似度找出一批“口味邻居”从邻居们吃过且评分高的食材里找出当前用户还没见过的推荐出来。这样做最大的价值是“惊喜度”能把用户自己都没意识到可能会喜欢的食材带进候选集。但协同过滤在这个领域有一个绕不开的问题数据稀疏。尤其是新菜品刚上架时几乎没有人评价过它协同过滤完全失效。我的解决方法是把协同过滤的召回结果和标签召回的候选集做并集然后统一送回排序模型打分而不是让协同过滤直接决定最终顺序。这样既保留惊喜度又不至于因为稀疏数据导致排序崩坏。4. 工程落地从离线数据到线上接口4.1 特征工程与样本构造的实操细节如果你只是跑通demo那数据预处理可以很随意但要做成一个能长期迭代的系统特征工程必须规范。我的处理流程是数值特征标准化热量、蛋白质、脂肪这些数值字段量纲差异很大直接喂给模型会导致量级大的特征主导结果。我用StandardScaler做Z-score标准化让每个数值特征都在均值0、方差1附近波动。分类标签One-Hot化食材类别主食/蔬菜/肉蛋/奶类转成One-Hot编码避免模型给类别隐式排序。文本标签向量化tags字段用TF-IDF向量化这一步我在召回阶段已经算过一次排序阶段直接复用向量结果。交叉特征比如“蛋白质密度 × 健身目标标记”这类交叉特征能让模型捕捉到“健身的人应该多吃高蛋白食物”这类组合规则单纯依赖原始特征模型学不到这种组合逻辑。样本构造方面我用了“正负样本对比法”从安全过滤后的候选池里按规则认为“很合适”的标记为正样本label1“不推荐”的标记为负样本label0。负样本不能只选那些“看着就离谱”的因为那样模型学不到边界。我会刻意加入一些“看似合理但营养结构不佳”的样本作为难负例让模型学会分辨边界情况。这一步对最终推荐质量的提升非常明显。4.2 推荐主流程把模块串成一条流水线整个推荐服务的主流程可以用下面这段伪代码表达def recommend(user_id, top_n10): # 1. 加载/构建用户画像 profile UserProfileService.get_profile(user_id) # 2. 计算用户每日营养限额 limits NutritionCalculator.compute_daily_limits(profile) # 3. 召回候选食材 candidates recall_by_tags(food_repository.get_all(), profile, top_k80) # 4. 安全规则过滤硬约束 safe_candidates, reasons apply_safety_filters(candidates, profile, limits) # 5. 特征工程 feature_df FeatureEngineer.transform(safe_candidates, profile, user_history) # 6. 排序模型打分 feature_df[score] ranking_model.predict_proba(feature_df)[:, 1] # 7. 多样性重排如果需要按类别/口味做MMR式重排 reranked diverse_rerank(feature_df, top_n) # 8. 生成解释 result generate_recommendation_response(reranked, reasons, profile) return result第4步和第6步之间有一个容易出问题的地方安全过滤后候选集如果不足所需数量怎么办比如用户同时有“糖尿病高血压花生过敏海鲜过敏”四个约束叠加后候选食材可能只剩下20多个直接排序推荐出来的结果会非常单调。此时我的兜底策略是放宽“软偏好”的权重优先保证候选集规模——宁可推荐用户没那么喜欢的健康食材也不能因为候选太少而把带风险的食物放进来。4.3 Flask接口设计让算法服务可以被调用算法做得再好最后也要通过接口交付给上层应用。我用Flask包装了三个核心接口from flask import Flask, request, jsonify app Flask(__name__) app.route(/api/v1/recommend, methods[POST]) def recommend_meals(): 请求体: {user_id: 123, meal_type: lunch, top_n: 10} 返回: 推荐食材/菜品列表 推荐理由 营养预估 data request.get_json() user_id data.get(user_id) meal_type data.get(meal_type, lunch) top_n data.get(top_n, 10) result recommendation_service.recommend(user_id, top_ntop_n, meal_typemeal_type) return jsonify({code: 0, data: result}) app.route(/api/v1/feedback, methods[POST]) def collect_feedback(): 请求体: {user_id: 123, food_id: 45, action: like|dislike|skip} 用途: 收集用户反馈用于后续模型微调和画像更新 data request.get_json() feedback_service.record_feedback(data) return jsonify({code: 0, msg: ok}) app.route(/api/v1/user/profile, methods[POST]) def update_profile(): 更新用户画像字段如新增疾病标签、过敏原、偏好变化等。 data request.get_json() user_service.update_profile(data[user_id], data[profile]) return jsonify({code: 0, msg: ok})接口设计上我特别注意了返回结构。每条推荐结果里除了食材ID、名称、预估热量还带了一个reason字段。这个字段是“推荐理由”比如“因您目标为减脂选择了高蛋白低脂的鸡胸肉因您忌海鲜已为您过滤三文鱼”。为什么坚持做这一步因为用户在健康饮食场景下对推荐结果有很强的“质疑心理”如果系统只说“猜你喜欢”而不说“为什么合适”用户很容易不信任结果最终弃用。5. 踩坑记录与常见问题排查5.1 冷启动问题新用户和新食材该怎么推冷启动在饮食推荐里比其他领域更棘手因为新用户可能一个行为数据都没有只有注册时填的问卷信息。我的策略分三层基于规则的冷启动用户填了过敏原和疾病标签后先用规则过滤掉所有不安全的食材再按健康目标推荐模板菜谱。比如目标是减脂就推“香煎鸡胸肉沙拉、清蒸鲈鱼、凉拌黄瓜”这类标准化组合。虽然缺少个性化但安全且合理。基于画像相似度的冷启动计算新用户与老用户的画像相似度把老用户中高满意度的推荐结果作为新用户的候选。这其实是一种“迁移”对于只有问卷数据的新用户来说效果立竿见影。探索式推荐在每天的推荐列表里固定留出10%的坑位给“新食材”或“小众食材”哪怕用户画像显示他对这类食材匹配度不高。这是为了积累行为数据避免马太效应导致热门食材永远霸榜、冷门健康食材永远出不了头。新食材的冷启动更依赖内容特征一道新菜上线没有用户行为数据我就把它的标签相似度作为唯一依据先让它在“适合”的用户面前小流量展示等积累几十次曝光和点击反馈后再参与协同过滤的候选生成。5.2 数据稀疏与噪声如何保证推荐这碗水端平数据稀疏是推荐系统的通病在垂直领域更明显。我的几个心得噪声处理。用户的行为反馈并不总是可靠的。比如用户因为手滑点了一道“红烧肉”并不代表他喜欢或者适合。我处理的方法是给负反馈更高的权重——“踩”和“跳过”比“点赞”更能反映真实态度。同时只采纳多次一致的行为信号单次行为打五折计入画像更新。覆盖度监控。我每天会跑一个数据报表统计推荐列表里食材类别的覆盖情况。如果连续一周“蔬菜类”的推荐占比超过70%说明排序模型可能对高纤维特征过度加权了这时需要调整特征权重或者强化多样性重排。类别不均衡。食材数据里“主食类”和“蔬菜类”明显多于“菌菇类”和“海产类”如果直接拿原始数据训练小众类别会被模型忽略。我用类别权重来平衡损失函数让模型对少样本类别多付出一分注意力。实测这个操作能明显提升菌菇、海产、内脏类食材在推荐结果中的曝光率。5.3 常见问题速查表问题现象排查思路解决方案推荐结果里反复出现同一类食材多样性重排未生效或候选集本身太小开启MMR式重排或扩充食材知识库热量超标警告频繁触发规则校验的估算热量与实际菜谱热量不一致细化菜品记录按实际份量而非统一120克估算用户反馈“这个我不能吃”过敏原/疾病禁忌标签不完整或映射错误检查allergy_info字段建立“食材-替代食材”映射表新用户推荐结果与老用户完全一样冷启动阶段规则过于固定个性化不足加入画像相似度冷启动按基础代谢率差异调整推荐模型特征重要度不符合预期特征工程阶段可能有泄露或量纲问题检查特征标准化、交叉特征是否正确构造菜品标签与用户偏好匹配度高但用户不点标签可能没有覆盖“烹饪方式”等隐性偏好增加烹饪方式、菜系等维度的标签标注最值得注意的一个坑是规则过滤与模型排序的顺序。我曾经为了“让模型自己学会避开高风险食材”把过敏原信息也作为特征丢给模型结果模型在训练数据里把“花生”学成了“高风险特征”但如果是没见过的坚果类过敏原模型根本没能力泛化。最后还是老老实实退回“先规则后模型”的两段式结构。有些安全底线不应该让模型自己摸索规则就是规则。6. 源码结构说明与后续扩展方向6.1 项目源码的目录组织源码我按模块拆分每个模块都有清晰的职责边界方便二次开发smart-diet-recommendation/ ├── README.md ├── requirements.txt ├── data/ │ ├── foods.csv # 食材基础数据 │ ├── recipes.csv # 菜品-食材关联数据 │ └── users.csv # 用户画像示例数据 ├── src/ │ ├── model/ │ │ ├── recall.py # 标签召回 │ │ ├── safety_rules.py # 安全规则校验 │ │ ├── ranking.py # 排序模型随机森林 │ │ └── collaborative.py # 协同过滤补充召回 │ ├── service/ │ │ ├── user_profile.py # 用户画像管理与更新 │ │ ├── nutrition_limit.py # 营养限额计算基于BMR │ │ └── recommendation.py # 推荐主流程调度 │ ├── api/ │ │ └── app.py # Flask接口层 │ └── utils/ │ ├── feature_engineer.py │ └── diversity.py # 多样性重排 ├── tests/ │ ├── test_safety_rules.py │ ├── test_recommend_flow.py │ └── test_bmr_calculator.py └── scripts/ ├── train_ranking_model.py └── init_data.py这套结构我尽量保持了“可替换”的灵活性。如果后续你不想用随机森林想换成LightGBM或者XGBoost只需要替换ranking.py里的模型加载部分就行上游的召回、规则校验、特征工程都不用动。6.2 扩展方向从“推荐食材”升级到“完整餐单规划”目前这套系统能做到“单餐推荐”但真正的智能饮食管理下一步必然是“整日餐单规划”——早餐、午餐、晚餐、加餐四餐之间的营养搭配要互相平衡。比如早餐吃了高碳水午餐就应该适当补蛋白质全天蛋白质分配要均匀不能全堆在晚餐。做这层扩展的思路其实也不复杂把“单餐推荐”作为备选池然后对四餐的整体营养做约束优化。可以用贪心算法逐餐填充也可以用线性规划求解“在总热量、蛋白质总量、脂肪供能比约束下选出最优的四餐组合”。我建议先上贪心加规则校验效果已经足够线性规划反而会让系统状态变得很难解释。另外一个值得做的是给推荐结果挂接“替代食材”关系用户说“我不爱吃鸡胸肉”系统能推荐“火鸡胸肉”或“去皮鸡腿肉”作为营养成分相近的替代品。这需要建立一张food_substitutes表按营养成分相似度预先计算好替代关系。这个功能对用户体验的提升是质的飞跃推荐系统第一次展现出“懂我”的体贴感。6.3 部署与性能调优一个轻量系统的上线心得这个系统我最终部署在一台2核4G的云服务器上用Gunicorn跑Flask应用没有上Kubernetes那套重型容器编排。为什么这么轻因为这个系统的算法计算流程里最重的是TF-IDF向量化和余弦相似度计算食材知识库最多几千条记录在内存里直接完成计算完全没压力。我把食材数据和向量缓存到本地避免每次请求都重新加载模型和向量单次推荐接口的响应时间能压在150毫秒以内。如果你预测未来用户量会涨瓶颈大概率不在推荐算法本身而在反馈日志存储和画像更新的并发写。这时再把日志接入消息队列把画像更新做成异步任务用上Redis缓存热用户画像就能平滑扩展。小项目先用简单方案扛住真到扛不住再上重型组件这是我反复验证过的务实路径。写在最后的一些心里话整个项目做下来我最大的体会是在饮食推荐这样涉及健康安全的场景里算法能力只占三分之一剩下的三分之二是数据质量和产品逻辑。没有干净的食材营养数据再强的模型也是空中楼阁没有规则兜底再聪明的AI也可能闯祸。我们做技术的人尤其是做推荐和决策类系统的一定要多想一层“我的推荐结果如果被用户当真了会有什么后果”。这一点比任何模型指标都重要。如果你正在动手做类似的项目我建议从食材知识库和规则引擎入手先把底座打牢再逐步加入机器学习的玩法。方向上可以从“单餐推荐”扩展到“餐单规划”也可以试试引入大模型做更自然的推荐理由生成。源码里的基础代码可以直接拿去用但更重要的是把我踩过的那些坑避开让你能花更少时间搞定推荐花更多心思打磨产品和营养专业度。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →