1. 项目概述
作为一名在校园信息化领域摸爬滚打多年的开发者,我深知每到饭点学生们面对十几个食堂窗口时的选择困难。去年为某高校开发的这套美食推荐系统,上线三个月后用户复购率提升了37%,今天就来完整拆解这个基于K-means算法的Django项目。
这个系统的核心价值在于:通过分析校园一卡通消费记录和用户主动评分,自动识别出"无辣不欢族"、"轻食主义者"等用户群体,再结合菜品特征聚类,实现千人千面的美食推荐。不同于市面上通用的推荐算法,我们针对校园餐饮场景做了大量优化,比如:
- 考虑用餐时间段(早/中/晚餐推荐不同品类)
- 结合地理位置(优先推荐距离最近的食堂)
- 动态调整预算(月末自动推荐平价菜品)
2. 系统架构设计
2.1 技术栈选型考量
选择Django框架主要基于三个实际需求:
- 快速迭代:高校需求变更频繁,Django的MTV架构和Admin后台能快速响应
- 数据安全:自带CSRF防护、XSS过滤等安全机制,适合处理校园敏感数据
- ORM支持:方便对接不同数据库,我们最终选用MySQL 5.7是因为学校信息中心已有现成集群
机器学习部分采用scikit-learn而非TensorFlow,原因很实在:
- 校园场景数据量有限(日均约2万条记录)
- K-means等传统算法完全够用
- 便于部署在学校的老旧服务器上
2.2 数据流设计
系统处理数据的完整流程如下:
python复制# 伪代码展示核心流程
def daily_pipeline():
raw_data = get_from_card_system() # 从一卡通系统获取原始数据
cleaned_data = preprocess(raw_data) # 数据清洗
user_features = extract_user_features(cleaned_data) # 用户特征工程
dish_features = extract_dish_features() # 菜品特征提取
# 每日凌晨执行聚类
user_clusters = KMeans(n_clusters=8).fit(user_features)
dish_clusters = KMeans(n_clusters=12).fit(dish_features)
update_recommendation_engine(user_clusters, dish_clusters) # 更新推荐模型
关键细节:数据预处理时特别处理了"套餐拆分"问题。比如学生刷了15元的套餐A,我们需要通过食堂菜单将其拆解为:米饭(2元)+红烧肉(8元)+青菜(5元)
3. 核心算法实现
3.1 特征工程实战
用户特征向量包含7个维度:
- 消费金额均值(标准化后)
- 辣味偏好指数(根据川湘菜消费频次计算)
- 早餐消费频率
- 素食倾向比例
- 价格敏感度(方差计算)
- 新品尝试意愿(新菜品首次购买时间差)
- 时段偏好(早/中/晚餐消费占比)
菜品特征则包括:
python复制{
"price_level": 2, # 1-5档
"spicy_level": 0.7, # 0-1
"is_vegetarian": False,
"cooking_method": ["fried", "stewed"], # 烹饪方式多选
"nutrition": {"protein": 0.3, "fat": 0.2}, # 营养成分
"seasonal": True # 是否时令菜
}
3.2 K-means调优过程
确定最佳聚类数时,我们对比了三种方法:
- 肘部法则:SSE下降拐点出现在K=8(用户)和K=12(菜品)
- 轮廓系数:验证了上述K值的合理性
- 业务验证:邀请20名学生试吃确认分类合理性
最终参数配置:
python复制KMeans(
n_clusters=8, # 用户分8类
init='k-means++', # 优化初始中心点选择
max_iter=300, # 实测200代已收敛
random_state=42, # 固定随机种子便于调试
n_init=10 # 避免局部最优
)
4. 推荐策略融合
4.1 混合推荐机制
系统采用三层推荐策略:
-
初筛层(基于规则):
- 排除过敏食材
- 过滤非营业时段
- 按地理位置排序
-
召回层(算法推荐):
mermaid复制graph LR A[用户聚类] --> B[协同过滤推荐] C[菜品聚类] --> D[内容推荐] B --> E[混合排序] D --> E -
排序层(业务加权):
- 食堂补贴菜品加权30%
- 时令菜加权20%
- 高评分菜品加权15%
4.2 实时推荐优化
针对高峰时段特别设计:
python复制def get_realtime_recommendations(user, time_window):
# 动态调整特征权重
if time_window == 'breakfast':
weights = {'speed': 0.6, 'price': 0.3, 'taste': 0.1}
elif time_window == 'lunch':
weights = {'taste': 0.5, 'wait_time': 0.3, 'nutrition': 0.2}
return sorted_recommendations(user, weights)
5. 工程实现细节
5.1 Django模型设计
核心模型关系如下:
python复制class UserProfile(models.Model):
card_id = models.CharField(max_length=20) # 学号/工号
taste_preference = JSONField() # 口味偏好向量
cluster_id = models.IntegerField() # 所属用户群ID
class Dish(models.Model):
canteen = models.ForeignKey(Canteen)
features = JSONField() # 菜品特征向量
cluster_id = models.IntegerField()
class ConsumptionRecord(models.Model):
user = models.ForeignKey(UserProfile)
dish = models.ForeignKey(Dish)
rating = models.FloatField(null=True)
timestamp = models.DateTimeField()
5.2 性能优化技巧
-
缓存策略:
- 用户聚类结果缓存24小时
- 热门推荐列表缓存5分钟
- 使用Django的cache_page装饰器
-
批量处理:
python复制# 坏实践
for record in records:
process(record)
# 好实践
batch_size = 500
for i in range(0, len(records), batch_size):
bulk_process(records[i:i+batch_size])
- 数据库索引:
- 为user_id + timestamp建立联合索引
- 菜品表添加cluster_id索引
6. 踩坑实录
6.1 数据质量问题
冷启动问题:
- 初期只有30%用户主动评分
- 解决方案:用消费频次隐式反馈(复购=好评)
菜单变动问题:
- 食堂每周更换20%菜品
- 解决方案:建立菜品相似度匹配库
6.2 算法陷阱
K-means的局限性:
- 对异常值敏感:大额团体消费会干扰聚类
- 改进方法:用DBSCAN检测并剔除异常点
特征漂移问题:
- 学生毕业导致用户群体变化
- 解决方案:季度性重新训练模型
7. 效果验证
上线后关键指标变化:
| 指标 | 上线前 | 上线3个月后 |
|---|---|---|
| 平均决策时间 | 4.2min | 1.8min |
| 窗口排队长度 | 8.3人 | 5.1人 |
| 剩餐率 | 22% | 14% |
| 用户满意度 | 68分 | 83分 |
典型用户反馈:
"系统推荐的川味窗口比我自己找的还地道"
"月底自动推荐便宜套餐很贴心"
8. 扩展方向
-
社交化推荐:
- 加入"好友常吃"维度
- 社团聚餐推荐
-
健康干预:
- 营养均衡提醒
- 饮食分析报告
-
供应链优化:
- 预测菜品需求量
- 减少食材浪费
这套系统给我最深的体会是:校园场景的算法落地,不能只追求技术先进,更要理解食堂大妈的工作流程和学生真实的干饭需求。下次我会尝试加入排队时间预测功能,这需要接入食堂监控视频流做实时人数统计...