1. 项目概述:当美食遇上AI
每次打开美食推荐APP,总会被千篇一律的"网红店"推荐刷屏,这让我萌生了开发个性化推荐系统的想法。这个基于Python和深度学习的携程美食数据推荐系统,核心目标是通过分析用户历史行为数据,为每位食客打造专属的美食地图。不同于传统基于评分或距离的推荐,我们采用协同过滤与内容嵌入相结合的混合模型,准确率在实际测试中达到87.6%,比携程原生推荐高出12个百分点。
系统架构分为三个关键层:数据采集层通过Scrapy爬虫获取携程公开的餐厅信息(包括评分、菜系、人均消费等18个维度);特征工程层使用BERT处理用户评论生成情感向量;推荐引擎则采用改进的NeuMF神经网络模型。最让我惊喜的是,通过引入时间衰减因子,系统能自动识别用户口味的变化趋势——比如检测到用户近期频繁搜索"轻食"后,会动态调整推荐权重。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术解析
2.1 数据采集与清洗
爬虫部分采用Scrapy-Redis分布式架构,关键突破在于解决了携程动态加载的反爬机制。我们通过分析XHR请求,发现美食数据实际通过接口返回,其中最重要的两个接口是:
/api/restaurant/list获取餐厅基础信息/api/review/list获取用户评论
python复制# 示例:处理动态加载的Ajax请求
def parse_restaurant(self, response):
data = json.loads(response.text)
for item in data['result']['items']:
yield {
'id': item['id'],
'name': item['name'],
'cuisine': '|'.join(item['tagList']),
'avg_price': item['price'],
'geo_hash': item['geohash'][:6] # 用于地理位置聚类
}
# 构造评论页请求
review_url = f"https://ctrip.com/api/review/list?resId={item['id']}"
yield Request(review_url, callback=self.parse_review)
数据清洗时遇到三个典型问题:
- 人均价格存在"¥100-150"这样的区间值 → 取中位数处理
- 用户评论包含大量emoji → 转换为文本描述(如[笑脸])
- 重复店铺因分店导致 → 通过名称相似度+地理距离去重
重要提示:爬取频率需控制在15秒/请求以上,并设置随机User-Agent,实测触发反爬的阈值是连续10次相同UA的请求
2.2 特征工程构建
特征维度设计直接影响模型效果,我们最终确定了5大类特征:
| 特征类型 | 处理方式 | 维度数 |
|---|---|---|
| 用户画像 | One-Hot编码(年龄/性别/职业) | 15 |
| 餐厅属性 | 菜系标签+价格分段 | 8 |
| 时空特征 | 工作日/周末+早中晚餐时段 | 6 |
| 评论情感 | BERT-base提取768维向量 | 768 |
| 行为序列 | 最近20次点击的餐厅ID序列 | 20 |
其中评论情感分析采用蒸馏后的BERT模型,在美食领域准确率达到91.2%:
python复制from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('food-bert-zh')
def get_comment_embedding(text):
inputs = tokenizer(text, return_tensors="pt", max_length=128, truncation=True)
outputs = model(**inputs)
return outputs.last_hidden_state.mean(dim=1) # 池化操作
2.3 混合推荐模型
采用神经矩阵分解(NeuMF)框架改进的模型结构包含三个关键组件:
-
矩阵分解分支:处理用户-餐厅交互矩阵
- 用户嵌入层:64维
- 餐厅嵌入层:64维
- 使用LeakyReLU(α=0.2)激活
-
深度神经网络分支:处理各类特征
- 全连接层结构:256 → 128 → 64
- BatchNorm层+Dropout(0.3)
-
注意力机制:对历史行为序列加权
- 计算公式:$Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V$
模型训练采用两阶段策略:
- 第一阶段:仅用显式评分数据预训练MF分支
- 第二阶段:联合训练全网络,损失函数为:
$$L = \sum_{(u,i)\in D} (y_{ui} - \hat{y}_{ui})^2 + \lambda||\Theta||^2$$
在GTX 1080Ti上的训练耗时约3小时/epoch,早停策略设置为连续5个epoch验证集loss不下降。
3. 系统实现细节
3.1 实时推荐流程
线上推理采用TF Serving部署,平均响应时间控制在120ms内:
- 用户发起请求 → 2. 特征服务获取实时特征 → 3. 模型服务返回Top100候选 → 4. 业务规则过滤(如距离筛选)→ 5. 多样性采样 → 6. 返回Top10结果
多样性采样算法值得特别说明:
python复制def diversity_sample(items, k=10):
""" 保证推荐结果覆盖不同菜系 """
clusters = defaultdict(list)
for item in items:
clusters[item['cuisine']].append(item)
result = []
while len(result) < k:
for cuisine in clusters:
if clusters[cuisine] and random.random() < 0.7: # 70%概率选取该菜系
result.append(clusters[cuisine].pop(0))
return result[:k]
3.2 冷启动解决方案
针对新用户采用三级降级策略:
- 首先尝试基于注册信息的推荐(如选择过"喜欢川菜")
- 其次使用热门餐厅的地理围栏推荐
- 最后展示基于协同过滤的相似用户偏好
新餐厅则通过内容相似度计算:
$$sim(i,j) = \cos(\text{TF-IDF}(desc_i), \text{TF-IDF}(desc_j))$$
3.3 效果评估指标
离线测试结果:
| 指标 | 本系统 | 携程原生 |
|---|---|---|
| Hit Rate@10 | 0.413 | 0.362 |
| NDCG@5 | 0.521 | 0.457 |
| 覆盖率 | 0.87 | 0.68 |
| 新颖度 | 0.62 | 0.55 |
在线A/B测试(两周数据):
| 组别 | 点击率 | 下单转化率 | 平均访问时长 |
|---|---|---|---|
| 实验组 | 6.7% | 2.1% | 3m12s |
| 对照组 | 5.3% | 1.6% | 2m47s |
4. 踩坑实录与优化技巧
4.1 数据层面的教训
-
地理信息处理:
- 错误做法:直接使用经纬度作为特征
- 正确方案:转换为GeoHash编码后做区域聚类
- 效果提升:NDCG@5提高0.08
-
评论数据利用:
- 初期仅使用评分,忽略文本信息
- 加入BERT特征后Hit Rate提升23%
4.2 模型调参经验
- 学习率设置:Adam优化器初始lr=0.001,配合ReduceLROnPlateau策略
- 负采样比例:显式反馈数据按1:3(正:负),隐式反馈按1:5
- 嵌入维度实验:64维性价比最高(相比128维仅损失0.8%准确率但节省40%计算资源)
4.3 工程化陷阱
-
线上服务内存泄漏:
- 现象:TF Serving内存每小时增长200MB
- 原因:未清理请求中间数据
- 修复:添加请求生命周期管理
-
特征不一致:
- 离线训练与在线推理的特征处理逻辑存在差异
- 解决方案:统一使用Feature Store
python复制# 正确做法:使用特征存储
from feast import FeatureStore
store = FeatureStore(repo_path=".")
def get_features(user_id, restaurant_ids):
return store.get_online_features(
entity_rows=[{"user_id": user_id, "restaurant_id": rid} for rid in restaurant_ids]
)
5. 扩展方向与实践建议
当前系统仍可优化三个方向:
- 多模态融合:加入餐厅环境图片的CNN特征
- 强化学习:将用户反馈作为reward优化策略
- 知识图谱:构建"菜系-食材-口味"关系网络
对于想复现的开发者,我的环境配置建议是:
- Python 3.8+(必须)
- CUDA 11.1(GPU加速必备)
- PostgreSQL(存储用户行为)
- Redis(缓存特征数据)
部署时特别注意:
- 模型服务需要至少4核CPU+16GB内存
- 特征数据库建议SSD存储
- 监控推荐结果的多样性指标
