1. 项目概述:当美食遇上大数据
去年帮学弟调试这个系统时,我们对着三万多条食物营养成分数据发愁——如何让算法理解"健康"这个抽象概念?传统推荐系统往往只考虑用户历史行为,但健康饮食需要突破信息茧房。这个基于Django和大数据平台的推荐系统,核心价值在于建立了"营养成分-身体状况-烹饪方式"的三维推荐模型。
典型用户场景是这样的:一位高血压患者登录系统,输入近期体检数据后,不仅能获得低钠食谱推荐,还会看到"为什么推荐荞麦面而非全麦面包"的详细解释。系统后端通过Spark计算食物成分矩阵,结合用户健康档案生成个性化推荐,前端用Django渲染带营养雷达图的交互界面。
2. 核心架构设计
2.1 技术栈选型对比
我们放弃了SpringBoot而选择Django,主要考虑三个因素:
- 数据科学工具链整合:Python生态的Pandas/NumPy与Spark的PySpark API无缝对接
- 快速原型开发:Django Admin半小时就能搭建出数据管理后台
- 地理围栏扩展性:将来接入LBS推荐时,GeoDjango提供开箱即用的GIS支持
mermaid复制graph TD
A[用户终端] --> B(Django Web层)
B --> C{推荐策略路由}
C -->|实时推荐| D[Spark MLlib]
C -->|冷启动| E[协同过滤]
F[MySQL] --> G[数据仓库]
H[Hadoop] --> G
G --> D
特别注意:生产环境建议将Spark替换为Flink以获得更好的实时性,但课程设计场景下Spark更易部署
2.2 数据库设计精要
营养成分表采用星型schema设计,核心表结构:
sql复制CREATE TABLE food (
id BIGINT PRIMARY KEY,
name VARCHAR(100) COLLATE utf8mb4_bin,
food_group ENUM('谷物','蔬菜','肉类') NOT NULL,
glycemic_index TINYINT UNSIGNED
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE nutrient (
food_id BIGINT,
calorie DECIMAL(6,2),
protein DECIMAL(6,2),
fat DECIMAL(6,2),
carbohydrate DECIMAL(6,2),
sodium DECIMAL(6,2),
FOREIGN KEY (food_id) REFERENCES food(id)
) PARTITION BY RANGE (food_id);
设计陷阱警示:
- 食物名称必须指定utf8mb4_bin排序规则,否则"菠菜"和"波菜"会被判为相同
- 营养成分表需要按food_id分区,超过50万条数据后查询性能差异可达10倍
3. 大数据处理实战
3.1 数据清洗流水线
我们从USDA食品数据库获取原始数据集,需要处理三个典型问题:
- 单位统一化:将"mg/100g"等23种单位统一转换为每千克含量
- 异常值处理:用中位数替代超出3σ的营养成分值
- 空缺值填补:对维生素等微量元素使用同类食物均值填补
python复制# 使用PySpark实现自动化清洗
from pyspark.sql.functions import when, median
df = spark.read.csv('food_data.csv', header=True)
median_values = df.approxQuantile(['protein','fat'], [0.5], 0.01)
df_clean = df.withColumn('protein',
when(df.protein > 3*median_values[0], median_values[0])
.otherwise(df.protein))
3.2 推荐算法实现
采用混合推荐策略解决冷启动问题:
- 基于内容的推荐:用TF-IDF计算食物描述文本相似度
- 协同过滤:使用ALS矩阵分解处理用户评分数据
- 健康规则引擎:硬编码医学营养学规则(如糖尿病患者限制GI>70的食物)
python复制# Django视图函数中的推荐逻辑
def recommend(request):
user_profile = UserHealth.objects.get(user=request.user)
# 实时Spark计算
rec_rdd = spark_context.parallelize([user_profile.__dict__])
results = food_model.predict(rec_rdd).collect()
# 本地规则过滤
filtered = [f for f in results
if not (user_profile.diabetes and f['gi'] > 70)]
return render(request, 'recommend.html', {'foods': filtered[:5]})
性能优化技巧:
- 对Spark DataFrame启用Catalyst优化:
spark.conf.set('spark.sql.optimizer.enabled', 'true') - Django层面使用
select_related预加载营养数据,减少N+1查询
4. 关键问题解决方案
4.1 营养成分数据冲突
不同来源的数据存在矛盾,比如:
- 中国食物成分表:菠菜含铁2.9mg/100g
- USDA数据库:Spinach含铁3.5mg/100g
我们的解决方案:
- 建立数据源权重体系(临床实验数据 > 政府数据 > 商业数据)
- 在前端展示数据来源标识
- 允许用户投票修正数据
4.2 实时性挑战
最初方案每小时批量计算推荐结果,但用户需要即时反馈。改进方案:
- 用Redis缓存常见用户画像的推荐结果
- 对新增用户采用轻量级规则引擎
- 使用Celery异步更新缓存
python复制# 改造后的推荐视图
@cache_page(60*15)
def recommend(request):
cache_key = f"rec_{request.user.id}"
data = cache.get(cache_key)
if not data:
data = _compute_recommendation(request.user)
cache.set(cache_key, data)
return JsonResponse(data)
5. 部署与监控
5.1 服务器配置建议
最低生产环境配置:
- 4核CPU/8GB内存(仅Django)
- 独立Spark集群:3节点/16GB内存每节点
- Redis缓存至少1GB内存空间
使用Supervisor管理进程:
ini复制[program:django]
command=/opt/venv/bin/gunicorn foodrec.wsgi -b 0.0.0.0:8000
user=www-data
autostart=true
5.2 监控指标
必须监控的四个关键指标:
- 推荐响应时间P99 < 500ms
- 缓存命中率 > 80%
- Spark任务积压数
- 用户修正数据比例(反映数据质量)
6. 扩展方向
6.1 图像识别扩展
通过CNN实现食物图像识别:
- 使用Food-101数据集预训练模型
- 迁移学习fine-tuning本地特色食物
- 输出层对接营养成分数据库
python复制# 使用Keras实现
base_model = VGG16(weights='imagenet', include_top=False)
x = base_model.output
x = GlobalAveragePooling2D()(x)
predictions = Dense(101, activation='softmax')(x)
model = Model(inputs=base_model.input, outputs=predictions)
6.2 社交功能设计
健康饮食需要社交激励:
- 食谱分享墙(注意隐私保护)
- 营养摄入排行榜
- 好友饮食习惯对比
数据库需新增:
sql复制CREATE TABLE user_meal (
user_id INT,
food_id INT,
eaten_at DATETIME,
satisfaction TINYINT,
FOREIGN KEY (user_id) REFERENCES auth_user(id),
FOREIGN KEY (food_id) REFERENCES food(id)
) ENGINE=InnoDB;
7. 避坑指南
-
中文分词问题:
- 错误做法:直接用jieba分词处理菜名
- 正确方案:构建餐饮领域词典(如"宫保鸡丁"应作为一个整体)
-
营养单位混淆:
- 易错点:国际单位(IU)与重量单位的换算
- 解决方案:建立单位换算中间层
-
Django ORM陷阱:
python复制# 错误写法:N+1查询问题 [food.name for food in Food.objects.all()] # 每次循环查询nutrient表 # 正确写法 Food.objects.select_related('nutrient').all() -
Spark性能优化:
- 避免使用collect()操作
- 合理设置partition数量(建议CPU核数x3)
这个项目最让我意外的是用户对"解释权"的需求——超过60%的用户会点击"为什么推荐这个",因此我们在第二版增加了推荐理由生成模块,使用预定义的规则模板结合用户特征动态生成解释文本。比如对高血压用户推荐芹菜时,会显示"芹菜中的芹菜素有助于扩张血管,且每100g仅含160mg钠"。
源码中特别值得关注的是recommendation_engine/rules.py文件,里面实现了基于临床指南的硬规则判断,这是确保推荐安全性的关键。而data_cleaning/pipeline.py则展示了如何处理现实世界中脏乱的营养数据。
